一. 介绍
多态性是面向对象编程的又一个重要特征,它是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。
对面向对象来说,多态分为编译时多态和运行时多态。其中编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的方法。通过编译之后会变成两个不同的方法,在运行时谈不上多态。
而运行时多态是动态的,它是通过动态绑定来实现的,也就是大家通常所说的多态性。
Java 实现多态有 3 个必要条件:继承、重写和向上转型。只有满足这 3 个条件,开发人员才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而执行不同的行为。
- 继承:在多态中必须存在有继承关系的子类和父类。
- 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
- 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才既能可以调用父类的方法,又能调用子类的方法
多态的格式:
父类类型 变量名 = new 子类类型();
变量名.方法名();
1.1 举例说明
当使用多态方式调用方法是,首先检查父类中是否有该方法,如果没有,则编译错误,如果有,执行的是子类重写后的方法。如果子类没有重写该方法,就会调用父类的该方法。总结:编译看左边,运行看右边
定义动物父类:
package com.base.learn;
/**
* 先定义一个父类 ---> 动物类
* 动物都有吃的行为属性
*/
public class Animal {
public void eat() {
System.out.println("动物它们都会吃东西!!!");
}
}
定义猫咪子类:
package com.base.learn;
/**
* 定义猫咪继承动物类
* 随之重写里面的吃的行为,因为猫咪也有吃的行为,但是猫咪喜欢吃罐头
*/
public class Cat extends Animal {
public void eat() {
System.out.println("小猫咪都喜欢吃罐头!");
}
}
定义狗子类:
package com.base.learn;
/**
* 定义狗子类继承动物类
* 随之重写里面的吃行为,因为狗也有吃的行为,但是狗喜欢啃骨头
*/
public class Dog extends Animal {
public void eat() {
System.out.println("小狗爱吃骨头!");
}
}
定义测试类,测试多态性:
package com.base.learn;
public class Demo {
public static void main(String[] args) {
// 多态形式,创建猫类对象
Animal animal = new Cat();
// 调用的是 Cat 的 eat
animal.eat();
// 多态形式,创建狗类对象
Animal animal12 = new Dog();
// 调用的是 Dog 的 eat
animal12.eat();
}
}
//小猫咪都喜欢吃罐头!
//小狗爱吃骨头!
可以看出我们可以使用多态性得到不同动物的一个吃的行为属性
1.2 多态的好处
提高了代码的拓展性,使用父类类型作为方法形参,传递子类对象给方法,进行方法的调用
package com.base.learn;
public class Demo2 {
public static void main(String[] args) {
animalEat(new Cat());
animalEat(new Dog());
}
public static void animalEat(Animal animal) {
animal.eat();
}
}
//小猫咪都喜欢吃罐头!
//小狗爱吃骨头!
可以看出由于多态性,我们的 animalEat 方法的形参是 Animal 类型参数,父类型接受子类对象,进而执行子类对象的方法。
1.3 多态的弊端
无法访问子类独有的功能
package com.base.learn;
/**
* 定义猫咪继承动物类
* 随之重写里面的吃的行为,因为猫咪也有吃的行为,但是猫咪喜欢吃罐头
*/
public class Cat extends Animal {
public void eat() {
System.out.println("小猫咪都喜欢吃罐头!");
}
public void playBall() {
System.out.println("小猫咪都喜欢小球!");
}
}
package com.base.learn;
public class Demo3 {
public static void main(String[] args) {
Animal animal = new Cat();
animal.eat();
// 多态的弊端,无法访问子类独有的方法
// animal.playBall(); //编译报错,编译看左边,Animal没有这个方法
}
}
可以看到动物类和猫咪类有个共同的方法 eat,但是猫咪多了一个 playBall 方法。而对于动物对象来说,它本身动物类没有 playBall 方法,所以编译报错
二. 引用类型转换:
2.1 引用类型转换
从上面的多态的弊端的案例中,我们可以看到,我们使用动物对象时无法直接访问到猫类中的玩球球方法,这也就是我们之前说的编译看左边,运行看右边。
而在我们使用多态方式调用方法时,首先检查会左边的父类中是否有该方法,如果没有,则编译错误。也就代表着,父类无法调用子类独有的方法。
所以说,如果编译都错误,更别说运行了。这也是多态给我们带来的一点小困扰,而我们如果想要调用子类特有的方法,必须做向下转型。
2.2 向上转型(自动转换)
对于向下转型,我们先来讲解下向上转型的概念吧。
多态本身是子类向父类向上转换(自动转换)的过程,这个过程是默认的。当父类引用指向一个子类对象时,便是向上转型。
对于父类和子类的关系来说,具体来看图说话:
父类相对与子类来说是大范围的类型,Animal 是动物类,是父类。而 Cat 是猫咪类,是子类。
所以对于父类 Animal 来说,它的范围是比较大的,它包含一切动物,包括猫咪类和小狗类。
所以对于子类类型这种范围小的,我们可以直接自动转型给父类类型的变量。
使用格式:
父类类型 变量名 = new 子类类型();
如:Animal animal = new Cat();
相当于有:
Animal animal = (Animal) new Cat();
相当于自动帮我们了一个隐形的转换为动物类的一个过程,因为动物本身就包含了猫咪。
2.3 向下转型(强制转换)
向上转型可以知道它是子类自动转换为父类的一个过程,所以我们现在再来看看向下转型的定义:
向下转型:
向下转型就是由父类向子类向下转换的过程,这个过程是强制的。一个需要将父类对象转为子类对象,可以使用强制类型转换的格式,这便是向下转型。
为什么这种就必须自己强制加上一个类型转换过程呢?
对于父类和子类的关系来说,我们接着看图说话:
对于猫咪类的话,它在动物类中只是其中的一部分吧,而对于动物类来说,它有许多其他子类动物如狗,牛,猪等等。
所以对于动物父类想要向下转型的时候, 它此时不知道指向那个子类,因为不确定呀,所以就必须自己加上强制的类型转换的一个过程。
使用格式:
子类类型 变量名 = (子类类型) 父类变量名;
如:
Animal animal = new Cat();
Cat cat = (Cat) animal;
cat.playBall();// 此时我们就可以使用猫咪的特有方法啦
所以对于多态的弊端,无法使用子类特有的参数,我们也解决啦,可以通过向下转型的方法,从而将类型强制转换为某个子类对象后,再去调用子类的特有方法!
2.4 转型的异常
虽然我们可以使用向下转型使得我们可以使用子类的独有方法,但是转型的过程中,一不小心就会遇到这样的问题了,来,我们来看看下面的代码:
定义狗类中额外的独有遛狗方法
package com.base.learn;
/**
* 定义狗子类继承动物类
* 随之重写里面的吃行为,因为狗也有吃的行为,但是狗喜欢啃骨头
*/
public class Dog extends Animal {
public void eat() {
System.out.println("小狗爱吃骨头!");
}
public void walk() {
System.out.println("遛狗!");
}
}
package com.base.learn;
public class Demo4 {
public static void main(String[] args) {
// 自动向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Dog d = (Dog) a;
d.walk(); // 调用 Dog 的 walk 方法,运行报错
}
}
// 小猫咪都喜欢吃罐头!
// Exception in thread "main" java.lang.ClassCastException: com.base.learn.Cat cannot be cast to com.base.learn.Dog
// at com.base.learn.Demo4.main(Demo4.java:10)
我们可以看到,虽然我们的代码通过编译,但是终究在运行时,还是出错了,抛出了 ClassCastException 类型转换的异常。
其实我们可以知道,我们在上面的时候,创建了 Cat 类型对象,而在向下转型时,将其强行转换为了 Dog 类型,所以程序在运行时,就会抛出类型转换的异常!
2.5 instanceof 关键字
Java为我们提供一个关键字 instanceof ,它可以帮助我们避免了 ClassCastException 类型转换异常的发生。
格式:
变量名 instanceof 数据类型
解释:
- 如果变量属于该数据类型或者其子类类型,返回true。
- 如果变量不属于该数据类型或者其子类类型,返回false。
代码实现:
package com.base.learn;
/**
* @author myf15609
* @Date 2022/7/8
*/
public class Demo5 {
public static void main(String[] args) {
// 自动向上转型
Animal animal = new Cat();
animal.eat(); // 调用的是 Cat 的 eat
// 向下转型
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.playBall(); // 调用的是 Cat 的 playBall
}else if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.walk(); // 调用的是 Dog 的 walk
}
}
}
// 小猫咪都喜欢吃罐头!
// 小猫咪都喜欢小球!
可以发现,它可以帮助我们在做类型转换前,判断该类型是否属于该类型或者子类类型,如果是,我们就可以强转啦!
2.6 视频教程总结
- 理解多态性:可以理解为一个事物的多种态性
- 何为多态性:
- 对象的多态性:父类的引用指向子类的对象(或子类的对象赋值给父类的引用)
- 多态的使用:虚拟方法调用
- 有了对象多态性以后,我们在编译期,只能调用父类声明的方法,但在执行期实际执行的是子类重写父类的方法
- 简称:编译时,看左边;运行时,看右边。
- 若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
- 多态情况下:
- “看左边”:看的是父类的引用(父类中不具备子类特有的方法)
- “看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)
- 多态性的使用前提:
- ① 类的继承关系
- ② 方法的重写
- 对象的多态性:只适用于方法,不适用于属性(编译和运行都看左边)
练习1
package com.base.learn;
/**
* 小练习来说明下多态性
* 1.若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。
* 2.对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量
*/
public class Demo6 {
public static void main(String[] args) {
Sub s= new Sub();
System.out.println(s.count); // 20
s.display(); // 20
Base b = s;
//==:对于引用数据类型来讲,比较的是两个引用数据类型变量的地址值是否一样。
System.out.println(b == s); // true
System.out.println(b.count); // 10
b.display(); // 20
}
}
class Base {
int count= 10;
public void display() {
System.out.println(this.count);
}
}
class Sub extends Base {
int count= 20;
public void display() {
System.out.println(this.count);
}
}
练习2
package com.base.learn;
public class Demo7 {
public static void main(String[] args) {
Base1 base = new Sub1();
base.add(1, 2, 3); // sub_1,编译看左,运行看右
Sub1 s = (Sub1) base;
// 确定多个参数的,优先调用
s.add(1, 2, 3); // sub_2
}
}
class Base1 {
public void add(int a, int... arr) {
System.out.println("base");
}
}
class Sub1 extends Base1 {
// int... arr 、 int[] arr,算是一样,重写了父类的方法
public void add(int a, int[] arr) {
System.out.println("sub_1");
}
public void add(int a, int b, int c) {
System.out.println("sub_2");
}
}
2.7 练习题
package com.base.learn;
/**
* 定义三个类,父类GeometricObject代表几何形状,子类Circle代表圆形,MyRectangle代表矩形
*
*/
public class GeometricObject {
protected String color;
protected double weight;
public GeometricObject(String color, double weight) {
this.color = color;
this.weight = weight;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
public double findArea() {
return 0.0;
}
}
package com.base.learn;
public class Circle extends GeometricObject{
private double radius;
public Circle(String color, double weight, double radius) {
super(color, weight);
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
@Override
public double findArea(){
return 3.14 * radius * radius;
}
}
package com.base.learn;
public class MyRectangle extends GeometricObject {
private double width;
private double height;
public MyRectangle(String color, double weight, double width, double height) {
super(color, weight);
this.width = width;
this.height = height;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
@Override
public double findArea() {
return width * height;
}
}
package com.base.learn;
/**
* 义一个测试类 GeometricTest,编写 equalsArea 方法测试两个对象的面积是否相等(注意方法的参数类型,利用动态绑定技术),
* 编写 displayGeometricObject 方法显示对象的面积(注意方法的参数类型,利用动态绑定技术)。
*
*/
public class GeometricTest {
public static void main(String[] args) {
GeometricTest test = new GeometricTest();
Circle c1 = new Circle("white", 1.0, 2.3);
test.displayGeometricObject(c1); // 面积为:16.610599999999998
Circle c2 = new Circle("white", 1.0, 3.3);
test.displayGeometricObject(c2); // 面积为:34.1946
boolean isEqual = test.equalsAres(c1, c2);
System.out.println(isEqual); // false
MyRectangle rect = new MyRectangle("red", 1.0, 2.1, 3.4);
test.displayGeometricObject(rect); // 面积为:7.14
}
// 对象的面积
public void displayGeometricObject(GeometricObject o) {
System.out.println("面积为:" + o.findArea());
}
// 测试两个对象的面积是否相等
public boolean equalsAres(GeometricObject o1, GeometricObject o2) {
return o1.findArea() == o2.findArea();
}
}
以上部分内容摘自多态性,这位博主写的文章非常好,就是这两年没有持续更新,其它内容来自视频教程内容。