zoukankan      html  css  js  c++  java
  • 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帮我们自动编写了一个静态实现类(不需要源码,可以直接生成字节码),并不存在可以直接实例化接口的黑魔法。

  • 相关阅读:
    线段拟合(带拉格朗日乘子,HGL)
    工作到位的标准
    Git的简单使用
    位移
    java日期格式化(util包下转成sql包下)
    java中继承的概念
    工作流驳回到指定连线节点上
    年终个人总结
    实现多条件查询 匹配数据库字段中多个数据
    activiti和SSH项目做整合
  • 原文地址:https://www.cnblogs.com/goloving/p/14810146.html
Copyright © 2011-2022 走看看