JAVA 接口、抽象类的关系和用处 详细解析
接口 - Java教程 - 廖雪峰的官方网站

一个 抽象类 如果实现了一个接口,可以只选择实现接口中的 部分方法(所有的方法都要有,可以一部分已经写具体,另一部分继续保留抽象),原因在于:
- 抽象类本身是 不完整的类,它可以有未实现的方法(即抽象方法),因此可以选择不完全实现接口。
- 由继承该抽象类的具体子类去完成未实现的方法。
这也是抽象类的一个强大功能,它在实现接口时,提供了一个“中间层次”,部分实现接口的行为,为具体的子类提供基础。
这里有两个箭头指向同一个类(例如 AbstractList),是因为:
- 接口(如
List)定义了行为规范:接口是用来定义类应该具有的功能和行为,例如List定义了与列表相关的方法(如add(),get()等),但不提供具体实现。 - 抽象类(如
AbstractList)提供了部分实现:抽象类用于实现接口的一部分行为,同时为具体类(如ArrayList和LinkedList)提供可以复用的代码。
AbstractList 和 List 的区别
-
List接口:- 是一个完全抽象的接口,只定义了列表操作的规范。
- 方法如
add(E element),get(int index),remove(int index)等都只是方法声明,没有实现。
-
AbstractList抽象类:- 是一个抽象类,实现了
List接口的大部分通用功能。 - 目的是让具体实现类(如
ArrayList和LinkedList)复用这些功能,只需实现特定的方法即可。例如,AbstractList中实现了addAll(),具体类无需再写这部分代码。
- 是一个抽象类,实现了
示例代码
假设你要实现一个自定义的列表,直接实现 List 和继承 AbstractList 的区别如下:
直接实现 List 接口
如果从零实现 List 接口,你需要定义接口中所有的方法(包括很多通用方法,比如 size() 和 addAll())。
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;public class CustomList<E> implements List<E> {private Object[] elements = new Object[10];private int size = 0;@Overridepublic boolean add(E e) {if (size == elements.length) {Object[] newElements = new Object[size * 2];System.arraycopy(elements, 0, newElements, 0, size);elements = newElements;}elements[size++] = e;return true;}@Overridepublic int size() {return size;}@Overridepublic E get(int index) {if (index < 0 || index >= size) {throw new IndexOutOfBoundsException("Index: " + index);}return (E) elements[index];}// 还需实现 List 中所有的方法,如 remove()、iterator() 等,工作量很大。
}
继承 AbstractList 抽象类
通过继承 AbstractList,你只需实现一些关键方法,剩下的方法由 AbstractList 提供默认实现。
import java.util.AbstractList;public class CustomList<E> extends AbstractList<E> {private Object[] elements = new Object[10];private int size = 0;@Overridepublic E get(int index) {if (index < 0 || index >= size) {throw new IndexOutOfBoundsException("Index: " + index);}return (E) elements[index];}@Overridepublic int size() {return size;}@Overridepublic boolean add(E e) {if (size == elements.length) {Object[] newElements = new Object[size * 2];System.arraycopy(elements, 0, newElements, 0, size);elements = newElements;}elements[size++] = e;return true;}// 不需要手动实现 addAll() 等通用方法,AbstractList 已提供默认实现。
}
运行示例
public class Main {public static void main(String[] args) {CustomList<String> list = new CustomList<>();list.add("A");list.add("B");list.add("C");System.out.println(list.get(1)); // 输出: BSystem.out.println(list.size()); // 输出: 3}
}
为什么 Java 集合框架中要设计接口和抽象类的这种关系?
1. 灵活性:接口用于定义行为规范
接口(如 List)允许不同的实现方式,适配多种需求,例如:
ArrayList:基于数组实现的列表,适合随机访问操作。LinkedList:基于链表实现的列表,适合插入和删除操作。- 自定义列表:可以实现特定的逻辑,比如线程安全或固定容量。
2. 代码复用:抽象类减少重复代码
抽象类(如 AbstractList)避免了在每个实现类中重复编写通用逻辑。例如:
size()的计算逻辑。- 批量添加方法(
addAll())的实现。 - 迭代器(
iterator())的通用实现。
通过这种设计,新实现类只需关注特定的细节。
3. 多层次抽象设计
如下图中的设计:
- 接口层: 定义行为规范(如
List,Collection)。 - 抽象类层: 提供部分实现(如
AbstractList,AbstractCollection)。 - 具体类层: 提供特定实现(如
ArrayList,LinkedList)。
这种多层次设计提供了灵活性和代码复用的平衡。
问题 1:调用的时候执行的是接口的方法还是抽象类的方法?
调用的是 对象的实际实现类中的方法,而不是接口或抽象类本身。尽管我们通过 List 这样的接口来引用一个对象,但具体执行的代码取决于 对象的具体实现类。
假设我们写了一段代码:
List<String> list = new ArrayList<>();
list.add("Hello");
-
编译时看接口,运行时看实现类:
list的编译时类型是List,所以编译器只会允许你调用List接口中声明的方法,比如add()、remove()等。- 但
list的运行时类型是ArrayList,所以具体执行的add()方法是ArrayList类中定义的实现。
-
接口 vs 抽象类:
List是接口,定义了add()的方法规范。AbstractList是一个抽象类,部分实现了List的规范,并提供了通用实现。- 但是:在
ArrayList中,它直接继承了AbstractList,并可能覆写了某些方法,所以最终调用的是ArrayList的实现。
为什么我们总是见到 List,而没有见过 AbstractList?
-
AbstractList是设计细节:AbstractList是为具体实现类(如ArrayList和LinkedList)服务的,目的是 减少代码重复。- 它为实现类提供了一些通用功能,比如:
- 默认实现
addAll()方法。 - 默认实现
iterator()方法。
- 默认实现
- 但是,
AbstractList是抽象的,不能直接使用,所以开发者不会直接实例化或引用它。
-
面向接口编程的原则:我们习惯通过接口(如
List)去引用对象,这是面向接口编程的核心思想。
default 方法
-
- 在 接口 中,
default方法允许有具体的实现,提供一个方法体。 - 抽象类 中不需要使用
default关键字,因为抽象类本身可以包含普通的具体方法(带方法体)和抽象方法(没有方法体)。
- 在 接口 中,
-
为什么
default方法存在于接口:- 原本接口中的方法必须全部是抽象的,这意味着接口升级时(比如增加新方法),所有实现这个接口的类都必须修改,去实现新增的方法。
- 为了兼容老代码,同时给接口增加新功能,Java 8 引入了
default方法。default方法是为了 在接口中提供默认实现,而不破坏已有的实现类。
-
抽象类和接口在具体方法上的区别:
- 抽象类的普通方法天然支持具体实现,不需要额外关键字。
- 接口中的
default方法则是接口为了支持具体实现而引入的额外能力。
具体对比:抽象类和接口中的具体方法
| 特点 | 抽象类中的具体方法 | 接口中的 default 方法 |
|---|---|---|
| 是否需要关键字 | 不需要,直接定义普通方法即可 | 需要使用 default 关键字 |
| 是否可以有具体实现 | 是的,普通方法都可以有实现 | 是的,default 方法允许提供具体实现 |
| 是否可以被覆写 | 可以,子类可以选择覆写抽象类中的普通方法 | 可以,子类可以选择覆写接口中的 default 方法 |
| 是否强制实现 | 不是,子类可以选择继承普通方法的实现或覆写它 | 不是,默认继承接口中的 default 方法 |
default 方法的实际意义
1. 向接口新增方法时的兼容性问题
假设你有一个接口 MyInterface 和两个实现类:
interface MyInterface {void methodA();
}class ClassA implements MyInterface {@Overridepublic void methodA() {System.out.println("ClassA: methodA");}
}class ClassB implements MyInterface {@Overridepublic void methodA() {System.out.println("ClassB: methodA");}
}
如果你需要给 MyInterface 添加一个新方法 methodB,所有的实现类(ClassA 和 ClassB)都必须实现这个方法,否则代码无法编译。
2. 使用 default 方法解决兼容问题
在这种情况下,可以用 default 方法为新方法提供一个默认实现,从而避免修改所有实现类:
interface MyInterface {void methodA();// 新增一个 default 方法default void methodB() {System.out.println("Default implementation of methodB");}
}class ClassA implements MyInterface {@Overridepublic void methodA() {System.out.println("ClassA: methodA");}
}class ClassB implements MyInterface {@Overridepublic void methodA() {System.out.println("ClassB: methodA");}
}
运行示例
public class Main {public static void main(String[] args) {MyInterface objA = new ClassA();objA.methodA(); // 输出: ClassA: methodAobjA.methodB(); // 输出: Default implementation of methodBMyInterface objB = new ClassB();objB.methodA(); // 输出: ClassB: methodAobjB.methodB(); // 输出: Default implementation of methodB}
}
如果某个实现类需要对 default 方法提供自定义实现,可以覆写它:
class ClassB implements MyInterface {@Overridepublic void methodA() {System.out.println("ClassB: methodA");}@Overridepublic void methodB() {System.out.println("ClassB: Custom implementation of methodB");}
}
运行后:
MyInterface objB = new ClassB();
objB.methodB(); // 输出: ClassB: Custom implementation of methodB
结合数据库任务的实际场景
在你的数据库任务中,default 方法可以为某些操作提供通用实现。例如:
接口定义
public interface DBOperations {boolean createTable(String tableName, List<String> columns);default boolean dropTable(String tableName) {System.out.println("[OK] Dropped table: " + tableName);return true;}
}
实现类
具体类可以选择覆写或继承接口中的 default 方法或者覆写
相关文章:
JAVA 接口、抽象类的关系和用处 详细解析
接口 - Java教程 - 廖雪峰的官方网站 一个 抽象类 如果实现了一个接口,可以只选择实现接口中的 部分方法(所有的方法都要有,可以一部分已经写具体,另一部分继续保留抽象),原因在于: 抽象类本身…...
反向代理模块b
1 概念 1.1 反向代理概念 反向代理是指以代理服务器来接收客户端的请求,然后将请求转发给内部网络上的服务器,将从服务器上得到的结果返回给客户端,此时代理服务器对外表现为一个反向代理服务器。 对于客户端来说,反向代理就相当于…...
Nuitka打包python脚本
Python脚本打包 Python是解释执行语言,需要解释器才能运行代码,这就导致在开发机上编写的代码在别的电脑上无法直接运行,除非目标机器上也安装了Python解释器,有时候还需要额外安装Python第三方包,相当麻烦。 事实上P…...
pytorch线性回归模型预测房价例子
import torch import torch.nn as nn import torch.optim as optim import numpy as np# 1. 创建线性回归模型类 class LinearRegressionModel(nn.Module):def __init__(self):super(LinearRegressionModel, self).__init__()self.linear nn.Linear(1, 1) # 1个输入特征&…...
练习题 - DRF 3.x Caching 缓存使用示例和配置方法
在构建现代化的 Web 应用程序时,性能优化是一个非常重要的环节。尤其是在使用 Django Rest Framework (DRF) 开发 API 服务时,合理地利用缓存技术可以显著提高应用的响应速度和减轻数据库的负担。DRF 提供了多种缓存机制,包括基于内存、文件系统、数据库以及第三方缓存服务(…...
如何解压7z文件?8种方法(Win/Mac/手机/网页端)
7z 文件是一种高效的压缩文件格式,由 7 - Zip 软件开发者所采用。它运用独特的压缩算法,能显著缩小文件体积,便于存储与传输各类数据,像软件安装包、大型资料集等。但要使用其中内容,就必须解压,因为处于压…...
python学opencv|读取图像(五十)使用addWeighted()函数实现图像加权叠加效果
【1】引言 前序学习进程中,学习了图像互相叠加的不同操作方法,包括add()函数直接叠加BGR值和使用bitwise()函数对BGR值进行按位计算叠加等,相关文章链接包括且不限于: python学opencv|读取图像(四十二)使…...
window中80端口被占用问题
1,查看报错信息 可以看到在启动项目的时候,8081端口被占用了,导致项目无法启动。 2,查看被占用端口的pid #语法 netstat -aon|findstr :被占用端口#示例 netstat -aon|findstr :8080 3,杀死进程 #语法 taikkill /pid…...
06-机器学习-数据预处理
数据清洗 数据清洗是数据预处理的核心步骤,旨在修正或移除数据集中的错误、不完整、重复或不一致的部分,为后续分析和建模提供可靠基础。以下是数据清洗的详细流程、方法和实战示例: 一、数据清洗的核心任务 问题类型表现示例影响缺失值数值…...
电梯系统的UML文档12
5.2.1 DoorControl 的状态图 图 19: DoorControl 的状态图 5.2.2 DriveControl 的状态图 图 20: DriveControl 的状态图 5.2.3 LanternControl 的状态图 图 21: LanternControl 的状态图 5.2.4 HallButtonControl 的状态图 图 22: HallButtonControl 的状态图 5.2.5 CarB…...
萌新学 Python 之运算符
Python 中运算符包括:算术运算符、比较运算符、逻辑运算符、赋值运算符、位运算符、海象运算符 算术运算符:加 减 - 乘 * 除 / 取整 // 求余 % 求幂 ** 注意:取整时,一正一负整除,向下取整 比如 5 // …...
嵌入式知识点总结 Linux驱动 (五)-linux内核
针对于嵌入式软件杂乱的知识点总结起来,提供给读者学习复习对下述内容的强化。 目录 1.内核镜像格式有几种?分别有什么区别? 2.内核中申请内存有哪几个函数?有什么区别? 3.什么是内核空间,用户空间&…...
zabbix7 配置字体 解决中文乱码问题(随手记)
目录 问题网传的方法(无效)正确的修改方式步骤 问题 zabbix 最新数据 中,图标的中文显示不出。 网传的方法(无效) 网传有一个方法:上传字体文件到/usr/share/zabbix/assets/fonts;修改/usr/…...
预测不规则离散运动的下一个结构
有一个点在19*19的平面上运动,运动轨迹为 一共移动了90步,顺序为 y x y x y x 0 17 16 30 10 8 60 15 15 1 3 6 31 10 7 61 14 15 2 12 17 32 9 9 62 16 15 3 4 12 33 10 9 63 18 15 4 3 18 34 15 12 6…...
CTFSHOW-WEB入门-命令执行29-32
题目:web 29 题目:解题思路:分析代码: error_reporting(0); if(isset($_GET[c])){//get一个c的参数$c $_GET[c];//赋值给Cif(!preg_match("/flag/i", $c)){eval($c);//if C变量里面没有flag,那么就执行C…...
SQL Server 建立每日自动log备份的维护计划
SQLServer数据库可以使用维护计划完成数据库的自动备份,下面以在SQL Server 2012为例说明具体配置方法。 1.启动SQL Server Management Studio,在【对象资源管理器】窗格中选择数据库实例,然后依次选择【管理】→【维护计划】选项࿰…...
doris:HLL
HLL是用作模糊去重,在数据量大的情况性能优于 Count Distinct。HLL的导入需要结合hll_hash等函数来使用。更多文档参考HLL。 使用示例 第 1 步:准备数据 创建如下的 csv 文件:test_hll.csv 1001|koga 1002|nijg 1003|lojn 1004|lofn …...
双层Git管理项目,github托管显示正常
双层Git管理项目,github托管显示正常 背景 在写React项目时,使用Next.js,该项目默认由git托管。但是我有在项目代码外层记笔记的习惯,我就在外层使用了git托管。 目录如下 code 层内也有.git 文件,对其托管。 我没太在意&…...
准备知识——旋转机械的频率和振动基础
旋转频率,也称为转速或旋转速率(符号ν,小写希腊字母nu,也作n),是物体绕轴旋转的频率。其国际单位制单位是秒的倒数(s −1 );其他常见测量单位包括赫兹(Hz)、每秒周期数(cps) 和每分钟转数(rpm)…...
知识库管理驱动企业知识流动与工作协同创新模式
内容概要 知识库管理在现代企业中扮演着至关重要的角色,其价值不仅体现在知识的积累,还在于通过优质的信息流动促进协作与创新。有效的知识库能够将分散的信息整合为有序、易于访问的资源,为员工提供实时支持,进而提升整体工作效…...
微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南
🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
