您的位置:首页 > 编程语言 > Java开发

《Java编程思想》第四版读书笔记 第五章

2016-05-07 00:00 267 查看
5.1

练习2中提到比较两个字符串域在定义就初始化和通过构造函数初始化的区别:

两个域都是在构造函数调用前就被初始化,不同的是一个给出了初始值,一个Java编译器赋给默认值null。然后通过构造函数再一遍复制。通过构造函数复制更灵活一些。

5.2

函数重载与基本类型提升相结合:

基本类型能从一个较小的类型自动提升至一个较大的类型,这种情况牵扯函数重载易造成混淆。

整数类型的字面常量都被当成int型参数。其他情况下,传入的数据类型(实参)小于方法中声明的形参类型,实参的数据类型就会被提升。char类型比较特殊,如果无法找到接受char参数的方法,就会把char直接提升成int型。

如果传入的实参类型小于重载方法声明的形参则必须强制转换成形参的类型,否则编译器会报错。

5.4

this关键字 只能在方法内部使用,表示对调用方法的那个对象的引用。本书作者不推荐在没有必要的时候使用this关键字。

可以在构造函数中使用this和参数列表,调用相关的构造函数。但在构造函数中仅可以调用一次,并且必须在构造函数的最起始处。除构造函数外,编译器禁止在其他任何方法中调用构造函数。

注意构造函数中不能通过构造函数的名字调用其他重载的构造函数。

5.5

垃圾回收期准备还释放对象占用的存储空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。finalize()方法用于释放对象中并非使用new获得的特殊内存区域(因为垃圾回收期只知道释放那些由new分配的内存),这种特殊的内存区域一般是由 本地方法创建的。

作者提到可以在finalize()方法中加入一些判断,这样对象在终结前若不处于正确的状态,可以处理这种错误或者提醒程序员。在示例程序中作者调用了System.gc()方法来使得对象的finalize()可以被调用,关于System.gc()它 只是提醒虚拟机:程序员希望进行一次垃圾回收。但是它不能保证垃圾回收一定会进行,而且具体什么时候进行是取决于具体的虚拟机的,不同的虚拟机有不同的对策。自己写程序测试了一下,new了一个对象然后调用System.gc()并没有使JVM调用垃圾回收器。new了一个对象的数组,当元素达到十万时开始有对象的finalize()方法被调用了。

在重写finalize()方法时应该总是假设基类的finalize()也要做某些重要的事情,因此有必要使用super调用基类的finalize()方法。

练习11要求写一个程序让finalize()总会被调用,在习题答案中连续使用了

System.gc();

System.runFinalization();

两个方法。作者说连续调用这两个方法会尽最大可能运行垃圾回收但并不一定。不同版本的JDK运行垃圾回收的行为会不同,调用这些方法仅仅是一个请求,并不能保证垃圾回收一定会运行。其实并没有方法能保证finalize()方法一定会被调用。

垃圾回收器一般有“标记-清扫”和“停止-复制”两种方式,如果有大量对象需要回收或碎片空间较多,就采用“停止-复制”模式,如果程序运行稳定垃圾较少则采用“标记-清扫”的方式,虚拟机会在合适的时间切换两种方式。

JVM中有一种即时(Just-In-Time,JIT)编译器的技术,这种技术可以把程序全部或部分翻译成本地机器码。当需要装载某个类时,编译器会先找到.class文件然后将字节码装入内存。此时有两种方案可供选择,一种让即时编译器编译所有加载的字节码,这种做法有两个缺陷,一是加载就需翻译全部加载代码增加额外开销,二是增加了可执行代码的长度(字节码要比即时编译器翻译后的本地机器码小很多),这将导致页面调度,从而降低程序速度。另外一种做法成为惰性评估(lazy evaluation),即时编译器只在必要的时候才编译代码。这样不被执行的代码不会被JIT编译。HotSpot采用了类型的方法。

5.7

静态初始化只有在必要时刻才会进行。

初始化的顺序是先静态对象,而后是非静态对象。

对象创建过程的总结,假设有个名为Dog的类:

1、当首次创建类型为Dog的对象或者Dog类的静态方法/静态域首次被访问时,Java解释器查找类路径,定位Dog.class文件;

2、载入Dog.class(这将创建一个Class对象),有关静态初始化的所有动作都会执行。因此,静态初始化只在Class对象首次加载的时候进行一次;

3、当用new Dog()创建对象的时候,首先将在堆上为Dog对象分配足够的存储空间;

4、这块存储空间会被清零,这就自动将Dog对象中的所有基本类型数据都设置成了默认值;

5、执行所有出现于字段定义处的初始化动作;

6、执行构造函数。

练习14中让类中的两个静态字段,一个在定义处初始化,一个在静态块中初始化。经过编程验证,定义处初始化先于静态块中的初始化。

可以使用非静态语句块初始化字段,方法与静态相同,只是去掉语句块前的static即可。经过编程验证,类中可以有多个语句块,执行的顺序按照语句块出现在代码中的顺序。

语句块执行在定义处初始化之后,构造函数执行。

5.8

用一对花括号括起来的值给数组初始化, 只能在创建数组的地方使用

Arrays.toString(array)方法产生数组的可以打印版本。

如果想在非定义的时候初始化数组,可采用以下的形式:

int[] ints;

ints = new int[] {1,2,3};

此种方法是错误的:ints = {1,2,3};

在1.5之后加入了可变的参数列表。指定参数时,编译器会自动去填充数组,获得的参数仍旧是个数组,可以用foreach来迭代。如果编译器发现参数已经是一个数组,则不会做任何的转换。可变长参数可以接受0个参数。

P102下部的示例中倒数第二行:

printArray((Object[]) new Integer[]{1,2,3,4,5});

之前一直认为数组是不能这么转换的,编程测试了一下是可以的,应该是向上转换可以即Integer[]转成Object[],而Object[]转Integer[]是不合法的,运行时报异常。

getClass()方法由System.out.println()打印出的信息中,前导的[表示是数组,其后是类名,如果是I表示是基本类型int。

可变长参数列表不依赖于自动包装机制,可以使用基本类型。但是,可变长参数依旧可以在必要的时候使用自动包装机制。

如果有多个可变长参数列表的重载函数,调用无参数的函数,编译器将不知道究竟要调用哪一个方法,而产生错误。

由于可变长参数列表与类型提升、自动包装等特性混合在一起使程序变得难以理解且不安全,所以作者推荐: 应该总是只在一个重载方法上使用可变长参数列表,或者压根不用。

从练习20可知,主函数的参数可以写成可变长参数列表的形式:

public static void main(String... args)

5.9

定义枚举

public enum Spiciness {

NOT, MILD, MEDIUM, HOT, FLAMING

}

为了使用枚举,需要创建该类型的一个引用,然后赋值:

Spiciness howHot = Spiciness.MEDIUM;

在创建enum时,编译器会自动添加一些有用的特性。如,它会创建toString()方法,以方便显示某个enum实例的名字,还会创建ordinal()方法,用来表示某个特定enum常量的声明顺序(也可以理解成enum常量所代表的值),以及static values()方法,用来按照enum常量的声明顺序,产生由这些常量值构成的数组。

尽管enum看起来像是一种新的数据类型,但是这个关键字只是为enum生成对应的类,并且产生了一些编译器行为,可以将enum当作类来处理。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: