Java编程思想读书笔记——多态

时间:2021-05-05 17:51:20

第八章 多态

多态(动态绑定、后期绑定或运行时绑定)分离做什么和怎么做,从另外一个角度讲接口和实现分离。

8.1 再论向上转型

将一个对象的引用同时看作其父类的引用的做法称为向上转型。

8.1.1 忘记对象类型

在方法中只接受父类作为参数,则其不同的子类对象也可作为参数传入。

8.2 转机

8.2.1 方法调用绑定

绑定:将一个方法调用同一个方法主体关联起来称为绑定。
前期绑定:若在程序执行前进行绑定(由编译器和连接程序实现),叫做前期绑定。
后期绑定:在运行时根据对象的类型绑定,也称为动态绑定或运行时绑定。

要实现后期绑定,就必须在运行时能够根据对象判断其类型,从而调用恰当的方法。

需要注意的是,后期绑定的前提是具有继承关系,并且在父类中有方法在子类中被重写。

如果某个方法在父类中被声明为final(或者是private),显然不能被子类继承,所以也就不存在后期绑定的说法。

对于static方法而言,虽然子类可以继承父类的静态方法,但是却不能重写,即便子类声明了与父类同名的静态方法,也只不过是将父类的相应静态方法隐藏而已。因此,static方法也不具备后期绑定的特性。

除了static方法和private方法之外,其他方法都是后期绑定的。

8.2.2 产生正确的行为

有了后期绑定之后,就可以只编写与父类打交道的代码了。

8.2.3 可扩展性

不需要改变原有代码,只需要编写新类去继承某一父类,即可用参数为父类引用的方法操作新类对象。

8.2.4 缺陷:”覆盖”私有方法

final方法不具备多态的性质。

public class PrivateOverride {

private void f(){
System.out.println("private f()");
}

public static void main(String[] args){
PrivateOverride po = new Derived();
po.f();
}
}

class Derived extends PrivateOverride{

public void f(){
System.out.println("public f()");
}
}

上面代码输出的是private f()。由于父类的f()方法被声明为private,所以子类Derived不能继承该方法,而子类中的f()方法只是子类新添加的方法,只是方法名相同而已。虽然说是父类引用指向子类对象,但是引用po是父类引用,它只能调用父类的方法,而子类的f()方法,它是不具备的。

针对父类的private方法,子类在新增方法时最好采用不同的名字,以免引起歧义。

8.2.5 缺陷:域与静态方法

针对域(成员变量):

public class FieldAccess {

public static void main(String[] args){
Super sup = new Sub();
System.out.println("sup.field = " + sup.field + ", sup.getField() = " + sup.getField());

Sub sub = new Sub();
System.out.println("sub.field = " + sub.field + ", sub.getField() = " + sub.getField() +
", sub.getSuperField() = " + sub.getSuperField());
}
}

class Super{
public int field = 0;

public int getField(){
return field;
}
}

class Sub extends Super{
public int field = 1;

public int getField(){
return field;
}

public int getSuperField(){
return super.field;
}
}

当Sub对象向上转型为Super引用时,对任何域的访问操作都将由编译器解析,因此不具备多态特性。在上述代码中,Super.field和Sub.field分配了不同的存储空间。实际上,Sub包含了两个field,一个是它本身的field,另外一个是从父类继承过来的field,但默认的field还是它自己的field,要想得到从父类继承的field,必须显式地声明super.field。

这种做法一般在实际中不会发生,一方面是因为域一般是声明为private,另一方面也是在子类不会声明与父类名字相同的域。

针对静态方法:

public class StaticPolymorphism {

public static void main(String[] args){
StaticSuper sup = new StaticSub();
System.out.println(sup.staticGet());
System.out.println(sup.dynamicGet());
}
}

class StaticSuper{
public static String staticGet(){
return "Base staticGet()";
}

public String dynamicGet(){
return "Base dynamicGet()";
}
}

class StaticSub extends StaticSuper{
public static String staticGet(){
return "Derived staticGet()";
}

public String dynamicGet(){
return "Derived dynamicGet()";
}
}

静态方法也不具备多态的特性。

8.3 构造器与多态

8.3.1 构造器的调用顺序

在创建子类对象时,总是要先调用父类的构造器(无论是无参构造器还是有参构造器),再调用子类本身的构造器。

8.3.2 继承与清理

在对子类对象进行清理时,如果除了父类的垃圾需要清理外,还需要清理别的垃圾,就必须要覆盖父类的清理方法,并且在方法的末尾显式地调用父类的清理方法。

public class Frog extends Amphibian{
private Characteristic p = new Characteristic("Croaks");
private Description t = new Description("Eats Bugs");

Frog(){
System.out.println("Frog()");
}

protected void dispose(){
System.out.println("Frog dispose");
t.dispose();
p.dispose();
super.dispose();
}

public static void main(String[] args){
Frog frog = new Frog();
System.out.println("Bye!");
frog.dispose();
}
}

class Characteristic{
private String s;

Characteristic(String s){
this.s = s;
System.out.println("Creating Characteristic " + s);
}

protected void dispose(){
System.out.println("disposing Characteristic " + s);
}
}

class Description{
private String s;

Description(String s){
this.s = s;
System.out.println("Creating Description " + s);
}

protected void dispose(){
System.out.println("disposing Description " + s);
}
}

class LivingCreature{
private Characteristic p = new Characteristic("is alive");
private Description t = new Description("Basic Living Creature");

LivingCreature(){
System.out.println("LivingCreature()");
}

protected void dispose(){
System.out.println("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(){
System.out.println("Animal()");
}

protected void dispose(){
System.out.println("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(){
System.out.println("Amphibian()");
}

protected void dispose(){
System.out.println("Amphibian dispose");
t.dispose();
p.dispose();
super.dispose();
}
}

清理顺序与初始化顺序相反。

在上个例子中,Frog对象拥有自己的成员对象,而且只要Frog存活,只要不显式清除,其成员对象的存活周期应该与Frog一致。但是当多个对象共享同一个成员对象时,情况就变得比较复杂,必须使用引用计数的手段了。

public class ReferenceCounting {

public static void main(String[] args){
Shared shared = new Shared();
Composing[] composings = {
new Composing(shared),
new Composing(shared),
new Composing(shared),
new Composing(shared),
new Composing(shared),
};

for(Composing c : composings){
c.dispose();
}
}
}

class Shared{
private int refcount = 0;
private static long counter = 0;
private final long id = counter++;

public Shared(){
System.out.println("Creatring " + this);
}

public void addRef(){
refcount++;
}

protected void dispose(){
if(--refcount == 0){
System.out.println("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){
System.out.println("Creating " + this);
this.shared = shared;
this.shared.addRef();
}

protected void dispose(){
System.out.println("disposing " + this);
shared.dispose();
}

public String toString(){
return "Composing " + id;
}
}
8.3.3 构造器内部的多态方法的行为

如果在父类的构造器中调用了某个非静态非final的方法(f()方法),在其子类中有对这一方法进行覆盖,此时创建子类对象时,情况就变得有点复杂:

public class PolyConstructors {

public static void main(String[] args){
new RoundGlyph(5);
}

}

class Glyph{

void draw(){
System.out.println("Glyph.draw()");
}

Glyph(){
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw()");
}
}

class RoundGlyph extends Glyph{
private int radius = 1;

RoundGlyph(int r){
radius = r;
System.out.println("RoundGlyph.RoundGlyph(), radius = " + radius);
}

void draw(){
System.out.println("RoundGlyph.draw(), radius = " + radius);
}
}

Glyph.draw()方法在子类RoundGlyph中被覆盖。在创建RoundGlyph对象时,在先调用的父类构造器中,其调用的draw()方法就是RoundGlyph类中的draw()方法,但是注意radius的值,此时其值不是1,而是0。我觉得应该是radius的初始化(即将其赋值为1)还没进行,所以其值是0。

一般来说,在构造器中应避免调用其他方法,即便要调用也应该调用final方法。

8.4 协变返回类型

在子类已覆盖的方法中,可以返回父类方法返回类型的子类,这是JDK 5之后加入的特性。

public class CovariantReturn {

public static void main(String[] args){
Mill m = new Mill();
Grain g = m.process();
System.out.println(g);
m = new WheatMill();
g = m.process();
System.out.println(g);
}
}

class Grain{
public String toString(){
return "Grain";
}
}

class Wheat extends Grain{
public String toString(){
return "Wheat";
}
}

class Mill{
Grain process(){
return new Grain();
}
}

class WheatMill extends Mill{
Wheat process(){
return new Wheat();
}
}

8.5 用继承进行设计

优先考虑组合,慎用继承。

状态设计模式:

public class Transmogrify {

public static void main(String[] args){
Stage stage = new Stage();
stage.performPlay();
stage.change();
stage.performPlay();
}
}

class Actor{
public void act(){

}
}

class HappyActor extends Actor{
public void act(){
System.out.println("HappyActor");
}
}

class SadActor extends Actor{
public void act(){
System.out.println("SadActor");
}
}

class Stage{
private Actor actor = new HappyActor();

public void change(){
actor = new SadActor();
}

public void performPlay(){
actor.act();
}
}

用继承表达行为之间的差异,用字段表达状态上的变化。

8.5.1 春继承与扩展

is-a关系:子类继承父类的成员,并不添加新的成员。
is-like-a关系:子类继承父类的成员,并添加新的成员,在此种情况下,当进行向上转型时,新扩展的功能就无法使用父类引用访问。

8.5.2 向下转型与运行时类型识别

向下转型:将父类对象转变成子类对象,这种转型是有风险的。
在Java中,所有的转型都需要检查,以便确保它的确是所需类型,否则会抛出类型转换异常(ClassCastException)。