Java反射基础知识笔记:反射的定义、class类的本质、class类的动态加载、class类的实例如何访问字段/方法/构造方法/继承关系、动态代理的本质

  什么是反射?反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息。

  反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。

一、class类

1、class(包括interface)的本质是数据类型(Type)。无继承关系的数据类型无法赋值。

Number n = new Double(123.456); // OK
String s = new Double(123.456); // compile error!

2、而class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class类型时,将其加载进内存

3、每加载一种class,JVM就为其创建一个Class类型的实例,并关联起来。

  注意:这里的Class类型是一个名叫Classclass。它长这样:

public final class Class {
    private Class() {}
}

  以String类为例,当JVM加载String类时,它首先读取String.class文件到内存,然后,为String类创建一个Class实例并关联起来:

Class cls = new Class(String);

  这个Class实例是JVM内部创建的,如果我们查看JDK源码,可以发现Class类的构造方法是private,只有JVM能创建Class实例,我们自己的Java程序是无法创建Class实例的。

  所以,JVM持有的每个Class实例都指向一个数据类型(classinterface):

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

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

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

5、由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。 —— 这种通过Class实例获取class信息的方法称为反射(Reflection)

6、如何获取一个classClass实例?有三个方法

// 方法一:直接通过一个class的静态变量class获取:
Class cls = String.class;

// 方法二:如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取:
String s = "Hello";
Class cls = s.getClass();

// 方法三:如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:
Class cls = Class.forName("java.lang.String");

7、因为反射的目的是为了获得某个实例的信息。因此,当我们拿到某个Object实例时,我们可以通过反射获取该Objectclass信息:

void printObjectInfo(Object obj) {
    Class cls = obj.getClass();
}

8、动态加载:JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载

  动态加载class的特性对于Java程序非常重要。利用JVM动态加载class的特性,我们才能在运行期根据条件加载不同的实现类。

9、小结:

  JVM为每个加载的classinterface创建了对应的Class实例来保存classinterface的所有信息;

  获取一个class对应的Class实例后,就可以获取该class的所有信息;

  通过Class实例获取class信息的方法称为反射(Reflection);

  JVM总是动态加载class,可以在运行期根据条件来控制加载class。

二、访问字段

1、通过Class实例获取字段信息。Class类提供了以下几个方法来获取字段:

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

2、Java的反射API提供的Field类封装了字段的所有信息:

  通过Class实例的方法可以获取Field实例:getField()getFields()getDeclaredField()getDeclaredFields()

  通过Field实例可以获取字段信息:getName()getType()getModifiers()

  通过Field实例可以读取或设置某个对象的字段,如果存在访问限制,要首先调用setAccessible(true)来访问非public字段。

  通过反射读写字段是一种非常规方法,它会破坏对象的封装。

三、调用方法

  Java的反射API提供的Method对象封装了方法的所有信息:

  通过Class实例的方法可以获取Method实例:getMethod()getMethods()getDeclaredMethod()getDeclaredMethods()

  通过Method实例可以获取方法信息:getName()getReturnType()getParameterTypes()getModifiers()

  通过Method实例可以调用某个对象的方法:Object invoke(Object instance, Object... parameters);对Method实例调用invoke就相当于调用该方法,invoke的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错。

  通过设置setAccessible(true)来访问非public方法;

  通过反射调用方法时,仍然遵循多态原则。

四、调用构造方法

  Constructor对象封装了构造方法的所有信息;

  通过Class实例的方法可以获取Constructor实例:getConstructor()getConstructors()getDeclaredConstructor()getDeclaredConstructors()

  通过Constructor实例可以创建一个实例对象:newInstance(Object... parameters); 通过设置setAccessible(true)来访问非public构造方法。

五、获取继承关系

  通过Class对象可以获取继承关系:

  • Class getSuperclass():获取父类类型;
  • Class[] getInterfaces():获取当前类实现的所有接口。

  通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现。

六、动态代理

1、我们来比较Java的classinterface的区别:

  • 可以实例化class(非abstract);
  • 不能实例化interface

  所有interface类型的变量总是通过向上转型并指向某个实例的。有没有可能不编写实现类,直接在运行期创建某个interface的实例呢?这是可能的。

  因为Java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。

2、静态代码怎么写:(1)定义接口;(2)编写实现了类;(3)创建实例,转型为接口使用。

3、动态代码怎么写:(1)定义接口;(2)不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()创建了一个Hello接口对象。

  这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。

  JDK提供的动态创建接口对象的方式,就叫动态代理。

4、Java标准库提供了动态代理功能,允许在运行期动态创建一个接口的实例;动态代理是通过Proxy创建代理对象,然后将接口方法“代理”给InvocationHandler完成的。

5、动态代理实际上是JVM在运行期动态创建class字节码并加载的过程,它并没有什么黑魔法,把上面的动态代理可以改写为静态实现类。

  动态代理的本质其实就是JVM帮我们自动编写了一个静态实现类(不需要源码,可以直接生成字节码),并不存在可以直接实例化接口的黑魔法。

posted @ 2021-05-25 19:13  古兰精  阅读(217)  评论(0编辑  收藏  举报