美文网首页
OnJava8笔记3 -- 多态

OnJava8笔记3 -- 多态

作者: 给点阳光我就灿烂_ab56 | 来源:发表于2020-04-16 15:09 被阅读0次

    多态

    陷阱:重写私有方法

    因为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 -- 多态

          本文链接:https://www.haomeiwen.com/subject/tcgrvhtx.html