5.1程序员
练习2中提到比较两个字符串域在定义就初始化和经过构造函数初始化的区别:编程
两个域都是在构造函数调用前就被初始化,不一样的是一个给出了初始值,一个Java编译器赋给默认值null。而后经过构造函数再一遍复制。经过构造函数复制更灵活一些。数组
5.2安全
函数重载与基本类型提高相结合:函数
基本类型能从一个较小的类型自动提高至一个较大的类型,这种状况牵扯函数重载易形成混淆。测试
整数类型的字面常量都被当成int型参数。其余状况下,传入的数据类型(实参)小于方法中声明的形参类型,实参的数据类型就会被提高。char类型比较特殊,若是没法找到接受char参数的方法,就会把char直接提高成int型。this
若是传入的实参类型小于重载方法声明的形参则必须强制转换成形参的类型,不然编译器会报错。lua
5.4spa
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的类:
一、当首次建立类型为Dog的对象或者Dog类的静态方法/静态域首次被访问时,Java解释器查找类路径,定位Dog.class文件;
二、载入Dog.class(这将建立一个Class对象),有关静态初始化的全部动做都会执行。所以,静态初始化只在Class对象首次加载的时候进行一次;
三、当用new Dog()建立对象的时候,首先将在堆上为Dog对象分配足够的存储空间;
四、这块存储空间会被清零,这就自动将Dog对象中的全部基本类型数据都设置成了默认值;
五、执行全部出现于字段定义处的初始化动做;
六、执行构造函数。
练习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看成类来处理。