反射,是Java的一种高级特性。就我而言,实际开发中很少遇到用反射的需求,但这并不能否定反射的重要地位,这篇文章来简单说一说。
前言
在Java里,有一个类,名字叫Class
,这个名字总是容易给人一种这是个特殊的类。其实,和其他类一样,这也是个普通的类,在java.lang
包下,lang就是language的简写,里面都是和Java语言本身相关的东西。这个类里面包含了一个类的所有信息:
1 | // 省略了一些代码 |
在JVM首次调用到一个类的时候,会先将这个类加载到JVM里,这个过程就是在为这个类创建Class实例,且JVM只会为每个类创建一个Class实例,如果存在则在直接使用。可以看到,Class只有一个构造方法,而且是私有的,只有JVM可以调用。
那么,怎么获取到JVM创建的Class实例呢?一共有3种方式:
1 | // 方式1:通过类的静态变量class |
通过类的Class实例,我们可以创建类的实例,调用类的所有成员变量、和成员方法,包括私有的。
方法概览
通过反射,一般有三个目的:获取构造方法Constructor来创建实例、获取方法Method来调用,以及获取字段Field来set/get值。除此之外,还可以获取其他信息,如注解,来完成额外的操作,这些在这先不讨论。
构造方法 | 字段 | 方法 | |
---|---|---|---|
单个public | getConstructor() | getField() | getMethod() |
所有public | getConstructors() | getFields() | getMethods() |
类里任一 | getDeclaredConstructor() | getDeclaredField() | getDeclaredMethod() |
类里所有 | getDeclaredConstructors() | getDeclaredFields() | getDeclaredMethods() |
- 不加s是获取单个,加s是获取所有
- 不加declared的,是获取public的,protected都不行,必须public,可以是自己的,也可以是继承的
- 加declared的,是获取类里面自己声明的,只能是写在类里的成员,不能是继承的,公开的、私有的都可以获取
示例类
创建了一个示例接口和一个示例类,很简单,看一眼就可以跳过。
1 | // Person接口 |
通过反射获取构造方法
- 获取无参构造方法
1
2
3
4
5// 获取Class实例
Class<Student> clz = Student.class;
Constructor<Student> cst = clz.getConstructor();
System.out.println(cst.newInstance().score); // 缺省值 0 - 获取有参数的构造方法
1
2
3
4Class<Student> clz = Student.class;
Constructor<Student> cst = clz.getConstructor(int.class, int.class);
System.out.println(cst.newInstance(80, 1).score); // 传入值 80 - 获取私有的构造方法
1
2
3
4
5
6Class<Student> clz = Student.class;
// 这里必须用加declared的,因为带一个参数的构造方法是私有的
Constructor<Student> ctr3 = clz.getDeclaredConstructor(int.class);
ctr3.setAccessible(true); // 使用私有的成员前,需要先设置为可访问
System.out.println(ctr3.newInstance(55).score);
通过反射获取字段
字段,Field,即类里面声明的成员变量。像上面,通过Class实例获取到类的构造函数,可以用其创建类的实例,那获取到字段有什么用呢?用字段可以获取其对应的值,所以,就需要一个类的实例,有了实例,才能获取。
- 获取公开字段
1
2
3
4
5
6
7Class<Student> clz = Student.class;
Field score = clz.getField("score"); // 通过字段名字获取
// 使用字段,需要一个Student实例
Student student = new Student(60, 1);
// 这句话的意思就是,获取student实例中score字段的值
System.out.println(score.get(student)); // 60 - 获取私有字段
1
2
3
4
5
6
7Class<Student> clz = Student.class;
Student student = new Student(60, 1);
Field sex = clz.getDeclaredField("sex"); // 这里必须加declared 通过字段名字获取
sex.setAccessible(true); // 设置可访问
sex.set(student, 10); // 先设置为10
System.out.println(sex.get(student)); // 10
通过反射获取方法
字段可通过名字获取,但方法不可只通过名字,因为会存在方法重载,即几个方法名字一样,但参数类型或者数量不同,所以要获取方法,就需要指定方法名称,同时和方法的参数的类型列表。和字段相同的是,方法在调用的时候,也需要一个实例,有了实例,才能在这个实例上调用其方法。
- 获取公开方法
1
2
3
4
5Class<Student> clz = Student.class;
Student student = new Student(60, 1);
Method sayHi = clz.getMethod("sayHi", String.class); // 指明方法名,和参数类型
sayHi.invoke(student, "Teacher"); // student say hi to :Teacher - 获取私有方法
1
2
3
4
5
6
7Class<Student> clz = Student.class;
Student student = new Student(60, 1);
Method setSex = clz.getDeclaredMethod("setSex", int.class); // 指明方法名,和参数类型
setSex.setAccessible(true); // setSex是私有的,先设置为可访问
setSex.invoke(student, 100); // 调用,设置sex的值
System.out.println(student.getSex()); // 100
总结
通过上面的例子,可以看出:反射都是基于类的Class实例,通过里面的成员,来进行一些操作的。先获取到Class实例,再获取对应的成员变量,比如字段、方法等,然后再利用这些成员,采取一些操作来达成目的。
没有太多代码,就不创建项目了,附上所有代码: