什么是反射
反射(reflection):通过Java反射机制可以在程序运行时动态的创建对象,并操作对象中的属性和方法。而这样的对象在编译期间是未知的。
Java 中反射提供以下功能:
- 运行时得到一个对象的所属类
- 运行时构造一个类的对象
- 运行时得到一个类中所有的方法和属性
- 运行时调用一个类中的任意方法
反射的运用
反射的基本使用
获得Class对象
有三种方式可以获得 Class 对象:
- 使用 Class 类的静态方法 forName:
1 | Class<?> aClass = Class.forName("java.lang.String"); |
- 直接使用 .class 的方式获取:
1 | Class<?> sClass = String.class; |
- 调用实例对象的 getClass() 方法:
1 | StringBuffer s = new StringBuffer(); |
判断是否是某个类的实例
一般使用 instanceof 来判断是否是某个对象的实例。在 Class 类中也提供了 isInstance() 方法来判断是否是某个类的实例。
1 | boolean flag = sClass.isInstance(new StringBuffer()); |
创建实例
有两种方式来通过反射创建实例对象
- 使用 Class 类的 newInstance() 方法创建
1 | Object o = sClass.newInstance(); |
- 先通过 Class 对象获得指定对象的 Constructor 对象,再通过 Constructor 的 newInstance() 方法来创建实例。这种方法可以指定构造器来生成新的实例对象。
1 | //获得 String 所对应的 Class 对象 |
获取方法
获取某个 Class 对象的方法的集合,主要有以下的方法:
- getDeclareMethods 方法返回类或接口声明的所有方法,但不包括继承的方法。
1 | public Method[] getDecalrMethods() throws SecurityException |
- getMethods 方法返回类的所有的 public 方法,包括继承的共用的方法。
1 | public Method[] getMethods() throws SecurityException |
- getMethod 方法返回一个特定的方法,第一个参数是方法名称,后面的参数是方法应的参数的 class 。
1 | public Method getMethod(String name, Class<?>... parameterTypes) |
下面举例说明下以上方法:
1 | public class SubClass{ |
运行结果如下:
可以发现,在类中的 main 方法也是可以通过这样的方式来获得的。
获取构造器
可以通过 getConstructor 方法的到 Constructor 的一个实例,并使用 newInstance 方法创建一个对象实例。
获取类的成员变量
- getFiled 获得所有 public 修饰的的变量,包括从所有父类继承的。
- getDeclaredFiled 获得这个类中所有的变量,不包括从父类继承的。
这两个方法类似 Method 的相关方法。
调用方法
在获得了类中的一个方法以后,可以通过 invoke 方法来执行调用这个方法。
1 | public Object invoke(Object object, Object... args) |
利用反射创建数组
1 | public static void main(String[] args) throws ClassNotFoundException{ |
其中 Array 类是 Java.lang.reflect.Array 类,通过 newInstance() 创建对象,再通过 get,set 方法来添加获取数组中的值。
利用反射修改私有的属性
先定义一个类 SuperClass
1 | public class SuperClass { |
在其他类中编写主方法来修改 SuperClass 中的私有属性
1 | public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { |
最后程序运行结果是 : sub 。
通过这样的方式可以访问和修改私有的属性。
那么对于类中的常量也可以通过这样的方法来修改吗?
修改 SuperClass
1 | public class SuperClass { |
再修改测试类
1 | public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { |
程序运行结果:
可以看见在中间确实改变了常量的值,但是最后输出的时候却没有改变。这是因为由 final 修饰的成员属性,在 Java 编译时为了提升效率会把引用了常量的地方,带入常量。我们现在查看一下 SuperClass 类在编译以后的 .class 文件:
可以看见在编译过后,新增了一个无参的构造方法,并且在 getter 方法中,把常量的引用修改成了常量值。但是这种优化并不是对所有类型的常量都会优化,对于 int,double,long,boolean,String 这些基本类型是会进行优化的,但是对 Integer, Double, Long 这样的包装类型是不会优化的对于 Date, Object 这样的类型也是不会优化的。
知道了问题以后修改 SuperClass
1 | public class SuperClass { |
这样再来看编译以后的 SuperClass :
这样再通过 getter 方法获得就是 FINAL_VALUE,而不是替换了过后的字符串。