多态可以简单理解为一个父类可以引用不同的子类,官方一点的说就是同一个行为具有多个不同表现形式或形态的能力。在代码运行时,使用不同的实例会执行不同的操作。多态可以基于继承实现也可以基于接口实现,关于继承和接口大家可以看这里
链接: https://blog.csdn.net/weixin_56680764/article/details/125521355
一、多态的实现条件
接下来我们就从多态实现的三个必要条件来了解一下多态
1、继承
在多态中必须存在子类和父类的继承关系
2、重写
就是要在子类中对父类的某些方法进行重新定义,在调用这些方法时就会调用子类的方法。当我们使用多态方式调用方法时,首先会检查父类中是否有该方法,如果有才会去调用子类的同名方法;如果没有就会报错。
3、父类引用指向子类对象
也就是要对子类做向上转型,向上转型:将子类的引用赋给父类对象,只有这样该引用才能调用父类的方法和子类的方法。(但是父类类型的引用只能调用在父类中定义的属性和方法,对于只存在于子类中的方法和属性就不能调用了)
Parent p=new Child();
4、代码实现
接下来咱们还是先看代码,概念太抽象了,咱们可以从代码中理解
class Animal{
public void eat() {
System.out.println("动物要吃东西");
play();
}
public void play() {
System.out.println("动物吃完东西要玩");
}
}
class Dog extends Animal{
public void fight() {
System.out.println("狗狗要打架哎");
play();
}
/**
* 子类重写了父类的 paly 方法
* 指向子类的父类引用调用 play()时,就会调用子类的此方法
*/
public void play() {
System.out.println("狗狗还要玩!");
}
}
public class Test1 {
public static void main(String[] args) {
Animal dog=new Dog();
dog.eat();
}
}
运行结果如下:
动物要吃东西
狗狗还要玩!
由运行结果可以看出来,dog.eat()首先是运行父类中的 eat()方法,然后再运行子类中的 paly()方法。因为子类中没有重写父类中的 eat()方法,所以指向 Dog 的 Animal 类型引用时会调用父类的eat()方法;而子类重写了父类的 play() 方法,那么指向 Dog 的 Animal 类型引用会调用子类Dog中的 play() 方法。
二、多态的优点
1、消除类型之间的耦合关系(这个不是很好解释,简单来说就是有了多态,你改了一个地方那个的代码,其他地方不用做修改),还是写一段代码来理解一下吧
首先是使用多态的
class Animal{
public void eat() {
System.out.println("动物要吃东西");
}
}
class Dog extends Animal{
public void eat() {
System.out.println("狗狗要吃饭");
}
}
class Cat extends Animal{
public void eat() {
System.out.println("猫咪要吃饭");
}
}
class Horse extends Animal{
public void eat() {
System.out.println("马儿要吃饭");
}
}
public class Test1 {
public static void main(String[] args) {
Animal dog=new Dog();
dog.eat();
Animal cat=new Cat();
cat.eat();
Animal horse=new Horse();
horse.eat();
}
}
运行结果如下:
狗狗要吃饭
猫咪要吃饭
马儿要吃饭
接下来是不使用多态的:
class Animal{
public void eat() {
System.out.println("动物要吃东西");
}
}
class Dog extends Animal{
public void eat() {
System.out.println("狗狗要吃饭");
}
}
class Cat extends Animal{
public void eat() {
System.out.println("猫咪要吃饭");
}
}
class Horse extends Animal{
public void eat() {
System.out.println("马儿要吃饭");
}
}
public class Test1 {
public static void main(String[] args) {
Animal animal=new Dog();
animal.eat();
animal=new Cat();
animal.eat();
animal=new Horse();
animal.eat();
}
}
运行结果如下:
狗狗要吃饭
猫咪要吃饭
马儿要吃饭
可以看出运行结果都是一样的,但是不使用多态的话,如果把 animal 改成其他的,后边的代码就都要作出改动。
2.灵活性、简化性、接口性、可替换性
三、经典实例
这个例子是摘自: http://blog.csdn.net/thinkGhoster/archive/2008/04/19/2307001.aspx
public class A {
public String show(D obj) {
return ("A and D");
}
public String show(A obj) {
return ("A and A");
}
}
public class B extends A{
public String show(B obj){
return ("B and B");
}
public String show(A obj){
return ("B and A");
}
}
public class C extends B{
}
public class D extends B{
}
public class Test {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println("1--" + a1.show(b));
System.out.println("2--" + a1.show(c));
System.out.println("3--" + a1.show(d));
System.out.println("4--" + a2.show(b));
System.out.println("5--" + a2.show(c));
System.out.println("6--" + a2.show(d));
System.out.println("7--" + b.show(b));
System.out.println("8--" + b.show(c));
System.out.println("9--" + b.show(d));
}
}
运行结果如下:
1–A and A
2–A and A
3–A and D
4–B and A
5–B and A
6–A and D
7–B and B
8–B and B
9–A and D
分析多态时,我们始终要记住一句话:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的。
继承中对对象方法的调用存在一个优先级:this.show(obj) super.show(obj) this.show((super)obj) super.show((super)obj)
我们可以分析一下上边的第四个输出 a2.show(b)。这里 a2 是引用变量,A为类型,他的引用对象是B。因此按照上面那句话的意思是说有B来决定调用谁的方法,所以a2.show(b)应该要调用B中的show(B obj),产生的结果应该是“B and B”,但是为什么会与前面的运行结果产生差异呢?这里我们忽略了后面那句话“但是这儿被调用的方法必须是在超类中定义过的”,那么show(B obj)在A类中不存在,所以不会去调用它。根据继承的调用方法的优先级可以确认,调用A类中的show(A obj)。(因为这里的 this 就是指A)。这样的话,前四个输出就都可以解释了。
接下来我们解释第五个,a2.show©,a2是A类型的引用变量,所以this就代表了A,a2.show©,它在A类中找发现没有找到,于是到A的超类中找(super),由于A没有超类(Object除外),所以跳到第三级,也就是this.show((super)O),C的超类有B、A,所以(super)O为B、A,this同样是A,这里在A中找到了show(A obj),同时由于a2是B类的一个引用且B类重写了show(A obj),因此最终会调用子类B类的show(A obj)方法,结果也就是B and A。