Java Reflection可以在运行时查看类,接口,字段和方法,而无需在编译时知道类,方法等的名称。 还可以使用反射来实例化新对象,调用方法和get/set字段值。
反射是Java的一项强大的功能,但不应该被随意使用,它有如下一些缺点:
- 性能不佳 : 由于java反射动态地解析类型,因此它涉及扫描类路径以查找要加载的类的处理,从而导致性能降低。
- 安全限制 : Reflection需要运行时权限,这些权限可能不适用于在安全管理器下运行的系统。 由于安全管理器,这可能导致应用程序在运行时失败。
- 安全问题 : 使用Reflection我们能访问被禁止访问的代码,比如能够访问类的private字段并修改它的值,这可能带来严重的安全威胁,并导致程序运行异常。
- 高维护成本 : Reflection的代码难以理解和调试,问题代码在编译时难以被发现,因此代码的维护成本更高。
1. 类反射
使用反射可以在运行时查看类,查看类通常是使用反射的第一步,通过反射我们能获取的类的信息包括但不限于:
- 类名
- 类修饰符 (比如 public,private,synchronized 等)
- 包信息
- 父类
- 实现的接口
- 构造方法
- 方法
- 字段
- 注解
1.1 Class 对象
在查看任意一个类之前,你需要获取其 java.lang.Class
对象,Java中所有类型包括原始数据类型都有一个相关的Class对象。如果你知道类的名称,可以如下获取其Class对象:
Class myObjectClass = MyObject.class
如果在编译时不知道类的名称,但是能在运行时获取其名称的字符串,也可以这样来获取其Class对象
String className = "com.xiaoqiang.app.MyObject";
Class myObjClass = Class.forName(className);
需要注意的是名称字符串必须是类的完整名称,即包含类所在包的包名。Class.forName()
方法还可能抛出 ClassNotFoundException
异常。
1.2 类名称
Class myClass = ...
String className = myClass.getName(); //包含包名
String simpleCName = myClass.getSimpleName(); //只包含类名
1.3 修饰符
Class myClass = ...
int modifiers = myClass.getModifiers();
getModifiers方法将类的所有修饰符都打包进了一个整数中,可以通过以下的方法判断修饰符的类型:
Modifier.isAbstract(int modifiers)
Modifier.isFinal(int modifiers)
Modifier.isInterface(int modifiers)
Modifier.isNative(int modifiers)
Modifier.isPrivate(int modifiers)
Modifier.isProtected(int modifiers)
Modifier.isPublic(int modifiers)
Modifier.isStatic(int modifiers)
Modifier.isStrict(int modifiers)
Modifier.isSynchronized(int modifiers)
Modifier.isTransient(int modifiers)
Modifier.isVolatile(int modifiers)
1.4 包信息
Class myClass = ...
Package package = myClass.getPackage();
1.5 父类
Class superClass = myClass.getSuperClass();
1.6 实现的接口
Class[] interfaces = myClass.getInterfaces();
1.7 构造方法
Constructor[] pubConstructors = myClass.getConstructors(); //获取所有public构造方法
Constructor[] constructors = myClass.getDeclaredConstructors(); //获取所有构造方法
1.8 方法
Method[] pubMethods = myClass.getMethods(); //获取所有public方法
Method[] methods = myClass.getDeclaredMethods(); //获取所有方法
1.9 字段
Field[] pubFields = myClass.getFields(); //获取所有public字段
Field[] fields = myClass.getDeclaredFields(); //获取所有字段
1.10 注解
Annotation[] annotations = myClass.getAnnotations();
2. 构造方法(Constructor)
使用反射可以在运行时查看类的构造方法并且初始化对象,这可以通过 java.lang.reflect.Constructor
类来实现。
2.1 获取Constructor对象
获取类的所有构造方法已经在 1.7 中介绍过了。
也可以通过传入参数类型,获取指定构造方法,如下:
Constructor constructor = myClass.getConstructor(String.class, int.class);
如果该类没有该指定参数的构造方法,将会抛出 NoSuchMethodException
异常。
2.2 构造参数
有了Constructor对象之后可以获取构造方法的参数类型
Class[] parameterTypes = myConstructor.getParameterTypes();
2.3 使用Constructor来实例化对象
Constructor constructor = Gas95.class.getConstructor(String.class, int.class);
Gas95 gas95 = (Gas95) constructor.newInstance("中石化91#", 22);
3. 字段(Field)
使用反射可以在运行时查看类的字段(成员变量)并且进行get/set操作,这可以通过 java.lang.reflect.Field
类来实现。
3.1 获取字段对象
获取类的所有字段已经在 1.9 中介绍过。
还可以通过字段名获取指定字段:
Field field = Gas95.class.getField("name"); //只能获取public字段
Field field = Gas95.class.getDeclaredField("name"); //无修饰符限制
此方法可能会导致 NoSuchFieldException
异常。
3.2 字段名
一旦获得了Field对象,就可以通过其getName方法获取字段名称,如下:
String fieldName = field.getName();
3.3 字段类型
通过Field.getType()方法可以获得字段的类型:
Class type = field.getType();
3.4 get/set 字段值
get值:
Gas95 gas95 = new Gas95("中石化", "95#");
Field field = Gas95.class.getDeclaredField("gasType");
String gasType = (String) field.get(gas95);
获取到的 gasType 就是 95#
set值:
field.set(gas95, "98#");
set之后,gas95 实例的 gasType 字段值为 98#
如果字段是静态(static)的,那么get/set的时候不需要传入类的实例,用 null
替代即可。
对于 private
的Field,如果想调用其get/set方法,需要先调用方法使之可访问,如下:
field.setAccessible(true);
4. 方法(Method)
使用反射可以在运行时查看并调用类的方法,这可以通过 java.lang.reflect.Method
类来实现。
4.1 获取Method对象
获取Method对象的方法在 1.8 中已经介绍过。
还可以通过指定的方法名和参数类型来获取指定的方法,如下:
Method method = Gas95.class.getMethod("printGasInfo", boolean.class);
其中 printGasInfo 方法需要传入一个 boolean 类型的参数
4.2 方法的参数和返回值类型
获取方法的所有参数的类型:
Class[] parameterTypes = method.getParameterTypes();
获取方法的返回值的类型:
Class returnType = method.getReturnType();
如果方法没有返回值,那么类型是 void
4.3 通过Method对象调用方法
Method method = Gas95.class.getMethod("printGasInfo", boolean.class);
method.invoke(new Gas95("Shell", "98#"), true);
方法 Method.invoke(Object target, Object ... parameters)
第一个参数为类的实例,后面的参数为方法需要的参数。如果方法是静态(static)的,第一个参数可以传入 null
5. 注解(Annotation)
使用反射可以在运行时访问Java类中的注解
5.1 类的注解
在 1.10 中已经介绍过
5.2 方法的注解
Method method = ...
Annotation[] annotations = method.getAnnotations();
当然还能指定注解的类型
Annotation annotation = method.getAnnotation(TheAnnotation.class);
5.3 方法参数的注解
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
获取该方法所有参数的所有注解,返回一个二维数组。
5.4 字段的注解
Annotation[] annotations = field.getAnnotations();
获取指定类型的注解
Annotation annotation = field.getAnnotation(TheAnnotation.class);
6. 访问非public方法、字段
以上的各种 getMethod()、getMethods()、getField()等方法,都只能访问对应修饰符为 public
的方法/字段。如果想访问使用了其它修饰符的方法/字段,需要调用对应的 getDeclaredXXX()
方法。
在获取到非public修饰的字段/方法之后,还不能直接对它进行调用,需要先调用
Method.setAccessible(true) / Field.setAccessible(true)
,将它们设置成可访问,然后再调用。
以上是反射常见的用法,如果想进一步了解,请参考
Java Reflection Tutorial