java 反射
一篇远古的坑..java 反射.
资料来源:
https://www.liaoxuefeng.com/wiki/1252599548343744
更新
1
220.01.30 远古大坑😥
20.02.17 填坑完毕
导语
- 最近提及反射的地方是 kt 的动态代理和泛型.
- 这里仅仅为反射的流水账,并非详细讲解,周知.
- 基本总结于廖雪峰的 java 教程.
反射原理
首次接触反射是在 Myprivacy(已弃坑).需要把对象的属性保存到键值.但是对象的类型非常多,不可能每一类都再写个保存方法.总结一下:大概是需要一次性获取对象的所有属性,得到属性的名称和值.这就是 java 反射的典型应用.
几乎与反射有关的教程都会提到 java 的 Class 类.
Class 类:一个类,名叫 Class 而已.跟其他类一样继承自 object .java 中万物皆对象,即使类也不例外,在 jvm 看来一个普通的类相当于一个 Class 类的对象.
代码在编译后都是一个个的 class 文件,jvm 运行时并不会一次性加载全部代码编译的 class 文件.当创建一个对象时,jvm 不认识,才会加载对应的 class 文件.加载 class 文件的过程,就是 jvm 创建一个 Class 类的对象的过程.这个 Class 对象中包含了普通类编译成 class 的所有信息,因此一个普通类只有一个 Class 对象与之对应.
当需要 new 一个普通类的对象时,jvm 就会获取普通类对应的 Class 对象,再生成普通类的对象.
java 反射实质上就是在运行时获取普通类对应的 Class 对象.
因为 Class 对象中包含了这个普通类的所有信息,所以我们利用这一点可以做很多.例:
- 获取类的属性/方法.进而获取对象的属性的值,调用方法等.
- 修改对象属性/方法.
- 运行时新建一个普通类的对象
- ….
获取 Class 对象
- class 的静态变量:
Class cls = String.class;
- 已知对象:
Class cls = s.getClass();
- 已知 class 的完整类名:
Class cls = Class.forName("java.lang.String");
- class 的静态变量:
以上获取到的 Class 对象都是同一个.
访问属性
访问属性大致是先获取到属性对应的 Field 对象,再传入具体的对象得到类型/值/修饰符等.
获取 Field 对象
- Field getField(name): 根据字段名获取某个public的field(包括父类)
- Field getDeclaredField(name): 根据字段名获取当前类的某个field(不包括父类)
- Field[] getFields(): 获取所有public的field(包括父类)
- Field[] getDeclaredFields(): 获取当前类的所有field(不包括父类)
Field 对象
- getName(): 返回字段名称,例 “name”.
- getType(): 返回字段类型,也是一个 Class 实例,例 String.class.
- getModifiers(): 返回字段的修饰符,它是一个 int 不同的bit表示不同的含义,具体定义在
java.lang.reflect.Modifier
中.
例:获取属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14class Person {
private String name;
public Person(String name) {
this.name = name;
}
}
Object p = new Person("Xiao Ming");
//获取 Person类的 Class
Class c = p.getClass();
//获取 Filed 对象
Field f = c.getDeclaredField("name");
//传入 Person 类的实例,获取属性值
Object value = f.get(p);特殊情况: 当属性被 private 修饰时,需要设置 (Field 对象).setAccessible(true).如果打开了 SecurityManager 可能会调用失败.
调用方法
与访问属性类似,获取方法也是通过 Class 对象获取到 Method 对象.Method 中包含了类中方法的所有信息.再通过这些信息回到普通类的对象调用方法.
获取 Method 对象(也非常类似 Field)
- Method getMethod(name, Class…): 获取某个public的Method(包括父类)
- Method getDeclaredMethod(name, Class…): 获取当前类的某个Method(不包括父类)
- Method[] getMethods(): 获取所有public的Method(包括父类)
- Method[] getDeclaredMethods(): 获取当前类的所有Method(不包括父类)
Method对象
- getName(): 返回方法名称,例如: “getScore”.
- getReturnType(): 返回方法返回值类型,也是一个Class实例,例如: String.class.
- getParameterTypes(): 返回方法的参数类型,是一个Class数组,例如: {String.class, int.class}.
- getModifiers(): 返回方法的修饰符,它是一个int,不同的bit表示不同的含义.具体定义还是在
java.lang.reflect.Modifier
中.
调用方法
- Method 实例调用 invoke = 调用方法.
- invoke 的第一个参数是对象实例,第二个及以后是可变参数必须与方法参数一致.
- 静态方法无需具体的对象实例,所以 invoke 第一个参数就是 null.
- 对于私有的方法类似的 (Method对象).setAccessible(true).如果打开了 SecurityManager 可能会调用失败.
- 对于重载的方法,分别调用就是传入不同的可变参数.
- 通过反射调用方法,也遵循多态,即子类与父类重名方法,子类会覆盖父类的方法.
例: 通过反射调用方法
1
2
3
4
5
6// String对象:
String s = "Hello world";
// 获取String substring(int)方法,参数为int:
Method m = String.class.getMethod("substring", int.class);
// 在s对象上调用该方法并获取结果:
String r = (String) m.invoke(s, 6);
动态创建对象(调用构造函数)
最简单的如果类有无参的且是 public 构造函数: Class.newInstance() 方法.例:
Person p = Person.class.newInstance();
.其他情况下,还是类似的通过 Class 可以获取到 Constructor 对象,包含了类的构造方法的所有信息.可以创建并返回一个新的对象示例.
获取 Constructor 对象
- getConstructor(Class…): 获取某个public的Constructor.
- getDeclaredConstructor(Class…): 获取某个Constructor.
- getConstructors(): 获取所有public的Constructor.
- getDeclaredConstructors(): 获取所有Constructor.
调用构造函数
- 调用 Constructor.newInstance(参数) 可以创建并返回一个新的对象.
- 非 public 的构造函数设置 Constructor.setAccessible(true).如果打开了 SecurityManager 可能会调用失败.
要注意的是 Constructor 对象,总是当前类定义的构造方法,和父类无关.
获取继承关系
没有使用过反射的这种用途.还是…挺新奇?
获取父类 Class 对象
- Class.getSuperclass()
- java 所有类的父类是 Object,Object 的父类是null.
获取interface
- Class.getInterfaces()
- 只返回当前类直接实现的接口类型数组,并不包括父类的接口.
- 没有直接实现接口返回空数组.
判断继承关系
- 正常情况下可以通过 instanceof 来判断.
- 两个 Class 之间可以通过 isAssignableFrom() 判断.例:
Class.isAssignableFrom(Class)
动态代理
所谓动态代理在刷 kotlin 那一篇也提到了,就是运行时创建一个实现 Interface 实例.
java 中是通过 Proxy.newProxyInstance() 运行时创建一个接口的实现类实例.
动态代理
- 定义一个InvocationHandler实例,它负责实现接口的方法调用.
- 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
- ClassLoader,通常就是接口类的ClassLoader.
- 实现的接口数组,至少需要传入一个接口进去.
- 用来处理接口方法调用的InvocationHandler实例.
- 将返回的Object强制转型为接口.
动态代理通过反射实现,带来了一定的性能损耗,除非必要否则无需使用.kotlin 就好多了.
例子(偷懒照搬了)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class Main {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
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 null;
}
};
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), // 传入ClassLoader
new Class[] { Hello.class }, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
hello.morning("Bob");
}
}
interface Hello {
void morning(String name);
}
结语
- java 反射给 java 带来了动态语言的一些特性,程序一定程度上可以修改自身.
- 但 java 的反射有一定的性能损失.还是要慎用.
- 终于填坑完毕..还有好多坑…😭…