反射Reflection

Reflection中文为反射、影子,读音为rəˈflekSH(ə)n,是动态语言的关键,在程序执行期间,可以通过反射的API获取类的内部信息,并且能够直接操作任意对象的内部方法、属性

在加载完类之后,在堆中产生了一个Class类型的对象,一个类只有一个Class对象,这个对象包含完整的类的结构信息,可以通过这个对象查看类的结构,所以被称为反射(Reflection)

反射提供的功能:

  • 运行时,判断任意一个对象所属的类
  • 运行时,构造任意一个类的对象
  • 运行时,判断任意一个类所具有的成员变量和方法
  • 运行时,获取泛型信息
  • 运行时,调用任意一个类的成员变量和方法
  • 运行时,处理任何注解
  • 生成动态代理

API主要是在java.lang.reflect.

Class类是用来描述类的类

在反射之前一个类外只能调用另一个类的非私有的方法、属性,可以通过反射调用一个其他类的私有属性和方法

每个类在运行时,都可以看作是Class类的一个实例

Class实例

获取Class实例的方式有4种,分别是:

  • 调用运行时的属性:类名.class返回Class类型,Class类是有泛型的,可以通过

    • Class<类名> 变量名 = 类名.class;
  • 通过运行时类的对象获取:首先要有一个要获取Class实例的类的实例,调用实例.getClass()方法来获取,由Object类所定义

  • 调用Class类中的静态方法:Class.forName(类的路径字符串),字符串中要包含类所在的包,会抛出异常,需要try-catch环绕

  • 使用类的加载器:

    • 首先获取当前类中已经加载的类

      • ClassLoader 变量名1 = 当前的类名.class.getClassLoader();
        
    • 再从已经加载的类中加载需要的类的实例

      • Class 变量名2 = 变量名1.loadClass("需要加载的类名");
        

以上方法获取的都是同一个实例,通过==可以判断出时true

加载到内存的运行时类会在内存中缓存一段时间,再此时间内,可以通过不同的方式获取这个类,以上就是不同的方式

image-20210814194920612

通过反射创建对象

  1. 首先要有一个Class实例
  2. 调用Class实例中的.newInstance()方法,该方法返回泛型(取决于上一步锁定义的Class实例传递给泛型的类型)

使用该方法进行创建时,需保证有一个空参构造器,并且访问权限不能是私有的

public class ReflectionTest {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Class<Cat> c = Cat.class;
        Cat cat2 = c.newInstance();
        System.out.println(cat2);
    }
}

class Cat {
    private String name;
    private int old, weight;

    public Cat(String name, int old, int weight) {
        this.name = name;
        this.old = old;
        this.weight = weight;
        System.out.println("非私有的构造器");
    }

    private Cat(String name){
        this.name = name;
        System.out.println("私有的构造器");
    }

    public Cat(){

    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", old=" + old +
                ", weight=" + weight +
                '}';
    }
}

获取类中的属性

  1. 创建一个Class实例
  2. 调用实例中的.getFields(),返回Field[]类型,中文为领域,读音为fēld

可以使用循环遍历输出数组内容,只能获取到父类以及本身的public属性

若调用实例中的.getDeclaredFields(),则返回该类中所有的属性(不包含父类),无论权限是怎么样的,Declared,中文声明的、宣布、公告的、公然的,读音为dəˈklerd

Field类

在返回的Field类型中,有直接获取的该类型的权限的方法,.getModifiers(),返回一个整数,modifiers,中文为修饰符,读音为ˈmädəˌfī(ə)r,通过.getName()方法获取方法名

可以使用.getType()获取数据类型 返回Class类型,但当类型为一个类时,输出时显示class 包名类名,可以再调用.getName()隐藏掉Class

//比方说String类型会显示 class java.lang.String
//隐藏之后显示 class java.lang.String
		for(Field f : fields){
            System.out.println(Modifier.toString(f.getModifiers()));
            System.out.println(f.getType());//类时会显示class
        }
		for(Field f : fields){
            System.out.println(Modifier.toString(f.getModifiers()));
            System.out.println(f.getType().getName().getName());//隐藏class
        }
Modifier类

Modifier类中有中有许多静态方法,其中Modifier.toString(int x)可将上述的getModifiers()方法中所获得的整数翻译为String类型,也可以用Modifier.isXXX(int x)获取是否为某个权限,返回布尔类型

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Class<Cat> c = Cat.class;
        Field[] fields = c.getDeclaredFields();
        for(Field f : fields){
            System.out.println(Modifier.toString(f.getModifiers()));
        }
    }
}

获取类中的方法

  1. 创建一个Class实例
  2. 调用实例的.getMethods()获取父类以及本类的公共方法,.getDeclaredMethods()获取本类的所有方法
    • 返回Method[]方法
    • 可以在Method类型中通过.getName()方法获取方法名
  • 获取方法声明的注解:
    • 首先要获取方法的属性
    • 调用.Annotations()方法进行获取,返回Annotation[]
  • 获取权限修饰符:
    • 与获取类中属性的权限修饰符的方法相同
    • 调用Method数组中元素的.getModifiers()方法,获得一个整型数字
    • 再使用Modifier.toString(int x)进行翻译
    • System.out.println(Modifier.toString(m.getModifiers()));
  • 获取方法的返回值类型
    • .getReturnType()方法
  • 获取方法名
    • .getName()
  • 获取方法的形式参数的类型:
    • .getParameterTypes(),返回Class[],获取方法的形式参数的类型
    • parameter中文为参数,读音为pəˈramədər
  • 获取方法可能抛出的异常:
    • .getExceptionTypes(),返回Class[],直接进行输出会显示class字符,跟前边一样,使用.getName(),不会显示class

获取类中的构造器

  • 获取class实例
  • 使用实例调用.getConstructors()方法,返回Constructor[]类型,获取当前类中的public权限的构造器
    • constructor中文为构造器,读音````kənˈstrəktər```
  • 使用实例调用.getDeclaredConstructors(),返回Constructor[]类型,获取当前类中的所有构造器

获取运行时类的父类

  • 获取一个class实例
  • 获取这个class实例的父类:
    • 调用.getSuperClass(),返回Class类型

获取带有泛型的父类

.getGenericSuperClass(),返回Type类型

获取带有泛型的父类的泛型

image-20210815175157588

获取运行时类实现的接口

调用.getInterfaces()方法,返回Class[]

获取运行时类的包

调用.getPackage(),返回Package类型

public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
    Class cat = Class.forName("Animal.Cat");
    Package aPackage = cat.getPackage();
    System.out.println(aPackage);
}

image-20210815180621791

调用运行时类的指定结构

调用指定的属性、方法、构造器

需要保证有一个实例化的Class,并且创建了一个运行时类的对象

属性

获取指定的属性

会抛出异常,因为不能确保有这个属性

.getField(String name)方法,获取权限为private的属性,返回Field类型

.getDeclaredField(String name)方法,返回Field类型

设置属性的值
  • 要有一个实例化的Class
  • 创建一个运行时类的对象
  • 调用.getField(String name)方法或者.getDeclaredField(String name)方法进行获取属性
    • 调用获取到的属性的.set(对象, 值)方法进行设置值
    • 获取一个对象中这个属性的值:获取到的属性.get(对象),返回Object类型,需要时可以进行强制类型转换
    • 对于私有的属性,可以通过.getDeclared()获取到属性,但不能直接使用set方法进行赋值,会抛出非法访问的异常,默认情况下不允许修改私有属性的值,若想使用set方法赋值,则必须要调用获取到的属性.setAccessible(true)的方法 将当前属性设置为可访问的,中文为设置无障碍
public class ReflectionTest {
    public static void main(String[] args) throws Exception {
        //要有一个实例化的Class
        Class cat = Class.forName("Animal.Cat");
        //创建一个运行时类的对象
        Cat c = (Cat) cat.newInstance();
        //获取特定的属性
        Field gggg = cat.getField("gggg");
        //设置值
        gggg.set(c, 11);
        //获取这个属性在一个对象中的值
		System.out.println(gggg.get(c));
        
        
        //获取私有的属性
        Field name = cat.getDeclaredField("name");
        //设置当前属性是可访问的
        name.setAccessible(true);
        //设置值
        name.set(c, "hello");
        //获取这个属性在一个对象中的值
        System.out.println(name.get(c));
    }
}

class Cat implements Serializable {
    private String name;
    private int old, weight;
    public int gggg;

    public Cat(String name, int old, int weight) {
        this.name = name;
        this.old = old;
        this.weight = weight;
        System.out.println("非私有的构造器");
    }

    private Cat(String name) {
        this.name = name;
        System.out.println("私有的构造器");
    }

    public Cat() {

    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", old=" + old +
                ", weight=" + weight +
                '}';
    }

    private void test(int x) {
        System.out.println("我是隐藏的属性");
    }

    public void test2() throws Exception, RuntimeException {

    }

    public int getGggg() {
        return gggg;
    }
}
调用运行时类的指定方法

非静态的必须要有一个运行时类的对象

非静态:

  • 要有一个实例化的Class

  • 创建一个运行时类的对象

  • 调用实例化的Class.getMethod(String name, 可变形参)实例化的Class.getDeclaredMethod(String name, 可变形参),返回Method类型

    • 因为一个方法可能会有多个重载,所以需要指定该方法是哪个,因此要在可变形参中指明,格式为类型.class

    • 例如

      class a{
          public void a(int x){
          }
      
          public void a(int x, String x){
          }
      }
      
      class b{
          public static void main(String[] args){
              Class cla = b.Class;
              Method a1 = cla.getMethod("a", int.class);//代表第二行的方法
              Method a2 = cla.getMethod("a", int.class, String a);//代表第五行的方法
          }
      }
      
  • 调用方法:调用获取到方法.invoke(对象, 可变参数的值),可变参数为上边各个参数的值,invoke中文为调用,读音为inˈvōk,该方法返回值为Object类型(如果没有,返回null),该方法的返回值为类中调用的方法的返回值

    • 对于私有的方法,仍然需要通过.setAccessible(true)才可以访问

    • public class ReflectionTest {
          public static void main(String[] args) throws Exception {
              //实例化class类
              Class cat = Class.forName("Animal.Cat");
              //创建对象
              Cat c = (Cat) cat.newInstance();
              //获取指定的方法
              Method test2_1 = cat.getDeclaredMethod("test2", int.class, String.class);
              //调用方法
              test2_1.invoke(c, 2, "cat");
      
              //获取指定的方法
              Method test2_2 = cat.getDeclaredMethod("test2", int.class);
              //调用方法
              test2_2.invoke(c, 2);
      
              //私有的方法
              Method test = cat.getDeclaredMethod("test");
              //设置当前方法是可访问的
              test.setAccessible(true);
              //调用方法
              test.invoke(c);
          }
      }
      
      class Cat implements Serializable {
          private String name;
          private int old, weight;
          public int gggg;
      
          public Cat(String name, int old, int weight) {
              this.name = name;
              this.old = old;
              this.weight = weight;
              System.out.println("非私有的构造器");
          }
      
          private Cat(String name) {
              this.name = name;
              System.out.println("私有的构造器");
          }
      
          public Cat() {
      
          }
      
          @Override
          public String toString() {
              return "Cat{" +
                      "name='" + name + '\'' +
                      ", old=" + old +
                      ", weight=" + weight +
                      '}';
          }
      
          private void test() {
              System.out.println("我是隐藏的方法");
          }
      
          public void test2(int x){
              System.out.println("我是公共的方法" + x);
          }
      
          public void test2(int x, String name){
              System.out.println("我是公共的方法" + x + name);
          }
      
          public void test2(){
              System.out.println("公共方法");
          }
      
          public int getGggg() {
              return gggg;
          }
      }
      

静态方法:

  • 要有一个实例化的Class

  • 无需创建一个运行时类的对象

  • 调用实例化的Class.getMethod(String name, 可变形参)实例化的Class.getDeclaredMethod(String name, 可变形参),返回Method类型

    • 因为一个方法可能会有多个重载,所以需要指定该方法是哪个,因此要在可变形参中指明,格式为类型.class
    • 调用时可以使用获取到方法.invoke(对象, 可变参数的值),其中对象可以写为null,即获取到方法.invoke(null, 可变参数的值)
    • 对于私有的方法,仍然需要通过.setAccessible(true)才可以访问
//静态方法
Method t1 = cat.getMethod("t1", int.class);
t1.invoke(null, 8);

Method t2 = cat.getDeclaredMethod("t2");
t2.setAccessible(true);
t2.invoke(null);

class Cat{
    public static void t1(int x){
        System.out.println("公共的静态方法" + x);
    }

    private static void t2(){
        System.out.println("私有的静态方法");
    }
}
静态变量/属性

可以参考静态方法,可以在对象的位置写null

调用运行时类的构造器
  • 要有一个实例化的Class
  • 创建一个运行时类的对象
  • 调用实例化的Class.getConstructor(可变形参)实例化的Class.getDeclaredConstructor(可变形参),返回Constructor类型,可变形参为构造器的参数列表
    • 对于私有的构造器,仍然需要通过.setAccessible(true)才可以访问
    • 创建对象:使用获取到Constructor类型的构造器.newInstance(参数)进行创建对象,返回Object,但也需要强制类型转换
public class ReflectionTest {
    public static void main(String[] args) throws Exception {
        //实例化class
        Class cat = Class.forName("Animal.Cat");
        //获取指定的构造器
        Constructor constructor = cat.getConstructor(String.class, int.class, int.class);
        //创建对象,
        Cat c = (Cat) constructor.newInstance("cat", 11, 22);
        System.out.println(c);

        //私有构造器
        Constructor constructor2 = cat.getDeclaredConstructor(String.class);
        //设置私有构造器可以访问
        constructor2.setAccessible(true);
        //创建对象
        Cat c2 = (Cat)constructor2.newInstance("cat2");
        System.out.println(c2);
    }
}

class Cat implements Serializable {
    private String name;
    private int old, weight;
    public int gggg;

    public Cat(String name, int old, int weight) {
        this.name = name;
        this.old = old;
        this.weight = weight;
        System.out.println("非私有的构造器");
    }

    private Cat(String name) {
        this.name = name;
        System.out.println("私有的构造器");
    }

    public Cat() {

    }
}

动态代理

反射的应用,是一种设计模式

Q.E.D.


念念不忘,必有回响。