社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
Java反射机制主要提供了以下功能:
很多框架都用到了反射机制,包括大名鼎鼎的Spring。因此,了解反射也可以说是为之后学习框架源码而打下坚实的基础。
即便编译时不知道类型和方法名称,也能使用反射。反射使用类对象提供的基本元数据,能从类对象中找出方法或字段的名称,然后获取表示方法或字段的对象。
在Java中,静态成员和普通数据类型不是对象,其他皆是。
那么问题来了,类是谁的对象?
是java.lang.Class
的实例对象。
Class.forName(ClassName)//可以动态加载类——也就是运行时加载
(使用 Class::newInstance() 或另一个构造方法)创建实例时也能让实例具有反射功能。如果有一个能反射的对象和一个 Method 对象,我们就能在之前类型未知的对象上调用任何方法。
反射出来的对象信息是几乎未知的,所以反射也并不是那么的好用。
很多,也许是多数 Java 框架都会适度使用反射。如果编写的架构足够灵活,在运行时之前都不知道要处理什么代码,那么通常都需要使用反射。例如,插入式架构、调试器、代码浏览器和 REPL 类环境往往都会在反射的基础上实现。
反射在测试中也有广泛应用,例如,JUnit 和 TestNG 库都用到了反射,而且创建模拟对象也要使用反射。如果你用过任何一个 Java 框架,即便没有意识到,也几乎可以确定,你使用的是具有反射功能的代码。
常见的反射手段有JDK
反射和cglib
反射。
在自己的代码中使用反射 API 时一定要知道,获取到的对象几乎所有信息都未知,因此处理起来可能很麻烦。
只要知道动态加载的类的一些静态信息(例如,加载的类实现一个已知的接口),与这个类交互的过程就能大大简化,减轻反射操作的负担。
使用反射时有个常见的误区:试图创建能适用于所有场合的反射框架。正确的做法是,只处理当前领域立即就能解决的问题。
使用反射的第一步就是获取Class对象,Class对象里存储了很多关键信息——毕竟这是用来描述类的class。
我们可以这样来获取Class信息:
Class<?> clz = Class.forName(obj.getClazz());
//通过class生成相应的实例
Object newObj = clz.newInstance
从Java1.5开始,Class类就支持泛型化了。比如:String.class就是Class<String>类型。
在反射最常用的API就是Method了。
类对象中包含该类中每个方法的 Method 对象。这些 Method 对象在类加载之后惰性创建,所以在 IDE 的调试器中不会立即出现。
Method对象中保存的方法和元数据:
private Class<?> clazz;
private int slot;
// This is guaranteed to be interned by the VM in the 1.4
// reflection implementation
private String name;
private Class<?> returnType;
private Class<?>[] parameterTypes;
private Class<?>[] exceptionTypes
private int modifiers;
// Generics and annotations support
private transient String signature;
// Generic info repository; lazily initialized
private transient MethodRepository genericInfo;
private byte[] annotations;
private byte[] parameterAnnotations;
private byte[] annotationDefault;
private volatile MethodAccessor methodAccessor;
我们可以通过getMethod获得对象的方法:
Object rcvr = "str";
try {
Class<?>[] argTypes = new Class[] { };
//其实这个参数没有也没关系,因为hashCode方法不需要参数
Object[] args = null;
Method hasMeth = rcvr.getClass().getMethod("hashCode", argTypes);
Object ret = hasMeth.invoke(rcvr,args);
System.out.println(ret);
} catch (IllegalArgumentException | NoSuchMethodException |
SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException | InvocationTargetException x) {
x.printStackTrace();
}
如果要调用非公开方法,必须使用 getDeclaredMethod() 方法才能获取非公开方法的引用,而且还要使用 setAccessible() 方法覆盖 Java 的访问控制子系统,然后才能执行:
public class MyCache {
private void flush() {
// 清除缓存……
}
}
Class<?> clz = MyCache.class;
try {
Object rcvr = clz.newInstance();
Class<?>[] argTypes = new Class[]{};
Object[] args = null;
Method meth = clz.getDeclaredMethod("flush", argTypes);
meth.setAccessible(true);
meth.invoke(rcvr, args);
} catch (IllegalArgumentException | NoSuchMethodException |
InstantiationException | SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException | InvocationTargetException x) {
x.printStackTrace();
}
Java 的反射 API 往往是处理动态加载代码的唯一方式,不过 API 中有些让人头疼的地方,处理起来稍微有点困难:
void 就是个明显的问题——虽然有 void.class,但没坚持用下去。Java 甚至不知道 void 是不是一种类型,而且反射 API 中的某些方法使用 null 代替 void。
这很难处理,而且容易出错,尤其是稍微有点冗长的数组句法,更容易出错。
Java反射的API中还提供了动态代理。动态代理是实现了一些接口的类(扩展 java.lang.reflect.Proxy 类)。这些类在运行时动态创建,而且会把所有调用都转交给 InvocationHandler 对象处理:
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws javaThrowable {
String name = method.getName();
System.out.println("Called as: "+ name);
switch (name) {
case "isOpen":
return false;
case "close":
return null;
}
return null;
}
};
Channel c =
(Channel) Proxy.newProxyInstance(Channel.class.getClassLoader(),
new Class[] { Channel.class }, h);
c.isOpen();
c.close();
代理可以用作测试的替身对象(尤其是测试使用模拟方式实现的对象)。
代理的另一个作用是提供接口的部分实现,或者修饰或控制委托对象的某些方面:
public class RememberingList implements InvocationHandler {
private final List<String> proxied = new ArrayList<>();
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
String name = method.getName();
switch (name) {
case "clear":
return null;
case "remove":
case "removeAll":
return false;
}
return method.invoke(proxied, args);
}
}
RememberingList hList = new RememberingList();
List<String> l =
(List<String>) Proxy.newProxyInstance(List.class.getClassLoader(),
new Class[] { List.class },
hList);
l.add("cat");
l.add("bunny");
l.clear();
System.out.println(l);
Java7中提供了方法句柄,比起“传统”的反射机制。更为好用,而且性能更好。
以之前的反射hashCode为例
Object rcvr = "str";
try {
MethodType mt = MethodType.methodType(int.class);
MethodHandles.Lookup l = MethodHandles.lookup();
MethodHandle hashMeth = l.findVirtual(rcvr.getClass(), "hashCode", mt);
int result;
try {
result = (int) hashMeth.invoke(rcvr);
System.out.println(result);
} catch (Throwable t) {
t.printStackTrace();
}
} catch (IllegalArgumentException |
NoSuchMethodException | SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
MethodType.methodType(int.class);
指定了方法的返回类型,其实不止如此。methodType还可以填入其函数参数MethodHandles.Lookup
可以获得当前执行方法的上下文对象,在这个对象上可以调用几个方法(方法名都以 find 开头),查找需要的方法,包括findVirtual()
、findConstructor()`` 和
findStatic()`
findGetter()
和 findSetter()
方法,分别可以生成读取字段和更新字段的方法句柄。MethodHandles.Lookup.findVirtual()
获得了方法句柄(MethodHandle)方法句柄提供的动态编程功能和反射一样,但处理方式更清晰明了。而且,方法句柄能在 JVM 的低层执行模型中很好地运转,因此,性能比反射好得多。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!