JAVA反射机制

836 阅读8分钟

一、反射是什么?

首先这个反射肯定和物理的反射不一样(废话?)
但是!它有着异曲同工之妙,我们先来聊一聊正射是什么,在java中我们通常创建一个对象,是直接调用一个类的构造方法, new 一个对象出来,我们手上是有一个确定的类的。但是其实这是有局限性的:就是我们不能够根据具体的情况,具体的需求来动态的编写我们的程序。但是反射就能够做到这一点,在代码运行之前,我们不确定将来会使用哪一种数据结构,只有在程序运行时才决定使用哪一个数据类,而反射可以在程序运行过程中动态获取类信息调用类方法,这样就能够实现我们的动态编程。反射就好像是一个类的对象站在镜子前面看镜子里的自己就能得到类的各种信息

举个小栗子,在我们游戏运行的时候,想搞个外挂,那么我外挂的设计肯定不能是写死的,而是根据游戏程序具体问题具体分析来修改游戏数据,这时候我们就要使用到反射的原理。

  • 反射的思想在程序运行过程中确定和解析数据类的类型。
  • 反射的作用:对于在编译期无法确定使用哪个数据类的场景,通过反射可以在程序运行时构造出不同的数据类实例

反射中的用法有非常非常多,常见的功能有以下这几个:

  • 在运行时获取一个类的 Class 对象
  • 在运行时构造一个类的实例化对象
  • 在运行时获取一个类的所有信息:变量、方法、构造器、注解

二、如何使用反射

首先给出本章后面举栗的类User

class User {
    private String name;
    private int id;
    private int age;
    public User() {
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setId(int id) {
        this.id = id;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public User(String name, int id, int age) {
        this.name = name;
        this.id = id;
        this.age = age;
    }
    public String getName() {return name;}
    public int getId() { return id;}
    public int getAge() { return age;}
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + ''' +
                ", id=" + id +
                ", age=" + age +
                '}';}
}

在 Java 中,每一个类都会有专属于自己的 Class 对象,当我们编写完.java文件后,使用javac编译后,就会产生一个字节码文件.class,在字节码文件中包含类的所有信息,如属性构造方法方法······当字节码文件被装载进虚拟机执行时,会在内存中生成 Class 对象,它包含了该类内部的所有信息,在程序运行时可以获取这些信息。

获取class对象

  • 类名.class:这种获取方式只有在编译前已经声明了该类的类型才能获取到 Class 对象
Class c1 = Class.forName("reflection.User");
  • Class.forName(className):通过类的全限定名获取该类的 Class 对象
Class c1 = Class.forName("reflection.Student2");
  • 实例.getClass():通过实例化对象获取该实例的 Class 对象
User user = new User();
Class aClass = user.getClass();

那么在获取到class对象以后我们就能够做到很多的事情,毕竟class对象有着一个类全部的信息,我们自然而然也就能够获取到这个类的成员变量方法类信息等等。甚至还能够破解原本这个类设置的private权限....

  • Class本身也是一个类
  • Class对象只能由系统建立对象
  • 一个加载的类在JVM中只会有一个Class实例
  • 一个Class对象对应的是一个加载到JVM中的一个.class文件
  • 每个类的实例都会记得自己是由哪个Class实例所生成
  • 通过Class可以完整地得到一个类中的所有被加载的结构
  • Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象

image.png

通过反射实例化对象

我们之前没学反射的时候,都是new一个对象出来,那么现在我们还可以通过class对象来实例化一个类对象。

通过反射构造一个类的实例方式有2种:

  • Class 对象调用newInstance()方法 (新版本不推荐使用这个方法,这个方法调用的是无参的构造方法,所以在User类上需要加上无参构造方法,不然会报异常)
User user = (User) c1.newInstance();
System.out.println(user);

image.png

  • Constructor 构造器调用newInstance()方法(这个是比较推荐的方法,并且可以根据需要传入参数来控制调用有参的构造器)
User user2 = (User) c1.getDeclaredConstructor(String.class, int.class, int.class).newInstance("马千里",1,21);
System.out.println(user2);

image.png

通过class获取类的信息

image.png

一个类主要的信息就是图中罗列的这些,我们如何通过class对象获取到这些东西?

获取类中的变量(Field)

class对象有常用的几个方法:

  • Field[] getFields():获取类中所有被public修饰的所有变量

  • Field getField(String name):根据变量名获取类中的一个变量,该变量必须被public修饰

  • Field[] getDeclaredFields():获取类中所有的变量,但无法获取继承下来的变量(这也是我之前说破解private的地方

  • Field getDeclaredField(String name):根据姓名获取类中的某个变量,无法获取继承下来的变量

//通过反射操作属性
User user4 = (User)c1.newInstance();
Field name = c1.getDeclaredField("name");
//不能直接访问私有属性,关闭程序的安全检测,属性或者方法的
name.setAccessible(true);//关闭程序的安全检测,属性或者方法的
name.set(user4,"田浩辰");
System.out.println(user4.getName());

这里我们还使用了一个Field.setAccessible方法来关闭程序的安全检查机制,在我们编程的时候如果不需要让程序检查权限就可以关闭这个,能够提高程序的运行效率,这里就不多做解释

image.png

获取类中的方法(Method)

        //通过反射调用普通方法
        User user3 = (User) c1.newInstance();
//        System.out.println(age);
        //通过反射获取一个方法
        Method setName = c1.getMethod("setName", String.class);
        //invoke :激活的意思
        //(对象,“方法的值”)
        setName.invoke(user3,"赵一鑫");
        System.out.println(user3.getName());

image.png

获取类的构造器(Constructor)

  • Constuctor[] getConstructors():获取类中所有被public修饰的构造器
  • Constructor getConstructor(Class...<?> paramTypes):根据参数类型获取类中某个构造器,该构造器必须被public修饰
  • Constructor[] getDeclaredConstructors():获取类中所有构造器
  • Constructor getDeclaredConstructor(class...<?> paramTypes):根据参数类型获取对应的构造器

每种功能内部以 Declared 细分为2类:

Declared修饰的方法:可以获取该类内部包含的所有变量、方法和构造器,但是无法获取继承下来的信息

Declared修饰的方法:可以获取该类中public修饰的变量、方法和构造器,可获取继承下来的信息

获取注解(Annotation)

获取注解单独拧了出来,因为它并不是专属于 Class 对象的一种信息,每个变量,方法和构造器都可以被注解修饰,所以在反射中,Field,Constructor 和 Method 类对象都可以调用下面这些方法获取标注在它们之上的注解。

  • Annotation[] getAnnotations():获取该对象上的所有注解
  • Annotation getAnnotation(Class annotaionClass):传入注解类型,获取该对象上的特定一个注解
  • Annotation[] getDeclaredAnnotations():获取该对象上的显式标注的所有注解,无法获取继承下来的注解
  • Annotation getDeclaredAnnotation(Class annotationClass):根据注解类型,获取该对象上的特定一个注解,无法获取继承下来的注解

只有注解的@Retension标注为RUNTIME时,才能够通过反射获取到该注解,@Retension 有3种保存策略:

  • SOURCE:只在**源文件(.java)**中保存,即该注解只会保留在源文件中,编译时编译器会忽略该注解,例如 @Override 注解
  • CLASS:保存在字节码文件(.class)中,注解会随着编译跟随字节码文件中,但是运行时不会对该注解进行解析
  • RUNTIME:一直保存到运行时用得最多的一种保存策略,在运行时可以获取到该注解的所有信息

获取泛型

我们通过调用getGenericParameterTypes在获取参数类型的时候,如果对象的参数是个Map,List这样的,我们只能够得到带有泛型的Map和List名称,那么我想要得到真实的泛型如何操作呢?需要在遍历一次,然后调用getActualTypeArguments方法得到它真实的类型,具体实现看代码:

Method method = test11.class.getMethod("test1", Map.class, List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();

//获得泛型的参数类型
for (Type genericParameterType : genericParameterTypes) {
    System.out.println("*"+genericParameterType);
    if(genericParameterType instanceof ParameterizedType){
        Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
        for (Type actualTypeArgument : actualTypeArguments) {
            System.out.println(actualTypeArgument);
        }
    }
}

System.out.println("---------------------------");
method = test11.class.getMethod("test2",null);
Type genericReturnType = method.getGenericReturnType();
if(genericReturnType instanceof ParameterizedType) {
    Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
    for (Type actualTypeArgument : actualTypeArguments) {
        System.out.println(actualTypeArgument);
    }
}

三、小练习:自定义注释 并通过反射得到注释信息

Class c1 = Class.forName("reflection.Student2");
//通过反射获取注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
    System.out.println(annotation);
}
//获取注解的value的值
Tablema tablema = (Tablema) c1.getAnnotation(Tablema.class);
System.out.println(tablema.value());
//获得类指定的注解
Field f = c1.getDeclaredField("name");
Fieldma annotation = f.getAnnotation(Fieldma.class);
System.out.println(annotation.columnName());
System.out.println(annotation.Type());
System.out.println(annotation.length());
@Tablema("db_student")
class Student2{
    @Fieldma(columnName = "db_id", Type = "int" ,length = 10)
    private int id;
    @Fieldma(columnName = "db_age", Type = "int" ,length = 10)
    private int age;
    @Fieldma(columnName = "db_name", Type = "varchar" ,length = 3)
    private String name;

    public Student2() {
    }

    public Student2(int id, int age, String name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Student2{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + ''' +
                '}';
    }
}
//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Tablema{
    String value();
}

//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Fieldma{
    String columnName();
    String Type();
    int length();
}

image.png 那么反射就讲到这里,文章部分引用至:程序员cxuan
链接:juejin.cn/post/686432… 侵删。