反射机制是java语言提供动态特性的机制。动态特性,指一段代码, 改变其中的变量,即可对这段代码产生功能性的变化[1]。

基本语法

设想有一个Person类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Person {
    public Person(){}

    private String name;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

当我们想使用此类时,需要实例化它,并使用它的属性

1
2
3
Person p = new Person();
p.setName("xiaoming");
System.out.println(p.getName());

这是我们正常使用类时的方法, 除了这种方法,有时候我们不想 import 此类就想使用其方法,反射就可以派上用场了。

同样是上面的setName、getName功能, 用反射实现的代码为:

1
2
3
4
Class cls = Class.forName("reflect.Person"); 
Object personInstance = cls.newInstance();
cls.getMethod("setName", String.class).invoke(personInstance,"xiaohong");
System.out.println(cls.getMethod("getName").invoke(personInstance));

可以看到,只需要一个包名:reflect.Person 即可使用Person的public函数了

反射用到的方法如下:

  • 获取类的⽅法: forName
  • 实例化类对象的⽅法: newInstance
  • 获取函数的⽅法: getMethod
  • 执⾏函数的⽅法: invoke

newInstance 方法的一些问题

当我们使用反射的newInstance方法时,可能会出现一些问题。

此方法的文档如下:

Creates a new instance of the class represented by this Class object. The class is instantiated as if by a new expression with an empty argument list. The class is initialized if it has not already been initialized.

当我们使用newInstance,实际上是调用该类的无参构造函数, 例如在上面的例子里Object personInstance = cls.newInstance();就 调用了Person类的Person()方法。

但是要注意, 以下情况会导致newInstance调用失败

  • 该类没有无参构造函数(注意:当我们没写构造函数时默认存在一个无参构造函数)
  • 该类的构造函数是私有的

以下针对上面两种情况分析.

该类没有无参构造函数

这种情况可以使用getConstructor函数传入构造函数参数

img.png

Params: parameterTypes – the parameter array

Returns: the Constructor object of the public constructor that matches the specified parameterTypes

假如上面例子的构造函数为:

1
2
3
public Person(String name){
    System.out.println(name);
}

我们可以这样传入参数反射:

1
2
3
4
Class cls = Class.forName("reflect.Person");
Object personInstance = cls.getConstructor(String.class).newInstance("name");
cls.getMethod("setName", String.class).invoke(personInstance,"xiaohong");
System.out.println(cls.getMethod("getName").invoke(personInstance));

如果参数类型是List

1
2
3
public Person(list<String> name){
    System.out.println(name);
}

可以传入list类型的参数

1
2
3
4
Class cls = Class.forName("reflect.Person");
Object personInstance = cls.getConstructor(List.class).newInstance(Arrays.asList("name"));
cls.getMethod("setName", String.class).invoke(personInstance,"xiaohong");
System.out.println(cls.getMethod("getName").invoke(personInstance));

总之传入对应的参数类型和参数即可。

值得一提的是, 对于可变参数, 如Person(String...names), 它和Person(String[] names)是等价的 因此我们可以这样写:

1
Object personInstance = cls.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}});

该类的构造函数是私有的

一般这种类用于单例模式。

改下上面的例子的构造函数为private, 为避免复杂化, 先不要参数:

1
2
3
private Person(){
    System.out.println(”“);
}

这时候需要用到反射的getDeclaredConstructor函数了,先看最终代码:

1
2
3
4
5
Class cls = Class.forName("reflect.Person");
Constructor m = cls.getDeclaredConstructor();
m.setAccessible(true);
Object personInstance = m.newInstance();
cls.getMethod("setName", String.class).invoke(personInstance,"xiaohong");

这里可以先介绍以下getDeclaredMethod函数,它和getMethod函数的区别在于:

  • getDeclaredMethod 方法获取的是当前类中“声明”的方法,包括私有的方法,但不包含从父类里继承来的方法
  • getMethod 方法获取的是当前类中所有公共方法,包括从父类继承的方法

getDeclaredMethod获取到的method可以设置可访问性: 即 setAccessible, 设置为true之后就可以访问私有的方法了。

至于上面私有的构造方法,也是同理。