类的加载机制与反射学习笔记

重点介绍 java.lang.reflect 包下的接口和类。包括 Class 、Method、Feild、Constructor、Array 等,分别代表类、方法、成员变量、构造器和数组。Java 程序可以使用这些类动态的获取某个对象、某个类的运行时信息,并可以动态的创建 Java 对象,动态的调用 Java 方法,访问并修改制定对象的成员变量值。

类的加载、连接、初始化

1.同一个JVM的所有线程、变量都处于同一个进程中,并使用该JVM中的内存区。JVM 会终止的情况:

    》 程序运行完正常结束;
    》 程序运行到System.exit() 或 Runtime.getRuntime().exit();
    》 程序执行过程中遇到未捕获的异常或错误而结束。
    》程序所在平台强制结束JVM进程。

2.类加载/类初始化

当程序主动使用某个类时,这个类还未被加载到内存中,则系统会通过加载、连接、初始化三个步骤对这个类初始化,具体操作是将类的 class 文件读到内存,并为之创建一个 java.lang.Class 对象。

通过 JVM 提供的系统类加载器完成类的加载,我们可以通过继承 ClassLoader 基类创建自定义类加载器。

不会等到首次使用该类时加载,允许系统预先加载某些类,加载的是类的二进制数据,这些类的来源:

  • 本地文件系统要加载的类
  • JAR包文件中的类
  • 通过网络加载的 class 文件
  • Java源代码动态编译并加载
    3.类的连接
类加载 -> 生成 Class 对象 -> 类连接 -> 把二进制数据合并到JRE

类连接的三个阶段:

验证阶段检验被加载的类是否有正确的内部结构 -> 准备阶段为类的类变量分配内存并设初始值 -> 解析阶段将类的二进制数据中的符号引用换为直接引用

4.类初始化

对类变量初始化,指定初始值的俩种方式:1、声明类变量时指定初始值;2、使用静态初始化块指定初始值

5.类初始化的时机

某个类创建实例的方式有三种:

  1. 使用 new 操作符创建实例
  2. 通过反射创建实例
  3. 通过反序列化的方式创建实例

何时系统初始化该类或接口

  1. 创建类实例
  2. 调用类的类方法(静态方法)
  3. 访问类或接口的类变量,或为其赋值
  4. 使用反射强制创建某个类的或接口的 java.lang.Class 对象
  5. 初始化某个类的子类,这时该类的父类都会被初始化
  6. 使用 java.exe 命令运行某个类

注意!

  1. 访问 final 类型的类变量,在编译时已经确定其值,只会调用该变量的值,被当作常量访问,不会初始化该类;但编译时值不能被确定下来如下:
static final String compilezConstant = System.currentzTimeMillis() + ""

则会导致该类初始化;
2. 使用 ClassLoader 类的 loadClass() 方法加载类时只会加载,不会执行类初始化。使用 Class 的 forName() 静态方法会导致强制初始化类

类加载器

负责将磁盘或网络的 .class 文件加载到 JVM 内存,并生成对应的 java.lang.Class 对象。

JVM 启动时,形成三个类加载器组成的初始化类层次结构:

  1. Bootstrap ClassLoader 根类加载器/引导类加载器,加载 Java 的核心类,如 String 、System
  2. Extension ClassLoader 扩展类加载器, JAVA/jre/lib/ext/ 路径下的类
  3. System ClassLoader 系统类加载器,-classpath 选项、java.class/path 系统属性或 CLASSPATH 环境变量指定的JAR包或类路径
  4. 用户类加载器,继承ClassLoader

类的加载机制

  1. 全盘负责,即不仅加载类本身,还加载其依赖的和引用的类
  2. 父类委托,先通过父类加载,不成功则从自己的途径加载
  3. 缓存机制,先从缓存区搜寻类,没有则读系统的二进制数据

创建并使用自定义的类加载器

通过扩展ClassLoader 的字类,重写其方法可以实现自定义的类加载器,ClassLoader 的俩个关键方法:
1. loadClass(String name, boolean resolve) : ClassLoader 的入口点,根据指定名称加载类,系统调用这个方法来获取指定类对应的Class 对象
2. findClass(String name):根据名称查找类

重写以上方法可以实现自定义的类加载器,推荐重写 findClass(String name) ,可以避免覆盖默认的父类委托 、缓冲机制,loadClass 逻辑复杂

defineClass()方法管理 JVM 的复杂实现,将字节码分析成运行时结构,并校验有效性

URLClassLoader 类

这个类是系统类加载器和扩展类加载器的父类,功能强大,既能从本地文件系统获取二进制文件来加载类,也能从远程主机获取二进制文件来加载类。重要构造器:
1. URLClassLoader(URL[] urls) 使用默认父类加载器创建 ClassLoader
2. URLClassLoader(URL[] urls,ClassLoader parent) 使用指定的

一旦得到URLClassLoader 对象,就可以调用该对象的 loadClass() 方法加载指定类

public static Connection conn;

    public static Connection getConn(String url,String user,String pass) throws Exception{
        if(conn == null ){
            /*file:  从本地文件系统加载
            * http:  从互联网通过HTTP访问加载
            * fip:  从互联网通过FTP访问加载
            * */
            URL[] urls = {new URL("file:mysql-connection-java-5.1.3-bin.jar")};
            /*以默认的ClassLoader作为父类加载器*/
            URLClassLoader myURLClassLoader = new URLClassLoader(urls);
            Driver driver = (Driver)myURLClassLoader.loadClass("").newInstance();
            Properties properties = new Properties();
            properties.setProperty("User",user);
            properties.setProperty("Pass",pass);
            conn = driver.connect(url,properties);

        }
        return conn;
    }

通过反射查看类的信息

许多对象在运行时会出现俩种类型:编译时类型(存储在内存的)和运行时类型(访问到的数据)
Person p = new Student(); p变量的编译时类型Person;运行时类型 Student
由于这种需求,程序需要在运行时发现对象和类的真实信息,有俩种方法解决此问题:
1. 假设在编译时和运行时完全知道类型的具体信息,可以使用instanceof运算符进行判断,再利用强制类型转换为运行时类型的变量;
2. 在编译时无法预知该对象和类可能属于哪些类,只能通过运行时信息发现该对象和类的真实信息,需要用反射

获得 Class 对象

类加载 -> Class 对象 -> 通过这个Class 对象可以访问 JVM 中的这个类,Java 程序中如何获取 Class 对象?

  1. 使用Class 类的 forName(String clazzName)静态方法,参数是类的完整包名+类名
  2. 调用某个类的 class 属性获取其对应的 Class 对象,如Person.class 返回它对应的Class 对象
  3. 调用某个对象的 getClass()

获得了这个 Class 对象,则可以通过它访问原来类的构造器、方法、变量、注释等

Java 8 新增的方法参数反射:Executable 抽象基类

这个对象代表可执行的类成员,派生出Constructor 、Method 俩个字类
用来获取方法或构造器参数的真正信息

使用反射生成并操作对象

Class 对象可以获取该类里的方法(由 Method 对象表示)、构造器(Constructor)、成员变量(Field),这三个类都在 java.lang.reflect 包下,且实现了 java.lang.reflect.Member 接口,所以程序可以操作其中的对象。

创建对象

通过反射生成对象的俩种方式:
1. 使用 Class 对象的 newInstance() 方法使用默认构造器创建 Class 对象对应类的实例,这个类要有默认构造器;
2. 使用 Class 对象获取的 Constructor (构造器)对象调用newInstance() 方法创建 Class 对象对应类的实例

调用方法

获取类 Class 对象,可以通过 getMethods() 或 getMethod() 方法获取全部方法或指定方法,然后可以通过 Methed 来调用其对应的方法。
Methed 中 invoke() 来执行调用

  • Object invoke(Object obj, Object…args) obj 为主调,args 是传入的实参

访问成员变量值

通过 Class 对象的 getFields() 或 getField() 获取该类的全部成员变量或指定成员变量。Field 提供一下俩组方法读取或设置成员变量值

  • getXxx(Object obj) 获取变量的值
  • setXxx(Object obj,Xxx val) 设置变量的值

若是引用类型去掉后面的Xxx
通过setAccessible(boolean value) 可以设置通过反射访问该成员变量是否取消访问权限检查**

操作数组

Array 类,可以动态创建数组,操作数组。方法:

  • static Object newInstance(Class<?> componentType,int…length)创建指定元素类型和维度的数组
  • static xxx getXxx(Object array, int index)
  • static void setXxx(Object array, int index,xxx val)

使用反射生成 JDK 动态代理

在 java.lang.reflect 包下提供了 Proxy 类和 InvocationHandler 接口,使用它们可以生成 JDK 动态代理类或动态代理对象
1. 使用 Proxy 类和 InvocationHandler 接口 创建动态代理

Proxy 的俩个创建动态代理类和动态实例的方法

  • static Class<?> getProxyClass(ClassLoader loader, Class<?>… interfaces):创建一个动态代理类所对应的Class对象,实现interfaces所指定的多个接口。根据ClassLoader类加载器生成指定动态代理类。
  • static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h):直接创建一个动态代理对象,该代理对象的实现类实现了interfaces指定的系列接口,执行代理对象的每个方法时都会被替换执行InvocationHandler对象的invoke方法。

使用步骤:
创建原始的类对象,根据该对象创建动态代理类对象,此时动态代理类对象可以访问原始对象的所有方法,且在自实现的 InvocationHandler 类的 invoke() 方法中可以插入想要复用的代码块。

此为 AOP (aspect Orient Programming,面向切面编程),AOP 代理包含了目标对象的全部方法。

反射和泛型

在反射中使用泛型可以避免使用发射生成的对象需要强制类型转换。

使用反射获取泛型信息

例如 获取 Map

普通变量数据类型获取方法
Class<?> a = field.getType();
泛型书记类型获取方法
Type gTypr = field.getGenericType();

然后将这个 Type 对象强制转换为 ParameterizedType (带参数的类型),增加了泛型类型的限制,ParameterizedType 获取类型方法:
- getRawType() 返回没有泛型信息的原始类型
- getActualTypeArguments 返回泛型参数的类型

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值