Java多态完全指南:深入理解“一个接口,多种实现”

目录

一、什么是多态

二、为什么需要多态

三、多态的三种赋值方式

[3.1 直接赋值](#3.1 直接赋值)

[3.2 参数传递](#3.2 参数传递)

[3.3 返回值传递](#3.3 返回值传递)

四、多态的三大实现条件

[4.1 必须在继承体系下](#4.1 必须在继承体系下)

4.2子类必须重写父类方法

[4.3 父类引用指向子类对象(向上转型)](#4.3 父类引用指向子类对象(向上转型))

五、向上转型和向下转型

[5.1 向上转型](#5.1 向上转型)

[5.1.1 向上转型的语法](#5.1.1 向上转型的语法)

5.1.2向上转型的三种场景

直接赋值:

方法参数:

方法返回值

[5.1.2 向上转型的优势和局限](#5.1.2 向上转型的优势和局限)

[5.2 向下转型](#5.2 向下转型)

[5.2.1 向下转型的语法](#5.2.1 向下转型的语法)

[5.2.2 instanceof](#5.2.2 instanceof)

[5.2.3 向下转型的底层逻辑](#5.2.3 向下转型的底层逻辑)

六、多态的实际运用


一、什么是多态

想象一下你去餐厅点餐:

  • 你对服务员说:我需要一份主食
  • 服务员可能会给你:

米饭(你是一个中国南方人)

面条(你是一个中国北方人)

面包(你是一个西方人)

同一句话"来一份主食",却得到了不同的结果------这就是生活中的多态!

在编程中,多态(Polymorphism)指同一行为具有多种不同表现形式的能力。简单说:同一个方法调用,因为对象不同,执行结果不同

二、为什么需要多态

多态可以大大的提高我们的效率,我们现在写一个动物喂养小案例来对比一下:

文件结构如下:

我们先举一个没有使用多态的例子:

java 复制代码
public class ZooOld {
    // 每增加一种动物,就要新增一个方法!
    public void feedCat(Cat cat) {
        cat.eatFish();  // 调用特有方法
    }

    public void feedDog(Dog dog) {
        dog.eatBone();
    }

    public void feedBird(Bird bird) {
        bird.eatWorm();
    }

    // 如果以后加 Tiger、Lion... 还要继续写 feedTiger(), feedLion()...
}

大家就会发现,我如果这么写的话,多一个"动物"就得写一个专门的方法,虽然代码例子的代码量少,如果是我们日常开发中就会非常繁琐,那么我们看看使用了多态的代码:

我们先定义一个父类,这里定义了两个成员变量,一个成员方法.

java 复制代码
//父类
public class Animal {
    protected String name;
    protected int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void eat(){
        System.out.println(this.name + "在吃东西...");
    }
}

接下来再定义三个子类:Dog、Bied、Cat

java 复制代码
//Bird
public class Bird extends Animal{
    public Bird(String name , int age){
        super(name,age);
    }
    public void eat(){
        System.out.println(this.name + "在吃鸟粮");
    }
}
java 复制代码
//Cat
public class Cat extends Animal{

    public Cat(String name, int age) {
        super(name, age);
    }

    public void eat(){
        System.out.println(this.name + "在吃猫粮");
    }
}
java 复制代码
public class Dog extends Animal {

    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat(){
        System.out.println(this.name + "在吃狗粮");
    }
}

我们发现了,每个子类都有eat方法,但是全部都是独有的,因为每个动物的食物是不一样的.

这个时候,我们定义一个测试类来看看调用eat()方法会出现什么情况:

java 复制代码
public class text_zoo {
    public static void main(String[] args) {
        Animal animal = new Animal("小黑",7);
        Dog dog = new Dog("小白",1);
        Cat cat = new Cat("花花",2);
        Bird bird = new Bird("啾啾",3);
        animal.eat();//调用父类的eat方法
        dog.eat();//调用子类Dog的eat方法
        cat.eat();//调用子类Cat的eat方法
        bird.eat();//调子父类Bird的eat方法
    }
}

我们运行一下代码看看会发生什么:

这个时候大家能发现,我们每个eat方法所调用的都是不一样的,这就是多态的同一行为具有多种不同表现形式的能力 这一概念的表现,我们的多态在运行时决定调用哪个子类的eat().

三、多态的三种赋值方式

依旧是上面的例子,我们在测试类里修改一下代码来体现一下三种不同的赋值方式:

3.1 直接赋值

java 复制代码
//直接赋值
public class text_zoo {
    public static void main(String[] args) {
        Animal a1 = new Dog("小白", 1);
        Animal a2 = new Cat("花花", 2);

        a1.eat();
        a2.eat();
    }
}
  • 变量 a1、a2编译时类型Animal
  • 但它实际指向的对象 是 Dog和Cat 类型
  • 调用 a1.eat() 时,运行时 会执行 Dog 的 eat() 方法

3.2 参数传递

"父类引用指向子类对象"

java 复制代码
    public static void fun(Animal a){
        a.eat();
    }
    public static void main2(String[] args) {
        Dog dog = new Dog("小白",1);
        Cat cat = new Cat("花花",2);
        Bird bird = new Bird("啾啾",3);
        fun(dog);
        fun(cat);
        fun(bird);
    }

3.3 返回值传递

我们先正常定义子类,也适合上面的例子一样

java 复制代码
//子类Dog
public class Dog extends Animal {

    public Dog(String name,int age) {
        super(name, age);
    }

    @Override
    public void eat(){
        System.out.println(this.name + "在吃狗粮...");
    }
}

//子类Cat
public class Cat extends Animal{

    public Cat(String name,int age) {
        super(name, age);
    }

    public void eat(){
        System.out.println(this.name + "在吃猫粮...");
    }
}

在测试类中,我们写一个fun2方法用来实现我们的方法,注意:这里的方法必须是Animal类型,这其实也是继承思想所造成的,如果我们我们方法使用的是Dog或Cat类的话,我们就不能有这么多的引用.

java 复制代码
//测试类
public calss text_zoo{
    public static Animal fun2(String type) {
        if ("cat".equals(type)) {
            return new Cat("小猫",1);   // 返回 Cat 对象
        } else {
            return new Dog("小狗",2);   // 返回 Dog 对象
        }
    }
    public static void main(String[] args) {
        Animal a = fun2("cat"); // 接收返回值
        Animal b = fun2("dog");
        a.eat();
    }
}

四、多态的三大实现条件

4.1 必须在继承体系下

因为多态的前提是"这些对象属于同一家族 ",Java 才允许你用同一个变量(如 Animal a)去引用它们。

这一点我们可以举个例子,就拿上面的例子,我们把Cat的继承给删掉,再通过测试类运行一下代码看看会发生什么:

java 复制代码
//Cat
//注意!!!此时Cat是没有继承Animal的
public class Cat{
    public void eat(){
        System.out.println("在吃猫粮");
    }
}

我们用测试类测试一下代码:

java 复制代码
public class text_zoo {
    public static void main(String[] args) {
        Animal animal = new Animal("小黑",7);
        Cat cat = new Cat();
        animal.eat();//调用父类的eat方法
        cat.eat();//调用子类Cat的eat方法
    }
}

大家会发现,我们Cat的eat()方法并没有实现多态的效果

4.2子类必须重写父类方法

如果我们不进行重写的话,我们所调用的方法就一直都是父类的方法:

java 复制代码
父类:Animal
public class Animal {
    public String name;
    public int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void eat(){
        System.out.println(this.name + "在吃东西...");
    }
}


子类:Dog
public class Dog extends Animal {

    public Dog(String name,int age) {
        super(name, age);
    }
}
java 复制代码
public class text_zoo{
    public static void main(String[] args) {

        Animal animal = new Dog("xx",2);
        animal.eat();
    }

}

4.3 父类引用指向子类对象(向上转型)

使用父类引用来应用子类对象:

java 复制代码
Animal animal = new Dog("xx",4);
animal.eat();

五、向上转型和向下转型

5.1 向上转型

5.1.1 向上转型的语法

向上转型:父类引用 引用 子类对象

java 复制代码
父类 变量 = new 子类();

我们还是看一下之前的例子:

java 复制代码
父类:Animal
public class Animal {
    public String name;
    public int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void eat(){
        System.out.println(this.name + "在吃东西...");
    }
}
java 复制代码
子类:Dog
public class Dog extends Animal {

    public Dog(String name,int age) {
        super(name, age);
    }

    @Override
    public void eat(){
        System.out.println(this.name + "在吃狗粮...");
    }
}

我们现在测试一下会怎么样:

java 复制代码
public calss text_zoo{
    public static void main(String[] args) {
        Animal animal = new Dog("小黑",2);//向上转型
        animal.eat();
    }
}

这个时候就会调用我们子类的重写方法

5.1.2向上转型的三种场景

直接赋值:

java 复制代码
public class xxx{
   public static void main1(String[] args) {
       //直接赋值
        Animal animal = new Dog("小白",3);
        animal.eat();
    }
}

方法参数:

java 复制代码
public class xxx{
    public static void feed(Animal animal){
        animal.eat();
    }
    public static void main(String[] args) {
        //方法参数
        feed(new Cat("小花",3));
    }
}

方法返回值

java 复制代码
public class xxx{
   public static Animal getAnimal(String type) {
        if ("cat".equals(type)){
            return new Cat("喵喵",2);
        }else{
            return new Dog("汪汪",5);
        }
    }

    static void main(String[] args) {
        Animal animal = getAnimal("cat");//返回Dog,向上转型为Animal
    }
}

5.1.2 向上转型的优势和局限

向上转型的优势:

  • 代码更通用,可扩展性强

  • 降低耦合度(两个模块(这里是类)的相互依赖程度,耦合度越高,依赖性越强)
    向上转型的局限:

  • 只能调用重写的方法无法调用子类独有的方法

对比一下下面这个代码就能看懂

5.2 向下转型

5.2.1 向下转型的语法

向下转型(Downcasting)是指:将一个父类类型的引用,强制转换为它实际所指向的子类类型。

java 复制代码
 子类名 变量 = (子类名) 父类引用;

我们先回顾一下向上转型的限制:只能调用重写的方法无法调用子类独有的方法

那么我们有没有什么方法来解决这个限制呢???

那么我们就可以使用我们的向下转型:

java 复制代码
publc class xxx{
    //向下转型
    public static void main(String[] args) {
        Animal animal = new Dog("小白",2);
        if (animal instanceof Dog){
            Dog dog = (Dog)animal;//强转成子类Dog,向下转型
            dog.sleep();
        }
    }
}

那么这个时候我们就可以调用子类的sleep方法了

5.2.2 instanceof

使用instanceof 来进行安全的向下转型

java 复制代码
if (父类引用 instanceof 目标子类) {
    目标子类 变量 = (目标子类) 父类引用;
    // 安全使用子类特有方法
}

instanceof运行时检查对象的真实类型,确保转换安全。

5.2.3 向下转型的底层逻辑

当你写:

java 复制代码
Dog dog = (Dog)animal;

JVM 会做以下事情:

  • 检查 animal 是否为 null → 如果是,转换成功(null 可以转成任何引用类型)
  • 检查 animal 所指向的对象真实类型 是否是 CatCat 的子类
    • 如果是 → 转换成功
    • 如果不是 → 抛出 ClassCastException

六、多态的实际运用

我们举一个图形绘制系统的例子来看看多态的一个优势------降低**"圈复杂度"避免****大量if-else的困扰**

我们先看看没有使用多态的代码:

java 复制代码
public static void drawShapes(String[] shapes) {
    for (String shape : shapes) {
        if (shape.equals("circle")) {
            drawCircle();
        } else if (shape.equals("rect")) {
            drawRect();
        } else if (shape.equals("triangle")) {
            drawTriangle();
        }
        // 每新增一种图形,就要加一个if分支
    }
}

我们再看看有多态的代码:

java 复制代码
// 1. 定义抽象父类
abstract class Shape {
    public abstract void draw();
    
    // 可以有具体方法
    public void displayInfo() {
        System.out.println("正在绘制图形...");
    }
}

// 2. 定义具体子类
class Circle extends Shape {
    @Override
    public void draw() {
        System.out.println("绘制圆形 ○");
    }
}

class Rectangle extends Shape {
    @Override
    public void draw() {
        System.out.println("绘制矩形 ▭");
    }
}

class Triangle extends Shape {
    @Override
    public void draw() {
        System.out.println("绘制三角形 △");
    }
}

// 3. 使用多态的统一处理
public class ShapeDrawer {
    public static void drawAllShapes(Shape[] shapes) {
        for (Shape shape : shapes) {
            shape.draw();  // 多态调用:自动调用具体实现
            shape.displayInfo();
        }
    }
    
    public static void main(String[] args) {
        Shape[] shapes = {
            new Circle(),
            new Rectangle(),
            new Triangle(),
            new Circle()
        };
        
        drawAllShapes(shapes);
    }
}

例如我们现在需要扩展一个图形:

复制代码
新增一个五边形,只需添加新类,无需修改已有代码
java 复制代码
class Pentagon extends Shape {
    @Override
    public void draw() {
        System.out.println("绘制五边形 ⬠");
    }
}

// 使用方式完全不变
Shape[] newShapes = {new Circle(), new Pentagon()};
ShapeDrawer.drawAllShapes(newShapes);

相关推荐
TT哇2 小时前
Optional<T>
java·spring boot·java-ee
李拾叁的摸鱼日常2 小时前
Java泛型基本用法与PECS原则详解
java·后端·面试
MediaTea2 小时前
Python:实例 __dict__ 详解
java·linux·前端·数据库·python
stevenzqzq2 小时前
Compose基础入门
开发语言·compose
米优2 小时前
C/C++中实现自定义自动释放堆内存空间类
c语言·开发语言·c++
傻啦嘿哟2 小时前
Python上下文管理器:优雅处理资源释放的魔法工具
开发语言·python
阿方索2 小时前
Python 基础简介
开发语言·python
个案命题3 小时前
鸿蒙ArkUI组件通信专家:@Param装饰器的奇幻漂流
java·服务器·前端
Data_agent3 小时前
CNFANS模式淘宝1688代购系统搭建指南
大数据·开发语言·前端·javascript