由于反射之前一直没什么实际机会用到,最近在了解泛型擦除时有讲到,重新复习一下。
什么是反射
反射是指在程序运行的时候,可以动态地去获取一个类的所有属性并对可其进行操作。
通俗一点去解释就是,通过反射,我们可以拿到一个类的所有信息,包括:
- 类名
- 所有构造方法
- 所有字段属性
- 所有方法及其信息
它不仅仅可以获得以上信息,还可以获得以上信息的修饰符(private public等)。
反射到底获得了什么
先说答案:字节码文件对象
有人会问,这是什么?来看看我们Java常见的几种文件:
- java文件:编写的java代码。
- 字节码文件:java文件编译之后的class文件(是真实存在的,默认在/target下)
- 字节码文件对象:当class文件加载到内存之后,虚拟机自动创建出来的对象。
所以说,我们拿到的就是在程序编译运行后的字节码文件对象,因为它是由.java文件编译过来的,因此包含了整个类的信息。
字节码文件对象的获取方式
字节码文件对象
在Java反射包下提供了用于接受字节码对象Class
,我们一般用它来接受获取到的字节码对象。
获取对象的三种方式
- 通过Class类里面的静态方法
forName(“全类名”)
获取; - 通过
类.class
属性获取; - 通过实例
getClass()
方法获取。
// 通过 Class.forName("完整类名") 获取
Class<?> clazz = Class.forName("com.example.demo.CosClient");
// 通过 类名.class 获取
Class<CosClient> cosClientClass = CosClient.class;
// 通过 实例.getClass() 获取
CosClient cosClient = new CosClient();
Class<?> aClass = cosClient.getClass();
值得注意的是,因为获取到的是文件字节码对象,而根据上面所说的,它是由class文件运行加载到内存之后创造出来的对象,因此是在内存里是唯一的,由此可得
class == aClass
:true
即上述代码中三种方式获取到的对象是相同的!
获取类的构造方法
构造方法参数
前面说过,反射可以获得类内的一切信息,那么获得构造方法也肯定是可以的。
Java也提供了用于接受构造方法的类:Constructor
方法名 | 说明 |
---|---|
Constructor<?>[] getConstructors() | 获得所有的构造(只能public修饰) |
Constructor<?>[] getDeclaredConstructors() | 获得所有的构造(包含private修饰) |
Constructor | 获取指定构造(只能public修饰) |
Constructor | 获取指定构造(包含private修饰) |
从上面的方法可以看到,反射不仅仅可以获得公有构造,也可以获得私有构造,还可以通过指定的参数类型获取到指定的构造方法。
- 例子:
先给出类的定义(下文都用这个作为例子)
@Data // Lombok: 生成Getter和Setter
@NoArgsConstructor // Lombok: 生成无参构造
@AllArgsConstructor // Lombok: 生成全参构造
public class User {
private String name;
private Integer age;
private String address;
}
- 获得所有构造的例子:
Class<User> userClass = User.class; System.out.println(Arrays.toString(userClass.getConstructors())); // 运行结果:[public com.example.demo.User(), public com.example.demo.User(java.lang.String,java.lang.Integer,java.lang.String)]
- 获得空参构造的例子
Class<User> userClass = User.class; System.out.println(userClass.getConstructor()); // 运行结果: public com.example.demo.User()
- 获得全参构造的例子
Class<User> userClass = User.class; System.out.println(userClass.getConstructor(String.class, Integer.class, String.class)); // 运行结果: public com.example.demo.User(java.lang.String,java.lang.Integer,java.lang.String)
可见,已经成功获得了对应的构造方法。
获取到方法并调用
Constructor
类内提供了newInstance()
方法来对构造器进行调用,返回一个对应类的实例对象,我们只需要将被Constructor类实例中所包含的构造方法需要的参数传进去即可,下面是一个全参构造的例子:
Class<User> userClass = User.class;
System.out.println(userClass.getConstructor(String.class, Integer.class, String.class)
.newInstance("Polister", 18, "unknown"));
// 运行结果: User(name=Polister, age=18, address=unknown)
可见,已经获得了对应类的实例。
获取类的字段(成员变量)
成员变量参数
与构造方法相同,Java也为通过字节码对象获得的字段提供了接收对象:Field
。
方法名 | 说明 |
---|---|
Field[] getFields() | 返回所有成员变量对象的数组(只能拿public的) |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组,存在就能拿到 |
Field getField(String name) | 返回单个成员变量对象(只能拿public的) |
Field getDeclaredField(String name) | 返回单个成员变量对象,存在就能拿到 |
因为与构造方法获取比较相似,所以不做具体描述,大家去尝试即可
获取/修改字段的值
方法 | 说明 |
---|---|
void set(Object obj, Object value) | 赋值 |
Object get(Object obj) | 获取值 |
- obj:要操作的实例对象(我们要获得值/修改值肯定是要对实例对象操作,所以这里需要传入实例对象)
获取类的成员方法
参数说明
获取类里的成员方法也与上述方法相同,Java提供的接收对象为Method
。
具体获得的成员方法如下:
方法名 | 说明 |
---|---|
Method[] getMethods() | 返回所有成员方法对象的数组(只能拿public的) |
Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,存在就能拿到 |
Method getMethod(String name, Class<?>... parameterTypes) | 返回单个成员方法对象(只能拿public的) |
Method getDeclaredMethod(String name, Class<?>... parameterTypes) | 返回单个成员方法对象,存在就能拿到 |
如何调用
主要是使用 invoke
方法进行调用,在获取到对应方法后,对其调用invoke
方法并传入该方法所需要的参数,即可以进行调用,返回值就是被调用方法的返回值。
注意:如果调用的方法我们原来没有权限访问的话,需要通过setAccessible(true)
方法来使其暂时可访问!
获取方法/字段里的修饰符
通过方法对象的getModifiers()
的方法,可以获得他们的修饰符,但是我们会发现返回值是一个int类型,对照表如下:
修饰符 | 返回值 |
---|---|
public | 1 |
private | 2 |
protect | 4 |
static | 8 |
final | 16 |
synchronized | 32 |
volatile | 64 |
transient | 128 |
native | 256 |
interface | 512 |
abstract | 1024 |
strict | 2048 |
- 为什么做成2的幂指数? 其实就是为了在复杂多修饰符的情况可以直接叠加(类似于Linux的权限值)
eg:public static final int i = 1;
返回值就是1 + 8 + 16 = 25
反射的应用——泛型类型擦除实例
List<Integer> list = new ArrayList<>();
Class clazz = list.getClass();
Method[] methods = clazz.getMethods();
System.out.println(Arrays.toString(methods));
我们会发现输出了 :
public boolean java.util.ArrayList.add(java.lang.Object), public void java.util.ArrayList.add(int,java.lang.Object)......
参数类型是Object
而不是Integer
!
这说明已经进行了类型擦除!
总结
反射可以让我们直接获取到类的所有信息,在一些需要提供类信息的工具类里面可以灵活使用,比如动态代理、AOP等,理解了反射后会更好地去学习后面的内容。