反射
- 程序在运行期间可以拿到一个对象的所有信息
- 为了解决在运行期, 对某个实例一无所知的情况下, 如何调用其方法
Class类
- 除了
int
等基本类型, 其他类型都是class
class
的本质是数据类型(Type
). 无继承关系的数据类型无法赋值class
是JVM在执行过程中动态加载的. 每读取到一种class
类型, 就把他加载到内存中- 每加载一种
class
, JVM都会为其创建一个Class
类型的实例, 并关联起来 - 这里的
Class
类型, 是一个名叫Class
的class
- 加载
String
类时, 首先读取String.class
文件到内存, 然后为String
类创建一个Class
实例并关联起来
Class cls = new Class(String)
- Class是内部的, java程序无法创建
- 所以: JVM持有的每个
Class
实例都指向一个数据类型(class
或interface
)
┌───────────────────────────┐
│ Class Instance │──────> String
├───────────────────────────┤
│name = "java.lang.String" │
└───────────────────────────┘
┌───────────────────────────┐
│ Class Instance │──────> Random
├───────────────────────────┤
│name = "java.util.Random" │
└───────────────────────────┘
┌───────────────────────────┐
│ Class Instance │──────> Runnable
├───────────────────────────┤
│name = "java.lang.Runnable"│
└───────────────────────────┘
- 一个
Class
实例包含了该class
的所有完整信息
┌───────────────────────────┐
│ Class Instance │──────> String
├───────────────────────────┤
│name = "java.lang.String" │
├───────────────────────────┤
│package = "java.lang" │
├───────────────────────────┤
│super = "java.lang.Object" │
├───────────────────────────┤
│interface = CharSequence...│
├───────────────────────────┤
│field = value[],hash,... │
├───────────────────────────┤
│method = indexOf()... │
└───────────────────────────┘
- 所以我们可以通过这个
Class
实例获取到该实例的所有信息.
如何获取一个class
的Class
- 直接通过一个class的静态变量
class
获取:Class cls = String.class
- 有一个实例变量, 提供getClass()方法
- 使用
class
的完整类名, 通过Class.forName()
:Class cls = Class.forName("java.lang.String")
instanceof
-
instanceof
不但匹配指定类型, 还能匹配指定类型的子类 -
==
可以准确判断数据类型, 但不能判断子类 -
String[]也是一种
Class
, 不同于String.class, 类名是[Ljava.lang.String. -
可以通过
Class
实例, 来创建对应的实例
Class cls = String.class;
String s= (String) cls.newInstance();
- 上述代码相当于
new String()
Class.newInstance
局限: 无法调用带有参数的构造方法或者非public
的构造方法.
动态加载
- JVM在执行Java时, 并非一次性加载所有用到的class, 第一次需要用到class时才加载
- 利用动态加载特性, 我们可以在运行期间根据条件加载不同的实现类
- 这就是为什么Log4j放到classpath, Commons Logging就会自动使用.
访问字段
如何获取字段信息
- Field getField(name): 根据字段名获取某个public的field (包括父类)
- Field getDeclaredField(name): 根据字段名获取当前类的某个field (不包括父类)
- Field getFields(): 获取所有public的field (包括父类)
- Field getDeclaredFields(): 获取当前类的所有字段 (不包括父类)
Field对象包含的信息
getName()
: 返回字段名称getType()
: 返回字段类型getModifiers()
返回字段的修饰符, 它是一个int, 不同数值代表不同的含义
获取字段值
- 利用反射拿到字段的一个
Field
实例, 从而拿到一个实例对应的该字段的值 - setAccessible(true) 可能会失败, 因为JVM运行期间存在
SecurityManager
可能不允许java和javax
开头的package
的类调用. - 为了保证java核心库的安全
设置字段值
Field.set(Object, Object)
调用方法
- 同样可以用相同的方法, 获取
Class
所有的Method
- 一个
Method
对象包含一个方法所有的信息. - 获取到一个
Method
就可以对其进行调用
调用静态方法
- 调用静态方法无需指定实例对象.
invoke
方法第一参数永远为null
Method m = Integer.class.getMethod("parseInt", String.class);
Integer n = (Integer) m.invoke(null, "12345");
System.out.println(n);
调用非public方法
- 非public方法, 可以获取,但直接调用, 得到
IllegalAccessException
. - 我们通过
Method.setAccessible(true)
允许调用 - 需要用
getDeclaredMethod
来获取private
方法
多态
- 使用反射调用方法时, 仍然遵循多态原则
- 也就是总是调用实际类型的覆写方法
调用构造方法
- 可以用个
Class
提供的newInstance()
方法创建新的实例, 但只能调用public的无参构造方法 Constructor
方法用来调用任意的构造方法, 包含了构造方法的所有信息
通过Class实例获取Constructor的方法如下
-
getConstructor(Class...)
: 获取某个public
的Constructor
-
getDeclaredConstructor(Class...)
: 获取某个Constructor
-
getConstructors
: 获取所有public
的Constructor
-
getDeclaredConstructors()
获取所有的Constructor
-
调用非Public的
Constructor
时, 必须通过setAccessible(true)
获取继承关系
获取父类的Class
- Integer => Number => Object => null
- 任何一种非
interface
的Class
都必定存在一种父类类型
获取interface
getInterface
: 返回当前类直接实现的接口类型, 不包括父类实现的接口类型- 所有
interface
的Class
, 调用getSuperClass()
返回null
- 类没有实现接口,
getInterface
返回空数组
继承关系
- 判断一个实例是否属于某个类型:
instanceof
- 如果两个
Class
实例, 要判断向上转型是否成功, 使用isAssignable()
boolean c = Number.class.isAssignableFrom(Integer.class);
System.out.println(c); // true Integer可以赋值给Number
boolean e = Integer.class.isAssignableFrom(Number.class);
System.out.println(e); // false Number不能赋值给Integer
动态代理
- 比较
class
和interface
的区别- 可以实例化
class
- 不能实例化
interface
- 可以实例化
- 所有interface类型的变量总是通过向上转型并指向某个实例
CharSequence cs = new StringBuilder();
- 动态代理(Dynamic Proxy): 在运行期动态创建某个
interface
的实例 - 不编写实现类, 直接通过
Proxy.newProxyInstance()创建接口对象
- 没有实现类, 但在运行期动态创建了一个接口对象的方式, 成为动态代码
- JDK提供的动态创建接口对象的方式, 成为动态代理
InvocationHandler handler = new InvocationHandler() { // 1: 创建invocationHandler方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("morning")) {
System.out.println("Good morning: " + args[0]);
}
return new Object();
}
};
// 2: 创建通过`newProxyInstance`创建实例
Hello hello = (Hello) Proxy.newProxyInstance( // 3: 强制转换类型
Hello.class.getClassLoader(), // 传入ClassLoader
new Class[] { Hello.class }, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
hello.morning("bob");
运行期间创建interface
实例如下
- 创建
InvocationHandler
实例, 负责方法调用 - 通过
Proxy.newProxyInstance()
创建interface
实例, 三个参数如下:- 使用的ClassLoader, 通常就是接口类的
ClassLoader
- 需要实现的接口数组.
- 用来处理回调方法调用的
InvocationHandler
实例
- 使用的ClassLoader, 通常就是接口类的
- 将返回的Object类型强制转换
动态代理本质
-
动态代理其实就是: JDK在运行期间动态创建class字节码并加载过程.
-
动态类改为静态实现类:
-
JDK就是编写了这么一个静态类, 不需要源码, 直接生成字节码
interface Hello {
void morning(String name);
}
class HelloDynamicProxy implements Hello {
InvocationHandler handler;
public HelloDynamicProxy(InvocationHandler handler) {
this.handler = handler;
}
public void morning(String name) {
try {
handler.invoke(
this,
Hello.class.getMethod("mornging"),
new Object[] {name});
} catch (Throwable t) {
}
}
}
疑问
Class
类型的class
是什么意思?String s= (String) cls.newInstance();
中间的(String)
如何理解?- 完全不理解最后这个动态代理, 以及最后的静态类实现方式??
new Person () {}
是什么语法??