Java 核心与应用:Java 继承与多态

目录

  • Java 核心与应用:Java 继承与多态
    • 引言
    • 1. Java 继承基础
      • 1.1 什么是继承?
        • 1.1.1 继承的语法
        • 1.1.2 继承的类型
      • 1.2 方法重写(Override)
        • 1.2.1 方法重写的规则
        • 1.2.2 方法重写 vs 方法重载
      • 1.3 继承体系中的构造方法调用链
        • 1.3.1 构造方法调用链的执行顺序
      • 1.4 动态绑定原理与虚方法表
        • 1.4.1 动态绑定的实现原理
        • 1.4.2 虚方法表的结构
      • 1.5 继承的缺陷与组合优于继承原则
        • 1.5.1 继承的缺陷
        • 1.5.2 组合优于继承
        • 1.5.3 继承 vs 组合对比表
    • 2. 多态的应用场景
      • 2.1 多态的基本概念
        • 2.1.1 编译时多态
        • 2.1.2 运行时多态
      • 2.2 多态的实际应用
        • 2.2.1 多态在集合中的应用
        • 2.2.2 多态在工厂模式中的应用
    • 3. 继承与多态的代码优化建议
      • 3.1 避免过度使用继承
      • 3.2 使用接口实现多态
      • 3.3 使用抽象类提供默认实现
    • 4. 扩展阅读与练习题
      • 4.1 扩展阅读
      • 4.2 练习题
    • 5. 总结
    • 分享按钮

Java 核心与应用:Java 继承与多态

引言

“继承是代码复用的利器,多态是面向对象编程的灵魂。”

Java 作为一门面向对象的编程语言,继承与多态是其核心特性之一。理解这两者不仅能帮助你写出更优雅的代码,还能让你在面试中脱颖而出。本文将带你深入探索 Java 继承与多态的世界,从基础概念到高级应用,逐步揭开它们的神秘面纱。

学习目标

  1. 掌握继承关系的建立与使用场景
  2. 理解方法重写的限制条件与实现技巧
  3. 解析构造方法在继承链中的调用顺序
  4. 通过虚方法表理解动态绑定原理
  5. 学会在项目中应用组合优于继承原则

1. Java 继承基础

1.1 什么是继承?

继承是面向对象编程中的一个重要概念,它允许一个类(子类)继承另一个类(父类)的属性和方法。通过继承,子类可以复用父类的代码,并在此基础上进行扩展或修改。

1.1.1 继承的语法
class Animal {
    void eat() {
        System.out.println("Animal is eating");
    }
}

class Dog extends Animal {
    void bark() {
        System.out.println("Dog is barking");
    }
}

在上面的代码中,Dog 类继承了 Animal 类,因此 Dog 类可以使用 Animal 类中的 eat() 方法。

1.1.2 继承的类型

Java 支持单继承,即一个类只能有一个直接父类。但可以通过多层继承实现类似多继承的效果。

继承类型 描述
单继承 一个类只能有一个直接父类
多层继承 子类可以继承父类,父类又可以继承另一个类

1.2 方法重写(Override)

方法重写是子类对父类方法的重新实现。重写的方法必须与父类方法具有相同的方法签名(方法名、参数列表)和返回类型。

1.2.1 方法重写的规则
  1. 方法签名必须一致:方法名、参数列表必须与父类方法相同。
  2. 返回类型协变:子类方法的返回类型可以是父类方法返回类型的子类。
  3. 访问权限不能更严格:子类方法的访问权限不能比父类方法更严格。
  4. 异常限制:子类方法抛出的异常不能比父类方法抛出的异常更广泛。
// Animal.java
public class Animal {
    protected String name;
    
    public Animal(String name) {
        this.name = name;
        System.out.println("Animal构造器执行");
    }
    
    public void eat() {
        System.out.println(name + "正在进食");
    }
}

// Dog.java
public class Dog extends Animal {  // 继承语法
    private String breed;
    
    public Dog(String name, String breed) {
        super(name);  // 必须首先调用父类构造器
        this.breed = breed;
        System.out.println("Dog构造器执行");
    }
    
    @Override
    public void eat() {  // 方法重写
        super.eat();     // 调用父类实现
        System.out.println(name + "正在啃骨头");
    }
    
    public void bark() {
        System.out.println("汪汪!我是" + breed);
    }
}

在上面的代码中,Dog 类重写了 Animal 类的 eat() 方法,因此 Dog 类调用 eat() 方法时,执行结果为:Dog .eat() 的结果。

重写规则对比表

特性 父类方法 子类重写方法
访问修饰符 protected public/protected
返回类型 Animal Dog(协变类型)
异常声明 IOException 不声明或子异常
方法签名 eat(String food) eat(String food)
static修饰 允许 不允许(隐藏而非重写)
1.2.2 方法重写 vs 方法重载
特性 方法重写(Override) 方法重载(Overload)
方法签名 必须一致 必须不同
返回类型 必须一致或协变 可以不同
访问权限 不能更严格 可以不同
异常 不能更广泛 可以不同

1.3 继承体系中的构造方法调用链

在 Java 中,子类的构造方法会隐式调用父类的构造方法。如果父类没有无参构造方法,子类必须显式调用父类的构造方法。

class Animal {
    Animal(String name) {
        System.out.println("Animal constructor: " + name);
    }
}

class Dog extends Animal {
    Dog(String name) {
        super(name); // 显式调用父类构造方法
        System.out.println("Dog constructor: " + name);
    }
}

在上面的代码中,Dog 类的构造方法通过 super(name) 显式调用了 Animal 类的构造方法。

1.3.1 构造方法调用链的执行顺序
  1. 子类构造方法调用父类构造方法。
  2. 父类构造方法执行完毕后,子类构造方法继续执行。
public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog("Buddy");
    }
}

输出结果:

Animal constructor: Buddy
Dog constructor: Buddy

1.4 动态绑定原理与虚方法表

动态绑定是 Java 多态的核心机制。它允许在运行时确定调用哪个方法,而不是在编译时。

1.4.1 动态绑定的实现原理

Java 通过虚方法表(vtable)实现动态绑定。每个类都有一个虚方法表,其中存储了该类所有可重写方法的地址。当调用一个方法时,JVM 会根据对象的实际类型查找虚方法表,找到对应的方法并执行。

class Animal {
    void speak() {
        System.out.println("Animal is speaking");
    }
}

class Dog extends Animal {
    @Override
    void speak() {
        System.out.println("Dog is barking");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal myAnimal = new Dog();
        myAnimal.speak(); // 输出: Dog is barking
    }
}

在上面的代码中,myAnimal 的实际类型是 Dog,因此调用 speak() 方法时,JVM 会查找 Dog 类的虚方法表,找到 Dog 类的 speak() 方法并执行。

1.4.2 虚方法表的结构
方法名 地址
speak 0x1000
eat 0x2000

1.5 继承的缺陷与组合优于继承原则

虽然继承提供了代码复用的便利,但它也存在一些缺陷。过度使用继承会导致代码耦合度高,难以维护。因此,组合优于继承的原则被广泛提倡。

1.5.1 继承的缺陷
  1. 紧耦合:子类与父类紧密耦合,父类的修改可能会影响子类。
  2. 复杂性:多层继承会导致代码复杂性增加,难以理解和维护。
  3. 灵活性差:继承关系是静态的,无法在运行时改变。
1.5.2 组合优于继承

组合是指在一个类中引用另一个类的对象,通过调用该对象的方法来实现功能。组合比继承更灵活,耦合度更低。

class Engine {
    void start() {
        System.out.println("Engine is starting");
    }
}

class Car {
    private Engine engine;

    Car(Engine engine) {
        this.engine = engine;
    }

    void start() {
        engine.start();
    }
}

在上面的代码中,Car 类通过组合的方式使用了 Engine 类的功能,而不是通过继承。

1.5.3 继承 vs 组合对比表
维度 继承 组合
关系类型 IS-A HAS-A
耦合度 高(编译时绑定) 低(运行时绑定)
灵活性 父类修改影响所有子类 可动态替换组件
复用方式 白盒复用 黑盒复用
扩展性 受限于单继承 支持多组件组合

2. 多态的应用场景

多态是面向对象编程的三大特性之一(封装、继承、多态)。它允许不同的类对同一消息做出不同的响应。多态的应用场景非常广泛,下面我们通过几个实际案例来探讨多态的应用。

2.1 多态的基本概念

多态分为编译时多态和运行时多态。编译时多态主要通过方法重载实现,而运行时多态主要通过方法重写实现。

2.1.1 编译时多态
class Calculator {
    int add(int a, int b) {
        return a + b;
    }

    double add(double a, double b) {
        return a + b;
    }
}

在上面的代码中,Calculator 类通过方法重载实现了编译时多态。

2.1.2 运行时多态
class Animal {
    void speak() {
        System.out.println("Animal is speaking");
    }
}

class Dog extends Animal {
    @Override
    void speak() {
        System.out.println("Dog is barking");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal myAnimal = new Dog();
        myAnimal.speak(); // 输出: Dog is barking
    }
}

在上面的代码中,myAnimal 的实际类型是 Dog,因此调用 speak() 方法时,JVM 会调用 Dog 类的 speak() 方法,这是运行时多态的体现。

2.2 多态的实际应用

2.2.1 多态在集合中的应用

Java 集合框架中的 List 接口是一个典型的多态应用场景。List 接口有多种实现类,如 ArrayListLinkedList,它们对同一方法(如 add())有不同的实现。

List<String> list = new ArrayList<>();
list.add("Hello");

list = new LinkedList<>();
list.add("World");

在上面的代码中,list 变量可以指向 ArrayListLinkedList,这是多态的体现。

2.2.2 多态在工厂模式中的应用

工厂模式是一种常见的设计模式,它通过多态来实现对象的创建。

interface Shape {
    void draw();
}

class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing Circle");
    }
}

class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing Rectangle");
    }
}

class ShapeFactory {
    Shape getShape(String type) {
        if (type.equals("Circle")) {
            return new Circle();
        } else if (type.equals("Rectangle")) {
            return new Rectangle();
        }
        return null;
    }
}

public class Main {
    public static void main(String[] args) {
        ShapeFactory factory = new ShapeFactory();
        Shape shape = factory.getShape("Circle");
        shape.draw(); // 输出: Drawing Circle
    }
}

在上面的代码中,ShapeFactory 类通过多态返回不同的 Shape 对象,这是工厂模式的典型应用。


3. 继承与多态的代码优化建议

3.1 避免过度使用继承

虽然继承提供了代码复用的便利,但过度使用继承会导致代码耦合度高,难以维护。因此,建议在以下情况下使用组合而不是继承:

  1. 子类不需要继承父类的所有方法:如果子类只需要父类的部分功能,使用组合更合适。
  2. 需要动态改变行为:组合允许在运行时改变对象的行为,而继承是静态的。

3.2 使用接口实现多态

接口是实现多态的重要工具。通过接口,可以定义一组方法,让不同的类实现这些方法,从而实现多态。

interface Flyable {
    void fly();
}

class Bird implements Flyable {
    @Override
    public void fly() {
        System.out.println("Bird is flying");
    }
}

class Airplane implements Flyable {
    @Override
    public void fly() {
        System.out.println("Airplane is flying");
    }
}

public class Main {
    public static void main(String[] args) {
        Flyable flyable = new Bird();
        flyable.fly(); // 输出: Bird is flying

        flyable = new Airplane();
        flyable.fly(); // 输出: Airplane is flying
    }
}

在上面的代码中,Flyable 接口定义了一个 fly() 方法,BirdAirplane 类分别实现了这个方法,从而实现了多态。

3.3 使用抽象类提供默认实现

抽象类是一种介于接口和具体类之间的类,它可以提供部分方法的默认实现,同时要求子类实现其他方法。

abstract class Animal {
    abstract void speak();

    void eat() {
        System.out.println("Animal is eating");
    }
}

class Dog extends Animal {
    @Override
    void speak() {
        System.out.println("Dog is barking");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal myAnimal = new Dog();
        myAnimal.speak(); // 输出: Dog is barking
        myAnimal.eat();   // 输出: Animal is eating
    }
}

在上面的代码中,Animal 类是一个抽象类,它提供了 eat() 方法的默认实现,同时要求子类实现 speak() 方法。


4. 扩展阅读与练习题

4.1 扩展阅读

  1. 《Effective Java》:Joshua Bloch 的经典著作,深入探讨了 Java 编程中的最佳实践,包括继承与多态的使用。
  2. 《Java 编程思想》:Bruce Eckel 的经典书籍,全面介绍了 Java 的面向对象编程思想。
  3. Oracle 官方文档:Java 继承与多态

4.2 练习题

  1. 方法重写练习:编写一个父类 Vehicle 和一个子类 Car,要求 Car 类重写 Vehicle 类的 start() 方法。
  2. 多态练习:编写一个接口 Drawable,并实现两个类 CircleRectangle,要求通过多态调用它们的 draw() 方法。
  3. 组合优于继承练习:编写一个 Computer 类,通过组合的方式使用 CPUMemoryHardDisk 类。

5. 总结

继承与多态是 Java 面向对象编程的核心特性,理解它们不仅能帮助你写出更优雅的代码,还能让你在面试中脱颖而出。通过本文的学习,你应该已经掌握了继承与多态的基本概念、应用场景以及优化建议。希望你能在实际开发中灵活运用这些知识,写出高质量的代码。

“代码是写给人看的,顺便给机器执行。” —— 《代码大全》


分享按钮

分享到知乎 | 分享到掘金 | 分享到微博 | 分享到 QQ | 分享到 Stack Overflow

#Java全栈开发 #SpringBoot实战 #JVM调优 #微服务架构

期待与你相遇!

如果你对编程充满热情,想获取丰富的编程学习资料,如经典编程书籍、实用教程等,欢迎加入我们的大家庭!点击云盘链接获取入群方式,扫码添加入群,即可领取海量编程资源!一起提升技能,开启你的编程进阶之旅!


上一篇:Java 封装与访问控制
下一篇:Java 抽象类与接口

你可能感兴趣的:(《Java,核心与应用》,java,python,开发语言)