分享
分享赚钱 收藏 举报 版权申诉 / 16

类型class对象浅析.docx

  • 上传人:cjc2202537
  • 文档编号:206615
  • 上传时间:2018-03-24
  • 格式:DOCX
  • 页数:16
  • 大小:791.11KB
  • 配套讲稿:

    如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。

    特殊限制:

    部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。

    关 键  词:
    class对象浅析.docx
    资源描述:

    1、本篇主要是深入对 Java 中的 Class 对象进行分析,这对后续深入理解反射技术非常重要,主要内容如下: 深入理解 Class 对 象 RRTI 的概念以及 Class 对象作用认识Class对象之前,先来了解一个概念,RTTI(Run-Time Type Identification)运行时类型识别,对于这个词一直是 C+ 中的概念,至于Java中出现RRTI 的说法则是源于Thinking in Java一书,其作用是在运行时识别一个对象的类型和类的信息,这里分两种:传统的”RRTI”,它假定我们在编译期已知道了所有类型 (在没有反射机制创建和使用类对象时,一般都是编译期已确定其类型,

    2、如new对象时该类必须已定义好 ),另外一种是反射机制,它允许我们在运行时发现和使用类型的信息。在Java中用来表示运行时类型信息的对应类就是 Class类,Class 类也是一个实实在在的类,存在于JDK的java.lang包中,Class类被创建后的对象就是Class 对象,注意,Class对象表示的是自己手动编写类的类型信息,比如创建一个Shapes类,那么,JVM就会创建一个Shapes对应 Class类的Class对象,该Class对象保存了 Shapes类相关的类型信息。实际上在Java中每个类都有一个 Class对象,每当我们编写并且编译一个新创建的类就会产生一个对应Class对

    3、象并且这个Class对象会被保存在同名.class文件里(编译后的字节码文件保存的就是Class对象) ,那为什么需要这样一个Class 对象呢?是这样的,当我们new一个新对象或者引用静态成员变量时, Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM 中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。需要特别注意的是,手动编写的每个class类,无论创建多少个实例对象,在JVM中都只有一个Class 对象,即在内存中每个类有且只有一个相对应的Class对象,通过下图理解(内存中的简易现象图): 到这我们也就可以得出以

    4、下几点信息: Class 类也是类的一种,与 class 关键字是不一样的。 手动编写的类被编译后会产生一个 Class 对象,其表示的是创建的类的类型信息,而且这个 Class 对象保存在同名.class 的文件中(字节码文件),比如创建一个Shapes 类,编译 Shapes 类后就会创建其包含 Shapes 类相关类型信息的 Class对象,并保存在 Shapes.class 字节码文件中。 每个通过关键字 class 标识的类,在内存中有且只有一个与之对应的 Class 对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个 Class 对象。 Class 类只存私有构造函数

    5、,因此对应 Class 对象只能有 JVM 创建和加载 Class 类的对象作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要(关于反射稍后分析)。Class 对象的加载及其获取方式Class 对象的加 载前面我们已提到过,Class 对象是由 JVM 加载的,那么其加载时机是?实际上所有的类都是在对其第一次使用时动态加载到 JVM 中的,当程序创建第一个对类的静态成员引用时,就会加载这个被使用的类(实际上加载的就是这个类的字节码文件 ),注意,使用 new 操作符创建类的新实例对象也会被当作对类的静态成员的引用(构造函数也是类的静态方法) ,由此看来 Java 程序在它们开始运

    6、行之前并非被完全加载到内存的,其各个部分是按需加载,所以在使用该类时,类加载器首先会检查这个类的 Class 对象是否已被加载( 类的实例对象创建时依据 Class 对象中类型信息完成的),如果还没有加载,默认的类加载器就会先根据类名查找.class 文件(编译后 Class 对象被保存在同名的.class 文件中),在这个类的字节码文件被加载时,它们必须接受相关验证,以确保其没有被破坏并且不包含不良 Java代码(这是 java 的安全机制检测),完全没有问题后就会被动态加载到内存中,此时相当于Class 对象也就被载入内存了(毕竟.class 字节码文件保存的就是 Class 对象) ,同

    7、时也就可以被用来创建这个类的所有实例对象。下面通过一个简单例子来说明 Class 对象被加载的时机问题(例子引用自 Thinking in Java):在上述代码中,每个类 Candy、Gum 、Cookie 都存在一个 static 语句,这个语句会在类第一次被加载时执行,这个语句的作用就是告诉我们该类在什么时候被加载,执行结果:从结果来看,new 一个 Candy 对象和 Cookie 对象,构造函数将被调用,属于静态方法的引用,Candy 类的 Class 对象和 Cookie 的 Class 对象肯定会被加载,毕竟 Candy 实例对象的创建依据其 Class 对象。比较有意思的是从结

    8、果来看,new 一个 Candy 对象和 Cookie 对象,构造函数将被调用,属于静态方法的引用,Candy 类的 Class 对象和 Cookie 的 Class 对象肯定会被加载,毕竟 Candy 实例对象的创建依据其 Class 对象。比较有意思的是Class.forName(“com.zejian.Gum“);其中 forName 方法是 Class 类的一个 static 成员方法,记住所有的 Class 对象都源于这个 Class 类,因此 Class 类中定义的方法将适应所有 Class 对象。这里通过 forName 方法,我们可以获取到 Gum 类对应的 Class 对象引

    9、用。从打印结果来看,调用 forName 方法将会导致 Gum 类被加载 (前提是 Gum 类从来没有被加载过)。Class.forName 方法通过上述的案例,我们也就知道 Class.forName()方法的调用将会返回一个对应类的 Class对象,因此如果我们想获取一个类的运行时类型信息并加以使用时,可以调用Class.forName()方法获取 Class 对象的引用,这样做的好处是无需通过持有该类的实例对象引用而去获取 Class 对象,如下的第 2 种方式是通过一个实例对象获取一个类的 Class对象,其中的 getClass()是从顶级类 Object 继承而来的,它将返回表示该

    10、对象的实际类型的 Class 对象引用。注意调用 forName 方法时需要捕获一个名称为 ClassNotFoundException 的异常,因为forName 方法在编译器是无法检测到其传递的字符串对应的类是否存在的,只能在程序运行时进行检查,如果不存在就会抛出 ClassNotFoundException 异常。Class字面常量在 Java 中存在另一种方式来生成 Class 对象的引用,它就是 Class 字面常量,如下:/字面常量的方式获取 Class对象Class clazz = Gum.class;这种方式相对前面两种方法更加简单,更安全。因为它在编译器就会受到编译器的检查同

    11、时由于无需调用forName方法效率也会更高,因为通过字面量的方法获取Class 对象的引用不会自动初始化该类。更加有趣的是字面常量的获取Class对象引用方式不仅可以应用于普通的类,也可以应用用接口,数组以及基本数据类型,这点在反射技术应用传递参数时很有帮助,关于反射技术稍后会分析,由于基本数据类型还有对应的基本包装类型,其包装类型有一个标准字段TYPE,而这个 TYPE就是一个引用,指向基本数据类型的Class对象,其等价转换如下,一般情况下更倾向使用.class的形式,这样可以保持与普通类的形式统一。boolean.class = Boolean.TYPE;char.class = Ch

    12、aracter.TYPE;byte.class = Byte.TYPE;short.class = Short.TYPE;int.class = Integer.TYPE;long.class = Long.TYPE;float.class = Float.TYPE;double.class = Double.TYPE;void.class = Void.TYPE;前面提到过,使用字面常量的方式获取Class对象的引用不会触发类的初始化,这里我们可能需要简单了解一下类加载的过程,如下: 加载:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象

    13、链接:验证字节码的安全性和完整性,准备阶段正式为静态域分配存储空间,注意此时只是分配静态成员变量的存储空间,不包含实例成员变量,如果必要的话,解析这个类创建的对其他类的所有引用。 初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。由此可知,我们获取字面常量的 Class引用时,触发的应该是加载阶段,因为在这个阶段Class对象已创建完成,获取其引用并不困难,而无需触发类的最后阶段初始化。下面通过小例子来验证这个过程:import java.util.*;class Initable /编译期静态常量static final int staticFi

    14、nal = 47;/非编期静态常量static final int staticFinal2 =ClassInitialization.rand.nextInt(1000);static System.out.println(“Initializing Initable“);class Initable2 /静态成员变量static int staticNonFinal = 147;static System.out.println(“Initializing Initable2“);class Initable3 /静态成员变量static int staticNonFinal = 74;s

    15、tatic System.out.println(“Initializing Initable3“);public class ClassInitialization public static Random rand = new Random(47);public static void main(String args) throws Exception /字面常量获取方式获取Class对象Class initable = Initable.class;System.out.println(“After creating Initable ref“);/不触发类初始化System.out.

    16、println(Initable.staticFinal);/会触发类初始化System.out.println(Initable.staticFinal2);/会触发类初始化System.out.println(Initable2.staticNonFinal);/forName方法获取Class对象Class initable3 = Class.forName(“Initable3“);System.out.println(“After creating Initable3 ref“);System.out.println(Initable3.staticNonFinal);执行结果:Af

    17、ter creating Initable ref47Initializing Initable258Initializing Initable2147Initializing Initable3After creating Initable3 ref从输出结果来看,可以发现,通过字面常量获取方式获取 Initable 类的 Class 对象并没有触发 Initable 类的初始化,这点也验证了前面的分析,同时发现调用Initable.staticFinal变量时也没有触发初始化,这是因为 staticFinal 属于编译期静态常量,在编译阶段通过常量传播优化的方式将 Initable 类的常

    18、量 staticFinal存储到了一个称为 NotInitialization 类的常量池中,在以后对 Initable 类常量 staticFinal的引用实际都转化为对 NotInitialization 类对自身常量池的引用,所以在编译期后,对编译期常量的引用都将在 NotInitialization 类的常量池获取,这也就是引用编译期静态常量不会触发 Initable 类初始化的重要原因。但在之后调用了 Initable.staticFinal2变量后就触发了 Initable 类的初始化,注意 staticFinal2 虽然被 static 和 final 修饰,但其值在编译期并不能

    19、确定,因此 staticFinal2 并不是编译期常量,使用该变量必须先初始化 Initable 类。Initable2 和 Initable3 类中都是静态成员变量并非编译期常量,引用都会触发初始化。至于 forName 方法获取 Class 对象,肯定会触发初始化,这点在前面已分析过。到这几种获取 Class 对象的方式也都分析完,ok,到此这里可以得出小结论: 获取 Class 对象引用的方式 3 种,通过继承自 Object 类的 getClass 方法,Class类的静态方法 forName 以及字面常量的方式”.class”。 其中实例类的 getClass 方法和 Class 类

    20、的静态方法 forName 都将会触发类的初始化阶段,而字面常量获取 Class 对象的方式则不会触发初始化。 初始化是类加载的最后一个阶段,也就是说完成这个阶段后类也就加载到内存中(Class 对象在加载阶段已被创建),此时可以对类进行各种必要的操作了(如 new对象,调用静态成员等),注意在这个阶段,才真正开始执行类中定义的 Java 程序代码或者字节码。关于类加载的初始化阶段,在虚拟机规范严格规定了有且只有 5 种场景必须对类进行初始化: 使用 new 关键字实例化对象时、读取或者设置一个类的静态字段 (不包含编译期常量)以及调用静态方法的时候,必须触发类加载的初始化过程( 类加载过程最终阶段)。 使用反射包(java.lang.reflect)的方法对类进行反射调用时,如果类还没有被初始化,则需先进行初始化,这点对反射很重要。 当初始化一个类的时候,如果其父类还没进行初始化则需先触发其父类的初始化。 当 Java 虚拟机启动时,用户需要指定一个要执行的主类(包含 main 方法的类),虚拟机会先初始化这个主类

    展开阅读全文
    提示  道客多多所有资源均是用户自行上传分享,仅供网友学习交流,未经上传用户书面授权,请勿作他用。
    关于本文
    本文标题:class对象浅析.docx
    链接地址:https://www.docduoduo.com/p-206615.html
    关于我们 - 网站声明 - 网站地图 - 资源地图 - 友情链接 - 网站客服 - 联系我们

    道客多多用户QQ群:832276834  微博官方号:道客多多官方   知乎号:道客多多

    Copyright© 2025 道客多多 docduoduo.com 网站版权所有世界地图

    经营许可证编号:粤ICP备2021046453号    营业执照商标

    1.png 2.png 3.png 4.png 5.png 6.png 7.png 8.png 9.png 10.png



    收起
    展开