多态
在C++中,如果希望实现动态绑定,需要将成员函数声明为
virtual
。在Java中,动态绑定是默认的行为。如果不希望让一个方法是虚拟的,可以将它声明为final
。
1. 什么是多态
多态的概念
相同类型的对象,调用同一个方法时,可能展示不同的执行结果,这就是多态。
比如,如下代码中,当运行时调用该ploymophicBc
对象的方法时,其方法行为总是表现出子类方法的行为特征,而不是父类方法的行为特征:
// 编译时类型是BaseClass,运行时类型是SubClass
// 此时发生多态
BaseClass ploymophicBc = new SubClass();
注意,与方法不同,对象的实例变量不具备多态性,通过对象来访问其包含的实例字段时,系统总是试图访问它编译时类型所定义的成员变量,而不是它运行时类型所定义的成员变量。
Java的多态
绑定的概念——将一个方法调用与一个方法的方法体连接到一起,可分为编译时绑定与运行时绑定。
Java引用变量有编译时类型、运行时类型之分。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致且无报错,就是所谓的 多态(Polymorphism) 。
因为子类其实是一种特殊的父类,Java允许把一个子类对象直接赋给一个父类引用变量,无须任何类型转换,这被称为(upcasting),由系统自动完成。
public class Main {
public static void main(String[] args) {
Father ins1 = new Father(), ins2 = new Child();
ins1.f(); // 打印出:Father.
ins2.f(); // 打印出:Child. 因为运行时类型为 Child
ins1.g(); // 编译无法通过,因为完全是 Father 类型
ins2.g(); // 编译无法通过,因为编译时类型为 Father
((Child) ins1).g(); // 编译通过,但运行时错误
((Child) ins2).g(); // 打印出:New Money. 编译运行通过,因为运行时类型为 Child
}
}
class Father {
public void f() {
System.out.println("Father.");
}
}
class Child extends Father {
public void f() {
System.out.println("Child.");
}
public void g() {
System.out.println("New money.");
}
}
2. 强制类型转换
编译Java程序时,引用变量不能调用它的运行时类型的方法,若确实需要这样做,则必须进行强制类型转换,使它变成运行时类型,即将一个引用类型变量转换成其子类类型。
语法:(type)variable
(将variable
变量转换成一个type
类型的变量)。
注意:
- 基本类型之间的转换只能在数值类型之间进行,注意
boolean
类型被排除在外; - 引用类型之间的转换只能在具有继承关系的两个类型之间进行,否则会编译错误。
考虑到进行强制类型转换时可能出现异常,因此进行类型转换之前可以先通过instanceof
运算符来判断是否可以成功转换。
3. instanceof运算符
instanceof
运算符的前一个操作数通常是一个引用类型变量,后一个操作数是一个类/接口,用于判断前面的对象是否是后面的类,或者其子类、实现类的实例:
Object hello = "Hello";
System.out.println(hello instanceof Object); //true
System.out.println(hello instanceof String); //true
System.out.println(hello instanceof Math); //false
如果x
为null
,x instanceof C
将返回false
,因为null
不属于任何类型。
4. 构造方法的“多态性”
构造方法不具有多态性。但如果在构造方法里要去调用别的方法,那么这个构造方法和多态性就有了联系。
package com.chuan;
// 基类
abstract class Glypy {
Glypy() {
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw()");
}
abstract void draw();
}
// 派生类
class RoundGlyph extends Glypy {
int radius = 1;
RoundGlyph(int radius) {
this.radius = radius;
System.out.println("RoundGlyph.RoundGlyph(), raidus = " + radius);
}
@Override
void draw() {
System.out.println("RoundGlyph.draw(), radius = " + radius);
}
}
// 测试类
public class RoundGlyphTest {
public static void main(String[] args) {
new RoundGlyph(5);
}
}
// 输出结果
/*
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), raidus = 5
*/
// 结果解析
// https://www.bilibili.com/video/BV1U7411A7Cy?p=22&share_source=copy_web
因而,在定义构造方法时,要注意:
用尽可可能少的动作把对象的状态设置好;
如果可以,不要调用任何非构造方法;
在构造方法内唯一能够安全调用的是在基类中具有
final
属性的那些方法(也适用于private
方法,它们自动具有final
属性),这些方法不能被覆盖,所以不会出现上述潜在的问题。