C++为我们引入了“构造函数”的概念。这是一种特殊的方法,在一个对象创建之后自动调用。Java也沿用了这个概念,但新增了自己的“垃圾收集器”,能在资源不再需要的时候自动释放它们。
1.用构建器自动初始化
请注意所有方法首字母小写的编码规则并不适用于构建器。这是由于构建器的名字必须与类名完全相同!
若Tree(int)是我们唯一的构建器,那么编译器不会允许我们以其他任何方式创建一个Tree对象。
在Java中,定义和初始化属于统一的概念——两者缺一不可。
2.方法重载(Method overloading)
例如重载构建器。方法名相同,参数不同。
2.1 区分重载方法
每个过载的方法都必须采取独一无二的自变量类型列表。即使自变量的顺序也足够我们区分两个方法(尽管我们通常不愿意采用这种方法,因为它会产生难以维护的代码)。
2.2 基本类型的重载
基本(数据)类型能从一个“较小”的类型自动转变成一个“较大”的类型。涉及过载问题时,这会稍微造成一些混乱。
常数值5被当作一个int值处理。所以假若可以使用一个过载的方法,就能获取它使用的int值。在其他所有情况下,若我们的数据类型“小于”方法中使用的自变量,就会对那种数据类型进行“转型”处理。char获得的效果稍有些不同,这是由于假期它没有发现一个准确的char匹配,就会转型为int。
若我们的自变量“大于”过载方法期望的自变量,这时又会出现什么情况呢?
在这里,方法采用了容量更小、范围更窄的主类型值。若我们的自变量范围比它宽,就必须用括号中的类型名将其转为适当的类型。如果不这样做,编译器会报告出错。
大家可注意到这是一种“缩小转换”。也就是说,在造型或转型过程中可能丢失一些信息。这正是编译器强迫我们明确定义的原因——我们需明确表达想要转型的愿望。
2.3 返回值重载
我们很易对下面这些问题感到迷惑:为什么只有类名和方法自变量列出?为什么不根据返回值对方法加以区分?
可能调用一个方法,同时忽略返回值;我们通常把这称为“为它的副作用去调用一个方法”,因为我们关心的不是返回值,而是方法调用的其他效果。
Java怎样判断f()的具体调用方式呢?而且别人如何识别并理解代码呢?由于存在这一类的问题,所以不能根据返回值类型来区分过载的方法。
2.4 默认构建器
默认构建器是没有自变量的。它们的作用是创建一个“空对象”。若创建一个没有构建器的类,则编译程序会帮我们自动创建一个默认构建器。
2.5 this关键字
为了能用简便的、面向对象的语法来书写代码——亦即“将消息发给对象”,编译器为我们完成了一些幕后工作。其中的秘密就是第一个自变量传递给方法f(),而且那个自变量是准备操作的那个对象的句柄。
假定我们在一个方法的内部,并希望获得当前对象的句柄。由于那个句柄是由编译器“秘密”传递的,所以没有标识符可用。然而,针对这一目的有个专用的关键字:this。
this关键字(注意只能在non-static方法内部使用)可为已调用了其方法的那个对象生成相应的句柄。可以像对待其他任何对象句柄一样对待这个句柄。但要注意,假若准备从自己某个类的一个方法内部调用该类的另一个方法,就不必使用this。只需简单地调用那个方法即可。当前的this句柄会自动应用于其他方法。
this关键字只能用于那些特殊的类——需明确使用当前对象的句柄。例如,假若您希望将句柄返回给当前对象,那么它经常在return语句中使用。
2.5.1 在构建器里调用构建器
若为一个类写了多个构建器,那么经常都需要在一个构建器里调用另一个构建器,以避免写重复的代码。可用this关键字做到这一点。
尽管可用this调用一个构建器,但不可调用两个。
由于自变量s的名字以及成员数据s的名字是相同的,所以会出现混淆。为解决这个问题,可用this.s来引用成员数据。
Flower(String s, int petals) {
this(petals);
//! this(s); // Can't call two!
this.s = s; // Another use of "this"
System.out.println("String & int args");
}
编译器不让我们从除了一个构建器之外的其他任何方法内部调用一个构建器。
2.5.2 static含义
static(静态)方法意味着一个特定的方法没有this。不可从一个static方法内部发出对非static方法的调用。
在没有任何对象的前提下,我们可针对类本身发出对一个static方法的调用。事实上,那正是static方法最基本的意义。
它就好象我们创建一个全局函数的等价物(在C语言中)。除了全局函数不允许在Java中使用以外,若将一个static方法置入一个类的内部,它就可以访问其他static方法以及static字段。
3.清除:收尾和垃圾收集
- 1.Your objects might not get garbage collected.
- 2.Garbage collection is not destruction.
- 3.Garbage collection is only about memory.
3.1 finalize()用途何在
finalize()一般可作为清除C/C++本地方法申请内存的地方。
4.成员初始化
Java尽自己的全力保证所有变量都能在使用前得到正确的初始化。若被定义成相对于一个方法的“局部”变量,这一保证就通过编译期的出错提示表现出来。
若将基本类型设为一个类的数据成员,情况就会变得稍微有些不同。一个类的所有基本类型数据成员都会保证获得一个初始值。
4.1 规定初始化
在类内部定义变量的同时也为其赋值。
4.2 构建器初始化
可考虑用构建器执行初始化进程。这样便可在编程时获得更大的灵活程度,因为我们可以在运行期调用方法和采取行动,从而“现场”决定初始化值。但要注意这样一件事情:不可妨碍自动初始化的进行,它在构建器进入之前就会发生。
4.2.1 初始化顺序
在一个类里,初始化的顺序是由变量在类内的定义顺序决定的。
4.2.2 静态数据的初始化
若数据是静态的(static),那么同样的事情就会发生;如果它属于一个基本类型,而且未对其初始化,就会自动获得自己的标准基本类型初始值;如果它是指向一个对象的句柄,那么除非新建一个对象,并将句柄同它连接起来,否则就会得到一个空值(NULL)。
static初始化只有在必要的时候才会进行。在那以后,static对象不会重新初始化。 初始化的顺序是首先static(如果它们尚未由前一次对象创建过程初始化),接着是非static对象。
总结一下对象的创建过程。请考虑一个名为Dog的类:
- (1) 类型为Dog的一个对象首次创建时,或者Dog类的static方法/static字段首次访问时,Java解释器必须找到Dog.class(在事先设好的类路径里搜索)。
- (2) 找到Dog.class后(它会创建一个Class对象,这将在后面学到),它的所有static初始化模块都会运行。因此,static初始化仅发生一次——在Class对象首次载入的时候。
- (3) 创建一个new Dog()时,Dog对象的构建进程首先会在内存堆(Heap)里为一个Dog对象分配足够多的存储空间。
- (4) 这种存储空间会清为零,将Dog中的所有基本类型设为它们的默认值(零用于数字,以及boolean和char的等价设定)。
- (5) 进行字段定义时发生的所有初始化都会执行。
- (6) 执行构建器。这实际可能要求进行相当多的操作,特别是在涉及继承的时候。
4.2.3 明确进行的静态初始化
Java允许我们将其他static初始化工作划分到类内一个特殊的“static构建从句”(有时也叫作“静态块”)里。
class Spoon {
static int i;
static {
i = 47;
}
// . . .
与其他static初始化一样,这段代码仅执行一次——首次生成那个类的一个对象时,或者首次访问属于那个类的一个static成员时(即便从未生成过那个类的对象)。
4.2.4 非静态实例的初始化
针对每个对象的非静态变量的初始化,Java提供了一种类似的语法格式。
public class Mugs {
Mug c1;
Mug c2;
{
c1 = new Mug(1);
c2 = new Mug(2);
System.out.println("c1 & c2 initialized");
}
为支持对“匿名内部类”的初始化,必须采用这一语法格式。
5.数组初始化
数组代表一系列对象或者基本数据类型,所有相同的类型都封装到一起——采用一个统一的标识符名称。数组的定义和使用是通过方括号索引运算符进行的([])。
对于数组,初始化工作可在代码的任何地方出现,但也可以使用一种特殊的初始化表达式,它必须在数组创建的地方出现。这种特殊的初始化是一系列由花括号封闭起来的值。
int[] a1 = { 1, 2, 3, 4, 5 };
由于需要检查每个数组的访问,所以会消耗一定的时间和多余的代码量,而且没有办法把它关闭。这意味着数组访问可能成为程序效率低下的重要原因——如果它们在关键的场合进行。但考虑到因特网访问的安全,以及程序员的编程效率,Java设计人员还是应该把它看作是值得的。
若操作的是一个非基本类型对象的数组,那么无论如何都要使用new。在这里,我们会再一次遇到句柄问题,因为我们创建的是一个句柄数组。
由于所有类最终都是从通用的根类Object中继承的,所以能创建一个方法,令其获取一个Object数组。
5.1 多维数组
构成多维数组的每个矢量都可以有任意的长度。