Java 反射

什么是反射

反射(reflection):通过Java反射机制可以在程序运行时动态的创建对象,并操作对象中的属性和方法。而这样的对象在编译期间是未知的。

Java 中反射提供以下功能:

  • 运行时得到一个对象的所属类
  • 运行时构造一个类的对象
  • 运行时得到一个类中所有的方法和属性
  • 运行时调用一个类中的任意方法

反射的运用

反射的基本使用

获得Class对象

有三种方式可以获得 Class 对象:

  • 使用 Class 类的静态方法 forName
1
Class<?> aClass = Class.forName("java.lang.String");
  • 直接使用 .class 的方式获取:
1
Class<?> sClass = String.class;
  • 调用实例对象的 getClass() 方法:
1
2
StringBuffer s = new StringBuffer();
Class<?> sClass = s.getClass();

判断是否是某个类的实例

一般使用 instanceof 来判断是否是某个对象的实例。在 Class 类中也提供了 isInstance() 方法来判断是否是某个类的实例。

1
boolean flag = sClass.isInstance(new StringBuffer());

创建实例

有两种方式来通过反射创建实例对象

  • 使用 Class 类的 newInstance() 方法创建
1
Object o = sClass.newInstance();
  • 先通过 Class 对象获得指定对象的 Constructor 对象,再通过 Constructor 的 newInstance() 方法来创建实例。这种方法可以指定构造器来生成新的实例对象。
1
2
3
4
5
6
7
//获得 String 所对应的 Class 对象
Class<?> sClass = String.class;
//获取 String 类的构造器
Constructor<?> constructor = sClass.getConstructor(String.class);
//根据构造器创建实例对象
Object o = constructor.newInstance("123");
System.out.println(o);

获取方法

获取某个 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class SubClass{
private String subName;
public int subAge;

private void getPrivate(){
System.out.println("private sub");
}

public void getPublic(String s){
System.out.println(s);
}

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//获得相应的 Class 对象
Class<?> aClass = Class.forName("Reflection.SubClass");
//获得所所有方法
Method[] declaredMethods = aClass.getDeclaredMethods();
//获得这个类所有的 public 方法,包括从所有父类继承的方法
Method[] methods = aClass.getMethods();
//获得指定的方法
Method getPublic = aClass.getMethod("getPublic", String.class);
System.out.println("declaredMethods----");
for (Method m:declaredMethods){
System.out.println(m);
}
System.out.println("methods----");
for (Method m:methods){
System.out.println(m);
}
System.out.println("method----");
System.out.println(getPublic);
}
}

运行结果如下:

1443

可以发现,在类中的 main 方法也是可以通过这样的方式来获得的。

获取构造器

可以通过 getConstructor 方法的到 Constructor 的一个实例,并使用 newInstance 方法创建一个对象实例。

获取类的成员变量

  • getFiled 获得所有 public 修饰的的变量,包括从所有父类继承的。
  • getDeclaredFiled 获得这个类中所有的变量,不包括从父类继承的。

这两个方法类似 Method 的相关方法。

调用方法

在获得了类中的一个方法以后,可以通过 invoke 方法来执行调用这个方法。

1
public Object invoke(Object object, Object... args)

利用反射创建数组

1
2
3
4
5
6
7
8
public static void main(String[] args) throws ClassNotFoundException{
Class<?> aClass = Class.forName("java.lang.String");
Object array = Array.newInstance(aClass, 5);
Array.set(array,0,"1");
Array.set(array,1,"2");
Array.set(array,2,"3");
System.out.println(Array.get(array,0));
}

其中 Array 类是 Java.lang.reflect.Array 类,通过 newInstance() 创建对象,再通过 get,set 方法来添加获取数组中的值。

利用反射修改私有的属性

先定义一个类 SuperClass

1
2
3
public class SuperClass {
private String superName = "super";
}

在其他类中编写主方法来修改 SuperClass 中的私有属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
//创建实例,获得 Class 对象
SuperClass superClass = new SuperClass();
Class<?> aClass = superClass.getClass();
//获得 SuperClass 类中名字为 superName 的变量
Field superName = aClass.getDeclaredField("superName");
if (superName != null){
//设置访问权限,为 true 代表可以访问和修改私有对象
superName.setAccessible(true);
//修改私有属性的值
superName.set(superClass,"sub");
//没有获得访问权限的话是不能获得私有属性的值
System.out.println(superName.get(superClass));
}
}

最后程序运行结果是 : sub 。

通过这样的方式可以访问和修改私有的属性。


那么对于类中的常量也可以通过这样的方法来修改吗?

修改 SuperClass

1
2
3
4
5
6
7
public class SuperClass {
private final String FINAL_VALUE = "final";

public String getFINAL_VALUE() {
return FINAL_VALUE;
}
}

再修改测试类

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
SuperClass superClass = new SuperClass();
Class<?> aClass = superClass.getClass();
Field FINAL_VALUE = aClass.getDeclaredField("FINAL_VALUE");
if (FINAL_VALUE != null){
FINAL_VALUE.setAccessible(true);
System.out.println("before:" + FINAL_VALUE.get(superClass));
FINAL_VALUE.set(superClass,"change");
System.out.println("after:" + FINAL_VALUE.get(superClass));
}
System.out.println("get:" + superClass.getFINAL_VALUE());
}

程序运行结果:

1609

可以看见在中间确实改变了常量的值,但是最后输出的时候却没有改变。这是因为由 final 修饰的成员属性,在 Java 编译时为了提升效率会把引用了常量的地方,带入常量。我们现在查看一下 SuperClass 类在编译以后的 .class 文件:

1615

可以看见在编译过后,新增了一个无参的构造方法,并且在 getter 方法中,把常量的引用修改成了常量值。但是这种优化并不是对所有类型的常量都会优化,对于 int,double,long,boolean,String 这些基本类型是会进行优化的,但是对 Integer, Double, Long 这样的包装类型是不会优化的对于 Date, Object 这样的类型也是不会优化的。

知道了问题以后修改 SuperClass

1
2
3
4
5
6
7
8
9
10
11
public class SuperClass {
private final String FINAL_VALUE;

public SuperClass(){
FINAL_VALUE = "final";
}

public String getFINAL_VALUE() {
return FINAL_VALUE;
}
}

这样再来看编译以后的 SuperClass :

1625

这样再通过 getter 方法获得就是 FINAL_VALUE,而不是替换了过后的字符串。