Thinking in Java 第八章------多态(2)

8-3构造器和多态

  • 构造器不同于其他的方法,涉及到多态时也是如此.
  • 构造器是static的,只不过该static是隐式申明的.
  • 构造器方法不具有多态性.

8-3-1构造器的调用顺序

  1. 基类的构造器总是在导出类的构造过程中被调用,并且按照继承层次,逐渐向上调用,使每个基类的构造器都能够被调用.

  2. 导出类只能访问自己的成员,而不能访问基类的成员(因为基类成员通常是private类型),只有基类的构造器才具有恰当的权限来对自己的成员进行初始化.

  3. 必须令所有的构造器都得到调用,否则不能正确的构造对象.

  4. 在导出类的构造器主体中,如果没有明确指定调用某个类的构造器,他会“默默地”调用默认构造器. 如果不存在默认构造器,编译器就会报错,若某个类没有构造器,编译器就会默认的合成一个无参构造器.若构造器有参数存在,就必须显示的调用.

Example(展示组合,继承,多态在构建顺序上的作用):

class Meal {
  Meal() { print("Meal()"); }
}

class Bread {
  Bread() { print("Bread()"); }
}

class Cheese {
  Cheese() { print("Cheese()"); }
}

class Lettuce {
  Lettuce() { print("Lettuce()"); }
}

class Lunch extends Meal {
  Lunch() { print("Lunch()"); }
}

class PortableLunch extends Lunch {
  PortableLunch() { print("PortableLunch()");}
}

public class Sandwich extends PortableLunch {
  private Bread b = new Bread();
  private Cheese c = new Cheese();
  private Lettuce l = new Lettuce();
  public Sandwich() { print("Sandwich()"); }
  public static void main(String[] args) {
    new Sandwich();
  }
} /* Output:
Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()
*///:~

Sandwich类反映了三层继承(如果将Object类也算在内的话,就是四层),在main()里创建一个Sandwich时,就可以看到输出结构遵照下面的顺序:

  1. 调用基类构造器. (这个步骤会不断反复地传递下去,首先是构造这种层次结构的根,然后是下一层导出类,等等,直至最低层的导出类)
  2. 按声明顺序调用成员的初始化方法.
  3. 调用导出类构造器的主题.

8-3-2继承与清理

  • 由于继承的缘故,如果我们有其他作为垃圾回收一部分的特殊清理动作,就必须在导出类中覆盖dispose()方法.
  • 当覆盖被继承类的dispose()方法时,务必在最后显示调用基类的dispose方法(super.dispose()),否则基类的清理动作就不会发生.

Example:

class Characteristic {//特征
  private String s;
  Characteristic(String s) {
    this.s = s;
    print("Creating Characteristic " + s);
  }
  protected void dispose() {
    print("disposing Characteristic " + s);
  }
}

class Description {//种类
  private String s;
  Description(String s) {
    this.s = s;
    print("Creating Description " + s);
  }
  protected void dispose() {
    print("disposing Description " + s);
  }
}

class LivingCreature {//生物
  private Characteristic p =
    new Characteristic("is alive");
  private Description t =
    new Description("Basic Living Creature");
  LivingCreature() {
    print("LivingCreature()");
  }
  protected void dispose() {
    print("LivingCreature dispose");
    t.dispose();
    p.dispose();
  }
}

class Animal extends LivingCreature {//动物
  private Characteristic p =
    new Characteristic("has heart");
  private Description t =
    new Description("Animal not Vegetable");
  Animal() { print("Animal()"); }
  protected void dispose() {
    print("Animal dispose");
    t.dispose();
    p.dispose();
    super.dispose();
  }
}

class Amphibian extends Animal {//两栖动物
  private Characteristic p =
    new Characteristic("can live in water");
  private Description t =
    new Description("Both water and land");
  Amphibian() {
    print("Amphibian()");
  }
  protected void dispose() {
    print("Amphibian dispose");
    t.dispose();
    p.dispose();
    super.dispose();
  }
}

public class Frog extends Amphibian {//青蛙
  private Characteristic p = new Characteristic("Croaks");
  private Description t = new Description("Eats Bugs");
  public Frog() { print("Frog()"); }
  protected void dispose() {
    print("Frog dispose");
    t.dispose();
    p.dispose();
    super.dispose();
  }
  public static void main(String[] args) {
    Frog frog = new Frog();
    print("Bye!");
    frog.dispose();
  }
} /* Output:
Creating Characteristic is alive
Creating Description Basic Living Creature
LivingCreature()
Creating Characteristic has heart
Creating Description Animal not Vegetable
Animal()
Creating Characteristic can live in water
Creating Description Both water and land
Amphibian()
Creating Characteristic Croaks
Creating Description Eats Bugs
Frog()
Bye!
Frog dispose
disposing Description Eats Bugs
disposing Characteristic Croaks
Amphibian dispose
disposing Description Both water and land
disposing Characteristic can live in water
Animal dispose
disposing Description Animal not Vegetable
disposing Characteristic has heart
LivingCreature dispose
disposing Description Basic Living Creature
disposing Characteristic is alive
*///:~
  1. 万一某个子类对向要依赖于其他对象,销毁的顺序应该和初始化的顺序相反;对于字段,则意味着与声明的顺序相反(因为字段的初始化是按照声明的顺序进行的).
  2. 对于基类,应该首先对导出来进行清理,然后才是基类.这是因为导出类的清理可能会调用基类的某些方法,所以需要使用基类中的构建仍起作用而不应该过早的销毁他们.

在上面的示例中,Frog对象拥有其自己的成员对象.Frog对象创建了它自己的成员对象,并且知道它们应该存活多久(只要Frog活着),因此Frog对象知道何时调用dispose()去释放成员对象.
然而,如果这些成员对象中存在于其他一个或者多个对象的共享情况,问题就变得复杂了,不能够简单的假设可以调用dispose()方法.在这种情况下,就必须使用引用计数来跟踪仍旧访问者共享对象的对象数量了.

Example:

class Shared {
  private int refcount = 0;
  private static long counter = 0;
  private final long id = counter++;
  public Shared() {
    print("Creating " + this);
  }
  public void addRef() { refcount++; }
  protected void dispose() {
    if(--refcount == 0)
      print("Disposing " + this);
  }
  public String toString() { return "Shared " + id; }
}

class Composing {
  private Shared shared;
  private static long counter = 0;
  private final long id = counter++;
  public Composing(Shared shared) {
    print("Creating " + this);
    this.shared = shared;
    this.shared.addRef();
  }
  protected void dispose() {
    print("disposing " + this);
    shared.dispose();
  }
  public String toString() { return "Composing " + id; }
}

public class ReferenceCounting {
  public static void main(String[] args) {
    Shared shared = new Shared();
    Composing[] composing = { new Composing(shared),
      new Composing(shared), new Composing(shared),
      new Composing(shared), new Composing(shared) };
    for(Composing c : composing)
      c.dispose();
  }
} /* Output:
Creating Shared 0
Creating Composing 0
Creating Composing 1
Creating Composing 2
Creating Composing 3
Creating Composing 4
disposing Composing 0
disposing Composing 1
disposing Composing 2
disposing Composing 3
disposing Composing 4
Disposing Shared 0
*///:~
  1. static long counter跟踪所创建的Shared实例的数量,还可以为id提供数值.
  2. counter类型是long而不是int,这样可以防止溢出.
  3. idfinal的,因为我们不希望它的值在对象生命周期中被改变.
  4. 将一个共享对象附着到类上时,必须记住调用addRef(),但是dispose()方法将跟踪引用数,并决定何时清理.

8-3-3 构造器内部的多态方法的行为

问题:构造器调用的层次结构带来了一个问题:如果在一个构造器的内部调用正在构造的对象的某个动态绑定方法,会发生什么情况?(动态绑定的定义: Thinking in Java 第八章——多态(1),8.2小节)
在构造器内部调用动态绑定方法,而这个方法所操纵的成员可能还没有进行初始化,这样会招致灾难.

Example:

class Glyph {
  void draw() { print("Glyph.draw()"); }
  Glyph() {
    print("Glyph() before draw()");
    draw();
    print("Glyph() after draw()");
  }
}   

class RoundGlyph extends Glyph {
  private int radius = 1;
  RoundGlyph(int r) {
    radius = r;
    print("RoundGlyph.RoundGlyph(), radius = " + radius);
  }
  void draw() {
    print("RoundGlyph.draw(), radius = " + radius);
  }
}   

public class PolyConstructors {
  public static void main(String[] args) {
    new RoundGlyph(5);
  }
} /* Output:
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
*///:~
  • Glyph.draw()方法设计为将要被覆盖,在RoundGlyph中发生覆盖,但是Glphy的构造器会调用这个方法,结果导致对RoundGlphy.draw()的调用.但是输出结果显示,radius不是默认初始值1,而是0.
  • 初始化完整顺序是解决这一谜题所在:

1.在其他任何事物发生之前,将分配给对象的存储空间初始化为二进制的零;
2.调用基类构造器.此时,调用被覆盖后的draw()方法(要在调用RoundGlyph构造器之前调用),由于步骤1的缘故,我们此时会发现radius值为0;
3.按照声明的顺序调用成员初始化的方法;
4.调用导出类的构造器主体;

  • 编写构造器时有效准则:用尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其他方法.
  • 在构造器内,唯一能够安全调用的方法是基类中的final方法(也适用于private方法,因为private方法也是final的),这些方法不能够被覆盖,所以不会出现上述情况.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值