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 检查。
方法重写子类重写父类方法,运行时调用子类方法。
属性无多态属性的值取决于引用类型,而非对象类型。
静态方法无多态静态方法与类绑定,调用时取决于引用类型。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/961832.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

unity商店插件A* Pathfinding Project如何判断一个点是否在导航网格上?

需要使用NavGraph.IsPointOnNavmesh(Vector3 point) 如果点位于导航网的可步行部分,则为真。 如果一个点在可步行导航网表面之上或之下,在任何距离,如果它不在更近的不可步行节点之上 / 之下,则认为它在导航网上。 使用方法 Ast…

node 爬虫开发内存处理 zp_stoken 作为案例分析

声明: 本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 前言 主要说3种我们补环境过后如果用…

python——Django 框架

Django 框架 1、简介 Django 是用python语言写的开源web开发框架,并遵循MVC设计。 Django的**主要目的是简便、快速的开发数据库驱动的网站。**它强调代码复用,多个组件可以很方便的以"插件"形式服务于整个框架,Django有许多功能…

嵌入式知识点总结 Linux驱动 (五)-linux内核

针对于嵌入式软件杂乱的知识点总结起来,提供给读者学习复习对下述内容的强化。 目录 1.内核镜像格式有几种?分别有什么区别? 2.内核中申请内存有哪几个函数?有什么区别? 3.什么是内核空间,用户空间&…

SpringBoot+Vue的理解(含axios/ajax)-前后端交互前端篇

文章目录 引言SpringBootThymeleafVueSpringBootSpringBootVue(前端)axios/ajaxVue作用响应式动态绑定单页面应用SPA前端路由 前端路由URL和后端API URL的区别前端路由的数据从哪里来的 Vue和只用三件套axios区别 关于地址栏url和axios请求不一致VueJSPS…

网络直播时代的营销新策略:基于受众分析与开源AI智能名片2+1链动模式S2B2C商城小程序源码的探索

摘要:随着互联网技术的飞速发展,网络直播作为一种新兴的、极具影响力的媒体形式,正逐渐改变着人们的娱乐方式、消费习惯乃至社交模式。据中国互联网络信息中心数据显示,网络直播用户规模已达到3.25亿,占网民总数的45.8…

将ollama迁移到其他盘(eg:F盘)

文章目录 1.迁移ollama的安装目录2.修改环境变量3.验证 背景:在windows操作系统中进行操作 相关阅读 :本地部署deepseek模型步骤 1.迁移ollama的安装目录 因为ollama默认安装在C盘,所以只能安装好之后再进行手动迁移位置。 # 1.迁移Ollama可…

《Trustzone/TEE/安全从入门到精通-标准版》

CSDN学院课程连接:https://edu.csdn.net/course/detail/39573 讲师介绍 拥有 12 年手机安全、汽车安全、芯片安全开发经验,擅长 Trustzone/TEE/ 安全的设计与开发,对 ARM 架构的安全领域有着深入的研究和丰富的实践经验,能够将复杂的安全知识和处理器架构知识进行系统整…

手撕Diffusion系列 - 第十一期 - lora微调 - 基于Stable Diffusion(代码)

手撕Diffusion系列 - 第十一期 - lora微调 - 基于Stable Diffusion(代码) 目录 手撕Diffusion系列 - 第十一期 - lora微调 - 基于Stable Diffusion(代码)Stable Diffusion 原理图Stable Diffusion的原理解释Stable Diffusion 和Di…

基于 AWS SageMaker 对 DeepSeek-R1-Distilled-Llama-8B 模型的精调与实践

在当今人工智能蓬勃发展的时代,语言模型的性能优化和定制化成为研究与应用的关键方向。本文聚焦于 AWS SageMaker 平台上对 DeepSeek-R1-Distilled-Llama-8B 模型的精调实践,详细探讨这一过程中的技术细节、操作步骤以及实践价值。 一、实验背景与目标 …

三、SysTick系统节拍定时器

3.1 SysTick简介 系统节拍定时器SysTick是ARM Cortex-M0内核提供的一个24位递减定时器,当计数值达到0时产生中断,可以为操作系统和其他管理软件提供固定时间的中断。 当系统节拍定时器被被使能时,定时器从重装值递减计数,到0进中断…

算法每日双题精讲 —— 前缀和(【模板】一维前缀和,【模板】二维前缀和)

在算法竞赛与日常编程中,前缀和是一种极为实用的预处理技巧,能显著提升处理区间和问题的效率。今天,我们就来深入剖析一维前缀和与二维前缀和这两个经典模板。 一、【模板】一维前缀和 题目描述 给定一个长度为 n n n 的整数数组 a a a&…

学习数据结构(2)空间复杂度+顺序表

1.空间复杂度 (1)概念 空间复杂度也是一个数学表达式,表示一个算法在运行过程中根据算法的需要额外临时开辟的空间。 空间复杂度不是指程序占用了多少bytes的空间,因为常规情况每个对象大小差异不会很大,所以空间复杂…

MybatisX插件快速创建项目

一、安装插件 二、创建一个数据表测试 三、IDEA连接Mysql数据库 四、选择MybatiX构造器 五、配置参数 六、项目结构

基于SpringBoot的假期周边游平台的设计与实现(源码+SQL脚本+LW+部署讲解等)

专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…

Java设计模式:结构型模式→组合模式

Java 组合模式详解 1. 定义 组合模式(Composite Pattern)是一种结构型设计模式,它允许将对象组合成树形结构以表示“部分-整体”的层次。组合模式使得客户端能够以统一的方式对待单个对象和对象集合的一致性,有助于处理树形结构…

FastReport.NET控件篇之富文本控件

简介 FastReport.NET 提供了 RichText 控件,用于在报表中显示富文本内容。富文本控件支持多种文本格式(如字体、颜色、段落、表格、图片等),非常适合需要复杂排版和格式化的场景。 富文本控件(RichText)使用场景不多&#xff0c…

单片机基础模块学习——NE555芯片

一、NE555电路图 NE555也称555定时器,本文主要利用NE555产生方波发生电路。整个电路相当于频率可调的方波发生器。 通过调整电位器的阻值,方波的频率也随之改变。 RB3在开发板的位置如下图 测量方波信号的引脚为SIGHAL,由上面的电路图可知,NE555已经构成完整的方波发生电…

(done) MIT6.S081 2023 学习笔记 (Day6: LAB5 COW Fork)

网页:https://pdos.csail.mit.edu/6.S081/2023/labs/cow.html 任务1:Implement copy-on-write fork(hard) (完成) 现实中的问题如下: xv6中的fork()系统调用会将父进程的用户空间内存全部复制到子进程中。如果父进程很大,复制过程…

三天急速通关JavaWeb基础知识:Day 1 后端基础知识

三天急速通关JavaWeb基础知识:Day 1 后端基础知识 0 文章说明1 Http1.1 介绍1.2 通信过程1.3 报文 Message1.3.1 请求报文 Request Message1.3.2 响应报文 Response Message 2 XML2.1 介绍2.2 利用Java解析XML 3 Tomcat3.1 介绍3.2 Tomcat的安装与配置3.3 Tomcat的项…