Java笔记04 - 反射

反射

  • 程序在运行期间可以拿到一个对象的所有信息
  • 为了解决在运行期, 对某个实例一无所知的情况下, 如何调用其方法

Class类

  • 除了int等基本类型, 其他类型都是class
  • class的本质是数据类型(Type). 无继承关系的数据类型无法赋值
  • class是JVM在执行过程中动态加载的. 每读取到一种class类型, 就把他加载到内存中
  • 每加载一种class, JVM都会为其创建一个Class类型的实例, 并关联起来
  • 这里的Class类型, 是一个名叫Classclass
  • 加载String类时, 首先读取String.class文件到内存, 然后为String类创建一个Class实例并关联起来
Class cls = new Class(String)
  • Class是内部的, java程序无法创建
  • 所以: JVM持有的每个Class实例都指向一个数据类型(classinterface)

┌───────────────────────────┐
│ Class Instance │──────> String
├───────────────────────────┤
│name = "java.lang.String" │
└───────────────────────────┘
┌───────────────────────────┐
│ Class Instance │──────> Random
├───────────────────────────┤
│name = "java.util.Random" │
└───────────────────────────┘
┌───────────────────────────┐
│ Class Instance │──────> Runnable
├───────────────────────────┤
│name = "java.lang.Runnable"│
└───────────────────────────┘

  • 一个Class实例包含了该class的所有完整信息

┌───────────────────────────┐
│ Class Instance │──────> String
├───────────────────────────┤
│name = "java.lang.String" │
├───────────────────────────┤
│package = "java.lang" │
├───────────────────────────┤
│super = "java.lang.Object" │
├───────────────────────────┤
│interface = CharSequence...│
├───────────────────────────┤
│field = value[],hash,... │
├───────────────────────────┤
│method = indexOf()... │
└───────────────────────────┘

  • 所以我们可以通过这个Class实例获取到该实例的所有信息.

如何获取一个classClass

  • 直接通过一个class的静态变量class获取: Class cls = String.class
  • 有一个实例变量, 提供getClass()方法
  • 使用class的完整类名, 通过Class.forName(): Class cls = Class.forName("java.lang.String")

instanceof

  • instanceof不但匹配指定类型, 还能匹配指定类型的子类

  • == 可以准确判断数据类型, 但不能判断子类

  • String[]也是一种Class, 不同于String.class, 类名是[Ljava.lang.String.

  • 可以通过Class实例, 来创建对应的实例

Class cls = String.class;
String s= (String) cls.newInstance();
  • 上述代码相当于new String()
  • Class.newInstance 局限: 无法调用带有参数的构造方法或者非public的构造方法.

动态加载

  • JVM在执行Java时, 并非一次性加载所有用到的class, 第一次需要用到class时才加载
  • 利用动态加载特性, 我们可以在运行期间根据条件加载不同的实现类
  • 这就是为什么Log4j放到classpath, Commons Logging就会自动使用.

访问字段

如何获取字段信息

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

Field对象包含的信息

  • getName(): 返回字段名称
  • getType(): 返回字段类型
  • getModifiers()返回字段的修饰符, 它是一个int, 不同数值代表不同的含义

获取字段值

  • 利用反射拿到字段的一个Field实例, 从而拿到一个实例对应的该字段的值
  • setAccessible(true) 可能会失败, 因为JVM运行期间存在SecurityManager可能不允许java和javax开头的package的类调用.
  • 为了保证java核心库的安全

设置字段值

  • Field.set(Object, Object)

调用方法

  • 同样可以用相同的方法, 获取Class所有的Method
  • 一个Method对象包含一个方法所有的信息.
  • 获取到一个Method就可以对其进行调用

调用静态方法

  • 调用静态方法无需指定实例对象.
  • invoke方法第一参数永远为null
Method m = Integer.class.getMethod("parseInt", String.class);
Integer n = (Integer) m.invoke(null, "12345");
System.out.println(n);

调用非public方法

  • 非public方法, 可以获取,但直接调用, 得到IllegalAccessException.
  • 我们通过Method.setAccessible(true)允许调用
  • 需要用getDeclaredMethod来获取private方法

多态

  • 使用反射调用方法时, 仍然遵循多态原则
  • 也就是总是调用实际类型的覆写方法

调用构造方法

  • 可以用个Class提供的newInstance()方法创建新的实例, 但只能调用public的无参构造方法
  • Constructor方法用来调用任意的构造方法, 包含了构造方法的所有信息

通过Class实例获取Constructor的方法如下

  • getConstructor(Class...): 获取某个publicConstructor

  • getDeclaredConstructor(Class...): 获取某个Constructor

  • getConstructors: 获取所有publicConstructor

  • getDeclaredConstructors() 获取所有的Constructor

  • 调用非Public的Constructor时, 必须通过setAccessible(true)

获取继承关系

获取父类的Class

  • Integer => Number => Object => null
  • 任何一种非interfaceClass都必定存在一种父类类型

获取interface

  • getInterface: 返回当前类直接实现的接口类型, 不包括父类实现的接口类型
  • 所有interfaceClass, 调用getSuperClass()返回null
  • 类没有实现接口, getInterface返回空数组

继承关系

  • 判断一个实例是否属于某个类型: instanceof
  • 如果两个Class实例, 要判断向上转型是否成功, 使用isAssignable()
boolean c = Number.class.isAssignableFrom(Integer.class);
System.out.println(c); // true Integer可以赋值给Number

boolean e = Integer.class.isAssignableFrom(Number.class);
System.out.println(e); // false Number不能赋值给Integer

动态代理

  • 比较classinterface的区别
    • 可以实例化class
    • 不能实例化interface
  • 所有interface类型的变量总是通过向上转型并指向某个实例 CharSequence cs = new StringBuilder();
  • 动态代理(Dynamic Proxy): 在运行期动态创建某个interface的实例
  • 不编写实现类, 直接通过Proxy.newProxyInstance()创建接口对象
  • 没有实现类, 但在运行期动态创建了一个接口对象的方式, 成为动态代码
  • JDK提供的动态创建接口对象的方式, 成为动态代理
    InvocationHandler handler = new InvocationHandler() { // 1: 创建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 new Object();
      }
    };
    // 2: 创建通过`newProxyInstance`创建实例
    Hello hello = (Hello) Proxy.newProxyInstance( // 3: 强制转换类型
        Hello.class.getClassLoader(), // 传入ClassLoader
        new Class[] { Hello.class }, // 传入要实现的接口
        handler); // 传入处理调用方法的InvocationHandler
    hello.morning("bob");

运行期间创建interface实例如下

  1. 创建InvocationHandler实例, 负责方法调用
  2. 通过Proxy.newProxyInstance()创建interface实例, 三个参数如下:
    • 使用的ClassLoader, 通常就是接口类的ClassLoader
    • 需要实现的接口数组.
    • 用来处理回调方法调用的InvocationHandler实例
  3. 将返回的Object类型强制转换

动态代理本质

  • 动态代理其实就是: JDK在运行期间动态创建class字节码并加载过程.

  • 动态类改为静态实现类:

  • JDK就是编写了这么一个静态类, 不需要源码, 直接生成字节码

interface Hello {
  void morning(String name);
}

class HelloDynamicProxy implements Hello {
  InvocationHandler handler;
  public HelloDynamicProxy(InvocationHandler handler) {
    this.handler = handler;
  }
  public void morning(String name) {
    try {
    handler.invoke(
        this,
        Hello.class.getMethod("mornging"),
        new Object[] {name});
    } catch (Throwable t) {
    }
  }
}

疑问

  • Class类型的class是什么意思?
  • String s= (String) cls.newInstance(); 中间的(String) 如何理解?
  • 完全不理解最后这个动态代理, 以及最后的静态类实现方式??
  • new Person () {}是什么语法??
posted @ 2020-03-26 17:44  张润昊  阅读(218)  评论(0编辑  收藏  举报