Java面向对象基础——4.继承
Java中的继承:面向对象的代码复用机制
继承的基本概念
在Java中,继承通过extends关键字实现,指子类(subclass)可以继承父类(superclass)的非私有成员(字段和方法)。这种机制的核心价值在于建立类之间的层次关系和代码复用。
示例:Person与Student的继承关系
假设我们需要定义Person类和Student类,其中Student是Person的一种特殊形式,具有Person的所有属性,同时还有自己的特有属性(如成绩):
// 父类:Person
class Person {
protected String name; // 受保护的字段,子类可访问
protected int age; // 受保护的字段,子类可访问
// 父类的构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 父类的方法
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
// 子类:Student,继承自Person
class Student extends Person {
private int score; // 子类特有字段:成绩
// 子类的构造方法
public Student(String name, int age, int score) {
super(name, age); // 调用父类的构造方法
this.score = score;
}
// 子类特有的方法
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
}
在这个例子中:
Student通过extends Person继承了Person类Student自动获得了Person的name和age字段,以及相关的getter/setter方法Student新增了score字段和对应的getter/setter方法- 子类构造方法通过
super(name, age)显式调用了父类的构造方法
继承的特性
单继承限制
Java只允许一个类直接继承自一个父类,即单继承。这种设计避免了多继承可能导致的歧义问题(如方法冲突)。
// 错误示例:Java不支持多继承
class Student extends Person, School { // 编译错误
// ...
}
所有类最终都间接继承自java.lang.Object类,Object是Java类层次结构的根。如果一个类没有显式声明父类,编译器会自动将其定义为Object的子类:
class Person { // 等价于 class Person extends Object
// ...
}
访问权限控制
子类不能直接访问父类的private(私有)成员,但可以访问protected(受保护)和public(公共)成员:
private:仅当前类可访问,子类不可见protected:当前类、子类可访问,同一包中的类也可访问public:任何类都可访问- 默认(无修饰符):当前类和同一包中的类可访问,子类不可访问(若不在同一包)
方法与字段的继承规则
- 子类继承父类的非私有方法和字段
- 子类可以定义与父类同名的字段,但这是不推荐的,会导致混淆(隐藏父类字段)
- 子类可以重写(override)父类的方法,实现自己的逻辑(后续章节详细介绍)
super关键字
super关键字用于在子类中访问父类的成员,主要有三种用途:
- 调用父类的构造方法:必须是子类构造方法的第一条语句
public Student(String name, int age, int score) {
super(name, age); // 调用父类的构造方法
this.score = score;
}
如果父类没有无参数的构造方法,子类必须显式调用父类的某个构造方法,否则会编译错误。
- 访问父类的字段:当子类字段与父类字段同名时使用
class Person {
protected String name = "Person";
}
class Student extends Person {
protected String name = "Student";
public void printNames() {
System.out.println(name); // 访问子类的name
System.out.println(super.name); // 访问父类的name
}
}
- 调用父类的方法:当子类重写了父类方法时使用
class Person {
public void greet() {
System.out.println("Hello!");
}
}
class Student extends Person {
@Override
public void greet() {
super.greet(); // 调用父类的greet()方法
System.out.println("I'm a student.");
}
}
类型转换与多态
继承关系允许我们进行向上转型和向下转型,这是实现多态的基础。
向上转型(Upcasting)
将子类类型转换为父类类型,是安全的隐式转换:
Student student = new Student("张三", 20, 90);
Person person = student; // 向上转型:Student -> Person
Object obj = student; // 向上转型:Student -> Object
向上转型后,变量只能访问父类中定义的方法和字段,无法访问子类特有的成员:
person.getScore(); // 编译错误:Person类没有getScore()方法
向下转型(Downcasting)
将父类类型转换为子类类型,需要显式强制转换,可能失败:
Person person = new Student("张三", 20, 90);//向上转型,Student对象赋值给Person引用
Student student = (Student) person; // 向下转型:Person -> Student,成功
Person anotherPerson = new Person("李四", 30);
Student anotherStudent = (Student) anotherPerson; // 运行时错误:ClassCastException
//因为试图将Person对象强制转换为Student类型,但该对象实际不是Student实例。
加深理解:
- 在Person person = new Student("张三", 20, 90); 这行代码中,虽然引用类型是 Person,但实际创建的对象仍然是 Student 类型,这意味着对象在内存中完整保留了所有字段,包括 Student 特有的 score 字段。
- Person 引用只能通过 Person 类定义的接口访问对象,因此无法直接访问 score 字段。但 score 字段在对象中实际存在,只是对 Person 类型的引用不可见
- 因此需要向下转型为 Student 类型后才能访问 score 字段
引用类型决定了可以通过什么接口访问对象,但不改变对象的实际结构。这是Java多态性的体现,对象的实际类型不变,只是引用类型限制了访问接口。
为了安全地进行向下转型,应先使用instanceof运算符判断对象的实际类型:
if (person instanceof Student) {
Student student = (Student) person; // 安全转换
System.out.println(student.getScore());
}
从Java 14开始,可以在instanceof判断后直接绑定变量,简化代码:
if (person instanceof Student student) {
// 直接使用student变量
System.out.println(student.getScore());
}
继承的限制
阻止继承
如果希望某个类不能被继承,可以使用final关键字修饰该类:
final class FinalClass {
// ...
}
class SubClass extends FinalClass { // 编译错误:无法继承final类
// ...
}
Java 15引入了sealed类,可以限制哪些类能继承它:
public sealed class Shape permits Circle, Rectangle, Triangle {
// ...
}
// 允许继承
public final class Circle extends Shape { ... }
// 不允许继承(未在permits列表中)
public class Ellipse extends Shape { ... } // 编译错误
继承与组合的选择
继承体现的是"is-a"(是一种)关系,而组合体现的是"has-a"(有一个)关系。应根据实际逻辑选择:
- 继承:当子类确实是父类的一种特殊形式时(如
Student是Person的一种) - 组合:当两个类是包含关系时(如
Student有Book,应使用组合)
错误使用继承的例子:
// 错误:Student和Book是has-a关系,不应使用继承
class Student extends Book {
// ...
}
正确的组合方式:
class Student extends Person {
private Book book; // 组合:Student有一个Book
// ...
}
综合示例:
// 顶层父类:Person
class Person {
protected String name;
protected int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void introduce() {
System.out.println("我叫" + name + ",今年" + age + "岁。");
}
}
// 子类:Student
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
super(name, age);
this.score = score;
}
@Override
public void introduce() {
super.introduce();
System.out.println("我是学生,成绩是" + score + "分。");
}
public void study() {
System.out.println(name + "正在学习。");
}
}
// 孙类:PrimaryStudent(小学生)
class PrimaryStudent extends Student {
private int grade; // 年级
public PrimaryStudent(String name, int age, int score, int grade) {
super(name, age, score);
this.grade = grade;
}
@Override
public void introduce() {
super.introduce();
System.out.println("我在读" + grade + "年级。");
}
public void play() {
System.out.println(name + "正在玩游戏。");
}
}
public class Main {
public static void main(String[] args) {
PrimaryStudent ps = new PrimaryStudent("小明", 9, 95, 3);
ps.introduce();
ps.study();
ps.play();
System.out.println("---------");
// 向上转型
Student s = ps;
s.introduce();
s.study();
System.out.println("---------");
// 继续向上转型
Person p = s;
p.introduce();
}
}
输出结果:
我叫小明,今年9岁。
我是学生,成绩是95分。
我在读3年级。
小明正在学习。
小明正在玩游戏。
---------
我叫小明,今年9岁。
我是学生,成绩是95分。
我在读3年级。
小明正在学习。
---------
我叫小明,今年9岁。
我是学生,成绩是95分。
我在读3年级。
这个示例展示了一个三级继承层次(Person <- Student <- PrimaryStudent),每个子类都在父类基础上扩展了新功能,并通过重写introduce()方法实现了个性化的行为。
加深理解:
这里依旧是Java多态性的体现。虽然引用类型是Person,但实际对象仍然是PrimaryStudent类型,Java会根据实际对象类型调用相应的方法实现。
- 动态绑定(Dynamic Binding):Java在运行时根据对象的实际类型来决定调用哪个方法实现,而不是根据引用类型
- 方法重写(Override):introduce()方法在Person、Student、PrimaryStudent中被重写,形成了方法重写链
总结
- 掌握继承的概念和作用
- 理解继承的特性:
- 单继承和默认继承Object类
- 父类字段和方法访问权限控制
- 子类可以重写父类方法
- 掌握
super关键字的使用,访问父类的构造方法、字段和方法 - 掌握向上转型和向下转型,并且理解转换的规则
- 了解
final可以阻止继承,sealed可以限制继承
Java面向对象基础——4.继承的更多相关文章
- 【重走Android之路】【Java面向对象基础(三)】面向对象思想
[重走Android之路][基础篇(三)][Java面向对象基础]面向对象思想 1 面向对象的WWH 1.1 What--什么是面向对象 首先,要理解“对象”.在Thinkin ...
- 【重走Android之路】【Java面向对象基础(二)】细说String、StringBuffer和StringBuilder
[重走Android之路][基础篇(二)][Java面向对象基础]细说String.StringBuffer和StringBuilder 1.String String是Java中的一个final ...
- java面向对象-基础入门
java面向对象-基础入门 面向过程:线性思维 面向对象思维:物以类聚,分类的思维 对于描述复杂的事物,为了从宏观上把握,从整体上合理分析,我们需要使用面向对象的思路来分析整个系统,但是具体到某个微观 ...
- 【重走Android之路】【Java面向对象基础(一)】数据类型与运算符
[重走Android之路][基础篇(一)][Java面向对象基础]数据类型与运算符 1.数据类型介绍 在Java中,数据类型分为两种:基本数据类型和引用类型. 基本数据类型共8种,见下表: 基本数 ...
- 086 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 03 面向对象基础总结 01 面向对象基础(类和对象)总结
086 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 03 面向对象基础总结 01 面向对象基础(类和对象)总结 本文知识点:面向对象基础(类和对象)总结 说明 ...
- 085 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 04 构造方法调用
085 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 04 构造方法调用 本文知识点:构造方法调用 说明:因为时间紧张,本人写博客过程中只是 ...
- 084 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 03 构造方法-this关键字
084 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 03 构造方法-this关键字 本文知识点:构造方法-this关键字 说明:因为时间紧 ...
- 083 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 02 构造方法-带参构造方法
083 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 02 构造方法-带参构造方法 本文知识点:构造方法-带参构造方法 说明:因为时间紧张, ...
- 082 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 01 构造方法-无参构造方法
082 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 01 构造方法-无参构造方法 本文知识点:构造方法-无参构造方法 说明:因为时间紧张, ...
- 081 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 06 new关键字
081 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 06 new关键字 本文知识点:new关键字 说明:因为时间紧张,本人写博客过程中只是 ...
随机推荐
- Vue3 开发需要安装的工具
Node 和Npm 扩展 node运行时和node包管理器 1.node.js.npm 处理TypeScript 2.tsc.tsc-node 创建vue3工程 3.create-preset 处理J ...
- Git 操作进阶
1. git基本操作 1.1 查看文件 还未被add的更改 使用git status命令,它会显示哪些文件已经被修改并添加到暂存区,以及那些还未被添加的修改. git status -uno //不显 ...
- kubernetes集群之资源配额(Resource Quotas)
一.简单介绍 资源配额(Resource Quotas)是用来限制用户资源用量的一种机制. 它的工作原理为: 资源配额应用在Namespace上,并且每个Namespace最多只能有一个Resourc ...
- ChatMoney让你变成HR高手!
本文由 ChatMoney团队出品 在快节奏的现代职场中,招聘是每一个企业都绕不开的重要环节.然而,传统的招聘流程往往繁琐而低效,从海量简历的筛选,再到后续的评估与决策,每一个环节都耗费着HR人员大量 ...
- 开源项目丨一文详解一站式大数据平台运维管家ChengYing如何部署Hadoop集群
课件获取:关注公众号"数栈研习社",后台私信 "ChengYing" 获得直播课件 视频回放:点击这里 ChengYing开源项目地址:github 丨 git ...
- Blazor学习之旅(9)用MudBlazor重构Todo
大家好,我是Edison. 在之前的学习之旅(3)开发一个Todo应用中,我们开发了一个简单版的Todo,这次我们基于MudBlazor来重构这个Todo应用. Todo V1回顾 在Blazor入门 ...
- Windows Server ServerManager.exe 应用程序错误 0xc0000135 ServerManager.exe 无法启用
将 Windows Server .NET Framework移除. IIS卸载后, Server Manager.exe.事件查看器等都无法正常开启. 解决方案: 在运行中,输入CMD,打开命令控 ...
- Sql Server日常运维!
内容来源于网络,如有侵权,请联系我删除. 一.基础命令 查看当前数据库的版本 SELECT @@VERSION; 查看服务器部分特殊信息 select SERVERPROPERTY(N'edition ...
- SQL Server 插入自增列
set identity_insert t on insert into t (id, name) values(1, 'sqlstudy') set identity_insert t off ht ...
- springBoot使用阿里云的证书
证书申请 这一步骤简单,不多赘述 证书下载 springBoot项目用到的证书是tomcat类型的,即如下 springBoot中配置证书 在application.yml配置文件增加ssl证书配置项 ...