一起读Java编程思想(4)---多态怎么理解?什么是向下转型与向上转型?

    科技2024-12-26  5

    多态

    只有非private的方法才能被覆盖,但是推荐在导出类当中对于基类中private的方法最好采用不同的名字。并且基类中private的方法子类中不可见,也不能被重载。

    只有普通方法的调用是多态的。域是没有多态性的(事实上一般被声明为private)。静态的方法也不具备多态性。

    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; } } public class FiedlAccess { 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()); } }

    输出:

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

    由此可见 将子类对象向上转型为基类的应用变量,变量的域是基类的域,但是方法都是按照子类的方法运行,同时子类的方法。Sub对象转型为Super引用时,域访问操作都会被编译器解析,不是多态。Super.field与Sub.field都分配了不同的存储空间。

    如果规范是静态的就不会有多态性:

    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()"; } } public class StaticPolymorphism { public static void main(String[] args) { StaticSuper sup = new StaticSub(); System.out.println(sup.staticGet()); System.out.println(sup.dynamicGet()); } }

    输出:

    Base staticGet() Derived dynamicGet()

    构造器和多态

    构造器的调用顺序
    class Meal { Meal() { System.out.println("Meal()"); } } class Bread { Bread() { System.out.println("Bread()"); } } class Cheese { Cheese() { System.out.println("Cheese()"); } } class Lettuce { Lettuce() { System.out.println("Lettuce()"); } } class Lunch extends Meal { Lunch() { System.out.println("Lunch()"); } } class PortableLunch extends Lunch { PortableLunch() { System.out.println("PortableLunch()"); } } public class Sandwich extends PortableLunch { private Bread b = new Bread(); public Cheese c = new Cheese(); private Lettuce l = new Lettuce(); public Sandwich() { System.out.println("Sandwich()"); } public static void main(String[] args) { new Sandwich(); } }

    输出:

    Meal() Lunch() PortableLunch() Bread() Cheese() Lettuce() Sandwich()

    调用构造器的顺序需要满足如下顺序:

    1)调用基类构造器。这个过程会不断的反复递归下去。

    2)按照声明顺序调用成员的初始化方法。

    3)调用导出类构造器的主体。

    继承与清理
    import sun.security.krb5.internal.crypto.Des; import java.awt.*; 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 dipose"); 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(); } } public class Frog extends Amphibian{ private Characteristic p = new Characteristic("Croaks"); private Description t = new Description("Eats Bugs"); public 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(); } } 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 dipose disposing Description Animal not Vegetable disposing Characteristic has heart LivingCreature dispose disposing Description Basic Living Creature disposing Characteristic is alive

    层次结构中每个类Characteristic 与Description两种类型的成员对象,并且必须被销毁。为了避免某个子对象依赖于其他对象,所以销毁的顺序应该与初始化顺序相反。故字段意味着与声明的顺序相反。先对其导出类进行清理,然后是基类。因为导出类的清理可能会调用基类中的某些方法。

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

    这里Glyph.draw()已经由RoundGlyph.draw()覆盖。由输出可以看出radius不是默认的初始值1而是0.所以这里必须要说明初始化的实际过程:

    1)在其他任何事情发生之前,将分配给对象的存储空间初始化为二进制的零

    2)如前所述调用基类构造器。此时调用覆盖后的draw方法(要在调用RoundGlyph构造器之前)由于步骤1的缘故,此时发现radius的值为0 故输出0.

    3)按照声明的顺序调用成员的初始化方法。

    4)调用导出类的构造器主体

    这样做有点就是所有东西至少都初始化为0,而不仅仅留作垃圾。

    用继承进行设计

    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(); } } public class Transmogrify { public static void main(String[] args) { Stage stage = new Stage(); stage.performPlay(); stage.change(); stage.performPlay(); } }

    输出:

    HappyActor SadActor

    此处Stage包含一个对Actor的引用,而Actor被初始化为HappyActor对象,意味着performPlay()会产生某种特殊行为。

    向上转型后,导出类中接口的扩展部分不能被基类访问。但是一样的部分可以被重载。

    向下转型?

    向上转型(在继承层次中向上移动)会丢失具体的类型信息。所以我们想通过向下转型(继承层次中向下移动)。向上转型是安全的,因为基类不会有大于其导出类的忌口,因此向上转型是安全的通过基类接口发送消息保证都能被接收。但是向下转型就存在不安全性。必须要有某种方法确保向下转型的正确性。

    Java中所有转型都会得到检查,即使是进行一次普通加括号的类型转化,进入运行期时仍然会对其进行检查,保证是希望的类型,如果不是就会返回一个ClassCastException(类型转型异常)。

    class Useful { public void f() { System.out.println("Useful f()"); } public void g() { System.out.println("Useful g()"); } } class MoreUseful extends Useful { public void f() { System.out.println("MoreUseful f()"); } public void g() { System.out.println("MoreUseful g()"); } public void u() {} public void v() {} public void w() {} } public class RTTI { public static void main(String[] args) { Useful[] x = { new Useful(), new MoreUseful() }; x[0].f(); x[1].f(); x[0].g(); x[1].g(); // x[1].u(); 报错 向上转型后 额外的方法无法被调用 ((MoreUseful)x[1]).u(); // Downcast/RTTI 向下转型 ((MoreUseful)x[0]).u(); //会抛出异常 } } Useful f() MoreUseful f() Useful g() MoreUseful g() Exception in thread "main" java.lang.ClassCastException: Useful cannot be cast to MoreUseful at RTTI.main(RTTI.java:32)

    MoreUseful接口扩展了Useful接口但是其为继承来的,所以可以向上转型到Useful,在main方法中对数组x进行初始化可以看到这种情况,因为两个new的对象都属于Useful类型的 故可以调用f()与g()方法,并且MoreUseful的方法对基类中的f() g()进行了重载,调用的时候调用的是子类的重载函数。但是如果调用u()则会报错,因为进行了向上转型,无法控制超出基类函数的方法。

    如果想要访问MoreUseful的对象的借口,就可以尝试进行向下转型,如果所转型的类型是正确的,那么转型成功。否则就会返回一个ClassCastException异常。

    小结

    多态意味着”不同的形式“。在面向对象设计过程中,我们持有从基类基础来的相同接口,以及使用该接口的不同形式:不同版本的动态绑定方法。

    欢迎加微信公众号进一步交流!

    Processed: 0.030, SQL: 8