“随着计算机的进步,‘不安全’的程序设计已成为造成编程代价高昂的罪魁祸首之一。”
1 用构建器自动初始化
构建器的名字必须与类名完全相同!和其他任何方法一样,构建器也能使用自变量,利用构建器的自变量,我们可为一个对象的初始化设定相应的参数。
构建器属于一种较特殊的方法类型,因为它没有返回值,但这与 void 返回值存在着明显的区别。
2 方法过载
在任何程序设计语言中,一项重要的特性就是名字的运用。方法名代表的是一种具体的行动。通过用名字描述自己的系统,可使自己的程序更易人们理解和修改。
1 . 区分过载方法
若方法有同样的名字, Java 怎样知道我们指的哪一个方法呢?一个简单的规则:每个过载的方法都必须采取独一无二的自变量类型列表。
2 . 主类型的过载
主(数据)类型能从一个“较小”的类型自动转变成一个“较大”的类型。涉及过载问题时,这会稍微造成一些混乱。
若我们的自变量“大于”过载方法期望的自变量,这时又会出现什么情况就必须用括号中的类型名将其转为适当的类型,如果不这样做,编译器会报告出错,这是一种“缩小转换”。
3 返回值过载
我们很易对下面这些问题感到迷惑:为什么只有类名和方法自变量列出?为什么不根据返回值对方法加以区分?
void f() {}
int f() {}
Java 怎样判断 f()的具体调用方式呢?由于存在这一类的问题,所以不能根据返回值类型来区分过载的方法。
4 默认构建器
默认构建器是没有自变量的,它们的作用是创建一个“空对象”。若创建一个没有构建器的类,则编译程序会帮我们自动创建一个默认构建器。
然而,如果已经定义了一个构建器(无论是否有自变量),编译程序都不会帮我们自动合成一个:
class Bush {
Bush(int i) {}
Bush(double d) {}
}
现在,假若使用下述代码:
new Bush();
编译程序就会报告自己找不到一个相符的构建器。这种情况下,编译程序就认为:“啊,你已写了一个构建器,所以我知道你想干什么;如果你不放置一个默认的,是由于你打算省略它。”
5 t h i s 关键字
this 关键字(注意只能在方法内部使用)可为已调用了其方法的那个对象生成相应的句柄,可象对待其他任何对象句柄一样对待这个句柄。
this 关键字只能用于那些特殊的类—需明确使用当前对象的句柄。例如,假若您希望将句柄返回给当前对象,那么它经常在 return 语句中使用。
public class Leaf {
private int i = 0;
Leaf increment() {
i++;
return this;
}
void print() {
System.out.println("i = " + i);
}
public static void main(String[] args) {
Leaf x = new Leaf();
x.increment().increment().increment().print();
}
}
由于 increment()通过 this 关键字返回当前对象的句柄,所以可以方便地对同一个对象执行多项操作。
1. 在构建器里调用构建器
若为一个类写了多个构建器,那么经常都需要在一个构建器里调用另一个构建器,以避免写重复的代码,可用 this 关键字做到这一点。
通常,当我们说 this 的时候,都是指“这个对象”或者“当前对象”。而且它本身会产生当前对象的一个句柄。
2. static 的含义
理解了 this 关键字后,我们可更完整地理解 static(静态)方法的含义。它意味着一个特定的方法没有this。我们不可从一个 static 方法内部发出对非 static 方法的调用,尽管反过来说是可以的。
3 清除:收尾和垃圾收集
垃圾收集器只知道释放那些由 new 分配的内存,所以不知道如何释放对象的“特殊”内存。为解决这个问题, Java 提供了一个名为finalize()的方法,可为我们的类定义它。
在理想情况下,它的工作原理应该是这样的:一旦垃圾收集器准备好释放对象占用的存储空间,它首先调用 finalize(),而且只有在下一次垃圾收集过程中,才会真正回收对象的内存。所以如果使用finalize(),就可以在垃圾收集期间进行一些重要的清除或清扫工作。
重点:我们的对象可能不会当作垃圾被收掉!垃圾收集只跟内存有关!
f i n a l i z e ( ) 用途何在
垃圾收集器存在的唯一原因是为了回收程序不再使用的内存。所以对于与垃圾收集有关的任何活动来说,其中最值得注意的是 finalize()方法,它们也必须同内存以及它的回收有关。
但这是否意味着假如对象包含了其他对象, finalize()就应该明确释放那些对象呢?答案是否定的—垃圾收集器会负责释放所有对象占据的内存,无论这些对象是如何创建的。
大家或许已弄清楚了自己不必过多地使用finalize()。这个思想是正确的;它并不是进行普通清除工作的理想场所。那么,普通的清除工作应在何处进行呢?
4 成员初始化
Java 尽自己的全力保证所有变量都能在使用前得到正确的初始化。在一个类的内部定义一个对象句柄时,如果不将其初始化成新对象,那个句柄就会获得一个空值。
1. 初始化顺序
在一个类里,初始化的顺序是由变量在类内的定义顺序决定的。即使变量定义大量遍布于方法定义的中间,那些变量仍会在调用任何方法之前得到初始化—甚至在构建器调用之前。
class Tag {
Tag(int marker) {
System.out.println("Tag(" + marker + ")");
}
}
class Card {
Tag t1 = new Tag(1); // Before constructor
Card() {
// Indicate we're in the constructor:
System.out.println("Card()");
t3 = new Tag(33); // Re-initialize t3
}
Tag t2 = new Tag(2); // After constructor
void f() {
System.out.println("f()");
}
Tag t3 = new Tag(3); // At end
}
public class OrderOfInitialization {
public static void main(String[] args) {
Card t = new Card();
t.f(); // Shows that construction is done
}
}
它的输入结果如下:
Tag(1)
Tag(2)
Tag(3)
Card()
Tag(33)
f()
2. 静态数据的初始化
若数据是静态的( static),那么同样的事情就会发生;如果它属于一个基本类型(主类型),而且未对其初始化,就会自动获得自己的标准基本类型初始值;如果它是指向一个对象的句柄,那么除非新建一个对
象,并将句柄同它连接起来,否则就会得到一个空值( NULL)。
如果想在定义的同时进行初始化,采取的方法与非静态值表面看起来是相同的。但由于static 值只有一个存储区域,所以无论创建多少个对象,都必然会遇到何时对那个存储区域进行初始化的问题。下面这个例子可将这个问题说更清楚一些:
class Bowl {
Bowl(int marker) {
System.out.println("Bowl(" + marker + ")");
}
void f(int marker) {
System.out.println("f(" + marker + ")");
}
}
class Table {
static Bowl b1 = new Bowl(1);
Table() {
System.out.println("Table()");
b2.f(1);
}
void f2(int marker) {
System.out.println("f2(" + marker + ")");
}
static Bowl b2 = new Bowl(2);
}
class Cupboard {
Bowl b3 = new Bowl(3);
static Bowl b4 = new Bowl(4);
Cupboard() {
System.out.println("Cupboard()");
b4.f(2);
}
void f3(int marker) {
System.out.println("f3(" + marker + ")");
}
static Bowl b5 = new Bowl(5);
}
public class StaticInitialization {
public static void main(String[] args) {
System.out.println("Creating new Cupboard() in main");
new Cupboard();
System.out.println("Creating new Cupboard() in main");
new Cupboard();
t2.f2(1);
t3.f3(1);
}
static Table t2 = new Table();
static Cupboard t3 = new Cupboard();
}
它的输出结果如下:
Bowl(1)
Bowl(2)
Table()
f(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f(2)
f2(1)
f3(1)
初始化的顺序是首先 static(如果它们尚未由前一次对象创建过程初始化),接着是非static 对象。static 初始化仅发生一次— 在 Class 对象首次载入的时候。
3. 明确进行的静态初始化
Java 允许我们将其他 static 初始化工作划分到类内一个特殊的“ static 构建从句”(有时也叫作“静态块”)里。
class Spoon {
static int i;
static {
i = 47;
}
// . . .
尽管看起来象个方法,但它实际只是一个 static 关键字,后面跟随一个方法主体。与其他 static 初始化一样,这段代码仅执行一次—首次生成那个类的一个对象时,或者首次访问属于那个类的一个 static 成员时(即便从未生成过那个类的对象)。
class Cup {
Cup(int marker) {
System.out.println("Cup(" + marker + ")");
}
void f(int marker) {
System.out.println("f(" + marker + ")");
}
}
class Cups {
static Cup c1;
static Cup c2;
static Cup c3 = new Cup(3);
static {
c1 = new Cup(1);
c2 = new Cup(2);
}
Cups() {
System.out.println("Cups()");
}
}
public class ExplicitStatic {
public static void main(String[] args) {
System.out.println("Inside main()");
Cups.c1.f(99); // (1)
}
static Cups x = new Cups(); // (2)
static Cups y = new Cups(); // (2)
}
输出为:
Cup(3)
Cup(1)
Cup(2)
Cups()
Cups()
Inside main()
f(99)
4. 非静态实例的初始化
针对每个对象的非静态变量的初始化, Java 提供了一种类似的语法格式。
class Mug {
Mug(int marker) {
System.out.println("Mug(" + marker + ")");
}
void f(int marker) {
System.out.println("f(" + marker + ")");
}
}
public class Mugs {
Mug c1;
Mug c2;
{
c1 = new Mug(1);
c2 = new Mug(2);
System.out.println("c1 & c2 initialized");
}
Mugs() {
System.out.println("Mugs()");
}
public static void main(String[] args) {
System.out.println("Inside main()");
Mugs x = new Mugs();
}
}
输出为:
Inside main()
Mug(1)
Mug(2)
c1 & c2 initialized
Mugs()
它看起来与静态初始化从句极其相似,只是 static 关键字从里面消失了。为支持对“匿名内部类”的初始化,必须采用这一语法格式。
5 数组初始化
数组代表一系列对象或者基本数据类型,所有相同的类型都封装到一起—采用一个统一的标识符名称。数组的定义和使用是通过方括号索引运算符进行的( [])。为定义一个数组,只需在类型名后简单地跟随一对空方括号即可:
int[] al;
也可以将方括号置于标识符后面,获得完全一致的结果:
int al[];
这种格式与 C 和 C++程序员习惯的格式是一致的。然而,最“通顺”的也许还是前一种语法,因为它指出类型是“一个 int 数组”。
为了给数组创建相应的存储空间,必须编写一个初始化表达式。一种特殊的初始化表达式是一系列由花括号封闭起来的值。存储空间的分配(等价于使用 new)将由编译器在这种情况下进行。例如:
int[] a1 = { 1, 2, 3, 4, 5 };
那么为什么还要定义一个没有数组的数组句柄呢?
int[] a2;
事实上在 Java 中,可将一个数组分配给另一个,所以能使用下述语句:a2 = a1;
演示:
public class Arrays {
public static void main(String[] args) {
int[] a1 = { 1, 2, 3, 4, 5 };
int[] a2;
a2 = a1;
for (int i = 0; i < a2.length; i++)
a2[i]++;
for (int i = 0; i < a1.length; i++)
prt("a1[" + i + "] = " + a1[i]);
}
static void prt(String s) {
System.out.println(s);
}
}
1 多维数组
在 Java 里可以方便地创建多维数组:
int[][] a1 = {
{ 1, 2, 3, },
{ 4, 5, 6, },
};
演示:
import java.util.*;
public class MultiDimArray {
static Random rand = new Random();
static int pRand(int mod) {
return Math.abs(rand.nextInt()) % mod + 1;
}
public static void main(String[] args) {
int[][] a1 = { { 1, 2, 3, }, { 4, 5, 6, }, };
for (int i = 0; i < a1.length; i++)
for (int j = 0; j < a1[i].length; j++)
prt("a1[" + i + "][" + j + "] = " + a1[i][j]);
// 3-D array with fixed length:
int[][][] a2 = new int[2][2][4];
for (int i = 0; i < a2.length; i++)
for (int j = 0; j < a2[i].length; j++)
for (int k = 0; k < a2[i][j].length; k++)
prt("a2[" + i + "][" + j + "][" + k + "] = " + a2[i][j][k]);
// 3-D array with varied-length vectors:
int[][][] a3 = new int[pRand(7)][][];
for (int i = 0; i < a3.length; i++) {
a3[i] = new int[pRand(5)][];
for (int j = 0; j < a3[i].length; j++)
a3[i][j] = new int[pRand(5)];
}
for (int i = 0; i < a3.length; i++)
for (int j = 0; j < a3[i].length; j++)
for (int k = 0; k < a3[i][j].length; k++)
prt("a3[" + i + "][" + j + "][" + k + "] = " + a3[i][j][k]);
// Array of non-primitive objects:
Integer[][] a4 = { { new Integer(1), new Integer(2) },
{ new Integer(3), new Integer(4) },
{ new Integer(5), new Integer(6) }, };
for (int i = 0; i < a4.length; i++)
for (int j = 0; j < a4[i].length; j++)
prt("a4[" + i + "][" + j + "] = " + a4[i][j]);
Integer[][] a5;
a5 = new Integer[3][];
for (int i = 0; i < a5.length; i++) {
a5[i] = new Integer[3];
for (int j = 0; j < a5[i].length; j++)
a5[i][j] = new Integer(i * j);
}
for (int i = 0; i < a5.length; i++)
for (int j = 0; j < a5[i].length; j++)
prt("a5[" + i + "][" + j + "] = " + a5[i][j]);
}
static void prt(String s) {
System.out.println(s);
}
}
输出略···