四、初始化与清理
- 执行构造函数实际上分为三阶段:
- 调用父类的构造函数
- 用这些字段的初始器和初始化块来初始化它们;
- 执行构造函数体
-
如果我们为类编写了一个或多个构造器那么jvm就不会生成默认构造器了。
-
Java中区分重载方法的规则:每个重载的方法都必须有一个独一无二的参数类型列表。(参数顺序的不同也可以,但这样会使代码难以维护);不能使用返回值来区分重载方法。
-
涉及基本类型的重载:
- 重载时,如果传入的实际参数类型“小于”方法中声明的形式参数类型,实际参数的类型就会被自动“提升”至刚好比实际参数类型大的类型。Char型略有不同,如果找不到恰好接受char参数的方法,就会把char直接“提升”至int型。
- 如果方法接受“较小”的参数类型,而传入的实际参数类型“较大”,就得做必要的显式类型转换,否则编译器会报错。
-
this关键字:只能在方法内部使用,表示对“调用方法的那个对象”的引用。一般在必要时,才使用this,比如使用this明确引用类的数据成员。
如果想在一个构造器中调用另一个构造器,使用this关键字。在构造器中,如果为this添加了参数列表,那么就有了不同的含义:这将产生对符合此参数列表的某个构造器的明确调用。
注:必须将构造器调用放在最起始处,否则编译器会报错。使用super()调用基类构造器时必须放在最起始处。且一次只能调用一个构造器。
- static的含义:静态方法就是没有this的方法。
- “静态方法”内部不能调用“非静态方法”,反过来是可以的。
- 在类中置入静态方法,就可以访问其他静态方法和静态字段。
注:传递一个对象到静态方法里,然后就可以访问非静态方法了,但是通常为了达到这种效果。只需写一个非静态方法
-
finalize():用于那些不是由java创建对象方式(不是new方式分配的内存)为对象分配的存储空间的释放。
- 对象可能不被垃圾回收。
- 垃圾回收并不等于“析构”。
- 垃圾回收只与内存有关。
-
在程序中可以通过函数System.gc()强制终止程序,执行finalize()方法。(并不绝对)垃圾回收机制的工作方式有:“停止-复制”和“标记-清扫”。
-
初始化的顺序是先“静态”,后“非静态”。
总结一下对象的创建过程,假设有个名为Dog的类:- 即使没有显式地使用static关键字,构造器实际上也是静态方法。因此,当首次创建类型为Dog的对象时,或者Dog类的静态方法/静态域首次被访问时,java解释器必须查找类路径,以定位Dog.class文件。
- 然后载入Dog.class,有关静态初始化的所有动作都会执行。因此,静态初始化只在Class对象首次加载的时候进行一次。
- 当用new Dog()创建对象的时候,首次将在堆上为Dog对象分配足够的存储空间。
- 这块存储空间会被清零,这就自动地将Dog对象中的所有基本类型数据都设置成了默认值,而引用则被设置称null。
- 执行所有出现于字段定义处的初始化动作。
- 执行构造器。
-
静态初始化:静态初始化动作仅执行一次:当你首次生成这个类的一个对象时,或首次访问属于那个类的一个静态成员时(即使从未生成过对象)。
-
数组初始化,可以通过以下几种方式实现:
- 聚合初始化:
int[] a1 = {1,2,3,4,5} ;
- 数组复制:
int[] a2; a2=a1; //实际上是将a1的引用复制给a2
- 通过new方式,可能的话应该尽量这么做:
int[] a = new int[rand.nextInt(20)];
- 聚合同new方式结合:
Integer[] b = new Integer[]{ new Integer[1],new Integer[2],new Integer[3] };
- 定义后使用for逐个初始化:
- 聚合初始化:
注:
(1)所有数组中都包含一个固定成员length,表明数组的长度。
(2)如果数组中存储的是对象,当你试图使用数组中的空引用时,就会在运行时产生“异常”。
- J2SE5中的新特性:可变参数列表:即调用方法时,提供的参数是可变个数的。
例如:
static void printArray(Object... args) {
for (Object obj : args) {
System.out.println(obj);
}
}
调用时,可提供任意个数的参数。如:printArray(1,2); printArray(“one”, “two”, “three”); printArray();
可变参数列表实际上等效于参数数组语法,上面的函数等效于:
static void printArray(Object[] args) {
for (Object obj : args) {
System.out.println(obj);
}
}
有了可变参数,就再也不用显示地编写数组语法了。当你指定参数时,编译器实际上会为你去填充数组。
但可变参数列表使得重载变得复杂。例如一下两个方法:
static void f(Character... args) {
//….
}
static void f(Interger... args) {
//…
}
调用方法:f(‘a’,‘b’,‘c’);f(1,2); 没有问题,因为编译器会使用自动包装机制来匹配重载的方法,然后调用最明确匹配的方法。但调用方法f()时,编译器无法知道应该调用那个方法,会产出编译错误。可以通过在方法中加入一个非可变参数来解决该问题。
注:应该总是只在重载方法的一个版本上使用可变参数列表,或者压根就不使用它。
- 枚举类型:enum。例如:
public enum Spiciness {
NOT, MILD, MEDIUM, HOT, FLAMING
}
Spiciness howHot = Spiciness.MEDIUM;
创建enum时,编译器会自动添加一些有用的特性,例如
- toString()方法,用来显示某个enum实例的名字
- ordinal()方法,用来表示某个特定enum变量的声明顺序;
- static values()方法,用来按照enum常量的声明顺序产生常量值数组。
enum有一个特别适用的特性是,它可以和switch语句结合使用:
Spiciness degree = Spiciness.MEDIUM;
switch(degree) {
case NOT:
case MILD:
case MEDIUM:
case HOT:
case FLAMING:
default:
}