详解Java面向对象的三大特征之三——多态性
- 何为多态性
- 多态性的使用前提:
- 多态的使用:虚拟方法调用
- 向下转型
- instanceof关键字
何为多态性
- 多态性可以理解为一个事物的多种形态。在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。
- 对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用
- 如在方法的重写中提到:多态性分为编译时多态和运行时多态。其中编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的方法。通过编译之后会变成两个不同的方法,在运行时谈不上多态。而运行时多态是动态的,主要指方法的重写,它是通过动态绑定来实现的,也就是大家通常所说的多态性。
举例
class Person{
}
class Man extends Person{
}
public class Test{
public static void main(String[] args){
Person p1 = new Man();
}
}
在上面代码中,定义了两个类,并且Man()类继承了Person()类,“Person p1 = new Man();”,此行代码即为父类的引用指向子类的对象。
多态性的使用前提:
- 需有类的继承关系
- 方法的重写
- 向上转型:在多态中需要将子类的对象赋给父类的引用,只有这样该引用才既能调用父类的方法,又能调用子类的方法。
多态的使用:虚拟方法调用
- 子类中定义了与父类同名同参数的方法(即方法的重写),在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
- 有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类中重写父类的方法。
一句话概括就是:编译,看左边;运行,看右边。
public class Test{
public static void main(String[] args) {
Person p1 = new Chinese();
p1.speak();
// p1不能调用子类中特有的方法,会报错
// p1.skin();
System.out.println("age:" + p1.age);
}
}
class Person{
int age = 20;
private String name;
public void speak(){
System.out.println("人可以说话");
}
}
class Chinese extends Person{
int age = 30;
@Override
public void speak() {
System.out.println("中国人说汉语");
}
public void skin(){
System.out.println("黄皮肤");
}
}
在上面代码中,父类Person()中定义了方法speak(),在子类Chinese()中,重写了此方法。根据前面总结的:编译时看左边,运行时看右边,此时如果我们想要查看“ p1.speak() ”具体调用的是哪个方法(将鼠标放在p1.speak()上,按住Ctrl键,然后点击鼠标),会发现eclipse指向了父类Person()中定义的speak()方法;但是,当我们运行程序时,实际输出的内容是“中国人说汉语”,说明运行时,实际调用的是子类Chinese()中重写的speak()方法。
注意:
- 对象p1不能调用子类中特有的方法,如代码中的“ skin() ”。
- 对象的多态性只适用于方法,不适用于属性。
上述代码运行结果:
向下转型
按照上面代码中的写法“ Person p1 = new Chinese(); ”,我们可以调用子类中重写父类的方法,但不能调用子类特有的方法,如果我们想要调用子类特有的方法,就需要用到向下转型(使用强制类型转换)。即
public class Test{
public static void main(String[] args) {
Person p1 = new Chinese();
p1.speak();
// p1不能调用子类中特有的方法,会报错
// p1.skin();
System.out.println("age:" + p1.age);
System.out.println("-----------------------");
Chinese c1 = (Chinese)p1; // 向下转型
c1.skin();
}
}
输出结果:
但是,在使用向下转型时,有可能出现ClassCastException的异常,例如:
class American extends Person{
@Override
public void speak() {
System.out.println("美国人说英语");
}
}
public class Test {
public static void main(String[] args) {
Person p1 = new Chinese();
p1.speak();
// p1不能调用子类中特有的方法,会报错
// p1.skin();
System.out.println("age:" + p1.age);
System.out.println("-----------------------");
Chinese c1 = (Chinese)p1;
c1.skin();
American a1 = (American)p1;
}
}
上面代码中,子类American()同样继承了父类Person(),在程序运行时,会在“ American a1 = (American)p1; ”出现异常:
为了避免在向下转型时,出现此异常,我们可以使用instanceof关键字。
- a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false
- 使用情境:为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,就不进行向下转型。
- 如果a instanceof A返回true,则a instanceof B也返回true。其中,类B是类A的父类。
举例
public class Test {
public static void main(String[] args) {
Person p1 = new Chinese();
p1.speak();
// p1不能调用子类中特有的方法,会报错
// p1.skin();
System.out.println("age:" + p1.age);
System.out.println("-----------------------");
Chinese c1 = (Chinese)p1;
c1.skin();
Person p2 = new American();
if(p2 instanceof Chinese){
System.out.println("p2 is a Chinese");
}
if(p2 instanceof American){
System.out.println("p2 is a American");
}
}
}
输出结果: