Skip to content

反射

  • 摆脱只能在编译时执行面向对象类型的操作的限制
  • 自省是指程序在运行时检查对象类型或属性的能力。它通常用于获取关于对象的信息,如类名、方法名、属性等,而不改变对象的行为。
  • 反射是指程序在运行时创建、检查和修改其自身行为的能力。与自省相比,反射不仅能获取对象的信息,还能在运行时改变对象的属性和行为。

  • 核心功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的方法

  • 向上转型可以自动发生,但是向下转型必须显示指出,并且如果这个对象并转型到目标并不是向下转型(不是其父类),会抛出ClassCastException

  • 通过反射可以获取实际类型并且进行安全的向下转型
    Animal animal = getAnimal(); // getAnimal()返回一个 Animal 对象
    if (animal instanceof Dog) {
        Dog dog = (Dog) animal;
        // 在这里,你可以安全地调用 Dog 类的方法
    }
    

Class对象

  • Class对象包含了与类相关的信息,Class对象被用来创建常规对象,Java使用Class对象执行反射
  • 每个类都有一个Class对象,存储在同名的class文件中
  • 类在首次使用时才会被动态加载到JVM,类加载器检查是否加载了类型的Class对象,如果没有就定位到对应的class文件并校验后加载,Class对象加载到内存后会用于创建类的所有对象
  • 类第一次被加载时会调用类的静态初始化块

获取class对象

  • 获取Class对象Class.forName("classname");
  • 需要使用包含包名称的完全限定类型
  • 找不到则抛出ClassNotFoundException

  • 如果有对应类型的对象,可以通过对象获取xx.getClass()

  • 使用类字面常量获取Class

  • ClassName.class'编译时检查们不用放在try
    • 包装类使用TYPE关键字
  • 不一定会立即导致初始化,等到首次使用静态方法再初始化,对于static final的编译时常量不会导致初始化

常用方法

  • getName()获取完整类名(含包路径),外部类内部类用$分隔
    • getCanonicalName()获取规范名,外部类内部类用.分隔,可能返回null(比如匿名内部类)
  • isInterface()
  • getSimpleName()
  • getInterfaces()返回Class数组,即所有实现的接口类型
  • getSuperClass()查询直接基类
  • Method[] getDeclaredMethods()返回当前 Class 对象所表示的类或接口的所有声明的方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
  • Annotation[] getDeclaredAnnotations()获取全部注解
  • newInstance()得到一个Object类型的引用,类型必须要有无参构造器(非public类要手动提供一个public的无参构造)

  • 创建对象

    //无参构造
    try {
        return type.getConstructor().newInstance();
    } catch(Exception e) {
        throw new RuntimeException(e);
    }
    //有参构造
    Class <?> clazz = MyClass.class;
    Constructor <?> constructor = clazz.getConstructor(String.class, int.class);
    MyClass myObject = (MyClass) constructor.newInstance("example", 123);
    

  • 结合泛型

  • Class<Integer>只接受特定类型的Class
  • 通配Class<?>(实际上\<?>可以省略)

    • Class<? extends Number>可以存储整数/浮点数
      Class <FancyToy> ftc = FancyToy.class;
          // Produces exact type:
          FancyToy fancyToy =
            ftc.getConstructor().newInstance();
          Class <? super FancyToy> up = ftc.getSuperclass();
          // 虽然知道 Toy 是基类,但编译不通过,只能是 FancyToy 的某个基类
          // Class <Toy> up2 = ftc.getSuperclass();
          //不确定具体类型,使用 Object 引用
          Object obj = up.getConstructor().newInstance();
      
  • cast方法

  • 使用Class引用类型转换
  • 用于将对象转换为由 Class 实例表示的类或接口
  • 这个方法在运行时动态地执行类型转换,与强制类型转换类似,但更加灵活,因为它允许在编译时不知道具体类的情况下进行转换。

    Class <String> clazz = String.class;
    Object obj = "Hello";
    String str = clazz.cast(obj);
    //str = (String)obj
    

  • 类方法提取器

  • 可以查看一个类(包含其基类在内)的全部可用方法

    //全部方法
    Method [] methods = c.getMethods();
    //全部构造方法
    Constructor [] ctors = c.getConstructors();
    

  • 方法Method

  • getName(): 返回方法的名称。
  • getReturnType(): 返回方法的返回类型。
  • getParameterTypes(): 返回方法参数的类型数组。
  • getModifiers(): 返回方法的修饰符,如 public, private 等。
  • getExceptionTypes(): 返回方法声明抛出的异常类型数组。
    • 动态调用方法method.invoke(Object obj, Object... args): 允许你动态地调用方法。obj 是要调用其方法的对象,args 是方法调用时传递的参数。
  • getAnnotation(Class<T> annotationClass): 返回该方法上指定类型的注解。
  • getAnnotations(): 返回该方法上所有注解的数组。
  • getGenericReturnType(): 返回方法的泛型返回类型。
  • getGenericParameterTypes(): 返回方法的泛型参数类型数组。

  • 字段Field

  • 获取类或接口的全部字段getDeclaredFields()
  • getName():返回字段的名称。
  • getType():返回一个Class对象,该对象表示字段的类型。
  • get(Object obj):返回指定对象上此字段的值。
  • set(Object obj, Object value):将指定对象上此字段的值设置为指定的新值。
  • getModifiers():返回此字段的Java语言修饰符,这可以用来检查字段是否为publicprivateprotectedstatic等。
  • getDeclaredAnnotations():返回此元素上直接存在的所有注解。

转型前检查

  • 类型检查x instanceof Dog返回布尔值,是否是特定类型的对象
  • 在向下转型之前进行检查,防止遇到ClassCastException

  • 动态isInstance

  • Class.isInstance()
  • 可以使用Class数组方便的使用Class对象.isInstance(待判断类型的对象)

  • instanceof和isInstance的比较是等价的

  • 行同类型/基类都满足比较的条件
  • 但是使用==或equal则表示要求类型完全相同

  • isAssignableFrom

  • 判断一个类是否相同或是另一个类的超类或接口。F
  • parentClass.isAssignableFrom(childClass);

代理模式

接口和类型信息

  • 把一个实现接口的类型赋值给接口类型,可以隐藏许多信息,只保留接口规定的方法,但是通过反射可以发现这一点并进行强制转换,从而破坏限制
  • 将实现接口的类声明为包作用域可以避免,因为包外根本没有这个类。但是虽然不能转型,如果知道方法的名字,仍然可以访问!
    callHiddenMethod(Object a, String methodName)
      throws Exception {
        //获取方法
        Method g = a.getClass().getDeclaredMethod(methodName);
        //设置为允许访问
        g.setAccessible(true);
        g.invoke(a);
      }
    
  • 无论是使用private方法还是匿名内部类都无法阻止