OnJava8笔记3 -- 多态

多态

陷阱:重写私有方法

因为private方法相当于final方法,是不能被重写的,若像以下代码那样重写:

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

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

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

输出:

private f()

因为private方法不能被重写,Derived中的 f( ) 相当于重新声明了一个叫做 f 的方法,而 PrivateOverride po = new Derived();这种向上转型中的多态只会作用于可以被重写的方法上,所以调用的是父类的 f( ) 方法

陷阱:向上转型中的属性访问

class Super {
    public int field = 0;

    public int getField() {
        return field;
    }
}

class Sub extends Super {
    public int field = 1;

    @Override
    public int getField() {
        return field;
    }

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

public class FieldAccess {
    public static void main(String[] args) {
        Super sup = new Sub(); // Upcast
        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())
    }
}

输出

sup.field = 0, sup.getField() = 1
sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0

可以看到,向上转型的对象在访问属性时,不会出现多态的现象,实际上会通过子类和父类不同的字段名来避免这种错误。

类构造器

对象初始化顺序

  1. 在分配内存的阶段,先为变量初始化默认值
  2. 之后先初始化最顶层父类,依次往下
  3. 每初始化一个类的对象,先按照声明顺序初始化变量,在调用构造函数

构造器内部调用对象方法

在构造器中调用对象方法,因为有多态的存在,可能会出现神奇bug,见例子代码。

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);
    }

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

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

输出:

Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5

可以看到,虽然在父类构造函数调用了draw方法,且符合多态结果(动态绑定到子类),但打印出的 radius 确实 0,这是因为在调用draw方法时,radius在子类中还没有被初始化为1,只有自己的默认值0。

尽量不要在构造函数里调用非final方法(或private)。

协变返回类型

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

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

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

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

见代码,Java5之后,被重写的方法的返回类型可以被改变成原返回类型的子类(16行和23行),这种叫协变返回类型。

向下转型

当把一个父类强制转型为子类时,若这个对象是父类引用子类对象,则可以正常执行新的自类扩展方法,因为java会在运行时检查类型(被称作运行时类型信息)。若这个对象是父类引用父类的对象,则调用子类方法会直接抛运行时异常。

class Useful {
    public void f() {
        System.out.println("userful f");
    }
    public void g() {
        System.out.println("userful g");
    }
}

class MoreUseful extends Useful {
    @Override
    public void f() {
        System.out.println("more f");
    }
    @Override
    public void g() {
        System.out.println("more g");
    }
    public void u() {
        System.out.println("more u");
    }
}

public class RTTI {
    public static void main(String[] args) {
        Useful[] x = {
                new Useful(),
                new MoreUseful()
        };
        x[0].f();
        x[1].g();
        // Compile time: method not found in Useful:
        //- x[1].u();
        ((MoreUseful) x[1]).u(); // Downcast/RTTI
//        ((MoreUseful) x[0]).u(); // Exception thrown
    }
}

输出:

userful f
more g
more u

可以看到,x[0]不能向下转型,x[1]可以正确的向下转型

你可能感兴趣的:(OnJava8笔记3 -- 多态)