thinking in java 阅读笔记 第七章 多形性

1. 为什么要”上溯造型”?

                   比如有乐器(Instrument)和人(Person)两个类,人有演奏乐器这一方法(  play(Instrument i)  ).那么如果没有上溯造型,那么,如果乐器有n种子类,如 吉他、钢琴、贝斯,那么,在Person类中就要定义n种方法,而有了上溯造型,就只需要一个方法。减少代码量。

                   另外,就是扩展性更好,无论乐器下面的子孙类有多少,Person的play方法都能正常工作。

         2.讲一个方法定义为final之后,就会“关闭”动态绑定,告诉编译器不需要进行动态绑定。从而更高效。

         3.应该使用抽象类还是接口?

                   使用接口最重要的一个原因:能上溯造型至多个基础类。使用接口的第二个原因与使用抽象基础类的原因是一样的:防止客户程序员制作这个类的一个对象,以及规定它仅仅是一个接口。这样便带来了一个问题:到底应该使用一个接口还是一个抽象类呢?若使用接口,我们可以同时获得抽象类以及接口的好处。所以假如想创建的基础类没有任何方法定义或者成员变量,那么无论如何都愿意使用接口,而不要选择抽象类。事实上,如果事先知道某种东西会成为基础类,那么第一个选择就是把它变成一个接口。只有在必须使用方法定义或者成员变量的时候,才应考虑采用抽象类。

         4.接口中定义的字段会自动具有static,final,public属性

         5.内部类 在上溯造型的时候,可以非常方便地隐藏细节.

         6.class Egg {

       protected class Yolk {

      publicYolk() {

          System.out.println("Egg.Yolk()");

      }

       }

       private Yolk y;

       public Egg() {

         System.out.println("New Egg()");

         y =new Yolk();

         }

}

 

public classBigEgg extends Egg {

         public class Yolk {

          public Yolk() {

            System.out.println("BigEgg.Yolk()");

          }

  }

  public static void main(String[] args) {

    new BigEgg();

  }

} ///:~

 

默认构建器是由编译器自动合成的,而且会调用基础类的默认构建器。大家或许会认为由于准备创建一个BigEgg,所以会使用Yolk的“被覆盖”版本。但实际情况并非如此。输出如下:

New Egg()

Egg.Yolk()

 

7.构建器的调用顺序

(1) 调用基础类构建器。这个步骤会不断重复下去,首先得到构建的是分级结构的根部,然后是下一个衍生类,等等。直到抵达最深一层的衍生类。

(2) 按声明顺序调用成员初始化模块。

(3) 调用衍生构建器的主体。

 

8.构建器内部的多形性

构建器调用的分级结构(顺序)为我们带来了一个有趣的问题,或者说让我们进入了一种进退两难的局面。若当前位于一个构建器的内部,同时调用准备构建的那个对象的一个动态绑定方法,那么会出现什么情况呢?在原始的方法内部,我们完全可以想象会发生什么——动态绑定的调用会在运行期间进行解析,因为对象不知道它到底从属于方法所在的那个类,还是从属于从它衍生出来的某些类。为保持一致性,大家也许会认为这应该在构建器内部发生。

但实际情况并非完全如此。若调用构建器内部一个动态绑定的方法,会使用那个方法被覆盖的定义。然而,产生的效果可能并不如我们所愿,而且可能造成一些难于发现的程序错误。

从概念上讲,构建器的职责是让对象实际进入存在状态。在任何构建器内部,整个对象可能只是得到部分组织——我们只知道基础类对象已得到初始化,但却不知道哪些类已经继承。然而,一个动态绑定的方法调用却会在分级结构里“向前”或者“向外”前进。它调用位于衍生类里的一个方法。如果在构建器内部做这件事情,那么对于调用的方法,它要操纵的成员可能尚未得到正确的初始化——这显然不是我们所希望的。

通过观察下面这个例子,这个问题便会昭然若揭:

//:PolyConstructors.java

// Constructorsand polymorphism

// don't producewhat you might expect.

 

abstract classGlyph {

  abstract void draw();

  Glyph() {

    System.out.println("Glyph() beforedraw()");

    draw();

    System.out.println("Glyph() afterdraw()");

  }

}

 

class RoundGlyphextends Glyph {

  int radius = 1;

  RoundGlyph(int r) {

    radius = r;

    System.out.println(

      "RoundGlyph.RoundGlyph(), radius ="

      + radius);

  }

  void draw() {

    System.out.println(

      "RoundGlyph.draw(), radius = "+ radius);

  }

}

 

public classPolyConstructors {

  public static void main(String[] args) {

    new RoundGlyph(5);

  }

} ///:~

 

在Glyph中,draw()方法是“抽象的”(abstract),所以它可以被其他方法覆盖。事实上,我们在RoundGlyph中不得不对其进行覆盖。但Glyph构建器会调用这个方法,而且调用会在RoundGlyph.draw()中止,这看起来似乎是有意的。但请看看输出结果:

Glyph() beforedraw()

RoundGlyph.draw(),radius = 0

Glyph() afterdraw()

RoundGlyph.RoundGlyph(),radius = 5

 

当Glyph的构建器调用draw()时,radius的值甚至不是默认的初始值1,而是0。这可能是由于一个点号或者屏幕上根本什么都没有画而造成的。这样就不得不开始查找程序中的错误,试着找出程序不能工作的原因。

前一节讲述的初始化顺序并不十分完整,而那是解决问题的关键所在。初始化的实际过程是这样的:

(1) 在采取其他任何操作之前,为对象分配的存储空间初始化成二进制零。

(2) 就象前面叙述的那样,调用基础类构建器。此时,被覆盖的draw()方法会得到调用(的确是在RoundGlyph构建器调用之前),此时会发现radius的值为0,这是由于步骤(1)造成的。

(3) 按照原先声明的顺序调用成员初始化代码。

(4) 调用衍生类构建器的主体。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值