当前位置: 首页 > article >正文

Java-多态(详解)

目录

一、多态的概念

二、多态实现的条件

示例:

分析:

三、关于Java语言中的向上转型和向下转型:

1.向上转型(Upcasting)

(1).示例代码1

(2).示例代码2

2.向下转型(Downcasting)

(1).示例代码1

(2).示例代码2

 3.需求:程序运行阶段动态确定对象

4.总结

四、多态在开发中的应用

1.开闭原则(OCP)

(1)、原则本质

2.OCP与多态的示例

(1).问题描述

(2).问题分析:

(3).解决方案:通过多态与OCP重构

步骤1:定义抽象层

步骤2:实现具体宠物类

步骤3:重构Master类

步骤4:测试扩展性

(4).扩展性验证

(5).对比原始代码与重构后代码

(6).总结

五、多态总结

1.多态代码示例

(1). 父类 Animal

(2). 子类 Cat 和 Dog

(3). 测试类 Test

2.多态的特点

3、多态的优点

4、注意事项

5、总结


一、多态的概念

多态是面向对象编程的三大特性之一(封装、继承、多态)。

在Java中,多态是面向对象编程中的一个重要概念,它允许不同类型的对象对同一方法进行不同的实现。具体来说,多态性指的是通过父类的引用变量来引用子类的对象,从而实现对不同对象的统一操作。

例如:狗和猫都是动物,动物共同的行为都有吃这个动作,而狗可以表现为啃骨头,猫则可以表现为吃老鼠。这就是多态的表现,即同一件事情,发生在不同对象的身上,就会产生不同的结果。

多态是指同一个行为具有多个不同表现形式或形态的能力。
在 Java 中,多态分为两种:

  1. 编译时多态:方法重载(Overload)。

  2. 运行时多态:方法重写(Override),通过父类引用指向子类对象实现。


二、多态实现的条件


在Java中,要实现多态性,就必须满足以下条件:

1.继承关系
存在继承关系的类之间才能够使用多态性。多态性通常通过一个父类用变量引用子类对象来实现。

2.方法重写
子类必须重写(Override)父类的方法。通过在子类中重新定义和实现父类的方法,可以根据子类的特点行为改变这个方法的行为,如猫和狗吃东西的独特行为。

3.父类引用指向子类对象
使用父类的引用变量来引用子类对象。这样可以实现对不同类型的对象的统一操作,而具体调用哪个子类的方法会在运行时多态决定

多态的实现条件(简洁版)

  1. 继承关系:必须存在父子类关系。

  2. 方法重写:子类重写父类的方法。

  3. 向上转型:父类引用指向子类对象。

示例:

下面的案例是根据猫和狗叫的动作的不同,而实现的多态:

class Animal {
    public void sound() {
        System.out.println("动物发出声音");
    }
}

class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("狗发出汪汪声");
    }
}

class Cat extends Animal {
    @Override
    public void sound() {
        System.out.println("猫发出喵喵声");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Dog(); // 父类引用指向子类对象
        Animal animal2 = new Cat(); // 父类引用指向子类对象

        animal1.sound(); // 输出:狗发出汪汪声
        animal2.sound(); // 输出:猫发出喵喵声
    }
}

运行结果为:

分析:

这段Java代码演示了多态的三个核心要素:继承、方法重写和向上转型。以下是关键点解析:

1.继承关系:

(1).DogCat类继承自Animal基类

(2).子类获得了父类的特性并可以扩展新功能

2.方法重写(Override):

  • 子类通过@Override注解重写父类的sound()方法

  • 这是实现多态的必要条件

3.向上转型(Upcasting):

Animal animal1 = new Dog(); // 父类引用指向子类对象
Animal animal2 = new Cat();

●将子类对象赋值给父类类型的引用变量

●这是多态实现的基础​ 

4.动态绑定(Late Binding):

在运行时根据对象实际类型确定调用哪个方法

animal1.sound()实际调用的是Dog类的方法

animal2.sound()实际调用的是Cat类的方法

5.代码扩展优势:

新增动物类型只需继承Animal并实现sound()

调用方代码无需修改即可支持新类型

6.执行过程分析:

编译时检查父类是否存在sound()方法

运行时根据对象实际类型进行方法调用

JVM通过虚方法表实现动态绑定

注意事项

  • 使用多态时无法直接调用子类特有方法

  • 需要向下转型时建议使用instanceof进行类型检查

  • 父类如果不需要实例化应该声明为抽象类

技术原理

  • 动态绑定:Java在运行时通过虚方法表(vtable)查找实际要执行的方法实现。

  • 编译检查:编译器验证父类是否存在被调用方法(确保编译通过),运行时决定具体实现。

此设计符合面向对象的开闭原则(对扩展开放,对修改关闭),是Java实现多态的典型范例。

三、关于Java语言中的向上转型和向下转型:

1.关于基本数据类型之间的类型转换:
*      第一种:小容量转换成大容量,叫做自动类型转换。
*          int i = 100;
*          long x = i;
*      第二种:大容量转换成小容量,不能自动转换,必须添加强制类型转换符才行。叫做强制类型转换。
*          int y = (int)x;
*

2. 除了基本数据类型之间的类型转换之外,对于引用数据类型来说,也可以进行类型转换。
* 只不过不叫做自动类型转换和强制类型转换。我们一般称为向上转型和向下转型。
*
* 关于Java语言中的向上转型和向下转型:
*      向上转型(upcasting):子 ---> 父 (可以等同看做自动类型转换。)
*      向下转型(downcasting):父 ---> 子 (可以等同看做强制类型转换。)
*
* 不管是向上还是向下转型,两种类型之间必须要有继承关系,编译器才能编译通过。这是最基本的大前提。

注意:

我这里

向上转型的示例代码1对应向下转型的示例代码1

向上转型的示例代码2对应向下转型的示例代码2

1.向上转型(Upcasting)

向上转型是指将子类对象赋值给父类引用变量,这是一种自动类型转换,在编译和运行时都是安全的。

(1).示例代码1

// 父类
class Animal {
    public void sound() {
        System.out.println("动物发出声音");
    }
}

// 子类
class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("狗发出汪汪声");
    }

    public void bark() {
        System.out.println("狗在汪汪叫");
    }
}

public class Main {
    public static void main(String[] args) {
        // 向上转型
        Animal animal = new Dog();
        animal.sound(); // 调用子类重写后的方法

        // 以下代码会编译错误,因为父类引用无法调用子类特有的方法
        // animal.bark(); 
    }
}

代码解释:

Animal 是父类,Dog 是子类,Dog 类继承自 Animal 类,并重写了 sound() 方法,同时还有自己特有的 bark() 方法。

Animal animal = new Dog(); 这行代码进行了向上转型,Dog 类的对象赋值给了 Animal 类型的引用变量 animal

●调用 animal.sound() 时,由于多态的存在,实际调用的是 Dog 类中重写后的 sound() 方法。

●由于 animal Animal 类型的引用,它只能访问 Animal 类中定义的方法,不能直接访问 Dog 类特有的 bark() 方法,若尝试调用会导致编译错误。

错误原因分析:

这个错误通常出现在使用向上转型后,尝试通过父类引用调用子类特有的方法时。向上转型会使父类引用只能访问父类中定义的方法,而不能直接访问子类特有的方法。

解决办法:

① 向下转型

如果你确定 Animal 引用指向的是 Dog 类的对象,可以通过向下转型将其转换为 Dog 类型的引用,然后再调用 bark 方法。为了避免 ClassCastException 异常,建议在转型前使用 instanceof 进行类型检查。(下面讲

②在父类中定义方法(如果适用)

如果 bark 方法对于所有 Animal 子类都有一定的通用性,可以考虑将 bark 方法定义在 Animal 类中,并在子类中进行重写。不过这种方法可能不太适用于 bark 这种明显是狗特有的行为。

// 父类
class Animal {
    public void sound() {
        System.out.println("动物发出声音");
    }

    public void bark() {
        System.out.println("默认的叫声");
    }
}

// 子类
class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("狗发出汪汪声");
    }

    @Override
    public void bark() {
        System.out.println("狗在汪汪叫");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog();
        animal.bark(); // 可以正常调用
    }
}

通过上述两种方法,你可以解决 “Cannot resolve method 'bark' in 'Animal'” 错误。在实际开发中,向下转型是更常用的解决方式,尤其是当子类有特有的行为时。

(2).示例代码2

父类Animal:

public class Animal {
    public void move(){
        System.out.println("动物在移动");
    }
    public void eat(){
        System.out.println("正在吃东西");
    }
}

子类Cat:

public class Cat extends Animal{

    @Override
    public void move() {
        System.out.println("猫在走猫步");
    }

    /**
     * 这个方法/行为是子类特有的。父类没有。
     */
    public void catchMouse(){
        System.out.println("猫在抓老鼠");
    }
}

子类Bird:

public class Bird extends Animal{

    @Override
    public void move() {
        System.out.println("鸟儿在飞翔");
    }

    /**
     * 这个方法也是子类特有的。
     */
    public void sing(){
        System.out.println("鸟儿在歌唱!");
    }
}

测试类Test01:

public class Test01 {
    public static void main(String[] args) {
        Animal a1=new Cat();
        a1.move();


    }
}

运行结果:

解释:

java程序包括两个重要的阶段:  

  第一阶段:编译阶段        

●在编译的时候,编译器只知道a1的类型是Animal类型。

●因此在编译的时候就会去Animal类中找move()方法。        

●找到之后,绑定上去,此时发生静态绑定。

能够绑定成功,表示编译通过。    

第二阶段:运行阶段        

●在运行的时候,堆内存中真实的java对象是Cat类型。        

●所以move()的行为一定是Cat对象发生的。        

●因此运行的时候就会自动调用Cat对象的move()方法。        

●这种绑定称为运行期绑定/动态绑定。

   

因为编译阶段是一种形态,运行的时候是另一种形态。因此得名:多态。

向上转型(upcasting):    

1. 子 --> 父  

  2. 也可以等同看做自动类型转换  

  3. 前提:两种类型之间要有继承关系  

  4. 父类型引用指向子类型对象。这个就是多态机制最核心的语法。

      编译时看,但运行时看具体的对象

添加a1.catchMouse()语句;

解释:

编译错误是因为编译器只知道a1是Animal类型,去Animal类中找 catchMouse()方法了,结果没有找到,无法完成静态绑定,编译报错。

大白话:在运行时才能new对象,在编译的时候,编译器很傻的,编译器只知道a1的类型是Animal,只能去Animal找catchMouse()这个方法 ,而Animal没有这个方法自然会报错,所以说白了,就是程序还没有运行时就报错了


假如现在就是要让a1去抓老鼠,怎么办?    

向下转型:downcasting(父--->子)

什么时候我们会考虑使用向下转型?    

当调用的方法是子类中特有的方法。


2.向下转型(Downcasting)

向下转型是指将父类引用变量强制转换为子类引用变量。需要注意的是,向下转型必须确保该父类引用实际指向的是要转型的子类对象,否则会在运行时抛出 ClassCastException 异常。通常在向下转型之前会使用 instanceof 运算符进行类型检查。

(1).示例代码1

父类Animal:

// 父类
class Animal {
    public void sound() {
        System.out.println("动物发出声音");
    }
}

子类Dog:

// 子类
class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("狗发出汪汪声");
    }

    public void bark() {
        System.out.println("狗在汪汪叫");
    }
}

子类Cat:

// 另一个子类
class Cat extends Animal {
    @Override
    public void sound() {
        System.out.println("猫发出喵喵声");
    }

    public void meow() {
        System.out.println("猫在喵喵叫");
    }
}

测试类Main:

public class Main {
    public static void main(String[] args) {
        // 向上转型
        Animal animal = new Dog();

        // 向下转型前进行类型检查
        if (animal instanceof Dog) {
            Dog dog = (Dog) animal;
            dog.sound();
            dog.bark();
        }

        // 下面的代码会引发 ClassCastException
        Animal animal2 = new Cat();
        // 不进行类型检查直接转型
        // Dog dog2 = (Dog) animal2; 
        // dog2.bark(); 

        if (animal2 instanceof Dog) {
            Dog dog2 = (Dog) animal2;
            dog2.bark();
        } else {
            System.out.println("animal2 不是 Dog 类型的对象");
        }
    }
}

代码解释:

●首先创建了 AnimalDog Cat 三个类,DogCat 类都继承自 Animal 类,且都重写了 sound() 方法,同时各自有特有的方法。

Animal animal = new Dog(); 进行了向上转型,将 Dog 类的对象赋值给 Animal 类型的引用变量 animal

●使用 if (animal instanceof Dog) 进行类型检查,判断 animal 引用的对象是否为 Dog 类型的实例。如果是,则进行向下转型 Dog dog = (Dog) animal;,转型成功后就可以调用 Dog 类特有的 bark() 方法。

Animal animal2 = new Cat(); 同样进行了向上转型,但 animal2 实际指向的是 Cat 类的对象。若不进行类型检查直接将其转型为 Dog 类型,会在运行时抛出 ClassCastException 异常。使用 instanceof 检查后发现 animal2 不是 Dog 类型的对象,就不会进行转型操作,避免了异常的发生。

(2).示例代码2

父类Animal:

public class Animal {

    public void move(){
        System.out.println("动物在移动");
    }

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

子类Cat:

public class Cat extends Animal{

    @Override
    public void move() {
        System.out.println("猫在走猫步");
    }

    /**
     * 这个方法/行为是子类特有的。父类没有。
     */
    public void catchMouse(){
        System.out.println("猫在抓老鼠");
    }
}

子类Bird:

public class Bird extends Animal{

    @Override
    public void move() {
        System.out.println("鸟儿在飞翔");
    }

    /**
     * 这个方法也是子类特有的。
     */
    public void sing(){
        System.out.println("鸟儿在歌唱!");
    }
}

测试类Test01:

public class Test01 {
    public static void main(String[] args) {
        Animal a1=new Cat();
        a1.move();
       Cat c1=(Cat) a1;
       c1.catchMouse();
    }
}

运行结果:

注意点:

大前提:不管是向下还是向上,两种类型之间必须要有继承关系。
没有继承关系,编译器报错。

错误分析

Inconvertible types; cannot cast 'lianxi.oop16.Bird' to 'lianxi.oop16.Cat' 这个错误提示表明你尝试将一个 Bird 类型的对象强制转换为 Cat 类型,然而在 Java 里,这两种类型之间没有继承关系,属于不可转换的类型,所以不能进行这样的强制类型转换。

在上述代码中,BirdCat 类都继承自 Animal 类,但它们是 Animal 类的不同子类,彼此之间没有直接的继承关系,所以将 Bird 对象强制转换为 Cat 类型是不合法的,会导致编译错误。

添加

//多态
Animal x=new Cat();
//向下转型
 Bird y=(Bird)x;
public class Test01 {
    public static void main(String[] args) {
        Animal a1=new Cat();
        a1.move();
       Cat c1=(Cat) a1;
       c1.catchMouse();

       //多态
       Animal x=new Cat();
       //向下转型
        Bird y=(Bird)x;

    }
}

运行结果:

分析:

●为什么编译的时候可以通过?因为x是Animal类型,Animal和Bird之间存在继承关系,语法没问题,所以编译通过了。
●为什么运行的时候出现ClassCastException(类型转换异常)?因为运行时堆中真实对象是Cat对象,Cat无法转换成Bird,则出现类型转换异常。
●instanceof运算符的出现,可以解决ClassCastException异常。

 3.需求:程序运行阶段动态确定对象

如果对象是Cat,请抓老鼠。
如果对象是Bird,请唱歌。

测试类Test01

public class Test01 {
    public static void main(String[] args) {

        // 多态
        Animal a = new Bird();
        a.eat();   //父类特有方法
        a.move();// 虽然是Animal a,绑定的是Bird

        if (a instanceof Cat) {
            Cat cat = (Cat)a;
            cat.catchMouse();
        }else if(a instanceof Bird){
            Bird bird = (Bird)a;
            bird.sing();    //子类特有方法
            bird.move();  //父类,子类共有方法
        }

    }
}

运行结果:

代码分析:

(1).多态的实现

Animal a = new Bird();

●这里,a 是一个 Animal 类型的引用,但它实际上指向的是一个 Bird 类型的对象。
●由于 Bird 继承自 Animal,这种用法是合法的。

(2).方法调用:

a.move();

●尽管 a 是 Animal 类型的引用,但在运行时,Java 会根据对象的实际类型(这里是 Bird)来决定调用哪个方法。
●因此,调用的是 Bird 类中重写的 move() 方法,输出 "鸟儿在飞翔"。

(3).方法重写:
Bird 类重写了 Animal 类的 move() 方法:

@Override
public void move() {
    System.out.println("鸟儿在飞翔");
}

●当调用 a.move() 时,Bird 类的 move() 方法被调用,而不是 Animal 类的 move() 方法。

(4).为什么不是调用父类方法?


●Java 的方法调用是基于 运行时类型(Run-time Type)而不是编译时类型(Compile-time Type)。
●编译时类型是 Animal,所以编译器确保 move() 方法存在(父类中有定义)。
●运行时类型是 Bird,所以实际调用的是 Bird 类中重写的 move() 方法。

(5).总结

Animal a = new Bird();
a.move();

●编译时类型:Animal,确保 amove() 方法(继承自父类)。
●运行时类型:Bird,实际调用的是 Bird 类中的 move() 方法,输出 "鸟儿在飞翔"。

所以为什么 a.move() 调用的是子类方法?
①编译时类型 vs 运行时类型:

编译时类型:a 的类型是 Animal,编译器检查 Animal 类是否有 move() 方法。

运行时类型:a 实际指向的是 Bird 对象,JVM 在运行时动态绑定到 Birdmove() 方法。

②方法调用规则:

如果子类重写了父类的方法,调用时会优先执行子类的方法。

如果子类没有重写,则调用父类的方法。

这就是多态的核心思想:父类的引用可以指向子类的对象,并调用子类重写的方法。

在Java中,多态性允许一个引用类型变量指向其真实类型的对象,并且根据该对象的实际类型来决定调用哪个方法。这种行为是通过动态绑定(dynamic binding)实现的,它使得程序可以在运行时确定具体要执行的方法版本。

在代码示例中,Animal a = new Bird(); 这一行创建了一个 Bird 类型的对象,并将其赋值给一个 Animal 类型的引用 a。这里发生了向上转型(upcasting),即子类对象被当作父类类型来处理。尽管如此,a 实际上仍然指向的是一个 Bird 对象。

当你调用 a.move(); 时,尽管 a 的静态类型是 Animal但 JVM 在运行时会检查 a 引用的实际对象类型,发现它是 Bird 类型的一个实例由于 Bird 类重写了 move 方法,JVM 将调用 Bird 类中的 move 方法实现,而不是 Animal 类中的默认实现。因此,输出结果为“鸟儿在飞翔”。

这个过程涉及到以下几个关键概念:

①动态绑定:这是Java实现多态的方式之一,指的是在运行时根据对象的实际类型来选择调用的方法。对于非静态方法,Java使用虚方法表(vtable)或方法表(method table)来支持动态绑定。每个对象都有一个指向其类的方法表的指针,该表包含了所有可被动态调用的方法的地址。


②方法重写(Override):当一个子类提供了与父类相同签名(名称、参数列表)的方法定义时,就称为方法重写。在这个例子中,Bird 类重写了 Animal 类的 move 方法,从而改变了它的行为。


③向上转型:当你将一个子类对象赋值给父类类型的变量时,这被称为向上转型。这样做不会丢失任何信息,因为子类包含所有父类的信息,但它隐藏了子类特有的部分,除非你进行向下转型。


综上所述,a.move(); 输出“鸟儿在飞翔”的原因是由于Java的动态绑定机制确保了即使通过父类引用调用方法,也会根据实际对象的类型调用相应的方法实现。在这种情况下,尽管 a 是 Animal 类型的引用,但由于它实际上指向的是一个 Bird 对象,所以最终调用了 Bird 类中重写的 move 方法。

这也正是面向对象编程中多态性的核心所在——同一接口可以根据对象的不同表现出不同的行为。

4.总结

●向上转型是自动类型转换,是安全的,它可以实现多态,但会失去访问子类特有方法的能力。

●向下转型是强制类型转换,需要谨慎使用,在转型前使用 instanceof 进行类型检查可以避免 ClassCastException 异常。

四、多态在开发中的应用

1.开闭原则(OCP)


开闭原则(Open-Closed Principle, OCP)是面向对象设计的基石,其核心目标是构建高内聚、低耦合的系统架构,使系统既能灵活扩展新功能,又能保持核心逻辑的稳定性


(1)、原则本质

  • 开放扩展:通过新增代码实现功能扩展(如新增类、接口实现)

  • 关闭修改:避免修改已有运行良好的类、接口、方法

  • 关键手段:抽象化(接口/抽象类) + 多态(实现类替换)

2.OCP与多态的示例

(1).问题描述


假设现有代码实现主人(Master类)喂养宠物猫(Cat类)的功能。现在需要扩展功能,让主人也能喂养宠物狗(Dog类)。但当前代码设计存在缺陷:每次新增宠物类型时,必须修改 Master 类的源码,这直接违反了开闭原则(OCP)。

代码如下:

Cat类:

/**
 * 宠物猫
 */
public class Cat {
    public void eat(){
        System.out.println("猫吃鱼");
    }
}

Dog类:

public class Dog {
    public void eat(){
        System.out.println("狗狗啃骨头!");
    }
}

Master类:

/**
 * 主人类
 */
public class Master {
    public void feed(Cat c) {
        c.eat();
    }

    public void feed(Dog d){
        d.eat();
    }

}

测试类Test:

/**
 * 这个案例没有使用多态机制,看看设计上有什么缺陷?
 *      不符合OCP。不符合开闭原则。(因为这个功能的扩展是建立在修改Master类的基础之上的。)
 *      OCP倡导的是什么?进行功能扩展的时候,最好不要修改原有代码,最好是以新增代码来完成扩展。
 *      对修改关闭。对扩展开放。
 */
public class Test {
    public static void main(String[] args) {
        // 创建宠物猫对象
        Cat c = new Cat();
        Dog d = new Dog();
        // 创建主人对象
        Master zhangsan = new Master();
        // 主人喂猫
        zhangsan.feed(c);
        zhangsan.feed(d);
    }
}

(2).问题分析

  • 当新增 Dog 类时,必须修改 Master 类,添加 public void feed(Dog d) 方法。

  • 如果未来需要喂养其他宠物(如狗、蛇、鸟),Master 类将变得臃肿且难以维护。

  • 根本原因Master 类直接依赖具体实现(Cat),而非抽象层。

(3).解决方案:通过多态与OCP重构

步骤1:定义抽象层

创建抽象类 Pet,所有具体宠物继承它并实现 eat() 方法。

// Pet.java(抽象层)
public abstract class Pet {
    public abstract void eat(); // 定义公共行为规范
}
public class Pet {
    //更高的抽象,到具体类在实现
    public void eat(){

    }
}
步骤2:实现具体宠物类

Cat类:

public class Cat extends Pet{

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

Dog类:

public class Dog extends Pet
{
    public void eat(){
        System.out.println("狗狗在啃骨头");
    }
}
步骤3:重构Master类

让 Master 依赖抽象类 Pet,而非具体宠物类。

import lianxi.oop17.Cat;
import lianxi.oop17.Dog;

public class Master {
    public void feed(Pet p)   //等同于Pet p =new Cat();
                              // 参数类型为抽象类
    {
        p.eat();// 编译时调用Pet,运行时则调用Cat或Dog
                // 多态调用:运行时根据实际对象类型执行对应方法
    }

}
步骤4:测试扩展性

新增宠物类型(如 Snake)时,无需修改 Master 类:

新增Snake类:

public class Snake extends Pet{

    @Override
    public void eat() {
        System.out.println("蛇吞象");
    }
}

测试类Test:

public class Test {
    public static void main(String[] args) {
        //创建宠物
        Cat c=new Cat();
        Dog d=new Dog();

        //创建主人
        Master zhangsan=new Master();

        //喂养
        zhangsan.feed(c);
        zhangsan.feed(d);

        zhangsan.feed(new Cat());
        zhangsan.feed(new Dog());
        zhangsan.feed(new Snake());

    }
}

运行结果:

(4).扩展性验证

当需要新增宠物类型时:

新增类:例如 Bird extends Pet

实现方法:重写 eat()

直接使用zhangsan.feed(new Bird())。
无需修改任何已有类(Master、Cat、Dog 等),完全符合 OCP!

(5).对比原始代码与重构后代码

场景原始代码重构后代码
新增宠物类型必须修改 Master 类只需新增类,无需修改已有代码
Master 类的职责需要了解所有宠物类型只依赖抽象层 Pet
单元测试范围每次新增宠物需重新测试 Master仅需测试新增的宠物类
系统稳定性修改风险高核心模块(Master)保持稳定

(6).总结

通过多态和抽象层设计:

  1. 彻底解耦Master 类与具体宠物类无关。

  2. 符合 OCP:功能扩展通过新增代码完成,而非修改旧代码。

  3. 提升可维护性:系统核心模块保持稳定,变更影响范围最小化。

最终效果:当主人想养新宠物时,只需“买新宠物”(写新类),无需“改造房子”(改 Master 类)!


五、多态总结

1.多态代码示例

(1). 父类 Animal

public class Animal {
    public void eat() {
        System.out.println("动物在吃东西");
    }
}

(2). 子类 Cat 和 Dog

public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫在吃鱼");
    }

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

public class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗在啃骨头");
    }

    public void watchHouse() {
        System.out.println("狗看家");
    }
}

(3). 测试类 Test

public class Test {
    public static void main(String[] args) {
        // 向上转型:父类引用指向子类对象
        Animal animal1 = new Cat(); // 多态
        Animal animal2 = new Dog(); // 多态

        // 调用重写的方法
        animal1.eat(); // 输出:猫在吃鱼
        animal2.eat(); // 输出:狗在啃骨头

        // 无法调用子类特有的方法
        // animal1.catchMouse(); // 编译错误
        // animal2.watchHouse(); // 编译错误

        // 向下转型:调用子类特有方法
        if (animal1 instanceof Cat) {
            Cat cat = (Cat) animal1; // 向下转型
            cat.catchMouse(); // 输出:猫抓老鼠
        }

        if (animal2 instanceof Dog) {
            Dog dog = (Dog) animal2; // 向下转型
            dog.watchHouse(); // 输出:狗看家
        }
    }
}

2.多态的特点

  1. 向上转型

    • 父类引用指向子类对象。

    • 只能调用父类中定义的方法,不能调用子类特有的方法。

    • 如果子类重写了父类的方法,调用的是子类的方法。

  2. 向下转型

    • 将父类引用强制转换为子类类型。

    • 可以调用子类特有的方法。

    • 必须使用 instanceof 进行类型检查,避免 ClassCastException

  3. 方法调用规则

    • 编译时:检查父类是否有该方法。

    • 运行时:调用子类重写的方法。


3、多态的优点

  1. 提高代码的扩展性:新增子类时,无需修改父类代码。

  2. 提高代码的复用性:通过父类引用调用方法,统一处理不同子类对象。

  3. 降低耦合性:父类和子类之间的依赖关系更松散。


4、注意事项

  1. 属性没有多态性

    • 多态仅适用于方法,不适用于属性。

    • 属性的值取决于引用类型,而不是实际对象类型。

    class Fu {
        int num = 10;
    }
    
    class Zi extends Fu {
        int num = 20;
    }
    
    public class Test {
        public static void main(String[] args) {
            Fu fu = new Zi();
            System.out.println(fu.num); // 输出:10(属性没有多态性)
        }
    }
  2. 静态方法没有多态性

    • 静态方法与类绑定,不依赖于对象。

    • 调用静态方法时,取决于引用类型。

    class Fu {
        static void method() {
            System.out.println("父类静态方法");
        }
    }
    
    class Zi extends Fu {
        static void method() {
            System.out.println("子类静态方法");
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            Fu fu = new Zi();
            fu.method(); // 输出:父类静态方法
        }
    }

5、总结

特性说明
向上转型父类引用指向子类对象,只能调用父类方法。
向下转型将父类引用强制转换为子类类型,需用 instanceof 检查。
方法重写子类重写父类方法,运行时调用子类方法。
属性无多态属性的值取决于引用类型,而非对象类型。
静态方法无多态静态方法与类绑定,调用时取决于引用类型。

掌握多态,能够写出更灵活、可扩展的代码!


http://www.kler.cn/a/525893.html

相关文章:

  • Linux Samba 低版本漏洞(远程控制)复现与剖析
  • 详细解释java当中的所有知识点(前言及数据类型及变量)(第一部分)
  • 【微服务与分布式实践】探索 Sentinel
  • 基于Cipher的Java加密工具类
  • 面试经典150题——图
  • DeepSeek--通向通用人工智能的深度探索者
  • 记录使用EasyWeChat做微信小程序登陆和其他操作
  • OpenAI 宕机 | 如何让 k8s 集群更稳定
  • 基础位运算
  • AI时代来临:掌握信息收集,才能不被淘汰!!!
  • 实体类未设置字段如何不参与转化json?
  • Ubuntu中MySQL安装-02
  • 基于DeepSeek在藏语学习推广和藏语信息化方面可以做哪些工作?
  • 5.进程基本概念
  • fastadmin加密生成token
  • 数据分析系列--④RapidMiner进行关联分析(案例)
  • python编程环境安装保姆级教程--python-3.7.2pycharm2021.2.3社区版
  • 学习数据结构(4)顺序表+单链表
  • MySQL 索引存储结构
  • 在Windows上非ASCII(包括中文名)用户名导致Bazel不能使用的问题
  • 游戏开发领域 - 游戏引擎 UE 与 Unity
  • 从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架
  • 智云-一个抓取web流量的轻量级蜜罐-k8s快速搭建教程
  • 基于 WEB 开发的在线考试系统设计与实现
  • [创业之路-269]:《创业讨论会》- 系统之韵:从麻雀到5G系统的共通性探索
  • 蓝桥杯之c++入门(一)【C++入门】