07-Java面向对象多态与向下转型

Java面向对象核心:多态、向上转型与向下转型

一、前言

学习完继承、方法重写、this/super之后,我们已经能够构建父子类的体系结构了。但你可能会遇到这样的困惑:当方法的参数可能是多种子类类型时,到底该如何处理?

如果为每种子类都写一个重载方法,代码会越来越臃肿,后期维护成本陡增。Java用一种优雅的机制解决了这个问题------它就是面向对象三大特性之一的多态

本篇博客将带你从问题出发,理解多态的本质、向上转型与向下转型,并通过经典案例彻底掌握这一核心概念。

二、接口回顾

在进入多态之前,先回顾一下接口相关的知识,因为接口与多态关系密切。

2.1 接口与接口的关系

子类与父类之间是单继承,子类与父接口之间是多实现,而接口与接口之间是多继承

这意味着一个接口可以同时继承多个父接口,语法格式如下:

java 复制代码
【修饰符】 interface 子接口名 extends 父接口1, 父接口2, ... {
    // 接口成员
}

下面是一个完整示例,展示了接口之间的多继承关系:

java 复制代码
// 父接口A
public interface A {
    void a();
    void b(String s);
}

// 父接口B
public interface B {
    void b();
}

// 子接口C继承A和B
public interface C extends A, B {
    void c();
}

// 实现类D实现C,需要重写A、B、C中所有的抽象方法
public class D implements C {
    @Override
    public void c() { }

    @Override
    public void a() { }

    @Override
    public void b() { }

    @Override
    public void b(String s) { }
}

可以看到,实现类D在实现接口C时,必须重写从A、B继承来的所有抽象方法。这种设计让接口可以灵活组合,构建出强大的类型体系。

2.2 接口的冲突问题(了解)

当一个类既继承父类又实现接口,或实现多个接口时,可能会出现成员冲突。这部分了解即可。

1、成员变量冲突

父类的实例变量与接口的常量重名时,直接使用会产生歧义,需要明确指定来源:

java 复制代码
public class Father {
    int x = 1; // 实例变量
}

public interface DryDad {
    int x = 2; // 接口常量,默认 public static final
}

public class Son extends Father implements DryDad {
    public void method() {
        // System.out.println(x); // 错误,有歧义
        System.out.println("父类的x = " + super.x);  // 输出1
        System.out.println("父接口的x = " + DryDad.x); // 输出2
    }
}

2、成员方法冲突

当父类方法与父接口默认方法冲突时,遵循"亲爹优先"原则,即父类优先:

java 复制代码
public class Fu {
    public void method() {
        System.out.println("Fu.method");
    }
}

public interface DryFu {
    public default void method() {
        System.out.println("DryFu.method");
    }
}

public class Zi extends Fu implements DryFu {
    // 不需要重写,自动选择父类Fu的method()
}

当两个父接口的默认方法冲突时,实现类必须做出选择,即必须重写该方法:

java 复制代码
public interface DryA {
    public default void method() {
        System.out.println("DryA.method");
    }
}

public interface DryB {
    public default void method() {
        System.out.println("DryB.method");
    }
}

public class Sub implements DryA, DryB {
    @Override
    public void method() {
        // 必须重写,否则编译报错
        // 也可通过 DryA.super.method() 调用指定接口的默认方法
        System.out.println("Sub.method");
    }
}

三、为什么需要多态

理解一个概念,最好的方式是从问题出发。我们通过两个场景,看看不用多态会带来什么麻烦。

3.1 参数可能是多种子类类型

假设有Animal父类,以及Dog、Cat、Pig等子类:

java 复制代码
public class Animal {
    public void eat() {
        System.out.println("吃东西");
    }
}

public class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("吃骨头");
    }
    public void watchHouse() {
        System.out.println("看家");
    }
}

public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("吃鱼");
    }
    public void catchMouse() {
        System.out.println("抓老鼠");
    }
}

现在需要定义一个look方法,参数可能是Dog、Cat或Pig。不用多态的写法是这样的:

java 复制代码
public class AnimalTest {
    // 为每种动物写一个重载方法
    public static void look(Dog dog) { dog.eat(); }
    public static void look(Cat cat) { cat.eat(); }
    public static void look(Pig pig) { pig.eat(); }
    // 问题:有几个子类就要写几个方法
}

这样的代码有明显缺陷:每增加一个子类就要新增一个方法,每删除一个子类又要删掉一个方法,扩展性和可维护性都很差。

3.2 数组中存储多种类型对象

另一个场景是希望用一个数组统一管理Animal的所有子类对象:

java 复制代码
public class AnimalTest2 {
    public static void main(String[] args) {
        Dog[] dogs = new Dog[3];
        dogs[0] = new Dog();
        // dogs[1] = new Cat(); // 错误,类型不匹配
        // dogs[2] = new Pig(); // 错误,类型不匹配
    }
}

数组一旦声明为Dog\[\]类型,就只能存Dog对象,无法存放Cat或Pig。这种限制让我们无法统一管理同类对象。

四、多态的定义与核心思想

Java中的多态分为编译时多态和运行时多态。

  • 编译时多态:指方法的重载,一个方法有多种形式。
  • 运行时多态:指父类变量指向子类对象,调用的是子类重写的方法体。

我们通常说的多态,指的是运行时多态。

多态的核心语法:

java 复制代码
父类类型 变量名 = 子类对象;

例如:

java 复制代码
Animal animal = new Dog(); // 多态引用
animal.eat(); // 执行Dog重写的eat(),输出"吃骨头"

多态的本质:编译时看左边(变量声明的类型),运行时看右边(new的实际类型)。

这意味着,编译阶段只能调用父类中声明的方法,而运行阶段执行的是子类重写后的方法体。

五、多态的前提条件

要实现多态,必须同时满足三个条件:

  1. 继承或实现关系:必须有父子类或接口与实现类的关系。
  2. 方法重写:子类必须重写父类的方法,否则多态没有意义。
  3. 父类引用指向子类对象:用父类类型的变量接收子类对象。
java 复制代码
// 条件1:继承关系
class Animal {
    public void eat() { System.out.println("吃东西"); }
}

class Dog extends Animal {
    // 条件2:方法重写
    @Override
    public void eat() { System.out.println("吃骨头"); }
}

public class Test {
    public static void main(String[] args) {
        // 条件3:父类引用指向子类对象
        Animal animal = new Dog();
        animal.eat(); // 输出"吃骨头"
    }
}

小贴士:如果子类没有重写父类方法,多态调用时仍执行父类的方法体,多态就失去了意义。

六、向上转型(Upcasting)

向上转型是指子类类型转换为父类类型,这与基本数据类型的自动类型提升类似。

java 复制代码
// 基本数据类型:小转大,自动提升
double d = 1;

// 引用数据类型:子转父,自动向上转型
Animal a = new Dog();

向上转型的特点:

  • 只要发生多态引用,编译时就自动完成向上转型。
  • 向上转型是安全的,因为子类一定具备父类的所有成员。
  • 向上转型后,只能调用父类中声明的方法,不能调用子类扩展的方法。
java 复制代码
Animal animal = new Dog(); // 向上转型
animal.eat();        // 正确,调用Dog重写的eat()
// animal.watchHouse(); // 错误,编译时看左边,Animal没有watchHouse方法

七、多态的好处

多态是面向对象三大核心特性之一,它带来的好处主要体现在以下几个方面:

  1. 代码复用性增强:通过父类引用调用子类重写的方法,统一接口的不同实现,减少重复代码。
  2. 程序扩展性强:在不修改已有代码的前提下,新增子类即可扩展功能,符合开闭原则。
  3. 可维护性和灵活性提高:程序根据实际对象类型动态绑定方法,系统更灵活。
  4. 接口统一:上层逻辑可以面向接口或抽象类编程,屏蔽实现细节。
  5. 支持运行时动态绑定:JVM在运行时根据对象实际类型决定调用哪个方法。

回到前面的look方法,使用多态后只需一个方法即可:

java 复制代码
public class AnimalTestAnswer {
    // 形参是父类类型,实参是子类对象,称为多态参数
    public static void look(Animal animal) {
        animal.eat(); // 运行时执行子类重写的eat()
    }

    public static void main(String[] args) {
        look(new Dog()); // 输出"吃骨头"
        look(new Cat()); // 输出"吃鱼"
        look(new Pig()); // 输出"啥都吃"
    }
}

数组管理多种子类对象的问题也迎刃而解:

java 复制代码
public class AnimalTest2Answer {
    public static void main(String[] args) {
        // 用父类数组统一管理所有子类对象
        Animal[] animals = new Animal[3];
        animals[0] = new Dog();
        animals[1] = new Cat();
        animals[2] = new Pig();

        for (Animal a : animals) {
            a.eat(); // 执行各个子类重写的eat()
        }
    }
}

面试题:面向对象三大核心特征是什么?多态的好处和意义是什么?这就是标准答案。

八、核心规则:编译看左边,运行看右边

这是多态最核心的规则,务必牢记:

编译时看左边 :看变量声明的类型(编译时类型),只能调用该类型中声明的方法。

运行时看右边:看new的实际类型(运行时类型),执行该类型重写的方法体。

一个多态引用的变量有两个类型:

  • 编译时类型:声明变量时使用的类型,决定了能调用哪些方法。
  • 运行时类型:实际new出来的类型,决定了方法执行哪段代码。
java 复制代码
Animal animal = new Dog();
// animal的编译时类型是Animal,运行时类型是Dog
animal.eat(); // 编译时找Animal的eat(),运行时执行Dog的eat()

九、多态中成员访问特点

不同成员在多态下的访问规则不同,这是面试和开发中的高频考点。

9.1 成员变量:编译看左边,运行看左边

成员变量没有多态,访问规则是"编译看左边,运行看左边"。

java 复制代码
class Father {
    int a = 1;
    int b = 2;
}

class Son extends Father {
    int a = 10; // 与父类同名
}

public class TestSon {
    public static void main(String[] args) {
        Father f = new Son(); // 多态引用
        System.out.println(f.a); // 输出1,看左边Father
        System.out.println(f.b); // 输出2,看左边Father

        Son s = new Son(); // 非多态引用
        System.out.println(s.a); // 输出10,看左边Son
        System.out.println(((Father)s).a); // 输出1,强转后看Father
    }
}

注意:成员变量没有多态!访问成员变量只看编译时类型(左边)。

9.2 成员方法:编译看左边,运行看右边

成员方法是多态的核心体现,访问规则是"编译看左边,运行看右边"。

java 复制代码
class Animal {
    public void eat() {
        System.out.println("吃东西");
    }
}

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

public class Test {
    public static void main(String[] args) {
        Animal a = new Dog();
        // 编译时:找Animal的eat(),能找到,编译通过
        // 运行时:执行Dog重写的eat(),输出"吃骨头"
        a.eat();
    }
}

这种运行时根据对象实际类型决定执行哪个方法体的机制,称为动态绑定

9.3 静态方法:编译看左边,运行看左边

静态方法属于类,不属于对象,因此没有多态。访问规则是"编译看左边,运行看左边"。

java 复制代码
class Animal {
    public static void staticMethod() {
        System.out.println("Animal的静态方法");
    }
}

class Dog extends Animal {
    public static void staticMethod() {
        System.out.println("Dog的静态方法");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal a = new Dog();
        a.staticMethod(); // 输出"Animal的静态方法",看左边
    }
}

小贴士:静态方法不能被重写,只能被隐藏。调用静态方法时,看引用变量的编译时类型。

十、向下转型与instanceof

10.1 向下转型(Downcasting)

向上转型后,我们无法调用子类扩展的方法。如果确实需要调用子类特有方法,就必须向下转型。

向下转型是父类类型转换为子类类型,必须强制进行,类似于基本数据类型的强转:

java 复制代码
// 基本数据类型:大转小,强制转换
double a = 1.0;
int b = (int) a;

// 引用数据类型:父转子,强制转换
Animal animal = new Dog();
Dog d = (Dog) animal; // 向下转型
d.watchHouse(); // 调用Dog特有的方法

10.2 向下转型的风险:ClassCastException

向下转型有风险,如果转换的实际类型不匹配,运行时会抛出ClassCastException:

java 复制代码
Animal a = new Dog(); // 向上转型,a实际是Dog对象
a.eat();

Dog d = (Dog) a; // 正确,a确实是Dog
d.watchHouse();

Cat c = (Cat) a; // 编译通过,但运行时报错!
// ClassCastException:Dog无法转换为Cat
c.catchMouse();

为什么编译能通过?因为编译器只看a的编译时类型是Animal,而Animal是Cat的父类,允许向下转型。但运行时JVM发现a实际是Dog,Dog和Cat没有父子关系,无法转换。

注意:向下转型前,务必用instanceof判断,避免ClassCastException!

10.3 instanceof关键字

instanceof用于判断对象是否属于某个类型,返回boolean值:

java 复制代码
对象 instanceof 类型 // 返回true或false

instance代表实例,of代表"xx的",整体意思是"对象是不是某类型的实例"。

java 复制代码
public class ClassCastTest2 {
    public static void main(String[] args) {
        Animal a = new Dog(); // 向上转型
        a.eat();

        // 传统写法
        if (a instanceof Dog) {
            Dog d = (Dog) a;
            d.watchHouse();
        }
        if (a instanceof Cat) {
            Cat c = (Cat) a;
            c.catchMouse();
        }

        // JDK16+模式匹配写法,更简洁
        if (a instanceof Dog d) {
            d.watchHouse(); // 直接使用d
        }
        if (a instanceof Cat c) {
            c.catchMouse(); // 直接使用c
        }
    }
}

小贴士:JDK 16开始支持instanceof模式匹配,判断通过后可直接使用变量,无需再强转。

十一、经典案例练习

11.1 图形案例

定义图形父类Graphic,以及Circle、Rectangle、Triangle三个子类,用多态统一管理并按面积排序。

父类Graphic:

java 复制代码
public abstract class Graphic {
    // 抽象方法:求面积
    public abstract double area();
    // 抽象方法:求周长
    public abstract double perimeter();

    // 重写toString,这里调用的area()和perimeter()会执行子类重写的
    @Override
    public String toString() {
        return "面积:" + area() + ",周长:" + perimeter();
    }
}

子类Circle:

java 复制代码
public class Circle extends Graphic {
    private double radius;

    public Circle() { }

    public Circle(double radius) {
        this.radius = radius;
    }

    public double getRadius() { return radius; }
    public void setRadius(double radius) { this.radius = radius; }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }

    @Override
    public double perimeter() {
        return 2 * Math.PI * radius;
    }

    @Override
    public String toString() {
        return "半径:" + radius + "," + super.toString();
    }
}

子类Rectangle:

java 复制代码
public class Rectangle extends Graphic {
    private double length;
    private double width;

    public Rectangle() { }

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    public double getLength() { return length; }
    public void setLength(double length) { this.length = length; }
    public double getWidth() { return width; }
    public void setWidth(double width) { this.width = width; }

    @Override
    public double area() {
        return length * width;
    }

    @Override
    public double perimeter() {
        return 2 * (length + width);
    }

    @Override
    public String toString() {
        return "长:" + length + ",宽:" + width + "," + super.toString();
    }
}

测试类,使用多态管理并排序:

java 复制代码
public class GraphicTest {
    public static void main(String[] args) {
        // 父类数组存放子类对象
        Graphic[] arr = new Graphic[3];
        arr[0] = new Circle(2.5);
        arr[1] = new Rectangle(2, 1);
        arr[2] = new Triangle(1, 1, 1);

        // 遍历输出
        for (Graphic g : arr) {
            System.out.println(g); // 运行时调用子类toString()
        }

        // 按面积从小到大排序(直接选择排序)
        for (int lun = 0; lun < arr.length - 1; lun++) {
            int index = lun;
            for (int j = index + 1; j < arr.length; j++) {
                // 编译看Graphic,运行执行子类area()
                if (arr[j].area() < arr[index].area()) {
                    Graphic temp = arr[j];
                    arr[j] = arr[index];
                    arr[index] = temp;
                }
            }
        }

        System.out.println("按面积排序后:");
        for (Graphic g : arr) {
            System.out.println(g);
        }
    }
}

小贴士:Graphic的toString()中调用area(),虽然area()是抽象方法,但实际执行时调用的是子类重写的area()------这就是多态的威力。

11.2 员工案例

定义员工体系:Employee为抽象父类,SalaryEmployee为正式工,HourlyEmployee为小时工,Manager为经理(继承SalaryEmployee)。

父类Employee:

java 复制代码
public abstract class Employee {
    private String name;

    public Employee() { }
    public Employee(String name) { this.name = name; }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    // 抽象方法:实发工资
    public abstract double earning();
    // 抽象方法:基本信息
    public abstract String getBasicInfo();

    // 用final修饰,防止子类修改模板逻辑
    @Override
    public final String toString() {
        return "基本信息:" + getBasicInfo() + ",实发工资:" + earning();
    }
}

子类SalaryEmployee:

java 复制代码
public class SalaryEmployee extends Employee {
    private double salary;

    public SalaryEmployee() { }

    public SalaryEmployee(String name, double salary) {
        super(name);
        this.salary = salary;
    }

    public double getSalary() { return salary; }
    public void setSalary(double salary) { this.salary = salary; }

    @Override
    public double earning() {
        return salary;
    }

    @Override
    public String getBasicInfo() {
        return "姓名:" + getName() + ",月薪:" + salary;
    }
}

子类HourlyEmployee:

java 复制代码
public class HourlyEmployee extends Employee {
    private double hours;
    private double wage;

    public HourlyEmployee() { }

    public HourlyEmployee(String name, double hours, double wage) {
        super(name);
        this.hours = hours;
        this.wage = wage;
    }

    public double getHours() { return hours; }
    public void setHours(double hours) { this.hours = hours; }
    public double getWage() { return wage; }
    public void setWage(double wage) { this.wage = wage; }

    @Override
    public double earning() {
        return wage * hours;
    }

    @Override
    public String getBasicInfo() {
        return "姓名:" + getName() + ",时薪:" + wage + ",工作小时数:" + hours;
    }
}

子类Manager(继承SalaryEmployee):

java 复制代码
public class Manager extends SalaryEmployee {
    private double bonusRatio;

    public Manager() { }

    public Manager(String name, double salary, double bonusRatio) {
        super(name, salary);
        this.bonusRatio = bonusRatio;
    }

    public double getBonusRatio() { return bonusRatio; }
    public void setBonusRatio(double bonusRatio) { this.bonusRatio = bonusRatio; }

    @Override
    public double earning() {
        return (1 + bonusRatio) * getSalary();
    }

    @Override
    public String getBasicInfo() {
        return "职位:经理," + super.getBasicInfo() + ",奖金比例:" + bonusRatio;
    }
}

测试类,结合instanceof实现不同员工的差异化处理:

java 复制代码
public class EmployeeTest {
    public static void main(String[] args) {
        // 父类数组存储各类员工
        Employee[] all = new Employee[3];
        all[0] = new SalaryEmployee("张三", 15000);
        all[1] = new HourlyEmployee("李四", 100, 50);
        all[2] = new Manager("王五", 16000, 0.1);

        double sum = 0.0;
        for (Employee employee : all) {
            System.out.println(employee); // 多态调用toString()
            sum += employee.earning(); // 多态调用earning()

            // 用instanceof判断具体类型,做差异化处理
            if (employee instanceof HourlyEmployee) {
                System.out.println("5号领取现金");
            } else if (employee instanceof SalaryEmployee) {
                // Manager也是SalaryEmployee的子类,会匹配
                System.out.println("下个月15号团建");
            }
        }
        System.out.println("总工资:" + sum);
    }
}

小贴士:instanceof判断时,子类对象instanceof父类也返回true。Manager instanceof SalaryEmployee结果为true。

十二、模板设计模式

设计模式是编程工作中解决某些问题的经验总结,常见有23种。模板方法模式是其中一种行为型模式。

模板方法模式定义了一个算法的骨架,将一些步骤的具体实现延迟到子类中完成。父类封装公共逻辑,子类在不修改算法结构的前提下重定义某些步骤。

主要特点:

  • 抽象类定义算法骨架:在抽象类中定义抽象方法和一个模板方法。
  • 模板方法:通常用final修饰,防止子类修改算法流程。
  • 钩子方法:父类中可选实现的方法,子类可选择是否覆盖。

前面的Employee类就是一个典型的模板方法模式示例:

java 复制代码
public abstract class Employee {
    private String name;

    public abstract double earning();      // 钩子方法,由子类实现
    public abstract String getBasicInfo(); // 钩子方法,由子类实现

    // 模板方法,用final修饰防止子类修改
    @Override
    public final String toString() {
        return "基本信息:" + getBasicInfo() + ",实发工资:" + earning();
    }
}

toString()就是模板方法,它定义了"基本信息+实发工资"这个算法骨架,而getBasicInfo()和earning()的具体实现交给子类完成。这就是多态在模板模式中的应用。

十三、多态的误解:成员变量没有多态

这是一个常见的误区,必须澄清。

成员变量没有多态,访问成员变量只看编译时类型(左边),不存在"运行看右边"的情况。

java 复制代码
class Father {
    int a = 1;
    int b = 2;
}

class Son extends Father {
    int a = 10; // 与父类同名,这是隐藏,不是重写
}

public class TestSon {
    public static void main(String[] args) {
        Father f = new Son(); // 多态引用
        System.out.println(f.a); // 输出1,看左边Father
        System.out.println(f.b); // 输出2

        System.out.println(((Son) f).a); // 输出10,强转后看Son

        Son s = new Son(); // 非多态引用
        System.out.println(s.a); // 输出10,看左边Son
        System.out.println(((Father) s).a); // 输出1,强转后看Father
    }
}

注意:创建子类对象时,会同时包含子类和父类声明的所有属性值。访问哪个属性,取决于引用变量的编译时类型。

十四、虚方法深入理解

虚方法是指可以被子类重写的方法。虚方法的调用分析分为两步:

第一步:编译时静态分派(看左边)

先看对象的编译时类型,在该类型中找到能匹配的方法。匹配原则:

  • 找最匹配:实参的编译时类型 = 方法形参的类型
  • 找兼容:实参的编译时类型 < 方法形参的类型(即实参是形参的子类)

第二步:运行时动态绑定(看右边)

再看对象的运行时类型,如果运行时类型重写了刚匹配到的方法,就执行重写的;否则执行编译时类型中的方法。

来看一个综合示例:

java 复制代码
class MyClass {
    public void method(Father f) {
        System.out.println("father");
    }
    public void method(Son s) {
        System.out.println("son");
    }
}

class MySub extends MyClass {
    @Override
    public void method(Father d) {
        System.out.println("sub--father");
    }
    public void method(Daughter d) {
        System.out.println("daughter");
    }
}

class Father { }
class Son extends Father { }
class Daughter extends Father { }

测试分析:

java 复制代码
public class TestVirtualMethod {
    public static void main(String[] args) {
        Father f = new Father();
        Son s = new Son();
        Daughter d = new Daughter();
        Father f2 = new Son(); // 多态引用

        MyClass my = new MySub(); // 多态引用
        my.method(f); // 输出"sub--father"
    }
}

my.method(f)的分析:

  • 编译时:my的左边是MyClass,在MyClass中找匹配方法。实参f是Father类型,匹配method(Father f)
  • 运行时:my的右边是MySub,MySub重写了method(Father),执行重写的sub--father

再看my.method(s)

  • 编译时:实参s是Son类型,匹配method(Son s)
  • 运行时:MySub没有重写method(Son),执行父类的son

再看my.method(d)

  • 编译时:实参d是Daughter类型,MyClass没有method(Daughter),兼容匹配method(Father)
  • 运行时:MySub重写了method(Father),执行sub--father

小贴士:方法匹配时,实参看编译时类型而非运行时类型。这是初学者最容易混淆的点。

十五、小结

本篇博客从问题出发,系统讲解了Java多态的核心知识。总结如下:

  1. 接口与接口是多继承,类与接口是多实现,冲突时父类方法优先于接口默认方法。

  2. 多态的三要素:继承/实现、方法重写、父类引用指向子类对象,缺一不可。

  3. 向上转型是子转父,自动完成,安全但会丢失子类特有方法的访问能力。

  4. 核心规则:编译时看左边(调用看声明类型),运行时看右边(执行看实际类型)。

  5. 成员访问特点:成员变量看左边,成员方法编译看左边运行看右边,静态方法看左边。

  6. 向下转型是父转子,必须强转,有ClassCastException风险,需先用instanceof判断。

  7. 成员变量没有多态,这是常见误区,访问成员变量只看编译时类型。

  8. 虚方法是可以被重写的方法,调用时分静态分派和动态绑定两步。

  9. 模板设计模式利用多态,在父类定义算法骨架,子类实现具体步骤。

多态是Java面向对象的精髓,理解并熟练运用它,能让你的代码更灵活、更易扩展、更易维护。建议结合图形案例和员工案例多动手实践,真正体会多态的威力。

相关推荐
圆山猫6 小时前
[Virtualization](四):Linux KVM/RISC-V 的 vCPU 运行路径
java·linux·risc-v
微学AI7 小时前
一根针指向所有方向:挂谷猜想对 LLM Agent 技能-记忆架构的启示
开发语言·人工智能·架构·挂谷猜想
城管不管7 小时前
ReAct、Plan-and-Execute、Reflection 三大智能 Agent 范式核心区别
java·人工智能·算法·spring·ai·动态规划
IT小白杨7 小时前
从环境制备到自动化工作流:多账号运营的工程化架构拆解
java·经验分享·自动化·安全架构·指纹浏览器
豆瓣鸡7 小时前
算法日记 - Day3
java·开发语言·算法
萧瑟余晖8 小时前
Java深入解析篇九之NIO详解
java·网络·nio
韭菜炒鸡肝天8 小时前
VTK开发笔记(一):VTK介绍,Qt..+VSx+VTK.编译
开发语言·笔记·qt
The Chosen One9858 小时前
高进度算法模板速记(待完善)
java·前端·算法
Aaron - Wistron9 小时前
Web API C# (Furion版)带 单元测试
开发语言·后端·c#
Dxy123931021610 小时前
Python项目打包成EXE完整教程(PyInstaller实战避坑)
开发语言·python