多态性
在面向对象中多态性实际上是面向对象里的一个最大的最有用的特点,对于多态性在java中有两种体现:
1、 方法的重载及覆写
2、 对象多态性:指的是父类对象和子类对象之间的转型操作
一、对象多态性:
继承允许将对象视为它本身或者它的父类的类型来处理,即将继承自同一父类的基类可以视为同一类处理,一份代码就可以毫无差别的运行在这些不同类型上。多态方法调用允许一种类型表现出与其他相似类型之间的区别,只要它们是从同一基类导出的。虽然这些方法都通过基类调用。
实现原理:后期绑定:在运行时根据对象的类型进行绑定。后期绑定也称为多态绑定或者运行时绑定。
Java中除了static方法和final方法(private方法属于final方法),其他的方法都是后期绑定。
只有普通的方法调用时多态的。
对象多态性的例子
基类,形状类 Shape.java
public class Shape { public void draw() {} public void erase() {} }
子类1,圆形 Circle.java
public class Circle extends Shape { public void draw() { System.out.println("Circle.draw()"); } public void erase() { System.out.println("Circle.erase()"); } }
子类2,三角形 Triangle.java
public class Triangle extends Shape { public void draw() { System.out.println("Triangle.draw()"); } public void erase() { System.out.println("Triangle.erase()"); } }
子类3,正方形 Square.java
public class Square extends Shape { public void draw() { System.out.println("Square.draw()"); } public void erase() { System.out.println("Square.erase()"); } }
多态性体现
public class Shapes { public static void main(String[] args) { Shape shape1 = new Circle(); Shape shape2 = new Triangle(); Shape shape3 = new Square(); shape1.draw(); shape2.draw(); shape3.draw(); } } /* Output: Circle.draw() Triangle.draw() Square.draw() */
二、多态性理解:
2.1只有非私有方法才可以被覆盖
“覆盖”私有方法:
class PrivateOverride { private void f() { System.out.println("privatef()"); } } public class Derived extends PrivateOverride { public void f() { System.out.println("publicf()"); } public static void main(String[] args) { PrivateOverride po = new Derived(); po.f();//异常The method f() from the type PrivateOverride is not visible } }
例子2:覆盖私有方法的现象,编译器不报错,但是不会按照我们期望的执行。
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()"); } } /* * Output: private f() */
2.2、父类和子类有相同的属性,且为public的情况
只有普通方法的调用时多态的,如果访问某一个成员变量,该成员变量是public的,且父类子类都有这个属性,它的访问是在编译期进行解析。
虽然发生向上转型,Super sup = new Sub();sup.field就没有多态性,访问的是父类的field。
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 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()); } } /* Output: sup.field = 0, sup.getField() = 1 sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0 */
2.3静态方法不具有多态性
如果某一个方法是静态的,它的行为就不具有多态性。
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(); // Upcast System.out.println(sup.staticGet());//静态方法,调用子类的 System.out.println(sup.dynamicGet());//非静态方法 } } /* * Output: Base staticGet() Derived dynamicGet() */
三、构造器内部调用的方法被子类覆盖的情况
构造器内部调用的方法被子类覆盖了。这个调用的效果很难预料,因为被覆盖的方法在对象被完全构造之前就会调用。这一定会造成一些难以发现的错误。
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); } } /* Output: Glyph() before draw() RoundGlyph.draw(), radius = 0 Glyph() after draw() RoundGlyph.RoundGlyph(), radius = 5 */
输出结果显示当Glyph的构造器调用draw()方法时候,radius不是默认初始值1而是0。
仔细分析这段代码,可以发现
1.在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零。
2.如前所述那样调用基类构造器,此时,调用被覆盖后的draw()方法(注意,要在调用RoundGlyph构造器之前调用),由于步骤1的缘故,我们此时会发现radius的值为0
3.按照声明的顺序调用成员的初始化方法。
4调用导出类的构造器主体
这样有一个优点,至少保证所有的东西初始化为零或者null,而不仅仅是垃圾。其中通过‘组合’而嵌入到一个类内部的对象引用,其值是null,如果忘记为该值进行初始化,均会在运行时候出现异常。查看结果时候,发现其他所有的东西都是0.
结论:编写构造器的准则
用尽可能简单的方法使对象进入正常状态,可以的话,避免调用其他的方法。构造器内唯一能够安全调用的方法是基类中的final方法(也适用于private,它们自动属于final方法),这些方法不能被覆盖,因此也不会出现上述令人惊讶的问题。
四、向上转型
发生向上转型后,子类中自己的定义的操作是无法通过父类对象找到的。
class Useful { public void f() {} public void g() {} } class MoreUseful extends Useful { public void f() {} public void 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].g(); // x[1].u();// 编译错误 The method u() is undefined for the type Useful ((MoreUseful) x[1]).u(); // ((MoreUseful) x[0]).u(); // 必须现有upCast才能有downCast,错误的向下转型,java.lang.ClassCastException } }