Java笔记04 - 反射
反射
- 程序在运行期间可以拿到一个对象的所有信息
- 为了解决在运行期, 对某个实例一无所知的情况下, 如何调用其方法
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 () {}是什么语法??

浙公网安备 33010602011771号