什么是反射?

JAVA反射技术是在运行状态中:

  1. 对于任意一个类,都能够知道这个类的所有属性和方法(包括私有)
  2. 对于任意一个对象,都能够调用它的任意一个方法,访问任意一个属性。

这种动态获取类型信息,以及动态调用对象方法的功能,被称为Java语言的反射机制。

总之,反射是提供获取运行时类信息的一种技术,或者更通俗一点反射技术就是操作一个类的Class对象

反射可以在程序运行的过程中,动态地加载一个类,并且能拿到一个类的全部详细信息,从而可以操作该类的属性和方法。反射技术源于Class对象,其本质就是通过一个类的Class对象来获得该类的信息,并操作该类的属性和方法。反射技术应用的起点就是获取某个类的Class对象。那么什么是Class对象

关于Class对象

什么是Class对象?用来描述一个类的对象。Class对象作为一个对象,其也是由类创建出来的,这个类就是java.lang包下的Class类,Class类没有公共构造函数,Class对象是由Java虚拟机在加载类时自动构建的,通过调用类加载器中的defineClass方法来构建。类加载的过程可以分为三步:加载,链接,初始化。其中的第一步:加载,完成的功能就是将一个类的字节码文件读到方法区,并在堆区生成相应的Class对象

Class对象里面记录着每个类的运行时数据结构,基本包括类的所有信息,包括属性,方法,注解等等。基本Java里面的所有类型都有Class对象

  1. 普通的class类(外部类,内部类)
  2. 接口 interface
  3. [] 数组
  4. 枚举 enum
  5. 注解 @interface
  6. 基本数据类型 (boolean,byte,char,int,float,double,long)
  7. void

注意:如果两数组的维数以及元素的数据类型相同,那么他们的Class对象是同一个。

获取Class对象

一般上获取Class对象有4种方式

  1. Class.forName(“全限定类名”):此方法是Class类的静态成员方法,此方法会触发类加载阶段的最后一个阶段:类初始化阶段(执行Clinit()方法)

    1
    Class stringClass = Class.forName("java.lang.String");
  2. 对象名.getClass():此方法是Object类的方法,因此所有的类中都会继承该方法,但是需要用对象来调用。此方法会触发类加载阶段的最后一个阶段:类初始化阶段(执行Clinit()方法)

    1
    Class stringClass = "ZS".getClass();
  3. 类名/接口(类字面常量).class:此方法并不会触发类加载阶段的最后一个阶段:类初始化阶段(不会执行Clinit()方法)意味着该方法获得Class对象时不会自动初始化该Class对象,而是延迟到静态成员访问或静态方法调用的时候才初始化。(如果一个成员变量同时被staticfinal修饰,被称为“编译时常量”,在编译期就已经将其放进常量池,访问这个常量,并不会触发类加载,但是如果只是被他们的其中的一个修饰,则没有这个效果)

    1
    Class personClass = Person.class;
  4. (类加载器)ClassLoader

    1
    2
    ClassLoader classLoader = Person.class.getClassLoader();
    Class<?> pClass = classLoader.loadClass("com.xiaobin.Person");

因为类加载只有一次,因此同一个类的Class对象也只有一个,通过上面不同方法获取到的同一个类的Class对象是同一个对象。

反射的相关用法

Class对象是反射的入口,反射的几乎所有用法都是在使用Class类里面的相关方法,完成一些功能。对于一个类我们获取了其相应的Class对象,那么我们可以获取到它的所有信息(包括私有的)意味着我们可以做很多事情。

属性相关的操作

获取属性

通过Class对象可以获取一个类的所有属性,相关方法如下:

  1. 获取子类以及父类中的一个对应public权限的属性:

    1
    Field value = aClass1.getField(属性名);//获取相应名字的public属性,包含父类的public属性
  2. 获取子类中声明过的所有属性(包括private的)

    1
    Field value1 = aClass1.getDeclaredField(属性名);//获取相应名字的属性(可以是private的),但不包含父类的任何属性
  3. 获取子类和父类中的所有public属性:

    1
    Field[] fields = aClass1.getFields();//获取所有的public属性,包括父类中的所有public属性
  4. 获取本类中声明过的所有属性(包括private的)

    1
    Field[] declaredFields = aClass1.getDeclaredFields();//获取本类中所有的属性(含private的),但是不包含父类的任何属性

以上的方法的特点:

  1. 方法名后面有s的就是获取所有的属性,返回的是属性的数组(File类型数组,里面的每个元素就是一个属性)

  2. 方法名有Declared(宣布的,声明的)的表示只能获取到本类声明的属性(public是声明的、private也是声明的,所以能获取到本类中的所有属性,包括private)

  3. 通过一个子类的Class对象是没有办法直接获取到父类中的private属性的FieldMethodConstructor类的。要想获取到父类中的private属性,要先获取到父类的Class对象,如下:

    1
    2
    Class<?> superclass = aClass1.getSuperclass();
    Field value = superclass.getDeclaredField(父类私有属性名);

    ​ 但是子类会隐式继承父类的private属性的成员,而且在通过以上的方法获得父类中private权限的Field、Method、Constructor之后,通过传进一个子类对象,就可以访问到子类中通过隐式继承得到的父类的private成员,具体如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    public class Demo7 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
    Son son = new Son(666);
    Class<? extends Son> sonClazz = son.getClass();
    // 获取父类类型Class对象
    Class<?> fatherClazz = sonClazz.getSuperclass();
    Field numFiled = fatherClazz.getDeclaredField("num");
    // 破解访问权限
    numFiled.setAccessible(true);
    // 输出子类对象中a的结果(注意这里虽然是通过父类的Class对象获取到的Field类对象,但是是将子类的对象传进去的,表示获取子类对象中的这个成员)
    System.out.println(numFiled.get(son));

    // 子类对象调用父类私有成员方法
    Method sayHiMethod = fatherClazz.getDeclaredMethod("sayHi");
    sayHiMethod.setAccessible(true);
    //这里也是一样的,虽然是通过父类的Class对象获取到的Method类对象,但是调用的时候传进取得对象是子类对象,表示调用的是子类的这个方法。
    Object o = sayHiMethod.invoke(son);
    // 无返回值,o是null
    System.out.println(o);
    }
    }

    class Father {
    private int num;

    public Father(int num) {
    this.num = num;
    }

    private void sayHi(){
    System.out.println("你好啊,朋友!");
    }
    }

    class Son extends Father {
    public Son(int num) {
    super(num);
    }
    }

    以上的程序输出的结果为:

    1
    2
    3
    666
    你好啊,朋友!
    null
  4. 返回的是一个Field类的对象,这个Field类对象里面封装了对应类的属性信息:

    • getName():返回字段名称,例如,"name"
    • getType():返回字段类型,也是一个Class实例,例如,String.class
    • getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义。

    比如String类底层存储字符串的是一个char[],它的定义如下:

    1
    2
    3
     public final class String {
    private final char[] value;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //获取String类的Class对象
    Class strClass = Class.forName(“java.lang.String”);
    //获取成员变量
    Field field = strClass.getDeclaredField("value");
    //获取该成员变量的名
    String name = field.getName(); --> "value"
    //获取该成员变量的类型
    Class<?> type = field.getType(); --> class [C 表示为一个char类型的数组
    //获取该成员的modifiers,返回的是一个int型的值,不同的表示的内容不一样,通过Modifier.isXXX()方法可以获得该成员变量的修饰情况
    int modifiers = field.getModifiers();
    boolean aFinal = Modifier.isFinal(modifiers); --> true
    boolean aPublic = Modifier.isPublic(modifiers); --> false
    boolean aPrivate = Modifier.isPrivate(modifiers); --> true
    boolean aStatic = Modifier.isStatic(modifiers); --> false

设置属性值

在获取到相应的属性之后,我们可以给相应的属性赋值。

在这之前要先解决运行时访问检查的事情。

当Field、Method或Constructor(三者都是反射对象)分别用于设置字段(set(Object obj, Object value))或获取字段(get(Object obj))、调用方法(invoke(Object obj, Object... args))或创建和初始化类的新实例(newInstance(Object... initargs))时,将执行运行时访问检查。主要是检查相关的操作是否有权限等等。

AccessibleObject类,是FieldMethodConstructor的公共父类。通过继承该类,FieldMethodConstructor获得了一个方法setAccessible(boolean flag)方法,可以设置对一个操作是否进行访问检查。

  • accessible默认为false,表示会进行运行时访问检查。注意:就算是public权限的也会进行访问检查,只是访问检查后对其操作没有影响;而protected和private权限的进行访问权限检查之后可能会由于权限不够而报异常。
  • 可以通过setAccessible(true)取消运行访问检查,这样就可以任意访问任何权限的内容。同时因为运行访问检查要耗费一定的时间,因此跳过运行访问检查也可以提升反射的运行速度。

在解决完运行时访问检查之后,我们可以对任意的一个属性进行赋值操作:

  1. 获取相应的属性的值:

    1
    Field.get(对象a名);

    表示获取对象a的相应的属性的值

  2. 设置属性的值:

    1
    Field.set(对象a名,想设置的值);

    表示将对象a的相应属性设置为对应的值。

获取构造器、创建对象实例

通过一个Class对象,我么可以获取到相应类的构造器。

相应的方法如下:

  1. 获得一个public权限的构造器(因为构造器不能被继承,所以这里没有父类的构造器的事情):

    1
    Constructor<Son> constructor = sonClass.getConstructor(Integer.class , String.class);//根据传入的参数类型返回一个对应的public权限的构造器
  2. 获得本类中的所有构造器,包括private修饰的:

    1
    Constructor<Son> constructor = sonClass.getDeclaredConstructor(Integer.class , String.class);//根据传入的参数类型返回一个对应的构造器(任何权限都可以返回)
  3. 获得所有public修饰的构造器:

    1
    Constructor<?>[] constructors = sonClass.getConstructors();//获取所有的public的构造器
  4. 获得所有的构造器,包括private修饰的:

    1
    Constructor<?>[] declaredConstructors = sonClass.getDeclaredConstructors();//获取所有的构造器

获取到构造器之后就可以在程序运行的时候动态创建一个对象实例。

通过:Son son1 = constructor.newInstance(1, 2, "123");通过传进来相应的值创建一个实例。

另外还可以通过Class对象.newInstance()方法来创建一个实例,但是这样只能通过无参构造器来创建实例。

获取并调用方法

通过Class对象获取类里面的方法:

1
2
3
4
Method method1 = sonClass.getMethod(方法名 , 形参列表Calss);//获取相应的public方法
Method method2 = sonClass.getDeclaredMethod(方法名,形参列表Class);//获取相应的方法(含private)
Method[] methods = sonClass.getMethods();//获取所有的public方法
Method[] declaredMethods = sonClass.getDeclaredMethods();//获取所有的方法(含private)

调用方法:

1
method.invok(对象名,实参列表);//调用相应对象的方法,并传入实参,该实参列表是可变参数,因此可以使用Object[]进行传参

另外通过反射进行的方法调用仍然遵循多态,运行时总是呈现实际对象的结果

获取注解信息

通过反射可以获取一个类,类的方法,属性的注解,并拿到里面的相应的信息,这是许多框架的通用做法。

  1. 判断一个类上是否有相应的注解标记:

    1
    boolean annotationPresent = sonClass.isAnnotationPresent(注解名.class);
  2. 获得类上的注解:

    1
    注解名 annotation = (注解名) sonClass.getAnnotation(注解名.class);
  3. 拿到注解后,可以进一步拿到注解里面的信息。比如一个注解里面有一个value属性,则可以通过以下方法来获得:

    1
    String value = annotation.value();