详解Java面向对象的三大特征之三——多态性

  • 何为多态性
  • 多态性的使用前提:
  • 多态的使用:虚拟方法调用
  • 向下转型
  • instanceof关键字


何为多态性

  1. 多态性可以理解为一个事物的多种形态。在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。
  2. 对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用
  3. 如在方法的重写中提到:多态性分为编译时多态和运行时多态。其中编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的方法。通过编译之后会变成两个不同的方法,在运行时谈不上多态。而运行时多态是动态的,主要指方法的重写,它是通过动态绑定来实现的,也就是大家通常所说的多态性。

举例

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();”,此行代码即为父类的引用指向子类的对象。

多态性的使用前提:

  • 需有类的继承关系
  • 方法的重写
  • 向上转型:在多态中需要将子类的对象赋给父类的引用,只有这样该引用才既能调用父类的方法,又能调用子类的方法。

多态的使用:虚拟方法调用

  1. 子类中定义了与父类同名同参数的方法(即方法的重写),在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
  2. 有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类中重写父类的方法。
    一句话概括就是:编译,看左边;运行,看右边。
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();
    }
}

输出结果:

java类的多态举例 java类的多态性有哪些_java类的多态举例

但是,在使用向下转型时,有可能出现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; ”出现异常:

java类的多态举例 java类的多态性有哪些_父类_02

为了避免在向下转型时,出现此异常,我们可以使用instanceof关键字。

instanceof关键字
  1. a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false
  2. 使用情境:为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,就不进行向下转型。
  3. 如果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");
        }
    }
}

输出结果:

java类的多态举例 java类的多态性有哪些_java类的多态举例_03