Q:谈谈类的初始化?
从实例创建中区分出类的初始化是很重要的一点。实例在你利用new来调用构造函数时被创建。一个类C,是在第一次被激活使用的时候初始化的。在这个过程中,这个类的初始化代码会以文本顺序运行。一共有两种类初始化代码:静态初始化代码块(static {…})和类的变量初始化(static String var = …)。
以下是对激活使用(active use)的一些定义,当你第一次进行如下任何一种操作时,就出发了激活使用这个条件:
1、通过调用构造函数创建了一个C的实例。
2、调用了C中定义的的静态方法(不是继承来的)。
3、对C中定义的静态变量(不是继承来的)进行读写。如果静态变量是被常量表达式(比如一些只用到了原始操作符的表达式(如+或者||)、常量以及被static final所修饰的变量)那么不会算数,因为这些是在编译的时候被初始化的。
下面是一个例子:
Program:
class A { static String a1 = ABC.echo(" 1: a1"); static String a2 = ABC.echo(" 2: a2"); } class B extends A { static String b1 = ABC.echo(" 3: b1"); static String b2; static { ABC.echo(" 4: B()"); b1 = ABC.echo(" 5: b1 reset"); a2 = ABC.echo(" 6: a2 reset"); } } class C extends B { static String c1; static { c1 = ABC.echo(" 7: c1"); } static String c2; static String c3 = ABC.echo(" 8: c3"); static { ABC.echo(" 9: C()"); c2 = ABC.echo("10: c2"); b2 = ABC.echo("11: b2"); } } public class ABC { static String echo(String arg) { System.out.println(arg); return arg; } public static void main(String[] args) { new C(); } } 输出:
1: a1 2: a2 3: b1 4: B() 5: b1 reset 6: a2 reset 7: c1 8: c3 9: C() 10: c2 11: b2
Q:我有一个类,内有6个实例变量,每一个变量都可以选择初始化或者不初始化。那么我是应该写64个构造函数么?
你当然不需要写(26)个构造函数。假设你有一个类叫C,它的定义如下:
public class C { int a,b,c,d,e,f; }
你可以为构造函数做如下几件事:
1、对极有可能需要的几种变量组合进行猜测,并且为之提供构造函数。赞成的观点认为:这是惯用的做法。反对的观点认为:很难完全猜对;会产生大量冗余代码。
2、定义可串联的setter方法,因为它们会返回this。如此一来,为每个实例变量定义一个setter,然后调用默认构造函数之后调用它们:
public C setA(int val) { a = val; return this; } ... new C().setA(1).setC(3).setE(5);
赞成:这是一种相当简洁且高效的方法。一些类似的观点在Bjarne Stroustrop的The Design and Evolution of C++一书中第156页被讨论过了。反对:你需要实现所有的setter,这并不遵从JavaBean规则(因为它们返回this而不是void),并且如果两个值之间需要交互的话,那这种方法也不适用了。
3、在默认的构造函数中利用非静态的初始化代码块对匿名子类进行初始化:
new C() {{ a = 1; c = 3; e = 5; }}
赞成:十分简洁,没有使用setter那么凌乱;反对:实例变量不能是私有的,处理子类需要额外的间接成本,而这个对象可能根本就不是C这个类(虽然它是C的一个实例)。这仅在你对实例变量有访问权限的时候才管用,然而包括经验丰富的Java程序员在内的大多数人都不会明白。
实际上很简单:定义一个新的没有命名的(匿名的)C的子类,而这个子类没有新添任何方法或变量,但初始化代码块初始化了a、c和e。如此定义这个类的话,你就相当于在创建一个实例。当我把这展示给Guy Steele看得时候,他说“哈哈!这太酷了,好吧,但我可能不会提倡这么做……”。和平时一样,Guy是对的(对了,你还可以用这种方法创建并初始化向量。你要知道能如此创建并初始化是非常给力的一件事儿,想想看,new String[] {“one”, “two”, “three”}就可以初始化一个String数组了。 曾经你以为必须用赋值语句对vector进行初始化的工作,现在也可以用类似的方法解决了new Vector(3) {{add(“one”); add(“two”); add(“three”)}})。
4、你可以换一种支持选择性初始化部分变量的语言。比如,C++就支持默认参数。所以你可以这么写:
class C { public: C(int a=1, int b=2, int c=3, int d=4, int e=5); } ... new C(10); // Construct an instance with defaults for b,c,d,e
Common Lisp和Python都有关键字参数,也支持默认参数,所以你可以这么写:
C(a=10, c=30, e=50) # Construct an instance; use defaults for b and d.
|