JavaSE-反射

简要介绍一下反射概念及基本方法

由于反射之前一直没什么实际机会用到,最近在了解泛型擦除时有讲到,重新复习一下。

什么是反射

反射是指在程序运行的时候,可以动态地去获取一个类的所有属性并对可其进行操作。
通俗一点去解释就是,通过反射,我们可以拿到一个类的所有信息,包括:

  • 类名
  • 所有构造方法
  • 所有字段属性
  • 所有方法及其信息

它不仅仅可以获得以上信息,还可以获得以上信息的修饰符(private public等)。

反射到底获得了什么

先说答案:字节码文件对象
有人会问,这是什么?来看看我们Java常见的几种文件:

  • java文件:编写的java代码。
  • 字节码文件:java文件编译之后的class文件(是真实存在的,默认在/target下)
  • 字节码文件对象:当class文件加载到内存之后,虚拟机自动创建出来的对象。

所以说,我们拿到的就是在程序编译运行后的字节码文件对象,因为它是由.java文件编译过来的,因此包含了整个类的信息。

字节码文件对象的获取方式

字节码文件对象

在Java反射包下提供了用于接受字节码对象Class,我们一般用它来接受获取到的字节码对象。

获取对象的三种方式

  1. 通过Class类里面的静态方法forName(“全类名”)获取;
  2. 通过类.class属性获取;
  3. 通过实例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 getConstructor(Class<?>... parameterTypes)获取指定构造(只能public修饰)
Constructor getDeclaredConstructor(Class<?>... parameterTypes)获取指定构造(包含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类型,对照表如下:

修饰符返回值
public1
private2
protect4
static8
final16
synchronized32
volatile64
transient128
native256
interface512
abstract1024
strict2048
  • 为什么做成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等,理解了反射后会更好地去学习后面的内容。

LICENSED UNDER CC BY-NC-SA 4.0
Comment