6.1 合成的语法

6.1 合成的语法

就以前的学习情况来看,事实上已进行了多次“合成”操作。为进行合成,我们只需在新类里简单地置入对象指针即可。举个例子来说,假定需要在一个对象里容纳几个 String 对象、两种基本数据类型以及属于另一个类的一个对象。对于非基本类型的对象来说,只需将指针置于新类即可;而对于基本数据类型来说,则需在自己的类中定义它们。如下所示(若执行该程序时有麻烦,请参见第 3 章 3.1.2 小节“赋值”):

//: SprinklerSystem.java
// Composition for code reuse
package c06;

class WaterSource {
  private String s;
  WaterSource() {
    System.out.println("WaterSource()");
    s = new String("Constructed");
  }
  public String toString() { return s; }
}

public class SprinklerSystem {
  private String valve1, valve2, valve3, valve4;
  WaterSource source;
  int i;
  float f;
  void print() {
    System.out.println("valve1 = " + valve1);
    System.out.println("valve2 = " + valve2);
    System.out.println("valve3 = " + valve3);
    System.out.println("valve4 = " + valve4);
    System.out.println("i = " + i);
    System.out.println("f = " + f);
    System.out.println("source = " + source);
  }
  public static void main(String[] args) {
    SprinklerSystem x = new SprinklerSystem();
    x.print();
  }
} ///:~

WaterSource 内定义的一个方法是比较特别的:toString()。大家不久就会知道,每种非基本类型的对象都有一个 toString()方法。若编译器本来希望一个 String,但却获得某个这样的对象,就会调用这个方法。所以在下面这个表达式中:

System.out.println("source = " + source) ;

编译器会发现我们试图向一个 WaterSource 添加一个 String 对象(“source =")。这对它来说是不可接受的,因为我们只能将一个字串“添加”到另一个字串,所以它会说:“我要调用 toString(),把 source 转换成字串!”经这样处理后,它就能编译两个字串,并将结果字串传递给一个 System.out.println()。每次随同自己创建的一个类允许这种行为的时候,都只需要写一个 toString()方法。

如果不深究,可能会草率地认为编译器会为上述代码中的每个指针都自动构造对象(由于 Java 的安全和谨慎的形象)。例如,可能以为它会为 WaterSource 调用默认构造器,以便初始化 source。打印语句的输出事实上是:

valve1 = null
valve2 = null
valve3 = null
valve4 = null
i = 0
f = 0.0
source = null

在类内作为字段使用的基本数据会初始化成零,就象第 2 章指出的那样。但对象指针会初始化成 null。而且假若试图为它们中的任何一个调用方法,就会产生一次“异常”。这种结果实际是相当好的(而且很有用),我们可在不丢弃一次异常的前提下,仍然把它们打印出来。

编译器并不只是为每个指针创建一个默认对象,因为那样会在许多情况下招致不必要的开销。如希望指针得到初始化,可在下面这些地方进行:

(1) 在对象定义的时候。这意味着它们在构造器调用之前肯定能得到初始化。

(2) 在那个类的构造器中。

(3) 紧靠在要求实际使用那个对象之前。这样做可减少不必要的开销——假如对象并不需要创建的话。

下面向大家展示了所有这三种方法:

//: Bath.java
// Constructor initialization with composition

class Soap {
  private String s;
  Soap() {
    System.out.println("Soap()");
    s = new String("Constructed");
  }
  public String toString() { return s; }
}

public class Bath {
  private String
    // Initializing at point of definition:
    s1 = new String("Happy"),
    s2 = "Happy",
    s3, s4;
  Soap castille;
  int i;
  float toy;
  Bath() {
    System.out.println("Inside Bath()");
    s3 = new String("Joy");
    i = 47;
    toy = 3.14f;
    castille = new Soap();
  }
  void print() {
    // Delayed initialization:
    if(s4 == null)
      s4 = new String("Joy");
    System.out.println("s1 = " + s1);
    System.out.println("s2 = " + s2);
    System.out.println("s3 = " + s3);
    System.out.println("s4 = " + s4);
    System.out.println("i = " + i);
    System.out.println("toy = " + toy);
    System.out.println("castille = " + castille);
  }
  public static void main(String[] args) {
    Bath b = new Bath();
    b.print();
  }
} ///:~

请注意在 Bath 构造器中,在所有初始化开始之前执行了一个语句。如果不在定义时进行初始化,仍然不能保证能在将一条消息发给一个对象指针之前会执行任何初始化——除非出现不可避免的运行期异常。 下面是该程序的输出:

Inside Bath()
Soap()
s1 = Happy
s2 = Happy
s3 = Joy
s4 = Joy
i = 47
toy = 3.14
castille = Constructed

调用 print()时,它会填充 s4,使所有字段在使用之前都获得正确的初始化。

下一页