Java反射、类的加载过程、双亲委派机制

什么是反射

       反射是Java 程序开发语言的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类和对象的内部属性。 通过反射,我们可以在运行时获得程序中每一个类型成员和成员变量的信息。在未用到反射的程序中,所创建对象的类型直接在程序中静态给出,是在编译期就确定下来的,而Java 反射机制可以动态的创建对象并调用其属性,这样对象的类型在编译期是未知的,而在运行时确定对象的类型。 

       反射的核心:是 JVM 在运行时才动态加载的类或调用方法或属性,他不需要事先(写代码的时候或编译期)知道运行对象是谁。.

        举个栗子,我们现在有一个正在运行的客户端-服务器的程序集,服务器端需等待客户端发出指令。客户端界面有多个按钮,每个按钮代表不同的类,哪个按钮被按下就创建了该按钮代表的类的对象,按下后发出指令给服务器,服务器会反馈不同的信息。利用反射可以这样做:客户端界面某一按钮被按下,创建了某一对象并发出指令给服务器,服务器拿到了创建的对象名,可以通过反射确定这是哪一个类的对象从而做出不同的反应。

关于java.lang.Class类的理解

       我们来看一下源码中对Class类的解释:

Instances of the class Class represent classes and interfaces in a running Java application. An enum is a kind of class and an annotation is a kind of interface. Every array also belongs to a class that is reflected as a Class object that is shared by all arrays with the same element type and number of dimensions. The primitive Java types (boolean, byte, char, short, int, long, float, and double), and the keyword void are also represented as Class objects.
Class has no public constructor. Instead Class objects are constructed automatically by the Java Virtual Machine as classes are loaded and by calls to the defineClass method in the class loader.

       翻译一下:类 Class 的实例表示正在运行的 Java 应用程序中的类和接口。 枚举是一种类,注解是一种接口。 每个数组也属于一个类,该类反映为一个 Class 对象,该对象由具有相同元素类型和维数的所有数组共享。 Java中基本数据类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。
       Class没有公共构造函数。 取而代之的是,Java 虚拟机在加载类时或通过调用类加载器中的defineClass 方法自动构造类对象。

       如上,源码的注释中提到了:1、正在运行的 Java 应用程序中的类和接口,即运行时类;2、所有的类和接口、基本数据类型、引用数据类型、关键字void都表示为Class对象。这里也体现了java中一切皆对象的思想;3、我们可以通过Class中提供的方法或者类加载器获取运行时类的对象。

类的加载过程

       总述:Java程序经过编译(javac.exe命令)后,会生成一个或多个字节码文件(.class文件)。接着,我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程(加粗)就称为类的加载。加载到内存中的类,就称为运行时类,此运行时类,就作为Class的一个实例。

       以上是较为狭义和简洁的类的加载过程总述。如果要深入讨论类的加载在虚拟机上的过程,我们先看一下最常用的虚拟机HotSpot JVM的结构:

       图中,黄色框类加载器子系统完成的就是类的加载过程:将.class文件中类的元信息加载进内存,创建Class对象并进行解析、初始化类变量等的过程。

       其中,类的加载细分为:加载-->链接-->初始化三个过程:

       加载:

              通过一个类的类名获取定义此类的二进制字节流;

              将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;

              在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

       链接:

              验证:确保加载的类信息符合JVM规范。如:以CAFEBABE开头;

              准备:为类变量(如static修饰的变量)赋默认初始值;

              解析:将常量池内的符号引用转换为直接引用的过程

       初始化:

              执行类构造器方法<clinit>()的过程(不同于类中声明的构造器,前者是构造类,后者是构造类的对象);

              若该类具有父类,JVM会保证在子类的<clinit>()执行前,父类的<clinit>()已执行完毕;

              JVM保证一个类的<clinit>()方法在多线程下被同步加锁(即保证任一类在JVM中只能成功加载一次)。

获取Class的实例的方式

        加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式获取此运行时类。

        ①、调用运行时类的属性:类名.class

        ②、通过运行时类的对象,调用getClass():对象.getClass()

        ③、调用Class的静态方法:Class.forName(String className)

        ④、使用类的加载器ClassLoader(抽象类)

双亲委派机制

       先引进一个场景:大家都知道JDK中有在java.lang包下的String类,如果我自己定义一个java.lang包并且在其下定义一个String类,当我创建String类的对象时,究竟创建的是原来的String类对象还是自定义的String类对象呢?我们可以尝试一下:

package java.lang;

public class String {
    static{
        System.out.println("创建了自定义String类的对象");
    }
}
package relatesToJVM;

public class StringTest {
    public static void main(String[] args) {
        String s = new String();
        System.out.println("执行了该代码");
    }
}

       执行main方法后,并没有输出自定义String类中静态代码块的语句,故创建的还是原String的对象,也就是JVM中加载的是原String类,这里就可以用Java中的双亲委派机制来解释。

       JVM中最常用的类加载器如图所示:

       双亲委派模式原理如下:

①、如果一个类加载器收到了类加载请求,他不会自己先去加载,而是向上委托给父类的加载器去尝试加载;

②、如果父类加载器还存在其父类加载器,则进一步向上委托,请求最终到达最顶层的类加载器;

③、如果父类加载器可以成功完成类的加载,就成功返回;若父类加载器无法完成加载,子加载器才会自行尝试加载。

       这样,就可以解释上面的例子,实际加载的是Java核心类库中的String类。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值