java 反射

  • 一篇远古的坑..java 反射.

  • 资料来源:

    https://www.liaoxuefeng.com/wiki/1252599548343744

  • 更新

    1
    2
    20.01.30 远古大坑😥
    20.02.17 填坑完毕

导语

  • 最近提及反射的地方是 kt 的动态代理和泛型.
  • 这里仅仅为反射的流水账,并非详细讲解,周知.
  • 基本总结于廖雪峰的 java 教程.

反射原理

  • 首次接触反射是在 Myprivacy(已弃坑).需要把对象的属性保存到键值.但是对象的类型非常多,不可能每一类都再写个保存方法.总结一下:大概是需要一次性获取对象的所有属性,得到属性的名称和值.这就是 java 反射的典型应用.

  • 几乎与反射有关的教程都会提到 java 的 Class 类.

  • Class 类:一个类,名叫 Class 而已.跟其他类一样继承自 object .java 中万物皆对象,即使类也不例外,在 jvm 看来一个普通的类相当于一个 Class 类的对象.

  • 代码在编译后都是一个个的 class 文件,jvm 运行时并不会一次性加载全部代码编译的 class 文件.当创建一个对象时,jvm 不认识,才会加载对应的 class 文件.加载 class 文件的过程,就是 jvm 创建一个 Class 类的对象的过程.这个 Class 对象中包含了普通类编译成 class 的所有信息,因此一个普通类只有一个 Class 对象与之对应.

  • 当需要 new 一个普通类的对象时,jvm 就会获取普通类对应的 Class 对象,再生成普通类的对象.

  • java 反射实质上就是在运行时获取普通类对应的 Class 对象.

  • 因为 Class 对象中包含了这个普通类的所有信息,所以我们利用这一点可以做很多.例:

    • 获取类的属性/方法.进而获取对象的属性的值,调用方法等.
    • 修改对象属性/方法.
    • 运行时新建一个普通类的对象
    • ….
  • 获取 Class 对象

    • class 的静态变量: Class cls = String.class;
    • 已知对象: Class cls = s.getClass();
    • 已知 class 的完整类名: Class cls = Class.forName("java.lang.String");
  • 以上获取到的 Class 对象都是同一个.

访问属性

  • 访问属性大致是先获取到属性对应的 Field 对象,再传入具体的对象得到类型/值/修饰符等.

  • 获取 Field 对象

    • Field getField(name): 根据字段名获取某个public的field(包括父类)
    • Field getDeclaredField(name): 根据字段名获取当前类的某个field(不包括父类)
    • Field[] getFields(): 获取所有public的field(包括父类)
    • Field[] getDeclaredFields(): 获取当前类的所有field(不包括父类)
  • Field 对象

    • getName(): 返回字段名称,例 “name”.
    • getType(): 返回字段类型,也是一个 Class 实例,例 String.class.
    • getModifiers(): 返回字段的修饰符,它是一个 int 不同的bit表示不同的含义,具体定义在 java.lang.reflect.Modifier 中.
  • 例:获取属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Person {
    private String name;
    public Person(String name) {
    this.name = name;
    }
    }

    Object p = new Person("Xiao Ming");
    //获取 Person类的 Class
    Class c = p.getClass();
    //获取 Filed 对象
    Field f = c.getDeclaredField("name");
    //传入 Person 类的实例,获取属性值
    Object value = f.get(p);
  • 特殊情况: 当属性被 private 修饰时,需要设置 (Field 对象).setAccessible(true).如果打开了 SecurityManager 可能会调用失败.

调用方法

  • 与访问属性类似,获取方法也是通过 Class 对象获取到 Method 对象.Method 中包含了类中方法的所有信息.再通过这些信息回到普通类的对象调用方法.

  • 获取 Method 对象(也非常类似 Field)

    • Method getMethod(name, Class…): 获取某个public的Method(包括父类)
    • Method getDeclaredMethod(name, Class…): 获取当前类的某个Method(不包括父类)
    • Method[] getMethods(): 获取所有public的Method(包括父类)
    • Method[] getDeclaredMethods(): 获取当前类的所有Method(不包括父类)
  • Method对象

    • getName(): 返回方法名称,例如: “getScore”.
    • getReturnType(): 返回方法返回值类型,也是一个Class实例,例如: String.class.
    • getParameterTypes(): 返回方法的参数类型,是一个Class数组,例如: {String.class, int.class}.
    • getModifiers(): 返回方法的修饰符,它是一个int,不同的bit表示不同的含义.具体定义还是在 java.lang.reflect.Modifier 中.
  • 调用方法

    • Method 实例调用 invoke = 调用方法.
    • invoke 的第一个参数是对象实例,第二个及以后是可变参数必须与方法参数一致.
    • 静态方法无需具体的对象实例,所以 invoke 第一个参数就是 null.
    • 对于私有的方法类似的 (Method对象).setAccessible(true).如果打开了 SecurityManager 可能会调用失败.
    • 对于重载的方法,分别调用就是传入不同的可变参数.
    • 通过反射调用方法,也遵循多态,即子类与父类重名方法,子类会覆盖父类的方法.
  • 例: 通过反射调用方法

    1
    2
    3
    4
    5
    6
    // String对象:
    String s = "Hello world";
    // 获取String substring(int)方法,参数为int:
    Method m = String.class.getMethod("substring", int.class);
    // 在s对象上调用该方法并获取结果:
    String r = (String) m.invoke(s, 6);

动态创建对象(调用构造函数)

  • 最简单的如果类有无参的且是 public 构造函数: Class.newInstance() 方法.例: Person p = Person.class.newInstance();.

  • 其他情况下,还是类似的通过 Class 可以获取到 Constructor 对象,包含了类的构造方法的所有信息.可以创建并返回一个新的对象示例.

  • 获取 Constructor 对象

    • getConstructor(Class…): 获取某个public的Constructor.
    • getDeclaredConstructor(Class…): 获取某个Constructor.
    • getConstructors(): 获取所有public的Constructor.
    • getDeclaredConstructors(): 获取所有Constructor.
  • 调用构造函数

    • 调用 Constructor.newInstance(参数) 可以创建并返回一个新的对象.
    • 非 public 的构造函数设置 Constructor.setAccessible(true).如果打开了 SecurityManager 可能会调用失败.
  • 要注意的是 Constructor 对象,总是当前类定义的构造方法,和父类无关.

获取继承关系

  • 没有使用过反射的这种用途.还是…挺新奇?

  • 获取父类 Class 对象

    • Class.getSuperclass()
    • java 所有类的父类是 Object,Object 的父类是null.
  • 获取interface

    • Class.getInterfaces()
    • 只返回当前类直接实现的接口类型数组,并不包括父类的接口.
    • 没有直接实现接口返回空数组.
  • 判断继承关系

    • 正常情况下可以通过 instanceof 来判断.
    • 两个 Class 之间可以通过 isAssignableFrom() 判断.例: Class.isAssignableFrom(Class)

动态代理

  • 所谓动态代理在刷 kotlin 那一篇也提到了,就是运行时创建一个实现 Interface 实例.

  • java 中是通过 Proxy.newProxyInstance() 运行时创建一个接口的实现类实例.

  • 动态代理

    • 定义一个InvocationHandler实例,它负责实现接口的方法调用.
    • 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
      • ClassLoader,通常就是接口类的ClassLoader.
      • 实现的接口数组,至少需要传入一个接口进去.
      • 用来处理接口方法调用的InvocationHandler实例.
    • 将返回的Object强制转型为接口.
  • 动态代理通过反射实现,带来了一定的性能损耗,除非必要否则无需使用.kotlin 就好多了.

  • 例子(偷懒照搬了)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class Main {
    public static void main(String[] args) {
    InvocationHandler handler = new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println(method);
    if (method.getName().equals("morning")) {
    System.out.println("Good morning, " + args[0]);
    }
    return null;
    }
    };
    Hello hello = (Hello) Proxy.newProxyInstance(
    Hello.class.getClassLoader(), // 传入ClassLoader
    new Class[] { Hello.class }, // 传入要实现的接口
    handler); // 传入处理调用方法的InvocationHandler
    hello.morning("Bob");
    }
    }

    interface Hello {
    void morning(String name);
    }

结语

  • java 反射给 java 带来了动态语言的一些特性,程序一定程度上可以修改自身.
  • 但 java 的反射有一定的性能损失.还是要慎用.
  • 终于填坑完毕..还有好多坑…😭…