多态

  • 一、方法的多态
  • 二、对象的多态
  • 三、多态详解
  • 1. 向上转型:编译类型看左边,运行类型看右边。可以调用父类中的所有可调用的(访问权限)成员,不能调用子类中特有的成员。
  • 2. 向下转型:只能强转父类的引用,不能强转父类的对象,要求父类的引用必须指向的是当前目标类型的对象,可以调用子类类型中所有的成员。
  • 3. 属性没有重写之说,属性的值看编译类型。
  • 4. 类型转换异常:ClassCastException
  • 5. `instanceof`比较操作符,用于判断对象的类型是否为XX类型或者XX类型的子类型
  • 四、Java动态绑定机制【重要】


方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。

一、方法的多态

重载和重写就体现出方法的多态

当一个方法被重载时,使用不同的传参列表,可以调用不同的方法

java 多态this的用法 java多态详解_后端

而重写,则是根据方法对象的不同调用对应的方法

java 多态this的用法 java多态详解_java 多态this的用法_02

二、对象的多态

实际开发的过程中,父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用,更能体现出多态的扩展性与便利。

  • 一个对象的编译类型和运行类型可以不一致
  • 编译类型在定义对象时就确定了,不能改变
  • 运行类型是可以变化的
  • 编译类型看定义时=左边的,而运行类型看=右边的

比如:

Animal animal = new Dog();	// 编译类型是Animal,运行类型是Dog
animal = new Cat();	// 运行类型是可以改变的,变成了Cat,但是编译类型依然是Animal

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写后方法。所以不重写对象的多态则没有意义。

// 定义父类:
public class Animal {
	public void eat(){
		System.out.println("吃东西");
	}
}

// 定义子类:
class Cat extends Animal {
    public void eat() {
    	System.out.println("吃鱼");
    }
}

class Dog extends Animal {
	public void eat() {
		System.out.println("吃骨头");
	}
}

// 定义测试类:
public class Test {
    public static void main(String[] args) { 
        // 多态形式,创建对象
        Animal a1 = new Cat();
        // 调用的是 Cat 的 eat
        a1.eat();
        // 多态形式,创建对象
        Animal a2 = new Dog();
        // 调用的是 Dog 的 eat
        a2.eat();
    }
}

三、多态详解

定义初始类:

// 父类
public class Animal {

    public void eat() {
        System.out.println("吃");
    }
    
    public void run() {
        System.out.println("跑");
    }
    
    public void sleep() {
        System.out.println("睡");
    }

}
// 子类
public class Cat extends Animal{

    public void eat() {
        System.out.println("猫吃鱼");
    }

    public void catchMouse() {
        System.out.println("猫捉老鼠");
    }
}

1. 向上转型:编译类型看左边,运行类型看右边。可以调用父类中的所有可调用的(访问权限)成员,不能调用子类中特有的成员。

语法:

父类类型 变量名 = new 子类对象; 
变量名.方法名();

java 多态this的用法 java多态详解_java 多态this的用法_03


这里可以看到,调用子类中特有方法时,报错了,提示无法解析。

能调用哪些成员,是由编译类型决定的,调用的是运行类型的方法。
如果运行类型中没有该方法,则去父类中查找,如果父类中没有,则报错

如果运行类型中有该方法,并且可以访问,则执行运行类型中的该方法,也就是Cat类中的方法。
如果没有,则去父类中查找,如果有并且可以访问,则执行父类的,否则继续往上找。

运行效果如下:

java 多态this的用法 java多态详解_后端_04

2. 向下转型:只能强转父类的引用,不能强转父类的对象,要求父类的引用必须指向的是当前目标类型的对象,可以调用子类类型中所有的成员。

语法:

子类类型 引用名 = (子类类型) 父类引用;

java 多态this的用法 java多态详解_java 多态this的用法_05


可以给对象的引用,但是不能给对象运行效果如下:

java 多态this的用法 java多态详解_java_06


但是,父类的引用必须是强转目标类型的对象,否则就转换不了

java 多态this的用法 java多态详解_java 多态this的用法_07

3. 属性没有重写之说,属性的值看编译类型。

直接上代码+结果:

java 多态this的用法 java多态详解_后端_08

4. 类型转换异常:ClassCastException

public class Test {
    public static void main(String[] args) {
        // 向上转型
        Animal a = new Cat();
        a.eat(); // 调用的是 Cat 的 eat
        // 向下转型
        Dog d = (Dog)a;
        d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】 
    }
}

这段代码可以通过编译,但是运行时,却报出了 ClassCastException 类型转换异常
这是因为,创建Cat类型对象运行时,不能转换成Dog对象。这两个类并没有任何继承关系,不符合类型转换的定义。

为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验。

5. instanceof比较操作符,用于判断对象的类型是否为XX类型或者XX类型的子类型

这里bb对象是BBB类的对象,所以返回true

java 多态this的用法 java多态详解_父类_09

BBB bb = new BBB();
System.out.println(bb instanceof BBB);  // true
System.out.println(bb instanceof AAA);  // true
AAA aa = new BBB();
System.out.println(aa instanceof BBB);  // true
System.out.println(aa instanceof AAA);  // true
Object obj = new Object();
System.out.println(obj instanceof AAA);  // false

四、Java动态绑定机制【重要】

  • 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
  • 当调用对象属性时,没有动态绑定机制,哪里声明哪里使用

下面代码输出多少?

public class Dynamic {
    public static void main(String[] args) {
        A a = new B();
        System.out.println(a.sum());
        System.out.println(a.sum1());
    }
}

class A {
    public int i = 10;
    public int sum() {
        return getI() + 10;
    }
    public int sum1() {
        return i + 10;
    }
    public int getI() {
        return i;
    }
}

class B extends A {
    public int i = 20;
    public int getI() {
        return i;
    }
}

语句System.out.println(a.sum());解析:

  1. 当调用a.sum()的时候,会去找B类中的sum()方法,因为变量a的运行类型是B类
  2. 发现B类中没有sum()方法,则会去父类寻找,也就是A类
  3. 发现A类有sum()方法,调用
  4. 执行到getI()方法时,发现B类中有该方法,则会动态绑定到B类中的方法
  5. 执行B类中的方法得到20
  6. 再和10相加,得到30,然后打印输出

语句System.out.println(a.sum1());解析:

  1. 当调用a.sum1()的时候,会去找B类中的sum1()方法,因为变量a的运行类型是B类
  2. 发现B类中没有sum1()方法,则会去父类寻找,也就是A类
  3. 发现A类有sum1()方法,调用
  4. 因为属性是绑定到编译类型的,也就是A类,所以这里的i从A类中调用
  5. 找到A类中i的值并和10相加,得到20,打印输出