Java学习系列之抽象类和接口的区别和联系

article/2025/10/14 0:46:31

导读 本文首先分别介绍抽象类和接口的基础概念、特征和应用场景,然后介绍其区别和联系。

1 抽象类

1.1 定义抽象类

在Java中被abstract关键字修饰的类称为抽象类,被abstract关键字修饰的方法称为抽象方法,抽象方法只有方法的声明,没有方法体。

public abstract class AbstractPlayer {public abstract void eat();
}

关于抽象类的命名,《阿里的 Java 开发手册》上有强调,“抽象类命名要使用 Abstract 或 Base 开头”。

1.2 抽象类的特征

1 抽象类是用来捕捉子类的通用特性的,它不能被实例化,只能被继承。如果尝试通过new关键字实例化的话,编译器会报错,提示类是抽象的不能被实例化:
在这里插入图片描述
抽象类的子类通过extends关键词来继承抽象类:

public class BasketballPlayer extends AbstractPlayer {
}

但是要注意:使用extends只能单继承,同事继承多个抽象类编译器会报错class cannot extend multiple classes

2 包含抽象方法的一定是抽象类,但抽象类不一定含有抽象方法
当我们尝试在一个普通类中定义抽象方法的时候,编译器会有两处错误提示。第一处在类级别上,提示“这个类必须通过 abstract 关键字定义”,见下图。
在这里插入图片描述
第二处在尝试定义 abstract 的方法上,提示“抽象方法所在的类不是抽象的”,见下图。
在这里插入图片描述
而在抽象类中,可以定义普通方法,如下面的代码所示:

public abstract class AbstractPlayer {public abstract void play();public void sayName(String name) {System.out.println("My name is " + name);}
}

3 抽象类中的抽象方法只能为public或protected(如果是private则不能被子类继承),默认为public。

4 抽象类中的抽象方法只有方法体,没有具体实现,但可以有普通方法。

5 如果一个子类实现了父类(抽象类)的所有抽象方法,那么该子类可以不必是抽象类,否则就是抽象类

public abstract class AbstractPlayer {public abstract void eat();
}/*** AbstractFootballPlayer没有实现AbstractPlayer的抽象方法play,因此他也只能是抽象类*/
public abstract class AbstractFootballPlayer extends AbstractPlayer{public abstract void run();
}/*** FootballPlayer需要实现AbstractFootballPlayer中的所有抽象方法,否则它仍然是一个抽象类*/
public class FootballPlayer extends AbstractFootballPlayer{@Overridepublic void run() {}@Overridevoid play() {}
}

6 抽象类可以包含属性、方法、构造方法等,但是构造方法不能用于实例化,主要用途是被子类调用。

public abstract class AbstractPlayer {// 可以定义构造函数AbstractPlayer(int count) {this.count = count;}// 可以定义静态常量public static final int MAX_COUNT = 5;// 可以定义变量public int count;// 可以定义普通函数public void sayName(String name) {System.out.println("My name is " + name);}// 可以定义抽象方法abstract void play();
}// 如果抽象类定义了构造函数,其子类就需要调用抽象类的构造函数
public class BasketballPlayer extends AbstractPlayer{BasketballPlayer(int count) {super(count);}@Overridevoid play() {System.out.println(this.MAX_COUNT);System.out.println(this.count);}public static void main(String[] args) {AbstractPlayer player = new BasketballPlayer(11);player.sayName("Tom");player.play();}
}

1.3 抽象类的应用场景

场景1:希望一些通用的功能能够被多个子类复用

比如说,AbstractPlayer 抽象类中有一个普通的方法 sleep(),表明所有运动员都需要休息,那么这个方法就可以被子类复用。

abstract class AbstractPlayer {public void sleep() {System.out.println("运动员也要休息而不是挑战极限");}
}// 子类 BasketballPlayer 继承了 AbstractPlayer 类,就拥有了 sleep() 方法。
class BasketballPlayer extends AbstractPlayer {
}// 子类 FootballPlayer 继承了 AbstractPlayer 类,也就拥有了 sleep() 方法。
class FootballPlayer extends AbstractPlayer {
}// BasketballPlayer 的对象可以直接调用父类的 sleep() 方法
BasketballPlayer basketballPlayer = new BasketballPlayer();
basketballPlayer.sleep();// FootballPlayer 的对象可以直接调用父类的 sleep() 方法
FootballPlayer footballPlayer = new FootballPlayer();
footballPlayer.sleep();

场景2:希望所有子类能够自行实现在抽象类中定义的抽象方法

比如说,AbstractPlayer 抽象类中定义了一个抽象方法 play(),表明所有运动员都可以从事某项运动,但需要对应子类去扩展实现,表明篮球运动员打篮球,足球运动员踢足球。

abstract class AbstractPlayer {abstract void play();
}public class BasketballPlayer extends AbstractPlayer {@Overridevoid play() {System.out.println("我是张伯伦,我篮球场上得过 100 分,");}
}public class FootballPlayer extends AbstractPlayer {@Overridevoid play() {System.out.println("我是C罗,我能接住任意高度的头球");}
}

2 接口

2.1 定义接口

Java中的接口使用interface关键字修饰,接口是方法的集合。

public interface Runnable {public abstract void run();
}

2.2 接口的特征

public interface Electronic {// 常量String LED = "LED";// 抽象方法int getElectricityUse();// 静态方法static boolean isEnergyEfficient(String electtronicType) {return electtronicType.equals(LED);}// 默认方法default void printDescription() {System.out.println("电子");}
}

1 接口中可以含有 变量和方法,但是要注意,接口中定义的变量会在编译的时候自动加上 public static final 修饰符(并且只能是public static final变量,用private修饰会报编译错误),而方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误)。

2 Java8以前接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。
从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量

3 从 Java 8 开始,接口中允许有静态方法,比如上例中的 isEnergyEfficient() 方法。
静态方法无法由(实现了该接口的)类的对象调用,它只能通过接口名来调用,比如说 Electronic.isEnergyEfficient(“LED”)。

4 从 Java8 开始,接口中允许定义default方法,比如上例中的 printDescription() 方法。
default方法提供了默认的实现,实现该接口的子类可以不修改default方法直接使用模式实现,也可以override。

public class Computer implements Electronic{// 抽象方法必须在子类中实现@Overridepublic int getElectricityUse() {return 0;}// 可以选择覆写该default方法,不覆写也不会报错@Overridepublic void printDescription() {System.out.println("计算机");}public static void main(String[] args) {Computer computer = new Computer();computer.printDescription();}
}

5 接口不允许直接实例化,否则编译器会报错:
在这里插入图片描述
6 接口可以是空的,既可以不定义变量,也可以不定义方法。最典型的例子就是 Serializable 接口,Serializable 接口用来为序列化的具体实现提供一个标记,也就是说,只要某个类实现了 Serializable 接口,那么它就可以用来序列化了。

public interface Serializable {
}

7 接口支持多继承,即一个接口可以extends多个接口,间接的解决了Java中类的单继承问题。

2.3 接口的应用场景

场景1 作为标记,使某些实现类具有我们想要的功能

比如说,实现了 Cloneable 接口的类具有拷贝的功能,实现了 Comparable 或者 Comparator 的类具有比较功能。Cloneable 和 Serializable 一样,都属于标记型接口,它们内部都是空的。实现了 Cloneable 接口的类可以使用 Object.clone() 方法,否则会抛出 CloneNotSupportedException。

public class CloneableTest implements Cloneable {@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}public static void main(String[] args) throws CloneNotSupportedException {CloneableTest c1 = new CloneableTest();CloneableTest c2 = (CloneableTest) c1.clone();}
}

运行后没有报错。现在把 implements Cloneable 去掉。

public class CloneableTest {@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}public static void main(String[] args) throws CloneNotSupportedException {CloneableTest c1 = new CloneableTest();CloneableTest c2 = (CloneableTest) c1.clone();}
}

运行后抛出 CloneNotSupportedException:

Exception in thread "main" java.lang.CloneNotSupportedException: com.cmower.baeldung.interface1.CloneableTestat java.base/java.lang.Object.clone(Native Method)at com.cmower.baeldung.interface1.CloneableTest.clone(CloneableTest.java:6)at com.cmower.baeldung.interface1.CloneableTest.main(CloneableTest.java:11)

场景2 借助接口实现多重继承

如果有两个类共同继承(extends)一个父类,那么父类的方法就会被两个子类重写。然后,如果有一个新类同时继承了这两个子类,那么在调用重写方法的时候,编译器就不能识别要调用哪个类的方法了。这也正是著名的菱形问题,见下图。

在这里插入图片描述
而借助接口可以达到多重继承的目的。

场景3 实现多态

使用抽象类和接口都可以实现多态,下面举个栗子:

public interface Shape {String name();
}// Circle 类实现了 Shape 接口,并重写了 name() 方法
public class Circle implements Shape {@Overridepublic String name() {return "圆";}
}// Square 类也实现了 Shape 接口,并重写了 name() 方法
public class Square implements Shape {@Overridepublic String name() {return "正方形";}
}// 调用看看效果
List<Shape> shapes = new ArrayList<>();
Shape circleShape = new Circle();
Shape squareShape = new Square();shapes.add(circleShape);
shapes.add(squareShape);for (Shape shape : shapes) {System.out.println(shape.name());
}// 输出结果为:
// 圆
// 正方形

这就实现了多态,变量 circleShape、squareShape 的引用类型都是 Shape,但执行 shape.name() 方法的时候,Java 虚拟机知道该去调用 Circle 的 name() 方法还是 Square 的 name() 方法。

场景4 接口在设计模式中的应用

在使用接口的时候,经常会用到三种模式,分别是策略模式、适配器模式和工厂模式。

策略模式

策略模式的思想是,针对一组算法,将每一种算法封装到具有共同接口的实现类中,接口的设计者可以在不影响调用者的情况下对算法做出改变。示例如下:

// 接口:教练
interface Coach {// 方法:防守void defend();
}// 何塞·穆里尼奥
class Hesai implements Coach {@Overridepublic void defend() {System.out.println("防守赢得冠军");}
}// 德普·瓜迪奥拉
class Guatu implements Coach {@Overridepublic void defend() {System.out.println("进攻就是最好的防守");}
}public class Demo {// 参数为接口public static void defend(Coach coach) {coach.defend();}public static void main(String[] args) {// 为同一个方法传递不同的对象defend(new Hesai());defend(new Guatu());}
}

适配器模式

适配器模式的思想是,针对调用者的需求对原有的接口进行转接。生活当中最常见的适配器就是HDMI(英语:High Definition Multimedia Interface,中文:高清多媒体接口)线,可以同时发送音频和视频信号。适配器模式的示例如下:

interface Coach {void defend();void attack();
}// 抽象类实现接口,并置空方法
abstract class AdapterCoach implements Coach {public void defend() {};public void attack() {};
}// 新类继承适配器
class Hesai extends AdapterCoach {public void defend() {System.out.println("防守赢得冠军");}
}public class Demo {public static void main(String[] args) {Coach coach = new Hesai();coach.defend();}
}

工厂模式

所谓的工厂模式理解起来也不难,就是什么工厂生产什么,比如说宝马工厂生产宝马,奔驰工厂生产奔驰,A 级学院毕业 A 级教练,C 级学院毕业 C 级教练。示例如下:

// 教练
interface Coach {void command();
}// 教练学院
interface CoachFactory {Coach createCoach();
}// A级教练
class ACoach implements Coach {@Overridepublic void command() {System.out.println("我是A级证书教练");}}// A级教练学院
class ACoachFactory implements CoachFactory {@Overridepublic Coach createCoach() {return new ACoach();}}// C级教练
class CCoach implements Coach {@Overridepublic void command() {System.out.println("我是C级证书教练");}}// C级教练学院
class CCoachFactory implements CoachFactory {@Overridepublic Coach createCoach() {return new CCoach();}}public class Demo {public static void create(CoachFactory factory) {factory.createCoach().command();}public static void main(String[] args) {// 对于一支球队来说,需要什么样的教练就去找什么样的学院// 学院会介绍球队对应水平的教练。create(new ACoachFactory());create(new CCoachFactory());}
}

3 抽象类和接口的共同点和区别

3.1 共同点

  • 都不能被实例化
  • 都可以包含抽象方法
  • 都可以有默认实现的方法(Java 8 可以用 default 关键在接口中定义默认方法)

3.2 区别

  • 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系(比如说我们抽象了一个发送短信的抽象类)。
  • 一个类只能继承一个类,但是可以实现多个接口(如果想实现多继承就用接口)。
  • 接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。

4 抽象类和接口的应用场景

1、如果你拥有一些方法并且想让它们中的一些有默认实现,那么使用抽象类吧。
2、如果你想实现多重继承,那么你必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。因此你就可以使用接口来解决它。
3、如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。

本文参考博客

  • JavaGuide-接口和抽象类有什么共同点和区别?
  • Java抽象类,看这一篇就够了,豁然开朗
  • Java接口,看这一篇就够了,简单易懂
  • Java 抽象类和接口的区别,看这一篇就够了

http://chatgpt.dhexx.cn/article/3CFtlBc9.shtml

相关文章

从抽象类到接口—手把手教你写抽象类(一)—还在Ctrl C的伙伴们看过来了

目录 一、抽象类概述 二、抽象类的特点 三、抽象类的案例 四、抽象类的细节 五、抽象类的思想 抽象类概述 抽象定义 –抽象是从多个事物中将共性的&#xff0c;本质的内容抽取出来。 –例&#xff1a;狼和狗共性都是犬科&#xff0c;犬科就是抽象出来的概念。 抽象类 –…

Java基础学习:抽象类和接口

目录,更新ing,学习Java的点滴记录 目录放在这里太长了,附目录链接大家可以自由选择查看--------Java学习目录 抽象类和接口_抽象类和抽象方法 抽象类是普通的类与接口之间的一种中庸之道,尽管你可能在构建某些未实现方法的类是,第一想法可能是创建接口,但是抽象类仍旧是用于此…

SVD奇异值分解(理论与C++实现)

SVD奇异值分解 前言理论推导部分代码实现 前言 奇异值分解(singular value decomposition&#xff0c;以下简称SVD)是线性代数中一种重要的矩阵分解。SVD将矩阵分解为奇异向量(singular vector)和奇异值(singular value)。SVD将矩阵 A A A分解成三个矩阵的乘积 A U D V T A …

matlab实现奇异值分解

一、原理 二、实现 %% 两种方法计算矩阵 A 的 SVD A [0,1; 1,1; 1,0];%% 方法一&#xff1a;利用特征分解eig % 计算右奇异矩阵V [V,D1] eig(A*A); n size(D1,1); index n:-1:1; D1 diag(D1); D1 D1(index); D1 diag(D1, 0); V V(:,index); % 计算左奇异矩阵U [U,D2…

特征值分解和奇异值分解

特征值分解 特征值分解是将一个方阵A分解为如下形式&#xff1a; A Q Σ Q − 1 AQ\Sigma Q^{-1} AQΣQ−1 其中&#xff0c;Q是方阵A的特征向量组成的矩阵&#xff0c; Σ \Sigma Σ是一个对角矩阵&#xff0c;对角线元素是特征值。 通过特征值分解得到的前N个特征向量&am…

奇异值分解的揭秘(一):矩阵的奇异值分解过程

转载来源&#xff1a; 作者&#xff1a;Xinyu Chen 链接&#xff1a;https://zhuanlan.zhihu.com/p/26306568 来源&#xff1a;知乎 矩阵的奇异值分解&#xff08;singular value decomposition&#xff0c;简称SVD&#xff09;是线性代数中很重要的内容&#xff0c;并且奇…

奇异值分解(Singular Values Decomposition,SVD)

奇异值分解 1.奇异值分解1.1 变换&#xff08;Transformations&#xff09;1.2 线性变换&#xff08;Linear Transformations&#xff09;1.3 降维&#xff08;Dimensionality Reduction&#xff09;1.4 奇异值分解&#xff08;SVD&#xff09;1.4.1 如果矩阵 A A A是方阵&…

奇异值分解(SVD)的原理详解及推导

1. 写在前面 最近整理推荐系统模型的时候&#xff0c; 第二个模型打算整理一下隐语义模型&#xff0c; 这里面绕不开一种思想就是矩阵分解&#xff0c; 而作为矩阵分解的经典方法SVD感觉这次有必要学学了&#xff0c; SVD不仅是一个数学问题&#xff0c;在工程应用中的很多地方…

机器学习(29)之奇异值分解SVD原理与应用详解

微信公众号 关键字全网搜索最新排名 【机器学习算法】:排名第一 【机器学习】:排名第一 【Python】:排名第三 【算法】:排名第四 前言 奇异值分解(Singular Value Decomposition,简称SVD)是在机器学习领域广泛应用的算法,它不光可以用于降维算法中的特征分解,还可以用于…

【机器学习】这次终于彻底理解了奇异值分解(SVD)原理及应用

奇异值分解(Singular Value Decomposition&#xff0c;以下简称SVD)是在机器学习领域广泛应用的算法&#xff0c;有相当多的应用与奇异值都可以扯上关系&#xff0c;它不光可以用于降维算法中的特征分解&#xff0c;比如做feature reduction的PCA&#xff0c;做数据压缩&#x…

联邦学习——用data-free知识蒸馏处理Non-IID

《Data-Free Knowledge Distillation for Heterogeneous Federated Learning》ICML 2021 最近出现了利用知识蒸馏来解决FL中的用户异构性问题的想法&#xff0c;具体是通过使用来自异构用户的聚合知识来优化全局模型&#xff0c;而不是直接聚合用户的模型参数。然而&#xff0c…

【FLIS】Clustered Federated Learning via Inference Similarity for Non-IID Data Distribution

Clustered Federated Learning via Inference Similarity for Non-IID Data Distribution 基于推理相似性的非iid数据分布聚类联邦学习 Abstract1.INTRODUCTION2.FEDERATED LEARNING WITH CLUSTERINGA. Overview of FLIS AlgorithmB. Clustering Clients 3.EXPERIMENTSA. Exper…

Federated Learning with Non-IID Data 论文笔记

本文提出联邦学习中的由于Non-IID数据分布而精度降低是因为权重分散&#xff08;weight divergence&#xff09;&#xff0c;而权重散度可以用搬土距离&#xff08;EMD&#xff09;量化&#xff0c;最后提出了一种策略&#xff1a;通过创建一个在所有边缘设备之间全局共享的数据…

论文分享:「FED BN」使用LOCAL BATCH NORMALIZATION方法解决Non-iid问题

‍ ‍ 本次分享内容基于ICLR 2021收录的一篇文章&#xff1a;《FED BN: FEDERATED LEARNING ON NON-IID FEATURES VIA LOCAL BATCH NORMALIZATION》&#xff0c;这篇论文主要探讨了使用LOCAL BATCH NORMALIZATION方法解决Non-iid问题。围绕这篇论文的分享将分为4个部分&#…

On the convergence of FedAvg on non-iid data

在这篇blog中我们一起来阅读一下 On the convergence of FedAvg on non-iid data 这篇 ICLR 2020 的paper. 主要目的 本文的主要目的是证明联邦学习算法的收敛性。与之前其他工作中的证明不同&#xff0c;本文的证明更贴近于实际联邦学习的场景。特别的&#xff0c; 所有用户…

Federated Learning with Non-IID Data

Federated Learning with Non-IID Data 论文中分析了FedAvg算法在Non-IID数据时&#xff0c;准确率下降的原因。并提出共享5%的数据可提高准确率。 论文笔记参考&#xff1a;https://blog.csdn.net/GJ_007/article/details/104768415 Federated Learning with Non-IID Data …

什么是TLB文件,怎样从dll文件中提取TYPEID信息?- IID

文章目录 1.TLB是什么?2.怎样从dll中导出TLB文件?3.怎样创建TLB文件?4.如何导入TLB5.作者答疑Com是windows平台提供的二进制互操作解决方案。如果给你一个dll,或者windows自带的dll,是否有可能提取其Com接口信息,答案是可以的。 1.TLB是什么? TLB文件是一个说明文件,通…

怎么实现联邦学习中的Non-IID?

联邦学习的一大特点就是数据分布是Non-IID&#xff0c;Non-IID意为非独立同分布。那么怎么在实验中实现non-iid呢&#xff1f;这是我这篇博客想讨论的问题。 part 1&#xff1a; 在堪称联邦学习“开山之作”FedAvg这篇论文中&#xff0c;是这样描述的&#xff1a; 数据集是MN…

【联邦学习】联邦学习量化——non-iid数据集下的仿真

文章目录 改进项目背景量化函数的改进non-iid数据集的设置Fedlab划分数据集的踩雷 改进项目背景 在前面的项目中&#xff0c;虽然对联邦学习中&#xff0c;各个ue训练出来的模型上传的参数进行了量化&#xff0c;并仿真的相关结果。但是仍有一些俺不是非常符合场景的情况&…

「隐语小课」联邦学习之Non-IID问题

更多干货内容&#xff0c;请移步公众号&#xff1a;隐语的小剧场 一、引言 本文针对联邦学习中遇到的Non-IID问题进行探讨&#xff0c;介绍Non-IID产生的原因&#xff0c;分析Non-IID对联邦学习的影响&#xff0c;以及调研了近年来针对该问题的解决方案&#xff0c;并进行分类…