Effective Java笔记(20)接口优于抽象类
Java提供了两种机制,可以用来定义允许多个实现的类型:接口和抽象类。自从Java 8为继承引入了缺省方法( default method),这两种机制都允许为某些实例方法提供实现。主要的区别在于,为了实现由抽象类定义的类型,类必须成为抽象类的一个子类。因为Java只允许单继承,所以用抽象类作为类型定义受到了限制。任何定义了所有必要的方法并遵守通用约定的类,都允许实现一个接口,无论这个类是处在类层次结构中的什么位置。
现有的类可以很容易被更新,以实现新的接口。如果这些方法尚不存在,你所需要做的就只是增加必要的方法,然后在类的声明中增加一个implements子句。例如,当Comparable、Iterable 和Autocloseable接口被引入Java平台时,更新了许多现有的类,以实现这些接口。一般来说,无法更新现有的类来扩展新的抽象类。如果你希望两个类扩展同一个抽象类,就必须把抽象类放到类型层次( type hierarchy) 的高处,这样它就成了那两个类的一个祖先。遗憾的是,这样做会间接地伤害到类层次,迫使这个公共祖先的所有后代类都扩展这个新的抽象类,无论它对于这些后代类是否合适。
接口是定义mixin (混合类型)的理想选择。不严格地讲,mixin 类型是指:类除了实现它的“基本类型”之外,还可以实现这个mixin类型,以表明它提供了某些可供选择的行为。例如Comparable是一个mixin接口,它允许类表明它的实例可以与其他的可相互比较的对象进行排序。这样的接口之所以被称为mixin,是因为它允许任选的功能可被混合到类型的主要功能中。抽象类不能被用于定义mixin,同样也是因为它们不能被更新到现有的类中:类不可能有一个以上的父类,类层次结构中也没有适当的地方来插入mixin。
接口允许构造非层次结构的类型框架。类型层次对于组织某些事物是非常合适的,但是其他事物并不能被整齐地组织成一个严格的层次结构。例如,假设我们有一个接口代表一个singer (歌唱家),另一个接口代表一个songwriter (作曲家):
public interface Singer {AudioClip sing(Song s);
}
public interface Songwriter {Song compose(int chartPosition);
}
在现实生活中,有些歌唱家本身也是作曲家。因为我们使用了接口而不是抽象类来定义这些类型,所以对于单个类而言,它同时实现Singer和Songwriter是完全允许的。实际上,我们可以定义第三个接口,它同时扩展Singer和Songwriter,并添加一些适合于这种组合的新方法:
public interface SingerSongwriter extends Singer, Songwriter {AudioClip strum();void actSensitive();
}
也许并非总是需要这种灵活性,但是一旦这样做了,接口可就成了救世主。另外一种做法是编写一个臃肿( bloated)的类层次,对于每一-种要被支持的属性组合,都包含一个单独的类。如果在整个类型系统中有n个属性,那么就必须支持2”种可能的组合。这种现象被称为“组合爆炸”(combinatorialexplosion)。类层次臃肿会导致类也臃肿,这些类包含许多方法,并且这些方法只是在参数的类型上有所不同而已,因为类层次中没有任何类型体现了公共的行为特征。
通过第18条中介绍的包装类(wrapperclass)模式,接口使得安全地增强类的功能成为可能。如果使用抽象类来定义类型,那么程序员除了使用继承的手段来增加功能,再没有其他的选择了。这样得到的类与包装类相比,功能更差,也更加脆弱。
当一个接口方法根据其他接口方法有了明显的实现时,可以考虑以缺省方法的形式为程序员提供实现协助。通过缺省方法可以提供的实现协助是有限的。虽然许多接口都定义了object方法的行为,如equals和hashCode,但是不允许给它们提供缺省方法。而且接口中不允许包含实例域或者非公有的静态成员(私有的静态方法除外)。最后一点,无法给不受你控制的接口添加缺省方法。
但是,通过对接口提供一个抽象的骨架实现( skeletal implementation)类,可以把接口和抽象类的优点结合起来。接口负责定义类型,或许还提供一些缺省方法,而骨架实现类则负责实现除基本类型接口方法之外,剩下的非基本类型接口方法。扩展骨架实现占了实现接口之外的大部分工作。这就是模板方法。
按照惯例,骨架实现类被称为AbstractInterface,这里的Interface是指所实现的接口的名字。例如,Collections Framework为每个重要的集合接口都提供了一个骨架实现,包括AbstractCollection、AbstractSet、AbstractList和AbstractMap。将它们称作SkeletalCollection、SkeletalSet、 SkeletalList 和SkeletalMap也是有道理的,但是现在Abstract的用法已经根深蒂固。如果设计得当,骨架实现(无论是单独一个抽象类,还是接口中唯-包含的缺省方法)可以使程序员非常容易地提供他们自己的接口实现。例如,下面是一个静态工厂方法,除AbstractList之外,它还包含了一个完整的、功能全面的List实现:
static List<Integer> intArrayAsList(int[] a) {Objects. requireNonNull(a);return new AbstractList<>() {@Override public Integer get(int i) {return a[i];}@Override public Integer set(int i, Integer val) {int oldVal = a[i];a[i] = val;return oldVal;}@Override public int size() {return a.length;}};
}
如果想知道一个List实现应该为你完成哪些工作,这个例子就充分演示了骨架实现的强大功能。顺便提一下,这个例子是个Adapter,它允许将int数组看作Integer实例的列表。由于在int值和Integer实例之间来回转换需要开销,它的性能不会很好。注意,这个实现采用了匿名类(anonymous class)的形式。
骨架实现类的美妙之处在于,它们为抽象类提供了实现上的帮助,但又不强加“抽象.类被用作类型定义时”所特有的严格限制。对于接口的大多数实现来讲,扩展骨架实现类是个很显然的选择,但并不是必须的。如果预置的类无法扩展骨架实现类,这个类始终能手工实现这个接口。同时,这个类本身仍然受益于接口中出现的任何缺省方法。此外,骨架实现类仍然有助于接口的实现。实现了这个接口的类可以把对于接口方法的调用转发到一个内部私有类的实例上,这个内部私有类扩展了骨架实现类。这种方法被称作模拟多重继承( simulated multiple inheritance),它与第18条中讨论过的包装类模式密切相关。这项技术具.有多重继承的绝大多数优点,同时又避免了相应的缺陷。
编写骨架实现类相对比较简单,只是过程有点乏味。首先,必须认真研究接口,并确定哪些方法是最为基本的,其他的方法则可以根据它们来实现。这些基本方法将成为骨架实现类中的抽象方法。接下来,在接口中为所有可以在基本方法之.上直接实现的方法提供缺省方法,但要记住,不能为object方法(如equals和hashCode)提供缺省方法。如果基本方法和缺省方法覆盖了接口,你的任务就完成了,不需要骨架实现类了。否则,就要编写一个类,声明实现接口,并实现所有剩下的接口方法。这个类中可以包含任何非公有的域,以及适合该任务的任何方法。
以Map.Entry接口为例,举个简单的例子。明显的基本方法是getKey、getValue和(可选的) setValue。接口定义了equals和hashCode的行为,并且有一个明显的toString实现。由于不允许给object方法提供缺省实现,因此所有实现都放在骨架实现类中:
public abstract class AbstractMapEntry<K, V>implements Map.Entry<K,V> {@Overridepublic V setValue(V value) {throw new UnsupportedOperationException();}@Overridepublic boolean equals(Object o) {if (o == this)return true;if (!(o instanceof Map.Entry))return false;Map.Entry<?,?> e = (Map.Entry) o;return Objects.equals(e.getKey(), getKey())&& Objects.equals(e.getValue(), getValue());}@Overridepublic int hashCode() {return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());}@Overridepublic String toString() {return getKey() + "=" + getValue();}
}
注意,这个骨架实现不能在Map.Entry接口中实现,也不能作为子接口,因为不允许缺省方法覆盖object方法,如equals、hashCode和toString。因为骨架实现类是为了继承的目的而设计的,所以应该遵从第19条中介绍的所有关于设计和文档的指导原则。为了简洁起见,上面例子中的文档注释部分被省略掉了,但是对于骨架实现类而言,好的文档绝对是非常必要的,无论它是否在接口或者单独的抽象类中包含了缺省方法。
骨架实现上有个小小的不同,就是简单实现( simple implementation), AbstractMap.SimpleEntry就是个例子。简单实现就像骨架实现一样,这是因为它实现了接口,并且是为了继承而设计的,但是区别在于它不是抽象的:它是最简单的可能的有效实现。你可以原封不动地使用,也可以看情况将它子类化。
总而言之,接口通常是定义允许多个实现的类型的最佳途径。如果你导出了一个重要的接口,就应该坚决考虑同时提供骨架实现类。而且,还应该尽可能地通过缺省方法在接口中提供骨架实现,以便接口的所有实现类都能使用。也就是说,对于接口的限制,通常也限制了骨架实现会采用的抽象类的形式。
相关文章:
Effective Java笔记(20)接口优于抽象类
Java提供了两种机制,可以用来定义允许多个实现的类型:接口和抽象类。自从Java 8为继承引入了缺省方法( default method),这两种机制都允许为某些实例方法提供实现。主要的区别在于,为了实现由抽象类定义的类型,类必须成…...
react学习笔记——1. hello react
包含的包一共有4个,分别的作用如下: babel.min.js:可以进行ES6到ES5的语法转换;可以用于import;可以用于将jsx转换为js。注意,在开发的时候,这个转换(jsx转换js)不在线上…...
明明已经安装字体,但IDEA、CLION无法找到思源黑体/Source Hans Sans的问题解决
IDEA、CLION的Jetbrain系列软件不支持非TrueType的中文字体,而Adobe官方给出的字体却不是TrueType的,所以便会导致Jetbrain系软件无法找到已安装的中文字体,因此我们需要安装TrueType的字体 请在以下Github链接中下载: TrueType思…...
2023-08-03力扣今日四题
链接: 剑指 Offer 67. 把字符串转换成整数 题意: 按规则将字符串转换成整数,规则不详叙 解: 字符串处理 实际代码: #include<iostream> #include<cstring> #include<climits> using namespac…...
【学会动态规划】最佳买卖股票时机含冷冻期(15)
目录 动态规划怎么学? 1. 题目解析 2. 算法原理 1. 状态表示 2. 状态转移方程 3. 初始化 4. 填表顺序 5. 返回值 3. 代码编写 写在最后: 动态规划怎么学? 学习一个算法没有捷径,更何况是学习动态规划, 跟我…...
随机RSI震荡指标公式(StochRSI),RSI和KDJ二合一
随机RSI震荡指标(StochRSI)是由图莎尔钱德和斯坦利克罗发明的一种摆动指标,结合了相对强弱指标(RSI)和随机指标(KDJ)的原理,目的是提高灵敏度,解决RSI难以达到超买超卖区的问题,以便…...
轻松搭建酒店小程序
酒店小程序的制作并不需要编程经验,只需要按照以下步骤进行操作,就能很快地搭建自己的小程序商城。 第一步,注册登录账号进入操作后台,找到并点击【商城】中的【去管理】进入商城的后台管理页面,然后再点击【小程序商城…...
算法通过村——Hash和队列问题解析
算法的备胎Hash和找靠山的队列 备胎Hash Hash,不管是算法,还是在工程中都会大量使用。很多复杂的算法问题都用Hash能够轻松解决,也正是如此,在算法例就显得没什么思维含量,所以Hash是应用里的扛把子,但在算…...
租赁类小程序定制开发|租赁管理系统源码|免押租赁系统开发
随着互联网的发展,小程序成为了一种重要的移动应用开发方式。租赁小程序作为其中的一种类型,可以为很多行业提供便利和创新。下面我们将介绍一些适合开发租赁小程序的行业。 房屋租赁行业:租房小程序可以帮助房东和租户快速找到合适的租赁…...
后端进阶之路——浅谈Spring Security用户、角色、权限和访问规则(三)
前言 「作者主页」:雪碧有白泡泡 「个人网站」:雪碧的个人网站 「推荐专栏」: ★java一站式服务 ★ ★前端炫酷代码分享 ★ ★ uniapp-从构建到提升★ ★ 从0到英雄,vue成神之路★ ★ 解决算法,一个专栏就够了★ ★ 架…...
Mac 安装不在 Apple 商店授权的应用程序
文章目录 一、场景介绍二、实操说明 一、场景介绍 在日常的工作生活中,发现一些好用的应用程序,但是出于某些原因,应用程序的开发者并没有将安装包上架到苹果商店。 那么这些优秀的应用程序下载安装以后就会出现如下弹框被拒之门外 二、实操…...
【MyBatis】MyBatis把空字符串转换成0的问题处理方案(96)
先看问题: Postman入参: MyBatis采用map循环插入: // Mapper接口层void addPar(Param(value "question") Map<String, Object> paramMap);<!-- 新增:参数 --><insert id"addPar" parameterType"map">INSERT IGNO…...
OpenLayers实战,OpenLayers获取移动端精确定位,OpenLayers适配App混合H5方式调用手机定位位置并定位到指定点
专栏目录: OpenLayers实战进阶专栏目录 前言 本章讲解OpenLayers如何获取移动端精确定位位置。不使用任何native本地方法,只使用纯js实现。 本篇文章适用于App混合H5方式调用手机精确定位,打包时需要选择GPS位置权限,手机获取定位过程中会弹出是否允许定位的权限提示。 …...
Go指针取址问题:循环后每次都拿到相同内容
例子: func main() {yourList : [...]int{1, 2, 3}yourMap1 : make(map[int]*int)yourMap2 : make(map[int]*int)for key, value : range yourList {// 修改前yourMap1[key] &value// 修改后tmp : valueyourMap2[key] &tmpfmt.Println(value, &value…...
用Rust实现23种设计模式之简单工厂
在 Rust 中,可以使用结构体和 trait 来实现工厂方法模式。工厂方法模式是一种创建型设计模式,通过定义一个创建对象的接口,让子类决定实例化哪个类。下面是一个简单的示例,展示了如何使用 Rust 实现工厂方法模式: // …...
SpringBoot + minio实现分片上传、秒传、续传
什么是minio MinIO是一个基于Go实现的高性能、兼容S3协议的对象存储。它采用GNU AGPL v3开源协议,项目地址是https://github.com/minio/minio。 引用官网: MinIO是根据GNU Affero通用公共许可证v3.0发布的高性能对象存储。它与Amazon S3云存储服务兼容…...
logback 里面设置 自动删除3天之前的日志
目录 1 实现 1 实现 要实现达到一定大小后将日志文件压缩,并删除三天前的日志数据,可以结合使用 SizeAndTimeBasedRollingPolicy 滚动策略和 DeleteOlderThan 选项来配置。下面是一个示例配置,实现日志文件达到一定大小后进行滚动和压缩&…...
对于数据库查询索引和查字典索引的理解
之前面试问过我对于数据库索引的理解,这个问题不是具体的问题太宽泛,面试官也没进行引导,我不知道怎么回答,下面是结合查字典进行理解。 查字典 拿查字典举例,知道一个字怎么写但是不知道具体的意思以及发音ÿ…...
git删除已经提交的大文件
当你不小心把一个巨大的二进制文件提交到git仓库的时候,此时删除再提交也没有用了,大文件已经在仓库中留底了。另外比如需要删除某个需要保密的文件,都是相同的解决办法。 我本来想着把dll放在三方库里面提交到仓库里,省得在不同…...
【数据分析】pandas 一
目录 一,pandas简介: 二,pandas数据结构Series简介: 2.1 data为ndarray 2.2 data为字典 三,Serise切片操作: 四,Series性质: 4.1 Series类似于numpy,字典 4.2 矢量化操作和标…...
ofa_image-caption_coco_distilled_en实战案例:用URL/本地图批量生成英文描述的高效方案
ofa_image-caption_coco_distilled_en实战案例:用URL/本地图批量生成英文描述的高效方案 1. 项目概述:让图片自动"说话"的智能工具 你有没有遇到过这样的情况:手头有大量图片需要添加描述,但一张张手动编写既费时又费…...
等保.三级要求下Redis 安全测评应该怎么做?勤
在之前的文章中,我们花了大量的篇幅,从记录后端pod真实ip开始说起,然后引入envoy,再解决了各种各样的需求:配置自动重载、流量劫持、sidecar自动注入,到envoy的各种能力:熔断、流控、分流、透明…...
从聊天到办公全能:Kimi AI的隐藏功能大揭秘(含Prompt优化技巧)
从聊天到办公全能:Kimi AI的隐藏功能大揭秘(含Prompt优化技巧) 在AI工具井喷式发展的今天,Kimi AI凭借其独特的多场景适应能力,正在重新定义"智能助手"的边界。这款最初以聊天功能进入大众视野的工具&#x…...
__block 变量内存布局详解什
故障表现 发现请求集群 demo 入口时卡住,并且对应 Pod 没有新的日志输出 rootce-demo-1:~# kubectl get pods -n deepflow-otel-spring-demo -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NO…...
YOLO11实例分割教程:快速掌握数据标注、格式转换与模型训练
YOLO11实例分割教程:快速掌握数据标注、格式转换与模型训练 1. 准备工作与环境搭建 1.1 获取YOLO11镜像 YOLO11镜像提供了完整的计算机视觉开发环境,包含预装好的所有依赖项。您可以通过以下两种方式使用: Jupyter Notebook方式࿱…...
技术分析:League Akari智能游戏辅助工具的设计架构与配置策略
技术分析:League Akari智能游戏辅助工具的设计架构与配置策略 【免费下载链接】League-Toolkit An all-in-one toolkit for LeagueClient. Gathering power 🚀. 项目地址: https://gitcode.com/gh_mirrors/le/League-Toolkit League Akari作为一个…...
冷启动耗时从12s降至1.7s,我们如何用分层缓存+动态权重预热重构大模型服务SLA,关键代码已开源
第一章:大模型工程化中的冷启动优化 2026奇点智能技术大会(https://ml-summit.org) 大模型在首次部署或低流量场景下常面临推理延迟高、显存初始化慢、缓存未预热等“冷启动”瓶颈,直接影响用户体验与服务 SLA。冷启动并非仅由模型加载耗时决定…...
终极指南:如何让Mac外接鼠标获得触控板般丝滑滚动体验
终极指南:如何让Mac外接鼠标获得触控板般丝滑滚动体验 【免费下载链接】Mos 一个用于在 macOS 上平滑你的鼠标滚动效果或单独设置滚动方向的小工具, 让你的滚轮爽如触控板 | A lightweight tool used to smooth scrolling and set scroll direction independently f…...
Quansloth 本地 AI 服务器使用手册
Quansloth 本地 AI 服务器使用手册 📋 目录 项目简介 系统要求 安装指南 配置说明 使用教程 多 GPU 配置 故障排除 性能优化 项目简介 什么是 Quansloth? Quansloth 是一个基于 Google TurboQuant (ICLR 2026) 技术构建的本地 AI 服务器,专为消费级 GPU 设计。它通过 KV …...
2026奇点智能技术大会核心议程泄露(仅限前500名技术负责人获取的微调参数黄金组合)
第一章:2026奇点智能技术大会:大模型个性化微调 2026奇点智能技术大会(https://ml-summit.org) 微调范式的根本性演进 在2026奇点智能技术大会上,主流大模型微调已从全参数微调全面转向高效参数微调(PEFT)与上下文感…...
