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

第八章 多态

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

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)。

你可能感兴趣的:(java)