2019独角兽企业重金招聘Python工程师标准>>>

14.2

RTTI运行时类型识别。

Class对象包含了与类有关的信息,Java使用Class对象来执行其RTTI。每个类都有一个Class对象,每当编写并且编译了一个新类,就会产生一个Class对象。为了生成这个类的对象,JVM使用类加载器子系统。类加载器子系统可以包含一条类加载器链:

  • 启动类加载器是根上的加载器,它负责加载rt.jar文件中所有的Java类,即Java的核心类都由该类加载器加载,在Sun JDK中,它是由C++实现的。在Java语言中无法获得它的引用;
  • 系统类加载器负责加载启动参数中指定的classpath中的jar包及目录,自己写 的类也由该ClassLoader加载。在Sun JDK中,系统类加载器的名字叫AppClassLoader;
  • 用户自定义类加载器由用户自己定义类的加载规则,例如可能会在数据库中查找字节码而不是检查.class文件。

所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员引用时,就会加载这个类。注意构造函数也是类的静态方法。使用new操作符创建类的新对象也会被当作对类的静态成员的引用。

因此,Java程序在它开始运行之前并非被完全加载,其各个部分是在必需时才加载的,C++是静态加载。

类加载器首先检查这个类的Class对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名检查.class文件。这个类的字节码被加载时,它们会接受验证以确保其没有被破坏,并且不包含不良的Java代码。一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。

Class类的静态方法forName()接受目标类的名字为参数获取该类Class对象的引用。如果找不到要加载的类会抛出ClassNotFoundException。无论何时,只要你想在运行时使用类型信息,就必须首先获得恰当的Class对象的引用。Class.forName()是获取Class对象引用的便捷途径,因为不需要为了获得Class引用而持有该类型的对象。但是,如果已经有了一个该对象的引用,可以通过调用它的getClass()方法来获取Class对象的引用了,这个方法属于Object的一部分。第三种生成Class对象引用的方法是类字面常量,例如:

FancyToy.class;

这样做不仅简单而且更安全,因为它在编译时就会受到检查,因此也不会抛出异常。类字面常量不仅可以应用于普通的类,也可以应用于接口、数组以及基本数据类型。另外对于基本数据类型的包装器类还有一个标准字段TYPE。它是指向对应基本数据类型的Class对象,例如:

boolean.class 与Boolean.TYPE等价。

作者建议使用boolean.class来保持与普通类的一致性。

作者提到奇怪的一点是,Class.forName()得到Class的对象会使得此类进行初始化步骤(此处说的初始化步骤并不是指初始化Class的对象,关于使用类而准备的三个步骤见下文),而字面常量**.class得到Class的对象并不会使此类进行初始化步骤。

为了使用类而做的准备工作实际包含三个步骤:

(1)加载,由类加载器执行,该步骤查询字节码(通常在classpath所指定的路径中查找),并从这些字节码中创建一个Class对象;

(2)链接,此阶段将验证类中的字节码,为静态域分配存储空间,并且如果必须的话,将解析这个类创建的对其他类的所有引用;

(3)初始化,如果该类具有超类,则对其初始化,执行静态初始化函数和静态初始化快。初始化被延迟到了对静态方法或者非常数静态域进行首次引用时才执行。

例子的总结:

  • 仅使用.class来获取Class对象的引用不会引发初始化步骤,而使用Class.forName()立即就进行了初始化;
  • 如果一个static final域的值是“编译期常量”,那么这个值不需要对类进行初始化步骤就可以被读取;但是如果static和final的值不是编译期常量,就会进行初始化步骤;
  • 如果一个static域不是final,那么对它访问也将进行初始化步骤。

Class类对象的getName()来产生全限定的类名,getSimpleName()和getCanonicalName()来产生不含包名的类名和全限定的类名。getName()和getCanonicalName()(1.5引入)方法的区别,前者返回的是虚拟机里面的class表示,后者返回的是更容易理解的表示,比如:

byte[]类型,前者[B,后者是byte[];

byte[][]类型,前者是[[B,后者是byte[][]。

isInterface()方法判断这个Class对象是否是一个借口。getInterfaces()方法返回的是该对象实现的接口。getSuperclass()查询Class对象的基类。newInstance()方法得到该类的一个新对象,使用它来创建对象必须带有默认的构造函数。

练习10让我们编译一个程序,使它能过判断char数组是基本类型还是一个对象。

char c = new char[3];
System.out.println(c.getClass().getCanonicalName());
System.out.println(c.getClass().getSuperclass().getCanonicalName());

输出显示c的基类是Object,说明他是一个对象。

从1.5开始为Class添加了泛型化处理,如果事先不知道Class的具体类型,可以写Class,也可以写Class表示它就是一个Number的子类,具体类型暂时不详。

向Class引用添加泛型语法的原因是为了提供编译期类型检查。

对类FancyToy的Class对象的getSuperClass()方法仅仅返回的是Class的对象,? super FancyToy表示某个类,它是FancyToy超类。

在1.5中,Class添加了cast()方法,该方法接受对象,并将其转型为Class引用的类型。

14.3

P325代码后面:“抽象的getTypes()方法在导出类中实现”和本页最后一段“getTypes()方法通常只返回对一个静态List的引用”,方法名getTypes()错了应为types()。

Class的newInstance()方法需要处理两个异常:

  1. InstantiationException 如果此 Class 表示一个抽象类、接口、数组类、基本类型或 void; 或者该类没有空构造方法; 或者由于其他某种原因导致实例化失败。
  2. IllegalAccessException 表示违反了Java的安全机制,这里主要指默认构造器为private的情况。

instanceof有比较严格的限制,只能将对象与类的名字进行比较,而不能与Class对象做比较。

作者认为如果程序中编写了许多的instanceof表达式,说明程序的设计存在瑕疵。

Class对象的isInstance()方法与instanceof表达式的作用相同。

作者在本节最后的例子中使用了Class对象的isAssignableFrom(Class)方法,判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口。

14.5

用instanceof和Class.isInstance方法可以完美的判断类的继承的情况,而用==或者equals()比较两个Class对象,则不能正确的判断继承的情况。例如:

class Child extends Parent {}Child c = new Child();
System.out.println(c.getClass == Child.class);
System.out.ptintln(c.getClass == Parent.class);System.out.println(c instanceof Child);
System.out.println(c instanceof Parent);

输出的结果为

true

false

true

true

14.6

Class类与java.lang.reflect包中的类一起对反射进行了支持,该库包含了Field、Method以及Constructor类(每个类都实现了Member接口)。

注意getConstructor()和getConstructors()方法只能获取public的构造函数。

14.7

在任何时刻,只要想要将额外的操作从“实际”对象中分离到不同的地方,特别是当洗完容易做出修改,从没有使用额外操作转为使用这些操作,或者反过来时,代理显得特别有用。

Java的动态代理,可以动态的创建代理并动态的处理对所代理方法的调用。在动态代理商所做的所有调用都会被重定向到单一的调用处理器上。java.lang.reflect.Proxy的静态方法newProxyInstance()可以动态创建代理,这个方法需要得到一个类加载器(通常可以从已经被加载的对象中获取类加载器,然后传递给它),一个希望代理实现的接口列表(不是类或者抽象类),以及一个InvocationHandler接口的一个实现(即调用处理器)。动态代理可以将所有调用重定向到调用处理器,因此通常会向调用处理器的构造函数传递一个实际对象的引用,从而使得调用处理器在执行其中介任务时可以将请求转发。

InvocationHandler的invoke()方法中第一个参数时代理对象,用来需要时区分请求的资源,但是在许多情况下并不需要它。

练习21和练习22翻译有误,原文写的是so that it measures method-call times,第一眼我也觉得应该翻译成调用的次数,但是看答案的代码应该是调用的时间。

练习中让我们System.out.println(proxy),导致了异常,看起来应该是循环调用了,之前书中有一句话:“在invoke()内部,在代理上调用方法时需要格外当心,因为对接口的调用将被重定向为对代理的调用”。此错误应该跟这句话有关,但我并不是很理解。

14.8

空对象可以接受传递给它的代表对象的信息,但是将返回表示为实际上并不存在任何“真是”对象的值。通过这种方法,可以假设所有的对象都是有效的,而不必浪费编程精力去检查Null。

为了创建空对象,最简单的方式使创建一个标记接口:

public interface Null {
}

这使得instanceof可以探测空对象,而并不要求在所有的类中都添加isNull()方法。

通常可以将空对象设置为单例,这样就可以使用equals()方法和==来判断是否是空对象。如果使用接口取代具体的空类,还可以使用动态代理机制来自动创建空对象。

14.9

作者通过几个例子向我们展示了Java的反射机制破坏了封装特性,通过反射你可以接触到普通类、包访问权限的类、私有内部类、匿名内部类的任何访问权限的域和方法,但是有一个特殊的地方,final域在遭遇反射修改时是安全的,运行时系统会在不抛出异常的情况下接受任何修改尝试,但是实际上不会发生任何修改。

14.10

作者在总结中提到他的一个编程思想:不要太早的关注程序的效率问题,最好首先让程序运作起来,然后再考虑它的效率。

如果要解决解决效率问题可以使用profiler工具。

转载于:https://my.oschina.net/u/2453016/blog/689407

《Java编程思想》第四版读书笔记 第十四章 类型信息相关推荐

  1. Think in Java第四版 读书笔记8第14章 类型信息(RTTI与反射)

    Java如何在运行时识别对象和类的信息? 1.RTTI(Run-time type information) 它假定我们在编译时已经知道了所有类型 2.反射 它允许我们在运行时发现和使用类的信息 14 ...

  2. Java编程思想(第4版)读书笔记——01

    1.面向对象程序设计(Object-oriented Programming, OOP) 2.程序运行时,对象有五个不同的地方可以存储数据: (1)寄存器 (2)堆栈 (3)堆 (4)常量存储 (5) ...

  3. Java编程思想第五版(On Java8)(二十四)-并发编程

    文章目录 术语 并发的新定义 并发的超能力 为速度而生的并发 普通的编程:首先发生一件事,然后是下一件事.我们完全控制所有步骤及其发生的顺序. 如果我们将值设置为5,那么稍后会回来并发现它是47,这将 ...

  4. Think in Java第四版 读书笔记10 第16章 数组

    Think in Java第四版 读书笔记10 第16章 数组 数组和容器很像 但他们有一些差别 16.1 数组为什么特殊 数组与容器的区别主要在效率和存储类型 效率:数组是简单的线性序列 使得数组的 ...

  5. Think in Java第四版 读书笔记9第15章 泛型

    Think in Java第四版 读书笔记9第15章 泛型 泛型:适用于很多很多的类型 与其他语言相比 Java的泛型可能有许多局限 但是它还是有很多优点的. 本章介绍java泛型的局限和优势以及ja ...

  6. Think in Java第四版 读书笔记7第13章 字符串

    本章内容 1.string的基本使用 2.string拼接符 + 3.Object方法toString 4.String的常用方法 5.String的格式化输出 6.正则表达式 13.1 不可变字符串 ...

  7. Think in Java第四版 读书笔记6第12章 异常处理

    12.1 概念 异常可以将"在正常时候执行的代码"和"发生错误时的代码"相分离,达到结构清晰的目的. a.受检查异常checkedException 编译器强制 ...

  8. Think in Java第四版 读书笔记3第七章第八章

    第七章复用类 复用代码的方式 1组合 2继承 方式1组合 public class Box {String boxName;public Box(String s) {System.out.print ...

  9. Think in Java第四版 读书笔记5第11章

    第十一章 持有对象(主要讲容器类) 概要 通常程序中我们需要一些容器来存储对象或者对象的引用 在java中承担这一责任的是数组和容器类 数组VS容器类 数组存在一个缺陷:长度固定不够灵活 而容器类则没 ...

最新文章

  1. 牛逼哄哄的 BitMap,到底牛逼在哪?
  2. 活动报名 | MIT陈涛:如何让机器人学习高频接触的操作技能
  3. 让CPU占用率听你指挥
  4. Hbase shell详情
  5. plotcylinder matlab,Matlab在任意两点之间绘制三维圆柱
  6. php post json请求参数传递_php post json参数的传递和接收处理方法
  7. Golang——延迟调用defer
  8. 利用HTML5中Geolocation获取地理位置在Google Map上定位
  9. MYSQL char字符型区分大小写,查询区分大小写
  10. 【转】鼠标右键多余菜单清理
  11. 1076: 三位数求解
  12. EXCEPTION_ACCESS_VIOLATION异常
  13. 股票怎么量化选股?怎么在通达信接口运行公式?
  14. coco数据集大小分类_VOC、COCO数据集类别
  15. 使用excel打开文件,修正长度过长的数字后几位变为0的问题
  16. torch.nn.parameter详解
  17. 男性:曹云金再恋嫩模:男人最重要的是有趣
  18. 全国国家重点实验室分布总览
  19. 多策略融合改进的均衡优化算法
  20. 四川大学《数据库系统原理》课程资源汇总

热门文章

  1. 3月24日下午专家聊天室:轻松掌握WCF 帮你找到入门砖
  2. TD-SCDMA向HSPA+的演进
  3. vt Hypervisor Framework
  4. 步步为营UML建模系列二、部署图(Deployment Diagram)
  5. 2018年十大云宕机事故盘点:主流无一幸免!
  6. 如何在大一时候系统学习编程?
  7. Linux - SVN下载项目
  8. TBox Library
  9. 从来都没有爱情发生的人生
  10. Delphi解析类似\u97e9这样的Unicode字符串