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

Java面向对象编程:深入理解继承

面向对象编程(Object-Oriented Programming, OOP)的三大核心特性是封装、继承和多态。本文将深入探讨继承这一重要概念,它是Java中实现代码复用和构建层次化类结构的关键机制。

什么是继承?

继承是面向对象编程中的一个基本概念,它允许一个类(称为子类派生类)获取另一个类(称为父类超类基类)的属性和方法。这种关系通常被描述为“is-a”(是一个)的关系。例如,Dog is a AnimalCar is a Vehicle

通过继承,子类自动拥有了父类的非私有成员(字段和方法),并可以在此基础上添加自己特有的属性和行为,或者重写(Override)父类的方法以实现特定的功能。

为什么需要继承?

  1. 代码复用:子类可以直接使用父类的代码,减少冗余。
  2. 扩展性:轻松创建现有类的特殊版本,添加新功能。
  3. 多态的基础:继承是实现多态的前提,提高灵活性。(多态是另一个相关但更进阶的主题)
  4. 维护性:修改通用功能只需在父类进行,简化维护。

如何在Java中实现继承?

Java使用关键字 extends 来实现类之间的继承。

语法:

class SubclassName extends SuperclassName {
// 子类内容
}

示例:基本继承

在这个例子中,Dog 继承自 Animal,可以直接使用 Animalname 属性和 eat() 方法。

// 文件名: InheritanceDemo.java// 父类:Animal (非 public,可以在同一文件中)
class Animal {
String name; // 包访问权限(默认),子类在同一包中可以访问public void eat() {
System.out.println(name + " is eating.");
}
}// 子类:Dog,继承自 Animal
class Dog extends Animal {
// Dog 特有的方法
public void bark() {
// 可以直接访问从 Animal 继承来的 name 属性
System.out.println(name + " says: Woof! Woof!");
}
}// 主类:包含 main 方法
public class InheritanceDemo {
public static void main(String[] args) {
// 创建 Dog 对象
Dog myDog = new Dog();
myDog.name = "Buddy"; // 设置继承自 Animal 的属性myDog.eat();  // 调用继承自 Animal 的方法
myDog.bark(); // 调用 Dog 类自己的方法
}
}

输出:

Buddy is eating.
Buddy says: Woof! Woof!

继承中的细节

1. 继承的成员访问权限

子类对父类成员的访问权限取决于该成员的访问修饰符:

  • public:子类可以访问。
  • protected:子类可以访问(无论是否在同一个包内)。同一包内的其他类也可以访问。
  • 默认(无修饰符,包访问权限):只有在同一个包内的子类可以访问。
  • private:子类不能直接访问。私有成员被继承了(存在于子类对象中),但不可见。只能通过父类提供的 publicprotected 方法(如getter/setter)间接操作。

示例:访问权限

// 文件名: AccessDemo.javaclass Parent {
public String publicVar = "I am public";
protected String protectedVar = "I am protected";
String defaultVar = "I am default (package-private)";
private String privateVar = "I am private";public void showPrivate() {
System.out.println("Accessing privateVar via public method: " + privateVar);
}
}class Child extends Parent {
public void testAccess() {
System.out.println("Accessing publicVar: " + publicVar);       // OK
System.out.println("Accessing protectedVar: " + protectedVar);   // OK
System.out.println("Accessing defaultVar: " + defaultVar);     // OK (假设 Child 和 Parent 在同一包)
// System.out.println("Accessing privateVar: " + privateVar); // 编译错误!无法直接访问 private 成员// 间接访问 private 成员
super.showPrivate();
}
}public class AccessDemo {
public static void main(String[] args) {
Child c = new Child();
c.testAccess();
}
}

输出:

Accessing publicVar: I am public
Accessing protectedVar: I am protected
Accessing defaultVar: I am default (package-private)
Accessing privateVar via public method: I am private
2. 方法重写

子类可以提供一个与父类方法签名(方法名、参数列表)完全相同的方法,以实现自己的逻辑。

  • 使用 @Override 注解是最佳实践,可以帮助编译器检查重写是否正确。
  • 访问权限不能更低。
  • 返回类型必须相同或是父类方法返回类型的子类型(协变返回类型,初学者阶段了解即可)。

示例:方法重写

// 文件名: OverrideDemo.javaclass Vehicle {
public void startEngine() {
System.out.println("Vehicle engine starts.");
}
}class ElectricCar extends Vehicle {
@Override // 明确表示重写
public void startEngine() {
System.out.println("Electric car engine starts silently."); // 子类的特定实现
}public void charge() {
System.out.println("Electric car is charging.");
}
}public class OverrideDemo {
public static void main(String[] args) {
Vehicle myVehicle = new Vehicle();
myVehicle.startEngine(); // 输出: Vehicle engine starts.ElectricCar myTesla = new ElectricCar();
myTesla.startEngine(); // 输出: Electric car engine starts silently. (调用重写后的方法)
myTesla.charge();
}
}
3. super 关键字

super 用于在子类中引用父类的成员。

  • super.methodName(): 调用父类被重写的方法。
  • super.fieldName: 访问父类的属性(当子类有同名属性时尤其有用,但不推荐隐藏字段)。
  • super()super(arguments): 调用父类的构造方法。必须是子类构造方法的第一条语句

示例:使用 super

// 文件名: SuperDemo.javaclass Person {
String name;// 父类构造方法
public Person(String name) {
System.out.println("Person constructor executing.");
this.name = name;
}public void display() {
System.out.println("Name: " + name);
}
}class Employee extends Person {
String employeeId;// 子类构造方法
public Employee(String name, String employeeId) {
super(name); // 必须是第一句,调用父类 Person(String) 构造方法
System.out.println("Employee constructor executing.");
this.employeeId = employeeId;
}// 重写 display 方法
@Override
public void display() {
super.display(); // 调用父类的 display 方法来显示名字
System.out.println("Employee ID: " + employeeId); // 添加子类特有信息
}
}public class SuperDemo {
public static void main(String[] args) {
Employee emp = new Employee("Alice", "E123");
System.out.println("------");
emp.display();
}
}

输出:

Person constructor executing.
Employee constructor executing.
------
Name: Alice
Employee ID: E123
4. 构造方法的调用顺序

当创建一个子类的对象时,构造方法的调用链会从最顶层的父类开始,逐级向下执行,直到子类自己的构造方法。这个过程确保了对象的所有部分(从父类继承来的和子类自己定义的)都被正确初始化。

  • 子类构造方法的第一行(无论是显式 super(...) 还是隐式 super())会触发父类构造方法的调用。
  • 这个调用会一直传递上去,直到 Object 类的构造方法。
  • 然后,构造方法体按照从父到子的顺序依次执行完毕。

示例:构造方法调用顺序

// 文件名: ConstructorOrderDemo.javaclass Grandparent {
public Grandparent() {
System.out.println("Grandparent constructor executed.");
}
}class Parent extends Grandparent {
public Parent() {
// 隐式调用 super() -> Grandparent()
System.out.println("Parent constructor executed.");
}
}class Child extends Parent {
public Child() {
// 隐式调用 super() -> Parent()
System.out.println("Child constructor executed.");
}
}public class ConstructorOrderDemo {
public static void main(String[] args) {
System.out.println("Creating a Child object...");
Child myChild = new Child();
System.out.println("Child object created.");
}
}

输出:

Creating a Child object...
Grandparent constructor executed.
Parent constructor executed.
Child constructor executed.
Child object created.

这个输出清晰地展示了构造方法从最顶层父类(Grandparent)开始,依次向下执行到子类(Child)的顺序。

5. Java 的单继承性

Java 不允许一个类直接继承(extends)多个类。这是为了避免多重继承可能带来的“菱形问题”(Diamond Problem),即当两个父类有同名方法时,子类不知道该继承哪一个实现,从而产生歧义。

非法示例(演示多重继承的错误):

// // 以下代码无法编译通过,演示 Java 不支持多重类继承
// class Father {
//     void work() { System.out.println("Father working"); }
// }
// class Mother {
//     void work() { System.out.println("Mother working"); }
// }
//
// // 错误: Class cannot extend multiple classes
// class Kid extends Father, Mother {
//     // 如果允许多重继承,调用 work() 时会有歧义
// }

Java 只允许单继承,即一个子类只能有一个直接父类。这使得继承关系更清晰、简单。如果需要一个类表现出多种类型的特征,通常会用到更高级的概念(如接口或组合),但对于继承本身,请记住“一个子类只有一个直接父类”。

6. Object 类:所有类的根

所有 Java 类都隐式或显式地继承自 java.lang.Object 类。即使你没有写 extends Object,编译器也会自动添加。这意味着所有对象都天然拥有 Object 类的方法,如 toString(), equals(), hashCode() 等。

示例:隐式继承 Object 和重写 toString

// 文件名: ObjectRootDemo.javaclass MySimpleClass { // 隐式继承自 Object
int value;public MySimpleClass(int value) {
this.value = value;
}// 重写 Object 类的 toString 方法
@Override
public String toString() {
// 默认的 toString() 返回类似 "MySimpleClass@hashcode"
// 我们提供一个更有意义的表示
return "MySimpleClass[value=" + value + "]";
}
}public class ObjectRootDemo {
public static void main(String[] args) {
MySimpleClass obj = new MySimpleClass(10);
// 当打印对象时,会自动调用它的 toString() 方法
System.out.println(obj);
// 输出: MySimpleClass[value=10]// 如果不重写 toString(),输出可能是类似 MySimpleClass@15db9742
}
}

继承的优缺点

优点

  • 代码复用
  • 易于扩展和维护
  • 实现多态的基础

缺点

  • 紧耦合:子类与父类紧密关联,父类的改变可能影响所有子类。
  • 脆弱的基类问题:子类可能依赖父类的内部实现细节,父类实现的改变(即使方法签名不变)可能意外破坏子类的功能。
  • 层次复杂性:过深的继承树会使代码难以理解和维护。

继承 vs. 组合

面向对象设计中有一个重要的原则:“组合优于继承”。

  • 继承 (is-a): 代表“是一个”的关系,如 Dog is an Animal。是一种强烈的类间关系。
  • 组合 (has-a): 代表“有一个”的关系,如 Car has an Engine。一个类包含另一个类的对象作为其成员。

组合通常比继承更灵活,耦合度更低。当一个类需要另一个类的功能,但它们之间不是严格的“is-a”关系时,优先考虑使用组合。

示例(组合):

// 文件名: CompositionDemo.java// 代表引擎的类
class Engine {
public void start() {
System.out.println("Engine starting.");
}
public void stop() {
System.out.println("Engine stopping.");
}
}// Car 类包含一个 Engine 对象 (has-a)
class Car {
private String model;
private Engine engine; // Car "has an" Enginepublic Car(String model) {
this.model = model;
this.engine = new Engine(); // 在 Car 创建时创建 Engine
}// Car 的启动方法委托给 Engine 对象来完成
public void startCar() {
System.out.println(model + " is starting...");
engine.start();
}// Car 的停止方法也委托给 Engine 对象
public void stopCar() {
System.out.println(model + " is stopping...");
engine.stop();
}
}public class CompositionDemo {
public static void main(String[] args) {
Car myCar = new Car("Sedan");
myCar.startCar();
myCar.stopCar();
}
}

输出:

Sedan is starting...
Engine starting.
Sedan is stopping...
Engine stopping.

总结

继承是 Java OOP 的重要特性,通过 extends 关键字实现代码复用和类层次结构的构建。理解继承中的访问权限、方法重写、super 关键字的使用、构造方法的调用链、Java 的单继承限制以及所有类都源自 Object 类是掌握继承的基础。同时,也要认识到继承带来的耦合性问题,并在设计时权衡继承与组合,遵循“组合优于继承”的原则,以构建更健壮、灵活的应用。


练习题

练习 1:基础继承

  1. 创建一个名为 Shape 的基类,包含一个 color (String) 属性。
  2. 添加一个 displayColor() 方法,打印形状的颜色。
  3. 创建一个名为 Circle 的子类,继承自 Shape
  4. Circle 添加一个属性 radius (double)。
  5. 添加一个 calculateArea() 方法,计算并打印圆的面积 (Area = π * radius * radius, 使用 Math.PI)。
  6. 创建一个 publicGeometryTest,在 main 方法中创建一个 Circle 对象,设置其颜色和半径,并调用 displayColor()calculateArea() 方法。

练习 2:构造方法和 super

  1. Shape 类添加一个构造方法,接收 color 作为参数并初始化它。
  2. 修改 Circle 类,添加一个构造方法,接收 colorradius。确保此构造方法能正确调用 Shape 类的构造方法来初始化 color
  3. GeometryTestmain 方法中,使用新的构造方法创建 Circle 对象。

练习 3:概念题

  1. Java 支持一个类同时 extends 多个父类吗?简单说明为什么。

答案

练习 1 和 2 的答案代码:

// 文件名: GeometryTest.java// 基类:Shape
class Shape {
String color;// 练习 2: 添加构造方法
public Shape(String color) {
System.out.println("Shape constructor called.");
this.color = color;
}public void displayColor() {
System.out.println("Color: " + color);
}
}// 子类:Circle
class Circle extends Shape {
double radius;// 练习 2: 添加构造方法并调用 super()
public Circle(String color, double radius) {
super(color); // 调用父类 Shape 的构造方法
System.out.println("Circle constructor called.");
this.radius = radius;
}// 练习 1: 添加计算面积方法
public void calculateArea() {
double area = Math.PI * radius * radius;
System.out.println("Radius: " + radius);
System.out.println("Area: " + area);
}
}// 主测试类
public class GeometryTest {
public static void main(String[] args) {
// 使用练习 2 的构造方法创建对象
Circle myCircle = new Circle("Red", 5.0);System.out.println("\nCircle Details:");
myCircle.displayColor(); // 调用继承自 Shape 的方法
myCircle.calculateArea(); // 调用 Circle 自己的方法
}
}

预期输出:

Shape constructor called.
Circle constructor called.Circle Details:
Color: Red
Radius: 5.0
Area: 78.53981633974483

练习 3 的答案:

  1. Java 不支持一个类同时 extends 多个父类(即不支持多重类继承)。主要原因是为了避免菱形问题,这会导致在继承体系中出现方法调用的歧义,使得程序难以理解和维护。Java 通过强制单继承简化了继承模型。
http://www.xdnf.cn/news/1829.html

相关文章:

  • frome time import * 与 import time
  • 第14章:MCP服务端项目开发实战:多模态信息处理
  • 线程同步与互斥(互斥)
  • linux sudo 命令介绍
  • WGAN+U-Net架构实现图像修复
  • Python3(9) 列表
  • CGAL 网格等高线计算
  • 第16章:MCP服务端项目开发实战:对话系统
  • 【通关函数的递归】--递归思想的形成与应用
  • 正余弦位置编码和RoPE位置编码
  • Spring Security
  • 【C语言】C语言动态内存管理
  • 深度学习(第2章——卷积和转置卷积)
  • Python设计模式:MVC模式
  • C++学习笔记(三十八)——STL之修改算法
  • Python面向对象编程相关的单选题和多选题
  • 服务器部署LLaMAFactory进行LoRA微调
  • 大语言模型的“模型量化”详解 - 03:【超轻部署、极致推理】KTransformers 环境配置 实机测试
  • 蓝桥杯 1. 四平方和
  • Ubuntu主机上通过WiFi转有线为其他设备提供网络连接
  • 【Pandas】pandas DataFrame dot
  • JavaScript性能优化实战(4):异步编程与主线程优化
  • Linux网络编程 深入Linux网络栈:原始套接字链路层实战解析
  • 中式面点实训室建设规划与功能布局方案
  • esp32c3 合宇宙
  • 【FAQ】针对于消费级NVIDIA GPU的说明
  • 驱动安装有感叹号之关闭dell window11 笔记本数字签名
  • Day-3 应急响应实战
  • Java转Go日记(十二):Channel
  • python 练习 二