Skip to content

Commit 5c282a8

Browse files
committed
反射、注解、泛型
1 parent 4e1fd10 commit 5c282a8

File tree

3 files changed

+531
-1
lines changed

3 files changed

+531
-1
lines changed

src/develop/Java/5_Reflection.md

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,15 @@ public class Main {
703703
}
704704
```
705705

706-
运行上述代码,可以看到,`Integer`的父类类型是`Number``Number`的父类是`Object``Object`的父类是`null`。除`Object`外,其他任何非`interface``Class`都必定存在一个父类类型。
706+
运行上述代码,可以看到,
707+
708+
```
709+
class java.lang.Number
710+
class java.lang.Object
711+
null
712+
```
713+
714+
`Integer`的父类类型是`Number``Number`的父类是`Object``Object`的父类是`null`。除`Object`外,其他任何非`interface``Class`都必定存在一个父类类型。
707715

708716
### 获取interface
709717

@@ -727,6 +735,9 @@ public class Main {
727735
运行上述代码可知,`Integer`实现的接口有:
728736

729737
- java.lang.Comparable
738+
739+
Java14开始还额外有
740+
730741
- java.lang.constant.Constable
731742
- java.lang.constant.ConstantDesc
732743

@@ -783,3 +794,126 @@ Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋
783794
Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer
784795
```
785796

797+
## 动态代理
798+
799+
我们来比较Java的`class``interface`的区别:
800+
801+
- 可以实例化`class`(非`abstract`);
802+
- 不能实例化`interface`
803+
804+
所有`interface`类型的变量总是通过某个实例向上转型并赋值给接口类型变量的:
805+
806+
```java
807+
CharSequence cs = new StringBuilder();
808+
```
809+
810+
有没有可能不编写实现类,直接在运行期创建某个`interface`的实例呢?
811+
812+
这是可能的,因为Java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个`interface`的实例。
813+
814+
所谓动态代理,是和静态相对应的。我们来看静态代码怎么写:
815+
816+
定义接口:
817+
818+
```java
819+
public interface Hello {
820+
void morning(String name);
821+
}
822+
```
823+
824+
编写实现类:
825+
826+
```java
827+
public class HelloWorld implements Hello {
828+
public void morning(String name) {
829+
System.out.println("Good morning, " + name);
830+
}
831+
}
832+
```
833+
834+
创建实例,转型为接口并调用:
835+
836+
```java
837+
Hello hello = new HelloWorld();
838+
hello.morning("Bob");
839+
```
840+
841+
这种方式就是我们通常编写代码的方式。
842+
843+
还有一种方式是动态代码,我们仍然先定义了接口`Hello`,但是我们并不去编写实现类,而是直接通过JDK提供的一个`Proxy.newProxyInstance()`创建了一个`Hello`接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。
844+
845+
一个最简单的动态代理实现如下:
846+
847+
```java
848+
import java.lang.reflect.InvocationHandler;
849+
import java.lang.reflect.Method;
850+
import java.lang.reflect.Proxy;
851+
852+
public class Main {
853+
public static void main(String[] args) {
854+
InvocationHandler handler = new InvocationHandler() {
855+
@Override
856+
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
857+
System.out.println(method);
858+
if (method.getName().equals("morning")) {
859+
System.out.println("Good morning, " + args[0]);
860+
}
861+
return null;
862+
}
863+
};
864+
Hello hello = (Hello) Proxy.newProxyInstance(
865+
Hello.class.getClassLoader(), // 传入ClassLoader
866+
new Class[] { Hello.class }, // 传入要实现的接口
867+
handler); // 传入处理调用方法的InvocationHandler
868+
hello.morning("Bob");
869+
}
870+
}
871+
872+
interface Hello {
873+
void morning(String name);
874+
}
875+
```
876+
877+
在运行期动态创建一个`interface`实例的方法如下:
878+
879+
1. 定义一个`InvocationHandler`实例,它负责实现接口的方法调用;
880+
881+
2. 通过
882+
883+
```
884+
Proxy.newProxyInstance()
885+
```
886+
887+
创建
888+
889+
```
890+
interface
891+
```
892+
893+
实例,它需要3个参数:
894+
895+
1. 使用的`ClassLoader`,通常就是接口类的`ClassLoader`
896+
2. 需要实现的接口数组,至少需要传入一个接口进去;
897+
3. 用来处理接口方法调用的`InvocationHandler`实例。
898+
899+
3. 将返回的`Object`强制转型为接口。
900+
901+
动态代理实际上是JVM在运行期动态创建class字节码并加载的过程,它并没有什么黑魔法,把上面的动态代理改写为静态实现类大概长这样:
902+
903+
```java
904+
public class HelloDynamicProxy implements Hello {
905+
InvocationHandler handler;
906+
public HelloDynamicProxy(InvocationHandler handler) {
907+
this.handler = handler;
908+
}
909+
public void morning(String name) {
910+
handler.invoke(
911+
this,
912+
Hello.class.getMethod("morning", String.class),
913+
new Object[] { name }
914+
);
915+
}
916+
}
917+
```
918+
919+
其实就是JVM帮我们自动编写了一个上述类(不需要源码,可以直接生成字节码),并不存在可以直接实例化接口的黑魔法。

0 commit comments

Comments
 (0)