组合与继承语法(Thinking in Java 第七章)

7.1组合语法

假如,假设你需要某个对象,它要具有多个String对象、几个基本类型数据,以及另一个类的对象。对于非基本类型的对象,必须将其引用置于新的类中,但可以直接定义基本类型数据:

public class WaterSource {
    private String s;
    WaterSource(){
        System.out.println("watersource()");
        this.s = "Constructor";
    }
    public String toString(){
        return this.s;
    }
}

public class SpringSystem {
    private String value1 , value2 ,value3 , value4;
    private WaterSource source = new WaterSource();
    private int i;
    private float f;
    public String toString(){
        return
                "value1 = " + value1 + " " +
                "value2 = " + value2 + " " +
                "value3 = " + value3 + " " +
                "value4 = " + value4 + "\n"+
                "i = " + i + " " + "f = " + f +" " +
                "source = " + source;
        }
        public static void main(String[] args){
            SpringSystem springker = new SpringSystem();
            System.out.println(springker);
    }
}

结果如下

watersource()
value1 = null value2 = null value3 = null value4 = null
i = 0 f = 0.0 source = Constructor

在上面两个类所定义的方法中,有一个很特殊:toString()。每一个非基本类型的对象都有一个toString()方法,而且当编译器需要一个String而你却只有一个对象时,该方法便会被调用。所以在SpringSystem.toString()的表达式中:

"source" = + source ;

编译器将会得知你想要将一个String对象(“source=”)同waterSource相加。由于只能将一个String对象和另一个String对象相加,因此编译器会告诉你:“我将调用toString(),把source转换成为一个String”这样做之后,他就能将两个String连接到一起并将结果传递给System.out.println()。每当想要使所创建的类具备这样的行为时,仅需要编写一个toString()方法即可。

类中域为基本类型时能够自动被初始化为零。但是对象引用会被初始化为null,而且如果你试图为它们调用任何方法,都会得到一个异常——运行时错误。
编译器并不是简单地为每一个引用都创建默认对象,这一点是很有意义的,若真要那样做,就会在许多情况下增加不必要的负担。如果想初始化这些引用,可在在代码中的下列位置进行:

  1. 在定义对象的地方。者意味着它们总是能够在构造器被调用之前被初始化。
  2. 在类的构造器中。
  3. 就在正要使用这些对象之前,这种方式成为惰性初始化。在生成对象不值得及不必每次都生成对象的情况下,这种方式可以减少额外的负担。
  4. 使用示例初始化

以下是着四种方式的示例:

public class Soup {
    private String s;
    Soup(){
        System.out.println("Soup()");
        this.s = "Constructor";
    }
    public String toString(){
        return this.s;
    }
}
public class Bath {
    private String//定义对象的地方初始化
        s1 = "happy",
        s2 = "happy",
        s3 , s4 ;
    private Soup castille;
    private int i;
    private float toy;
    public Bath(){
        System.out.println("构造器内初始化");
        s3 = "joy";
        toy = 3.14f;
        castille = new Soup();
    }
    //示例初始化
    {
        i = 47;
    }
    public String toString(){
        if(s4 == null ){
            s4 = "joy"; //惰性初始化.
        }
        return "s1 = " + s1 + "\n" +
                "s2 = " + s2 + "\n" +
                "s3 = " + s3 + "\n" +
                "s4 = " + s4 + "\n" +
                "i = " + i + "\n" +
                "toy = " + toy + "\n" +
                "castille = " + castille;
    }
    public static void main(String[] args){
        Bath b = new Bath();
        System.out.println(b);
    }
}

结果如下

Soup()
s1 = happy
s2 = happy
s3 = joy
s4 = joy
i = 47
toy = 3.14
castille = Constructor

在Bath构造器中,有一行语句在所有初始化产生之前就已经执行了。如果没有在定义处初始化,那么除非发生了不可避免的运行期异常,否则将不能信息在发送给对象引用之前已经被初始化了。

7.2 继承语法

继承是所有OOP语言和Java语言不可缺少的组成部分。当创建一个类时,总是在继承,因此,除非已明确指出要从其他类中继承,否则就是在隐式地从Java的标准根类Object进行继承。
组合的语法比较平时,但是继承使用的是一种特殊的语法。在继承过程中,需要先声明“新类与旧类相似”。这种声明是通过在类主体的左边花括号之前,书写后面紧随基类名称的关键字extends而实现的。当这么做时,会自动得到基类中所有的域和方法。

public class Cleanser {
    private String s = "Cleanser";
    public void append(String a){
        s += a;
    }
    public void dilute(){
        append(" dilute() ");
    }
    public void apply(){
        append(" apply() ");
    }
    public void scrub(){
        append(" scrub() ");
    }
    public String toString(){
        return s;
    }
    public static void main(String[] args){
        Cleanser x = new Cleanser();
        x.dilute();
        x.apply();
        x.scrub();
        System.out.println(x);
    }
}

结果:

Cleanser dilute()  apply()  scrub() 

继承:

public class Detergent extends Cleanser {
    public void scrub(){
        append("Detergent.scrub()");
        super.scrub();//基类方法
    }
    //添加方法
    public void foam(){
        append(" foam() ");
    }
    public static void main(String[] args){
        Detergent x = new Detergent();
        x.dilute();
        x.apply();
        x.scrub();
        x.foam();
        System.out.println(x);
    }
}

结果:

Cleanser dilute()  apply() Detergent.scrub() scrub()  foam() 

Cleanser和Detergent均含有main()方法。可以为每一个类都创建一个main()方法。这种在每个类都设置一个main()方法的技术可使得每个类的单元测试都变得简单易行。而且在完成单元测试之后,也无需删除main(),可以将其留待下次测试。

Cleanser中所有的方法都必须是public的,这一点非常重要。请记住,如果没有加任何访问权限修饰词,那么成员默认的访问权限是包访问权限,它仅允许包内的成员访问。因此,在此包中,如果没有访问权限修饰词,任何人都可以使用这些方法。例如,Detergent就不成问题。但是其他包中的某个类若要从Cleanser中继承,则只能访问public成员。所以,为了继承,一般的规则是将所有的数据成员都指定为private,将所有的方法指定为public

正如我们在scrub()中所见,使用基类中定义的方法及对它们进行修改时可行的。在此例中,你可能想要在新版本中调用从基类而来的方法。但是在scrub()中,并不能直接调用scrub(),因为这样做将会产生递归,这并不是你所期望的。为解决此问题,**Java中用super关键字表示超类的意思,当前类就是从超类继承来的。**为此,表达式super.scrub()将调用基类版本的scrub()方法。
在继承的过程中,并不一定非得使用基类的方法。也可以在导出类中添加新方法,其添加方式与在类中添加任意方法一样,即对其加以定义即可。foam()方法即为一例。

初始化基类

由于现在涉及基类和导出类这两个类,而不是只有一个类,所以要试着想象导出类所产生的结果对象,会有点困惑。从外部来看,它就像是一个与基类具有相同接口的新类,或许还会有一些额外的方法和域。但继承并不只是复制基类的接口。当创建了一个导出类的对象时,该对象包含了一个基类的子对象。这个子对象与你用基类直接创建的对象是一样的。二者区别在于,后者来自于外部,而基类的子对象被包装在导出类对象内部。
当然,对基类子对象的正确初始化也是至关重要的,而且也仅有一种方法来保证这一点:在构造器中调用基类构造器来执行初始化,而基类构造器具有执行基类初始化所需要的所有知识和能力。 Java会自动在导出类的构造器中插入对基类构造器的调用。下例展示了上述机制在三层继承关系上是如何工作的:

public class Art {
    Art(){
        System.out.println("Art constructor");
    }
}
public class Drawing extends Art {
    Drawing(){
        System.out.println("Drawing constructor");
    }
}
public class Cartoon extends Drawing {
    Cartoon(){
        System.out.println("Cartoon constructoe");
    }
    public static void main(String[] args){
        Cartoon x = new Cartoon();
    }
}

结果:

Art constructor
Drawing constructor
Cartoon constructoe

构建过程是从基类“向外”扩散的,所以基类在导出类构造器可以访问它之前,就已经完成了初始化。即使你不为Cartoon()创建构造器,编译器也会为你合成一个默认的构造器,该构造器将调用基类的构造器。

带参数的构造器
上例中各个类均含有默认的构造器,即这些构造器都不带参数。编译器可以轻松地调用它们是因为不必考虑要传递什么样的参数的问题。但是,如果没有默认的基类构造器,或者想调用一个带参数的基类构造器,就必须使用关键字super显示地编写调用基类构造器的语句,并且配以适当的参数列表:

public class Game {
    Game(int i){
        System.out.println("Game constructor " + i);
    }
}
public class BoardGame extends Game {
    BoardGame(int i){
        super(i);
        System.out.println("BoardGame constructor " + i);
    }
}
public class Chess extends BoardGame {
    Chess(){
        super(11);
        System.out.println("Chess constructor");
    }
    public static void main(String[] args){
        Chess chess = new Chess();
    }
}


如果不在BoardGame()中调用基类构造器,编译器将“抱怨”无法找到符合Game()形式的构造器。而且,调用基类构造器必须是你在导出类构造器中要做的第一件事(如果你做错了,编译器会提醒你)。

思考一下下面这个能在屏幕上绘制图案的计算机辅助设计系统实例:

public class Shape {
    Shape(int i){
        System.out.println("Shape Constructor");
    }
    void dispose(){
        System.out.println("Shape Dispose");
    }
}
public class Trangle extends Shape {
    Trangle(int i){
        super(i);
        System.out.println("Drawing Trangle");
    }
    void dispose(){
        System.out.println("Erasing Trangle");
        super.dispose();
    }
}
public class Circle extends Shape {
    Circle(int i){
        super(i);
        System.out.println("Drawing Circle");
    }
    void dispose(){
        System.out.println("Erasing Circle");
        super.dispose();
    }
}
public class Line extends Shape {
    private int start , end ;
    Line(int start, int end){
        super(start);
        this.start = start;
        this.end = end;
        System.out.println("Drawing Line : " + start + " , " + end);
    }
    void dispose(){
        System.out.println("Erasing line : " + start + " , " + end);
        super.dispose();
    }
}
public class CADSystem extends Shape{
    private Circle c ;
    private Trangle t;
    private Line[] lines = new Line[3];
    public CADSystem(int i){
        super(i+1);
        for(int j = 0; j < lines.length; j++){
            lines[j] = new Line(j,j*j);
        }
        c = new Circle(1);
        t = new Trangle(1);
        System.out.println("Combine constructor");
    }
    public void dispose(){
        System.out.println("CADSyetem dispose");
        c.dispose();
        t.dispose();
        for(int i = lines.length - 1;i >= 0;i--){
            lines[i].dispose();
        }
        super.dispose();
    }
    public static void main(String[] args){
        CADSystem x = new CADSystem(47);
        try {
            //异常处理
        }finally {
            x.dispose();
        }
    }
}

输出结果:

Shape Constructor
Shape Constructor
Drawing Line : 0 , 0
Shape Constructor
Drawing Line : 1 , 1
Shape Constructor
Drawing Line : 2 , 4
Shape Constructor
Drawing Circle
Shape Constructor
Drawing Trangle
Combine constructor
CADSyetem dispose
Erasing Circle
Shape Dispose
Erasing Trangle
Shape Dispose
Erasing line : 2 , 4
Shape Dispose
Erasing line : 1 , 1
Shape Dispose
Erasing line : 0 , 0
Shape Dispose
Shape Dispose
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值