您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

Java中的抽象类与接口详解

时间:2022-08-02 13:36:11  来源:  作者:Java非凡

JAVA中的抽象类与接口

在Java中什么时候应该选择抽象类而不是接口?接受挑战吧!了解这些Java语言元素之间的区别以及如何在你的程序中使用它们。

在Java代码中,甚至在Java开发工具包(JDK)本身中,都有大量的抽象类和接口。每个代码元素都有一个基本的目的:

  • 接口是一种代码契约,必须由一个具体的类来实现。
  • 抽象类与普通类相似,不同的是它们可以包括抽象方法,也就是没有主体的方法。抽象类不能被实例化。

许多开发者认为接口和抽象类是相似的,但它们实际上是完全不同的。让我们来探讨一下它们之间的主要区别。

接口的本质

从本质上讲,接口是一个契约,所以它依赖于一个实现来达到其目的。一个接口永远不可能有状态,所以它不能使用可变的实例变量。一个接口只能使用最终变量。

何时使用接口

接口对于解耦代码和实现多态性非常有用。我们可以在JDK中看到一个例子,就是List 接口:

public interface List<E> extends Collection<E> {    int size();    boolean isEmpty();    boolean add(E e);    E remove(int index);    void clear();}复制代码

正如你可能注意到的,这段代码很短,而且描述性很强。我们可以很容易地看到方法的签名,我们将用一个具体的类来实现接口中的方法。

List 接口包含一个契约,可以由ArrayList,Vector,LinkedList, 和其他类来实现。

为了使用多态性,我们可以简单地用List 来声明我们的变量类型,然后选择任何一个可用的实例化。这里有一个例子:

List list = new ArrayList();System.out.println(list.getClass()); List list = new LinkedList(); System.out.println(list.getClass());复制代码

下面是这段代码的输出:

class java.util.ArrayListclass java.util.LinkedList复制代码

在这种情况下,ArrayList,LinkedList, 和Vector 的实现方法都是不同的,这就是使用接口的一个很好的场景。如果你注意到许多类都属于一个父类,其方法动作相同,但行为不同,那么使用接口是个好主意。

接下来,让我们来看看我们可以用接口做的几件事。

重写一个接口方法

记住,接口是一种必须由具体类来实现的契约。接口方法是隐含的抽象的,也需要一个具体类的实现。

这里有一个例子:

public class OverridingDemo {  public static void mAIn(String[] args) {    Challenger challenger = new JavaChallenger();    challenger.doChallenge();  }}interface Challenger {  void doChallenge();}class JavaChallenger implements Challenger {  @Override  public void doChallenge() {    System.out.println("Challenge done!");  }}复制代码

下面是这段代码的输出:

Challenge done!复制代码

注意这个细节,接口方法是隐式抽象的。这意味着我们不需要明确地将它们声明为抽象的。

常量变量

另一条要记住的规则是,一个接口只能包含常量变量。因此,下面的代码是可以的:

public class Challenger {    int number = 7;  String name = "Java Challenger";}复制代码

注意,这两个变量都是隐含的final 和static 。这意味着它们是常量,不依赖于一个实例,而且不能被改变。

如果我们试图改变Challenger 接口中的变量,例如,像这样:

Challenger.number = 8;Challenger.name = "Another Challenger";复制代码

我们会触发一个编译错误,像这样:

Cannot assign a value to final variable 'number'Cannot assign a value to final variable 'name'复制代码

缺省方法

当默认方法在Java 8中被引入时,一些开发者认为它们会和抽象类一样。然而这并不正确,因为接口不能有状态。

默认方法可以有一个实现,而抽象方法则不能。默认方法是lambdas和流的伟大创新的结果,但我们应该谨慎使用它们。

JDK中使用默认方法的一个方法是forEach() ,它是Iterable 接口的一部分。我们可以简单地重用forEach 方法,而不是将代码复制到每个Iterable 的实现中:

default void forEach(Consumer<? super T> action) {   // Code implementation here…复制代码

任何Iterable 实现都可以使用forEach() 方法,而不需要新的方法实现。然后,我们可以用一个默认方法来重用代码。

让我们来创建我们自己的默认方法:

public class DefaultMethodExample {  public static void main(String[] args) {    Challenger challenger = new JavaChallenger();    challenger.doChallenge();  }}class JavaChallenger implements Challenger { }interface Challenger {  default void doChallenge() {    System.out.println("Challenger doing a challenge!");  }}复制代码

下面是输出结果:

Challenger doing a challenge!复制代码

关于默认方法,需要注意的是,每个默认方法都需要一个实现。默认方法不能是静态的。

现在,让我们继续讨论抽象类。

抽象类的本质

抽象类可以有实例变量的状态。这意味着一个实例变量可以被使用和变异。这里有一个例子:

public abstract class AbstractClassMutation {  private String name = "challenger";  public static void main(String[] args) {    AbstractClassMutation abstractClassMutation = new AbstractClassImpl();    abstractClassMutation.name = "mutated challenger";    System.out.println(abstractClassMutation.name);  }}class AbstractClassImpl extends AbstractClassMutation { }复制代码

下面是输出结果:

mutated challenger复制代码

抽象类中的抽象方法

就像接口一样,抽象类可以有抽象方法。抽象方法是一个没有主体的方法。与接口不同,抽象类中的抽象方法必须明确地声明为抽象的。这里有一个例子:

public abstract class AbstractMethods {  abstract void doSomething();}复制代码

试图声明一个没有实现的方法,而且没有abstract 关键字,像这样:

public abstract class AbstractMethods {   void doSomethingElse();}复制代码

导致了一个编译错误,像这样:

Missing method body, or declare abstract复制代码

什么时候使用抽象类

当你需要实现可改变状态时,使用抽象类是一个好主意。作为一个例子,Java集合框架包括AbstractList类,它使用变量的状态。

在你不需要维护类的状态的情况下,通常使用一个接口更好。

实践中的抽象类

设计模式中的模板方法是使用抽象类的好例子。模板方法模式在具体方法中操作实例变量。

抽象类和接口的区别

从面向对象编程的角度来看,接口和抽象类的主要区别是,接口不能有状态,而抽象类可以用实例变量来有状态。

另一个关键区别是,类可以实现一个以上的接口,但它们只能扩展一个抽象类。这是一个基于多重继承(扩展一个以上的类)会导致代码死锁的设计决定。Java的工程师们决定要避免这种情况。

另一个区别是,接口可以被类实现,也可以被接口扩展,但类只能被扩展。

还需要注意的是,lambda表达式只能用于功能接口(指只有一个方法的接口),而只有一个抽象方法的抽象类不能使用lambdas。

接受Java代码挑战吧!

让我们通过一个Java代码挑战来探索接口和抽象类的主要区别。我们在下面提供了代码挑战,你也可以用视频的形式观看抽象类与接口的挑战。

在下面的代码中,同时声明了一个接口和一个抽象类,而且代码中还使用了lambdas:

public class AbstractResidentEvilInterfaceChallenge {  static int nemesisRaids = 0;  public static void main(String[] args) {    Zombie zombie = () -> System.out.println("Graw!!! " + nemesisRaids++);    System.out.println("Nemesis raids: " + nemesisRaids);    Nemesis nemesis = new Nemesis() { public void shoot() { shoots = 23; }};    Zombie.zombie.shoot();    zombie.shoot();    nemesis.shoot();    System.out.println("Nemesis shoots: " + nemesis.shoots +        " and raids: " + nemesisRaids);  }}interface Zombie {  Zombie zombie = () -> System.out.println("Stars!!!");  void shoot();}abstract class Nemesis implements Zombie {   public int shoots = 5;}复制代码

你认为当我们运行这段代码时,会发生什么?请从下列选项中选择一个。

选项A

     Compilation error at line 4复制代码

选项B

          Graw!!! 0     Nemesis raids: 23     Stars!!!     Nemesis shoots: 23 and raids:1复制代码

选项C

          Nemesis raids: 0     Stars!!!     Graw!!! 0     Nemesis shoots: 23 and raids: 1复制代码

选项D

          Nemesis raids: 0     Stars!!!     Graw!!! 1     Nemesis shoots: 23 and raids:1复制代码

选项E

     Compilation error at line 6复制代码

Java代码挑战视频

你为这个挑战选择了正确的输出吗?请观看视频或继续阅读以了解答案。

了解接口和抽象类及方法

这个Java代码挑战展示了许多关于接口、抽象方法等的重要概念。逐行浏览代码会让我们了解到输出中发生的很多事情。

代码挑战的第一行包括Zombie 接口的lambda表达式。请注意,在这个lambda中,我们正在增加一个静态字段。实例字段在这里也可以使用,但在lambda之外声明的局部变量就不行了。因此,到目前为止,这段代码可以正常编译。还要注意的是,lambda表达式还没有执行,所以nemesisRaids 字段还不会被递增。

在这一点上,我们将打印nemesisRaids 字段,它没有被增加,因为λ表达式还没有被调用,只是被声明。因此,这一行的输出将是:

Nemesis raids: 0复制代码

这个Java代码挑战中另一个有趣的概念是,我们正在使用一个匿名的内层类。这基本上意味着任何将实现Nemesis 抽象类的方法的类。我们并没有真正实例化Nemesis 抽象类,因为它实际上是一个匿名的类。还要注意的是,第一个具体的类在扩展它们的时候总是有义务实现抽象的方法。

在Zombie 接口里面,我们用一个lambda表达式声明了zombie static Zombie 接口。因此,当我们调用zombie shoot 方法时,我们会打印以下内容:

Stars!!!复制代码

下一行代码调用了我们在开始时创建的lambda表达式。因此,nemesisRaids 这个变量将被递增。然而,由于我们使用的是后增量运算符,它将只在这条代码语句之后被增量。接下来的输出将是:

Graw!!! 0 复制代码

现在,我们将从nemesis 中调用shoot 方法,这将改变其shoots 实例变量为23 。 注意,这部分代码展示了接口和抽象类之间的最大区别。

最后,我们打印nemesis.shoots 和nemesisRaids 的值。因此,输出结果将是:

Nemesis shoots: 23 and raids: 1复制代码

综上所述,正确的输出是选项C:

     Nemesis raids: 0     Stars!!!     Graw!!! 0     Nemesis shoots: 23 and raids: 1


Tags:抽象类   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
C++进阶:纯虚函数和抽象类的奥秘
在C++中,纯虚函数和抽象类是面向对象编程中重要的概念。本文将会深入探讨这两个概念的含义、如何定义和使用它们,以及它们在实际开发中的应用。 什么是纯虚函数?在C++中,纯虚函...【详细内容】
2023-09-07  Search: 抽象类  点击:(268)  评论:(0)  加入收藏
Java中的抽象类与接口详解
Java中的抽象类与接口在Java中什么时候应该选择抽象类而不是接口?接受挑战吧!了解这些Java语言元素之间的区别以及如何在你的程序中使用它们。在Java代码中,甚至在Java开发工...【详细内容】
2022-08-02  Search: 抽象类  点击:(352)  评论:(0)  加入收藏
PHP的抽象类的使用
抽象类 接口 多态* 抽象类是一种特殊的类, 接口是一种特殊的抽象类, 而多态就要使用到抽象类或是接口* 声明抽象类和接口,以及一些需要的技术 * 抽象类 * 什么是抽象方法?* 定义...【详细内容】
2020-04-04  Search: 抽象类  点击:(339)  评论:(0)  加入收藏
▌简易百科推荐
Java 8 内存管理原理解析及内存故障排查实践
本文介绍Java8虚拟机的内存区域划分、内存垃圾回收工作原理解析、虚拟机内存分配配置,以及各垃圾收集器优缺点及场景应用、实践内存故障场景排查诊断,方便读者面临内存故障时...【详细内容】
2024-03-20  vivo互联网技术    Tags:Java 8   点击:(18)  评论:(0)  加入收藏
如何编写高性能的Java代码
作者 | 波哥审校 | 重楼在当今软件开发领域,编写高性能的Java代码是至关重要的。Java作为一种流行的编程语言,拥有强大的生态系统和丰富的工具链,但是要写出性能优异的Java代码...【详细内容】
2024-03-20    51CTO  Tags:Java代码   点击:(25)  评论:(0)  加入收藏
在Java应用程序中释放峰值性能:配置文件引导优化(PGO)概述
译者 | 李睿审校 | 重楼在Java开发领域,优化应用程序的性能是开发人员的持续追求。配置文件引导优化(Profile-Guided Optimization,PGO)是一种功能强大的技术,能够显著地提高Ja...【详细内容】
2024-03-18    51CTO  Tags:Java   点击:(34)  评论:(0)  加入收藏
Java生产环境下性能监控与调优详解
堆是 JVM 内存中最大的一块内存空间,该内存被所有线程共享,几乎所有对象和数组都被分配到了堆内存中。堆被划分为新生代和老年代,新生代又被进一步划分为 Eden 和 Survivor 区,...【详细内容】
2024-02-04  大雷家吃饭    Tags:Java   点击:(63)  评论:(0)  加入收藏
在项目中如何避免和解决Java内存泄漏问题
在Java中,内存泄漏通常指的是程序中存在一些不再使用的对象或数据结构仍然保持对内存的引用,从而导致这些对象无法被垃圾回收器回收,最终导致内存占用不断增加,进而影响程序的性...【详细内容】
2024-02-01  编程技术汇  今日头条  Tags:Java   点击:(78)  评论:(0)  加入收藏
Java中的缓存技术及其使用场景
Java中的缓存技术是一种优化手段,用于提高应用程序的性能和响应速度。缓存技术通过将计算结果或者经常访问的数据存储在快速访问的存储介质中,以便下次需要时可以更快地获取。...【详细内容】
2024-01-30  编程技术汇    Tags:Java   点击:(78)  评论:(0)  加入收藏
JDK17 与 JDK11 特性差异浅谈
从 JDK11 到 JDK17 ,Java 的发展经历了一系列重要的里程碑。其中最重要的是 JDK17 的发布,这是一个长期支持(LTS)版本,它将获得长期的更新和支持,有助于保持程序的稳定性和可靠性...【详细内容】
2024-01-26  政采云技术  51CTO  Tags:JDK17   点击:(100)  评论:(0)  加入收藏
Java并发编程高阶技术
随着计算机硬件的发展,多核处理器的普及和内存容量的增加,利用多线程实现异步并发成为提升程序性能的重要途径。在Java中,多线程的使用能够更好地发挥硬件资源,提高程序的响应...【详细内容】
2024-01-19  大雷家吃饭    Tags:Java   点击:(111)  评论:(0)  加入收藏
这篇文章彻底让你了解Java与RPA
前段时间更新系统的时候,发现多了一个名为Power Automate的应用,打开了解后发现是一个自动化应用,根据其描述,可以自动执行所有日常任务,说的还是比较夸张,简单用了下,对于office、...【详细内容】
2024-01-17  Java技术指北  微信公众号  Tags:Java   点击:(108)  评论:(0)  加入收藏
Java 在 2023 年仍然流行的 25 个原因
译者 | 刘汪洋审校 | 重楼学习 Java 的过程中,我意识到在 90 年代末 OOP 正值鼎盛时期,Java 作为能够真正实现这些概念的语言显得尤为突出(尽管我此前学过 C++,但相比 Java 影响...【详细内容】
2024-01-10  刘汪洋  51CTO  Tags:Java   点击:(82)  评论:(0)  加入收藏
站内最新
站内热门
站内头条