【《On Java 8》学习之路——复用】知识点整理分享
admin
2024-03-23 07:21:22
0

文章目录

  • 复用
    • 组合语法
    • 继承语法
      • 初始化基类
      • 带参数的构造函数
    • 委托
    • 结合组合与继承
    • 组合与继承的选择
    • protected
    • 向上转型
    • final关键字
      • final 数据
      • final 参数
      • final 方法
      • final 类
    • 类初始化和加载
    • 复用总结

本文是对《On Java 8》即《Java编程思想》第五版的知识点汇总整理,仅供学习分享。

代码复用是面向对象编程(OOP)最具魅力的原因之一

任何语言都可通过简单复制来达到代码复用的目的,但是这样做的效果并不好。Java 围绕“类”(Class)来解决问题。我们可以直接使用别人构建或调试过的代码,而非创建新类、重新开始。

如何在不污染源代码的前提下使用现存代码:

【1】在新类中创建现有类的对象。这种方式叫做“组合”(Composition),通过这种方式复用代码的功能

【2】采用现有类形式,又无需在编码时改动其代码,这种方式就叫做“继承”(Inheritance),编译器会做大部分的工作。继承是面向对象编程(OOP)的重要基础之一。


复用


组合语法

  • @Override 是可选的,但它有助于验证你没有拼写错误 (或者更微妙地说,大小写字母输入错误)
  • 编译器不会为每个引用创建一个默认对象,这是有意义的,因为在许多情况下,这会导致不必要的开销
  • 初始化引用有四种方法:
    1. 当对象被定义时。这意味着它们总是在调用构造函数之前初始化。
    2. 在该类的构造函数中。
    3. 在实际使用对象之前。这通常称为延迟初始化。在对象创建开销大且不需要每次都创建对象的情况下,它可以减少开销。
    4. 使用实例初始化。
  • 如果你试图对未初始化的引用调用方法,则未初始化的引用将产生运行时异常。

继承语法

  • 在创建类时总是要继承,因为除非显式地继承其他类,否则就隐式地继承 Java 的标准根类对象(Object)

  • 在类主体的左大括号前的代码中声明这一点,使用关键字 extends 后跟基类的名称。将自动获得基类中的所有字段和方法

  • super 关键字引用了当前类继承的“超类”(基类)

初始化基类

  • 当你创建派生类的对象时,它包含基类的子对象。这个子对象与你自己创建基类的对象是一样的。只是从外部看,基类的子对象被包装在派生类的对象中。

  • 必须正确初始化基类子对象 : 通过调用基类构造函数在构造函数中执行初始化,该构造函数具有执行基类初始化所需的所有适当信息和特权。Java 自动在派生类构造函数中插入对基类构造函数的调用

  • 构造从基类“向外”进行,因此基类在派生类构造函数能够访问它之前进行初始化。即使不为派生类创建构造函数,编译器也会合成一个无参数构造函数,调用基类构造函数。

带参数的构造函数

  • 如果没有无参数的基类构造函数,或者必须调用具有参数的基类构造函数,则必须使用 super 关键字和适当的参数列表显式地编写对基类构造函数的调用
  • 对基类构造函数的调用必须是派生类构造函数中的第一个操作

委托

  • Java不直接支持的第三种重用关系称为委托。这介于继承和组合之间,因为你将一个成员对象放在正在构建的类中(比如组合),但同时又在新类中公开来自成员对象的所有方法(比如继承)

  • 示例代码如下:

方法被转发到底层 control 对象,因此接口与继承的接口是相同的。但是,你对委托有更多的控制,因为你可以选择只在成员对象中提供方法的子集。

public class SpaceShipControls {void up(int velocity) {}void down(int velocity) {}void left(int velocity) {}void right(int velocity) {}void forward(int velocity) {}void back(int velocity) {}void turboBoost() {}
}
public class SpaceShipDelegation {private String name;private SpaceShipControls controls =new SpaceShipControls();public SpaceShipDelegation(String name) {this.name = name;}// Delegated methods:public void back(int velocity) {controls.back(velocity);}public void down(int velocity) {controls.down(velocity);}public void forward(int velocity) {controls.forward(velocity);}public void left(int velocity) {controls.left(velocity);}public void right(int velocity) {controls.right(velocity);}public void turboBoost() {controls.turboBoost();}public void up(int velocity) {controls.up(velocity);}public static void main(String[] args) {SpaceShipDelegation protector =new SpaceShipDelegation("NSEA Protector");protector.forward(100);}
}

结合组合与继承

  • Java 没有 C++ 中析构函数的概念,析构函数是在对象被销毁时自动调用的方法。

  • 在Java中,通常是忘掉而不是销毁对象,从而允许垃圾收集器根据需要回收内存。

  • 你无法知道垃圾收集器何时会被调用,甚至它是否会被调用。因此,如果你想为类清理一些东西,必须显式地编写一个特殊的方法来完成它,并确保客户端程序员知道他们必须调用这个方法。

  • 必须注意基类和成员对象清理方法的调用顺序,以防一个子对象依赖于另一个子对象。

  • 如果 Java 基类的方法名多次重载,则在派生类中重新定义该方法名不会隐藏任何基类版本。不管方法是在这个级别定义的,还是在基类中定义的,重载都会起作用

  • @Override 注释防止你意外地重载。


组合与继承的选择

  • 组合和继承都允许在新类中放置子对象(组合是显式的,而继承是隐式的)

  • 当你想在新类中包含一个已有类的功能时使用组合,而非继承。在新类中嵌入一个对象(通常是私有的),以实现其功能。新类的使用者看到的是你所定义的新类的接口,而非嵌入对象的接口。

  • 成员对象隐藏了具体实现,所以这是安全的。当用户知道你正在组装一组部件时,会使得接口更加容易理解。

  • 当使用继承时,使用一个现有类并开发出它的新版本。通常这意味着使用一个通用类,并为了某个特殊需求将其特殊化


protected

  • 关键字 protected 作用:想把一个事物尽量对外界隐藏,而允许派生类的成员访问。它表示“就类的用户而言,这是 private 的。但对于任何继承它的子类或在同一包中的类,它是可访问的。”(protected 也提供了包访问权限)
  • 尽管可以创建 protected 属性,但是最好的方式是将属性声明为 private 以一直保留更改底层实现的权利。

向上转型

  • 继承最重要的方面不是为新类提供方法。它是新类与基类的一种关系。可以表述为“新类是已有类的一种类型”。
  • 因为继承保证了基类的所有方法在派生类中也是可用的,所以任意发送给该基类的消息也能发送给派生类。
  • Wind 引用转换为 Instrument 引用的行为称作向上转型:如下例所示
class Instrument {public void play() {}static void tune(Instrument i) {// ...i.play();}
}// Wind objects are instruments
// because they have the same interface:
public class Wind extends Instrument {public static void main(String[] args) {Wind flute = new Wind();Instrument.tune(flute); // Upcasting}
}
  • 因为是从一个更具体的类转化为一个更一般的类,所以向上转型永远是安全
  • 在向上转型期间,类接口只可能失去方法,不会增加方法。这就是为什么编译器在没有任何明确转型或其他特殊标记的情况下,仍然允许向上转型
  • 尽管在教授 OOP 的过程中我们多次强调继承,但这并不意味着要尽可能使用它。恰恰相反,尽量少使用它,除非确实使用继承是有帮助的。
  • 一种判断使用组合还是继承的最清晰的方法:问一问自己是否需要把新类向上转型为基类。如果必须向上转型,那么继承就是必要的

final关键字

final 数据

  • 根据上下文环境,Java 的关键字 final 的含义有些微的不同,但通常它指的是“这是不能被改变的”。

  • 许多编程语言都有某种方法告诉编译器有一块数据是恒定不变的。恒定是有用的,如:

    1. 一个永不改变的编译时常量。——编译器可以把常量带入计算中,在编译时计算,减少了一些运行时的负担
    2. 一个在运行时初始化就不会改变的值。
  • 在 Java 中,永不改变的编译时常量必须是基本类型,而且用关键字 final 修饰。你必须在定义常量的时候进行赋值。

  • 一个被 staticfinal 同时修饰的属性只会占用一段不能改变的存储空间。

  • 对于对象引用,final 使引用恒定不变。一旦引用被初始化指向了某个对象,它就不能改为指向其他对象。但是,对象本身是可以修改的

  • 按照惯例,带有恒定初始值的 final static 基本变量(即编译时常量)命名全部使用大写,单词之间用下划线分隔。

  • 空白 final 指的是没有初始化值的 final 属性

  • 编译器确保空白 final 在使用前必须被初始化。这样既能使一个类的每个对象的 final 属性值不同,也能保持它的不变性。

  • 必须在定义时或在每个构造器中执行 final 变量的赋值操作。这保证了 final 属性在使用前已经被初始化过。

final 参数

  • 在参数列表中,将参数声明为 final 意味着在方法中不能改变参数指向的对象或基本变量,只能读取而不能修改参数。这个特性主要用于传递数据给匿名内部类

final 方法

  • 使用 final 方法的原因:

    【1】第一个原因是给方法上锁,防止子类通过覆写改变方法的行为。这是出于继承的考虑,确保方法的行为不会因继承而改变。【2】第二个原因是效率。在早期的 Java 实现中,如果将一个方法指明为 final,就是同意编译器把对该方法的调用转化为内嵌调用。当编译器遇到 final 方法的调用时,就会很小心地跳过普通的插入代码以执行方法的调用机制,而用方法体内实际代码的副本替代方法调用。这消除了方法调用的开销。但是如果一个方法很大代码膨胀,你也许就看不到内嵌带来的性能提升,因为内嵌调用带来的性能提高被花费在方法里的时间抵消了。

  • 有很长一段时间,使用 final 来提高效率都被阻止。你应该让编译器和 JVM 处理性能问题,只有在为了明确禁止覆写方法时才使用 final

  • 类中所有的 private 方法都隐式地指定为 final。因为不能访问 private 方法,所以不能覆写它。

  • 如果一个方法是 private 的,它就不是基类接口的一部分。它只是隐藏在类内部的代码,且恰好有相同的命名而已。但是如果你在派生类中以相同的命名创建了 publicprotected 或包访问权限的方法,这些方法与基类中的方法没有联系,你没有覆写方法,只是在创建新的方法而已。

final 类

  • 当说一个类是 finalfinal 关键字在类定义之前),就意味着它不能被继承。因为类的设计永远不需要改动,或者是出于安全考虑不希望它有子类。

  • 由于 final 类禁止继承,类中所有的方法都被隐式地指定为 final,所以没有办法覆写它们


类初始化和加载

  • 每个类的编译代码都存在于它自己独立的文件中。该文件只有在使用程序代码时才会被加载
  • 一般可以说“类的代码在首次使用时加载“。这通常是指创建类的第一个对象,或者是访问了类的 static 属性或方法
  • 构造器也是一个 static 方法尽管它的 static 关键字是隐式的。
  • 一个类当它任意一个 static 成员被访问时,就会被加载。
  • 所有的 static 对象和 static 代码块在加载时按照文本的顺序(在类中定义的顺序)依次初始化。
  • static 变量只被初始化一次。

复用总结

  • 继承和组合都是从已有类型创建新类型。组合将已有类型作为新类型底层实现的一部分,继承复用的是接口(如果一个方法是 private 的,它就不是基类接口的一部分。****)
  • 使用继承时,派生类具有基类接口,因此可以向上转型为基类,这对于多态至关重要
  • 在开始设计时,优先使用组合(或委托),只有当确实需要时再使用继承。
  • 通过对成员类型使用继承的技巧,可以在运行时改变成员的类型和行为。因此,可以在运行时改变组合对象的行为
  • 每个类有特定的用途,而且既不应太大(包括太多功能难以复用),也不应太小(不添加其他功能就无法使用)
  • 如果设计变得过于复杂,通过将现有类拆分为更小的部分而添加更多的对象,通常是有帮助的。

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...