面向对象 05:三大特性之——继承,继承在 Java 中的相关使用,区分关键字 super 和 this,方法重写的注意点

一、前言

记录时间 [2024-05-13]

系列文章简摘:
面向对象 01:Java 面向对象相关内容整体概述
面向对象 02:区分面向过程与面向对象,类和对象的关系
面向对象 03:类与对象的创建、初始化和使用,通过 new 关键字调用构造方法,以及创建对象过程的内存分析
面向对象 04:三大特性之——封装,封装的含义和作用,以及在 Java 中的使用方式,附完整的测试案例代码

更多 Java 相关文章,请参考专栏哦。

本文讲述面向对象编程的三大特性之——继承。通过案例分析,讲述了继承的概念,及其在 Java 中的相关使用。此外,文章详细介绍了关键字 super 和 this 的区别,还有方法重写的注意点。


面向对象编程(Object-Oriented Programming, OOP)的三大特性是封装、继承和多态,这三大特性是 OOP 的基础,为设计灵活、可维护和可扩展的软件系统提供了核心机制。

这三个特性共同构成了面向对象编程的基础,使开发者能够设计出高内聚、低耦合的软件系统,促进了软件工程的高效管理和复杂问题的有效解决。

  • 高内聚:一个模块内部各个组成部分(如类的方法、函数、变量等)之间应该紧密关联,共同完成一个具体且单一的功能,不允许外部干涉。
  • 低耦合:各模块之间的依赖关系应该尽可能地减少和简化,一个模块应尽量少地依赖其他模块,模块之间的接口应清晰、简单,且只暴露必要的功能给外部使用。

二、什么是继承

1. 概述

继承是面向对象编程中的一个基本概念,它允许创建一个新类(称为子类、派生类或扩展类)来继承现有类(称为父类、基类或超类)的特性和行为。例如,学生属于人类,老师也属于人类,那么学生和老师作为子类,都继承了人类这个父类。

继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。

在 Java 语言中,只允许一个类直接继承自另一个类,这是单继承

子类继承父类,使用 extends 关键字。即,子类是父类的扩展。

继承应当用来表达类与类之间的 is-a 关系,即,子类是父类的一种特殊类型。

除继承外,类与类之间的关系还有依赖、组合、聚合等等。

继承的主要目的是促进代码的复用性和模块化设计,从而减少代码重复并提高程序的可维护性。设计时应当谨慎使用继承,避免滥用导致体系结构过于复杂,提倡使用组合和接口作为替代方案,以保持设计的灵活性和可维护性。


2. 基本特征

继承的基本特征主要有如下这些:

  • 属性和方法的继承:子类自动获得父类中所有非私有publicprotected)的属性和方法。这意味着子类可以使用或重写这些特性而无需重新实现它们。
  • 代码重用:通过继承,子类可以重用父类的代码,无需从头开始编写相同的功能,这样可以减少开发时间和潜在的错误。
  • 层次结构:继承有助于建立类的层次结构,使得类之间的关系更加清晰,反映出现实世界中的对象分类。
  • 多态性:继承是实现多态性的基础,即子类对象可以被视为父类对象使用,这样可以在不修改现有代码的情况下引入新的子类对象。
  • 类型兼容性:在大多数面向对象的编程语言中,子类对象被视为是其父类类型的对象,这称为向上转型 upcasting
  • 构造函数与初始化:尽管子类继承父类的属性和方法,但构造函数不会被继承。子类构造函数可以通过 super 关键字(或相关语言的等价物)显式或隐式地调用父类的构造函数来初始化父类的成员。
  • 访问权限:继承还涉及到访问权限控制,比如 Java 中的 publicprotectedprivate 修饰符,决定了哪些成员在继承体系中是可见和可访问的。

3. 案例分析

在 Java 中,子类继承父类,使用 extends 关键字。

下面使用动物类、狮子、长颈鹿,这三个类的关系,模拟 Java 中的继承。

动物类作为父类 / 基类;狮子、长颈鹿作为两个子类,都继承动物类:

// Animal:父类
public class Animal {}// Lion:子类 / 派生类
public class Lion extends Animal {}// Giraffe:子类 / 派生类
public class Giraffe extends Animal {}

三、如何使用继承

1. 子类继承父类

子类自动获得父类中所有非私有publicprotected)的属性和方法。

例如,在 Animal 类中编写一个方法 shout(),其子类 LionGiraffe 也可以使用这个方法。

Animal 类中编写一个属性 category,其子类 LionGiraffe 也可以使用属性。

// Animal:父类
public class Animal {// 父类中的属性public String category = "animal";// 父类中的方法public void shout() {System.out.println("the animal is shouting");}}

使用方式:

public class Application {public static void main(String[] args) {Lion lion = new Lion();// 子类继承父类的方法lion.shout();// 子类继承父类的属性System.out.println(lion.category);}
}

2. 封装私有属性

但是,当父类中的属性是私有 private 时,子类就无法使用了。方法同理。

私有的东西无法被继承。

例如,当 Animal 类中的属性 categoryprivate 时,其子类 LionGiraffe 便无法使用。

解决方式:

根据封装思想,我们可以为属性 category 预留一些可操作的方法,比如 GetterSetter 方法。

private String category = "animal";public String getCategory() {return category;
}public void setCategory(String category) {this.category = category;
}

3. Object 类

在面向对象编程中,继承树描述了一个或多个类之间的继承关系,形象地展示了类的层次结构。每个节点代表一个类,根节点通常是抽象类或者基类,没有父类,而叶子节点是具体类,不再有子类继承。

IDEA 中,通过快捷键 Ctrl+H 可以快速查看继承树。

在 Java 中,所有类都默认直接或间接地继承自 Object 类。

Object 类是 Java 类层次结构的根类,它位于 java.lang 包中。如果在定义一个类时没有明确指定其父类,那么这个类将自动继承 Object 类。

Object 类提供了许多通用的方法,这些方法在很多类中都会用到,例如:

  • toString():返回该对象的字符串表示。
  • equals(Object obj):比较两个对象是否相等。
  • hashCode():返回该对象的哈希码值。
  • getClass():返回此 Object 的运行时类。
  • clone():创建并返回此对象的一个副本。
  • notify(), notifyAll(), wait():用于线程间的通信。
  • finalize():在垃圾回收器确定不再有对该对象的引用之前,由对象的垃圾回收器调用。

由于所有类都是 Object 的子类,因此任何对象都可以调用 Object 类中的这些方法。如果需要,子类可以重写这些方法以提供更具体的实现。


四、关键字 super

1. 概述

在 Java 中,super 是一个关键字,用于引用父类(基类)的成员变量、方法或构造函数。

主要用于以下几个场景:

  • 访问父类的成员变量: 当子类中定义了与父类同名的成员变量(即变量重写或遮蔽),想要在子类中访问父类的该变量时,可以使用 super.变量名 的形式。
  • 调用父类的方法: 如果子类重写了父类的方法,但又想在子类方法内部调用父类中被重写的方法,可以使用 super.方法名(参数列表)
  • 调用父类的构造函数: 在子类的构造函数中,通过 super(参数列表) 形式调用父类的特定构造函数。这必须是子类构造函数中的第一个语句,并且只能出现一次。这样做是为了确保父类的成员被正确初始化。

2. 注意事项

正确使用 super 可以帮助实现代码的清晰性和逻辑的正确性,特别是在复用和扩展父类功能的场景下。

  • 使用 super基于继承关系的,只有在子类中才有意义。
  • super 不仅可以访问直接父类的成员,理论上可以通过多层继承链,间接访问到更上层祖先类的成员,只要这些成员在访问权限允许的范围内。
  • superthis 有相似之处,但 this 用于引用当前对象或当前类的成员,而 super 则用于引用父类的成员。
  • 当没有明确指定调用父类构造函数时,编译器会自动插入一个对父类无参构造函数的调用。如果父类没有无参构造函数,子类构造函数必须显式调用带有适当参数的父类构造函数。

3. 案例分析

在子类中访问父类变量

在子类中访问父类变量,使用 super.变量名 的形式;访问子类内部变量,使用 this.变量名 的形式。

接下来使用上面的 Animal 类,及其子类 Lion 编写测试案例。

Step 1:在父类 Animal 中新增 protected 属性 name

protected String name = "Animal";

Step 2:在子类 Lion 中新增 private 属性 name,以及测试方法 testName(String name)

// Lion:子类 / 派生类
public class Lion extends Animal {private String name = "Lion";public void testName(String name) {System.out.println(name);System.out.println(this.name);System.out.println(super.name);}}

Step 3:在测试类中进行测试

Lion lion = new Lion();
lion.testName("test");

Step 4:得到结果

# test 是测试类传入方法的局部变量
test# Lion 是 this 关键字指向的当前类中的变量
Lion# Animal 是 super 关键字指向的父类中的变量
Animal

在子类中访问父类方法

在子类中访问父类方法,使用 super.方法名(参数列表) 形式;访问子类内部方法,使用 this.方法名(参数列表) 的形式。

接下来使用上面的 Animal 类,及其子类 Giraffe 编写测试案例。

Step 1:在父类 Animal 中新增方法 print()

public void print() {System.out.println("父类打印内容:Animal");
}

Step 2:在子类 Giraffe 中同样新增方法 print(),以及测试方法 testPrint()

// Giraffe:子类 / 派生类
public class Giraffe extends Animal {public void print() {System.out.println("子类打印内容:Giraffe");}public void testPrint() {print();this.print();super.print();}}

Step 3:在测试类中进行测试

Giraffe giraffe = new Giraffe();
giraffe.testPrint();

Step 4:得到结果如下,进一步说明了使用好关键字,可以很好地避免混淆和歧义。

# print(); 语句打印的内容,调用了子类的方法
子类打印内容:Giraffe# this.print(); 语句打印的内容,调用了子类的方法
子类打印内容:Giraffe# super.print(); 语句打印的内容,调用了父类的方法
父类打印内容:Animal

子类调用父类构造

在子类的构造函数中,通过 super() 形式调用父类的无参构造函数;通过 super(参数列表) 形式调用父类的有参构造函数。

  • 当没有明确指定调用父类构造函数时,编译器会自动插入一个对父类无参构造函数的调用。
  • 如果父类没有无参构造函数,子类构造函数必须显式调用带有适当参数的父类构造函数。

调用父类构造函数,必须是子类构造函数中的第一个语句,并且只能出现一次。

接下来通过案例进行分析:

Step 1:给父类 Animal 显式地定义一个无参构造函数

public Animal() {System.out.println("Animal 无参构造执行了");
}

Step 2:新建子类 Dog,继承父类 Animal,在 Dog 中显式地定义一个无参构造函数

public class Dog extends Animal{public Dog() {System.out.println("Dog 无参构造执行了");}
}

Step 3:在测试类中实例化 Dog 对象,并进行测试

Dog dog = new Dog();

Step 4:得到结果,发现子类和父类的无参构造方法都执行了

# 得到结果
# 发现子类和父类的无参构造方法都执行了
Animal 无参构造执行了
Dog 无参构造执行了

说明子类实例化时,会默认调用父类的构造方法。

相当于在子类构造方法中,隐藏了代码 super();

public Dog() {// 隐藏代码,调用了父类的无参构造super();    // 调用父类构造器,必须在子类构造器的第一行System.out.println("Dog 无参构造执行了");
}

4. super 和 this

下面是有关 super 的注意点,以及 superthis 的区别。

super 注意点:1. super 调用父类的构造方法,必须在构造方法的第一个2. super 必须只能出现在子类的方法或者构造方法中3. super 和 this 不能同时调用构造方法VS this:代表的对象不同this: 本身调用者这个对象super: 代表父类对象的应用前提this: 没有继承也可以使用super: 只能在继承条件才可以使用构造方法this(): 本类的构造super(): 父类的构造

五、方法重写

1. 概述

方法重写(Override)是面向对象编程中的一个重要概念。它允许子类提供一个与其父类具有相同名称、返回类型和参数列表的方法实现。当子类的对象调用这个方法时,将执行子类中重写后的方法,而不是父类中的原始版本。这为多态性提供了基础,使得子类能够表现出与父类不同的行为。

方法重写应用场景:接口、抽象类等。

要进行方法重写,需要遵循以下规则:

  • 方法名、返回类型和参数列表必须与父类方法完全匹配
  • 访问权限不能比父类中被重写的方法更严格
    • 如果父类中的方法是 public 的,子类中的重写方法不能是 privateprotected
    • 子类可以增加访问权限,比如将父类的 protected 方法改为 public
  • 静态方法不能被重写
    • 静态方法属于类,而不是实例;
    • 即使子类定义了一个与父类同名的静态方法,这也被认为是两个独立的方法,而不是重写。
  • 最终方法(final methods)不能被重写
    • 父类中被声明为 final 的方法表示其设计为不允许子类改变其行为。
  • @Override 注解:虽然不是强制性的,但在方法声明前添加 @Override 注解是一个好习惯。编译器会检查是否正确地重写了父类的方法,如果方法签名不匹配,编译时会报错。

2. 静态方法不能重写

在 Java 中,静态方法不能被重写的原因主要在于静态方法是属于类本身的,而非实例,因此不参与面向对象的多态行为。

即使子类定义了一个与父类同名的静态方法,这也被认为是两个独立的方法,而不是重写。

下面通过一个具体的例子来说明这一点:

假设有一个父类 Parent,其中包含一个静态方法 printMessage(),以及一个子类 Child,也在尝试定义一个同名的静态方法 printMessage()

编写父类 Parent

class Parent {public static void printMessage() {System.out.println("这是父类的静态方法");}
}

编写子类 Child

class Child extends Parent {public static void printMessage() {System.out.println("这是子类的静态方法");}
}

编写测试类

  • 静态方法可以直接通过类名调用
  • 通过对象实例调用,尽管不推荐,但也是可行的
public class Main {public static void main(String[] args) {// 直接通过类名调用Parent.printMessage(); 		// 输出 "这是父类的静态方法"Child.printMessage(); 		// 输出 "这是子类的静态方法"// 通过对象实例调用,尽管不推荐,但也是可行的// 通过父类引用指向子类实例Parent parentInstance = new Child();parentInstance.printMessage(); 		// 依然输出 "这是父类的静态方法",说明不是重写行为}
}

从上面的例子可以看出,尽管 Child 类定义了自己的 printMessage() 静态方法,但这并不构成重写,因为当通过父类引用指向子类实例并调用静态方法时,打印的是父类的静态方法内容

这表明静态方法的调用是基于其所属类的类型,而非实例的运行时类型,证明了静态方法的调用是在编译时期就已经确定的,即静态绑定

当直接通过子类引用调用时,会调用子类的静态方法,但如果希望通过父类引用访问到子类的静态方法,这是不可能的,这进一步说明静态方法不支持多态性


3. 常规方法重写

子类重写父类的方法,一定是非静态的。

假设有一个父类 Mother,其中包含一个非静态方法 printMessage(),以及一个子类 Kid,也在尝试定义一个同名的非静态方法 printMessage()

当通过父类引用指向子类实例并调用方法时,打印的是子类的方法内容,说明构成了方法重写。

class Mother {public void printMessage() {System.out.println("这是父类的静态方法");}
}class Kid extends Mother {// 方法快速重写:右键 Generate ==> Override Methods@Override	// 注解,有功能的注释public void printMessage() {System.out.println("这是子类的静态方法");}
}public class OverrideNotStatic {public static void main(String[] args) {// 通过对象实例调用,通过父类引用指向子类实例Mother motherInstance = new Kid();// 输出 "这是子类的静态方法",子类重写了父类的方法motherInstance.printMessage();}
}

4. 方法重写注意点

下面是有关方法重写注意点:

重写: 需要有继承关系,子类重写父类的方法1. 方法名必须相同2. 参数列表列表必须相同3. 修饰符: 范围可以扩大但不能缩小     public > protected > default > private4. 抛出的异常: 范围可以被缩小,但不能扩大     Exception(大) --> ClassNotFoundException(小)重写,子类的方法和父类必要一致,方法体不同。为什么需要重写:1.父类的功能,子类不一定需要,或者不一定满足

六、总结

本文讲述面向对象编程的三大特性之——继承。通过案例分析,讲述了继承的概念,及其在 Java 中的相关使用。此外,文章详细介绍了关键字 super 和 this 的区别,还有方法重写的注意点


一些参考资料

狂神说 Java 零基础:https://www.bilibili.com/video/BV12J41137hu/
TIOBE 编程语言走势: https://www.tiobe.com/tiobe-index/
Typora 官网:https://www.typoraio.cn/
Oracle 官网:https://www.oracle.com/
Notepad++ 下载地址:https://notepad-plus.en.softonic.com/
IDEA 官网:https://www.jetbrains.com.cn/idea/
Java 开发手册:https://developer.aliyun.com/ebook/394
Java 8 帮助文档:https://docs.oracle.com/javase/8/docs/api/

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数字人解决方案——AniPortrait音频驱动的真实肖像动画合成

概述 在当今数字化时代,将静态图像和音频素材转化为动态、富有表现力的肖像动画,已经成为游戏、数字媒体、虚拟现实等多个领域的重要技术。然而,开发人员在创建既具有视觉吸引力又能保持时间一致性的高质量动画框架方面面临着巨大挑战。其中…

k8s endpoint

Endpoint Service 并不是和 pod 直接相连的,Endpoint 介于两者之间。Endpoint 资源就是暴露一个服务的 IP 地址和端口的列表。 虽然在 spec 服务中定义了 pod 选择器,但在重定向传入连接时不会直接使用它。选择器用于构建 IP 和端口列表,然…

数据特征降维 | t-分布随机邻域嵌入(t-SNE)附Python代码

t-分布随机邻域嵌入(t-Distributed Stochastic Neighbor Embedding,t-SNE)是一种非线性降维和可视化技术,广泛用于高维数据的可视化和聚类分析。 t-SNE的基本思想是通过在高维空间中测量样本之间的相似性,将其映射到低维空间中,以便更好地展示数据的结构和关系。与传统的…

优思学院:精益六西格玛如何影响企业文化?

精益六西格玛(Lean Six Sigma)是一种在优化生产过程、提高效率、减少浪费的管理方法论。其影响远不止于生产线或质量控制部门,实际上,精益六西格玛的实施可以深刻影响企业文化的各个层面,從而令企業獲得真正最大的成功…

libxml2解析xml文档,c语言实现,并给出案例

test.xml文档内容如下&#xff1a; <root><name>Debug.group</name><website>https://debug.group</website><function><json>https://debug.group/json.html</json><sql>https://debug.group/sql.html</sql><…

SpringBoot MockMvc

SpringBoot MockMvc 1 什么是 MockMvc&#xff1f;2 为什么使用 MockMvc&#xff1f;3 如何使用 MockMvc&#xff1f;3.1 Controller3.2 测试方法一3.3 测试方法二 1 什么是 MockMvc&#xff1f; Spring Boot 提供了一个方便的测试工具类 MockMvc&#xff0c;用于对 Controlle…

【JavaScript超详细的学习笔记-下】JavaScrip超详细的学习笔记,共27部分,12多万字,学习js不错的选择,内容超详细

想要获取笔记的可以点击下面链接获取&#xff0c;或者私信我 ** JavaScript超详细的学习笔记&#xff0c;点击我获取 13&#xff0c;闭包 13-1 闭包 闭包是指有权访问另一个函数作用域中的变量的函数&#xff1b;其本质就是在一个函数内部创建另一个内部函数&#xff1b;并…

基于若依的开源网盘系统

简介 在线网盘系统&#xff0c;使用了DFA算法&#xff0c;实现了文件夹的创建与修改&#xff0c;多级目录&#xff0c;很正常的文件夹一样&#xff0c;支持所有文件上传&#xff0c;并按文件类型分类&#xff0c;支持文件删除&#xff0c;回收站管理&#xff0c;恢复与彻底删除…

数据特征降维 | 局部线性嵌入(LLE)

局部线性嵌入(Locally Linear Embedding,LLE)是一种非线性降维和数据嵌入技术,用于将高维数据映射到低维空间中,以便更好地展示数据的结构和关系。 LLE的基本思想是假设数据样本在局部区域内可以近似由其相邻样本的线性组合表示。通过保持这种局部线性关系,LLE能够在低维…

掌握QPainter:Qt中的绘图艺术

目录标题 1. QPainter概述2. 创建QPainter对象3. 绘制基本图形4. 绘制文本5. 绘制图像6. 使用画笔&#xff08;QPen&#xff09;7. 使用画刷&#xff08;QBrush&#xff09;8. 图形变换9. 抗锯齿与优化10. 实例代码与解析11. 总结 在Qt的世界里&#xff0c;QPainter是一位多才多…

设计模式-11 - Adapter Pattern 适配器设计模式

设计模式-11 - Adapter Pattern 适配器设计模式 1.定义 适配器模式是一种结构型设计模式&#xff0c;它允许具有不同接口的两个类一起工作。它通过创建一个适配器类来实现这一点&#xff0c;该适配器类将一个类的接口转换为另一个类所需的接口。 2.内涵 适配器设计模式的内涵…

低空经济:无人机竞赛详解

无人机竞赛市场近年来呈现出蓬勃发展的态势&#xff0c;其市场价值不仅体现在竞赛本身&#xff0c;还体现在推动无人机技术创新、拓展应用场景以及促进产业链发展等多个方面。 一、比赛项目介绍 无人机竞赛通常分为多个项目&#xff0c;包括竞速赛、技巧赛、航拍赛等。每个项目…

Ubuntu下C++编程总结AllInOne

之前主要是在win下用VisualStudio&#xff0c;现在研究总结在ubuntu下使用CMake编程经验&#xff0c;填坑记录 目录 0. 前述1.Ubuntu下多线程使用 0. 前述 1.Ubuntu下多线程使用 出现的问题&#xff1a;undefined reference to symbol pthread_create 使用多线程&#xff0c…

HR人才测评:自控能力与岗位胜任力素质测评

自控能力是什么&#xff1f; 自控能力可以解释为自我控制的能力&#xff0c;指一个人在应对人事物突发事件时&#xff0c;及时调整进行的自我控制的表现&#xff0c;它是实行自我支配的一种能力&#xff0c;在能进行自主支配时&#xff0c;一个人就成熟不少了&#xff0c;也可以…

js的跳转传参方式

在JavaScript中&#xff0c;实现页面跳转并传递参数的方式有多种&#xff0c;以下是一些常见的方法&#xff1a; URL参数 你可以通过修改URL的查询字符串&#xff08;query string&#xff09;来传递参数。这些参数会在URL的?之后&#xff0c;并使用&进行分隔。 代码 /…

优选算法——双指针1

双指针 常⻅的双指针有两种形式&#xff0c;⼀种是对撞指针&#xff0c;⼀种是左右指针。 对撞指针&#xff1a;⼀般⽤于顺序结构中&#xff0c;也称左右指针。 对撞指针从两端向中间移动。⼀个指针从最左端开始&#xff0c;另⼀个从最右端开始&#xff0c;然后逐渐往中间逼 近…

std::mem_fn和std::mem_fun

std::mem_fn和std::mem_fun&#xff08;deprecated&#xff09; Convert member function to function object&#xff0c;即将成员函数转化为函数对象&#xff0c;这样就可以像普通函数那样使用。 示例一&#xff1a; #include <iostream> // std::cout #include …

MySQL-InnoDB数据存储结构

1、存储结构-页 索引结构提供了高效的索引方式&#xff0c;索引信息以及数据记录都保存在数据文件或索引文件中&#xff08;本质存储在页结构中&#xff09; 1.1、磁盘与内存交互的基本单位&#xff1a;页 在InnoDB中将数据划分为若干页&#xff0c;页的默认大小为&#xff…

webpack并行构建示例:

由于js的单线程特性&#xff0c;文件和任务时 要等待一个任务执行完成后执行下一个任务&#xff0c;但在实际开发中&#xff0c;很多任务是可以并行执行的&#xff08;如同时处理多个不同js文件或同事压缩多张图片&#xff09;&#xff0c;一些loader和插件&#xff08;thread-…

【数据结构】解密链表之旅(单链表篇)

前言 哈喽大家好&#xff0c;我是野生的编程萌新&#xff0c;首先感谢大家的观看。数据结构的学习者大多有这样的想法&#xff1a;数据结构很重要&#xff0c;一定要学好&#xff0c;但数据结构比较抽象&#xff0c;有些算法理解起来很困难&#xff0c;学的很累。我想让大家知道…