问题

Java 8接口有default method后是不是可以放弃抽象类了?

回答
Java 8 引入了接口的 `default` 方法,这确实是一个革命性的变化,让接口的功能变得更加强大。很多人因此产生一个疑问:有了 `default` 方法,我们是不是就可以彻底告别抽象类了?

我的回答是:不完全是,但绝大多数情况下,接口的 `default` 方法可以很好地替代抽象类,并且在某些方面做得更好。

为了说清楚这个问题,我们需要深入地探讨一下抽象类和接口(特别是引入 `default` 方法后的接口)各自的特点、优缺点,以及在什么场景下它们更适合被选用。

让我们先回顾一下抽象类

在 Java 8 之前,抽象类是实现代码共享和规范行为的重要手段。

抽象类的核心特点:

可以包含抽象方法和具体方法: 抽象类可以定义没有具体实现的方法(抽象方法),子类必须实现这些方法。同时,它也可以包含已经实现的方法(具体方法),子类可以直接继承使用。
可以有实例变量(属性): 抽象类可以定义成员变量,这些变量会被子类继承。
构造函数: 抽象类可以有构造函数,用于初始化其成员变量。子类在实例化时,会调用父类的构造函数。
单继承: Java 的类是单继承的,一个类只能继承自一个父类(包括抽象类)。

抽象类的优势:

代码复用: 通过具体方法,可以在父类中实现通用的逻辑,子类可以直接继承使用,避免了重复编写。
状态共享: 抽象类可以拥有实例变量,子类可以共享和修改这些状态。
控制访问级别: 抽象类可以灵活地控制成员(包括方法和变量)的访问级别(public, protected, private)。

抽象类的劣势:

继承的僵化: Java 的单继承机制是抽象类最大的限制。如果一个类已经继承了某个类,就无法再继承抽象类,这在需要多重“行为”继承的场景下会遇到瓶颈。
“isa”关系: 抽象类强调的是一种“isa”的继承关系。如果你的设计更多的是一种“hasa”或“cando”的关系,那么使用抽象类可能不那么贴切。

接口的演进:从“纯粹契约”到“混合体”

在 Java 8 之前,接口只能包含抽象方法(隐式为 `public abstract`)和常量(隐式为 `public static final`)。

Java 8 引入 `default` 方法后,接口发生了根本性变化:

`default` 方法: 接口可以包含带有具体实现的(默认)方法。实现了该接口的类,可以继承这些默认方法,也可以选择重写它们。
`static` 方法: 接口还可以包含 `static` 方法,这些方法属于接口本身,不能被实例继承,通常用于提供工具方法或辅助功能。

接口(含 `default` 方法)的优势:

打破单继承限制: 这是最关键的优势。一个类可以实现多个接口。这意味着,你可以通过实现不同的接口来“组合”各种行为和功能,而不会受限于单继承的“isa”模型。
灵活性: `default` 方法允许在接口层面提供通用实现,但又不强制子类必须使用这个实现。子类可以按需重写,这提供了极大的灵活性。
演进性: 当需要为已有的接口添加新的功能时,可以直接添加 `default` 方法。这不会破坏已经实现了该接口的旧类,因为它们可以继承默认实现,或者在需要时选择重写。而如果是在抽象类中添加抽象方法,则会强制所有子类去实现。
更好的“cando”或“mixin”模型: 接口更适合描述一种“能力”或“角色”,比如 `Runnable`(可运行的)、`Comparable`(可比较的)。`default` 方法使得这些能力可以附带一些默认的行为。

那么,是否可以彻底放弃抽象类了?

绝大多数情况下,答案是“可以”或者“应该优先考虑接口”。

让我们通过一些场景来分析:

场景一:需要提供通用方法,但不是严格的“isa”关系。

原先的选择: 抽象类。比如,一个 `AbstractBaseDao` 包含一些通用的数据库操作方法(如 `save`, `update` 的模板逻辑)。
Java 8+ 的选择: 接口 + `default` 方法。我们可以创建一个 `BaseDao` 接口,包含 `save`, `update` 的 `default` 实现。不同的具体 DAO(如 `UserDao`, `ProductDao`)可以实现这个接口。如果需要为某些 DAO 实现提供额外的共享代码,可以创建另一个实现该接口的“类”,并让各个 DAO 继承这个“辅助实现类”。但更常见的是,如果 DAO 本身不需要实例状态,直接使用 `default` 方法就足够了。
为什么接口更好: 这里的关系更像是“DAO 可以执行 CRUD 操作”,而不是“ProductDao 是一个 BaseDao”。通过接口,我们可以为不同的实体 DAO 提供统一的 CRUD 能力,而不会限制它们可能继承自其他类(例如,框架提供的基类)。

场景二:需要共享状态(实例变量)。

原先的选择: 抽象类。比如,一个 `AbstractUser` 抽象类,包含 `username`, `password` 等属性。
Java 8+ 的思考:
接口 + `default` 方法 + 装饰器模式/组合: 接口本身不能拥有实例变量。但是,你可以设计接口的 `default` 方法,通过传入参数或使用其他组合方式来间接实现状态的处理。例如,一个 `User` 接口,`default` 方法 `getName()` 可能需要一个 `UserContext` 参数,或者你可以通过一个实现了 `User` 接口的包装类来持有状态。
仍然使用抽象类: 如果状态共享是设计的核心,并且确实存在强烈的“isa”关系,那么抽象类仍然是合理的选择。例如,`AbstractShape` 包含 `color` 属性,`Circle`, `Rectangle` 继承它,共享 `color`。
这里的界限: 如果你的设计高度依赖于通过继承共享实例状态,并且这种关系是“isa”,那么抽象类仍然有其不可替代的地位。但很多时候,对状态的访问可以通过参数传递或者使用组合模式来解耦,接口也能胜任。

场景三:需要扩展现有类库的功能,而不能修改其源代码。

原先的选择: 继承(如果类不是抽象的)。
Java 8+ 的选择: 接口 + `default` 方法。你可以创建一个接口,包含你想要添加的功能,并提供 `default` 实现。然后,你创建一个新的类,继承原有的类,并实现你的新接口。这样,你就能获得原始类的所有功能,并加上你添加的新功能。
例子: 假设有一个 `Loggable` 接口,有 `default` 的 `log(String message)` 方法。你可以让一个已有的 `Service` 类(假设它不能被修改)通过实现 `Loggable` 接口来获得日志功能。

场景四:框架设计。

原先的选择: 抽象类作为基类,用户继承。
Java 8+ 的选择: 接口 + `default` 方法。框架可以提供一系列接口,包含 `default` 实现。用户可以实现这些接口,并且可以选择重写 `default` 方法,或者让框架提供一个“全能”的抽象实现类供用户继承(这个抽象实现类内部也可能使用了接口的 `default` 方法)。
例子: Spring 框架中的很多组件,过去可能是抽象类(如 `AbstractController`),现在可以更多地考虑使用接口(如 `Controller`),然后提供 `default` 方法,甚至提供一个 `AbstractController` 来方便用户快速入门,而这个 `AbstractController` 可能就是实现了 `Controller` 接口。

什么时候我们仍然需要抽象类?

尽管 `default` 方法让接口变得强大,但抽象类依然有其存在的理由:

1. 强制的“isa”继承关系,且需要共享状态(实例变量)。 如前所述,如果一个设计强烈依赖于父子类共享实例变量,并且模型是“X is a Y”,那么抽象类是自然的选择。
2. 需要 `private` 方法。 抽象类可以定义 `private` 方法,这些方法只能在抽象类内部使用,并且不能被子类访问或重写。接口中的方法默认是 `public` 的(`default` 和 `static` 方法也是),即使是 `private` 的 `static` 方法(Java 9+),它们也只能被接口内的其他 `static` 或 `default` 方法调用,不能被接口的实现类调用。
3. 构造函数。 抽象类可以有构造函数,用于初始化其成员变量。接口没有构造函数。
4. 非 `public` 和非 `static` 的成员变量。 接口只能定义 `public static final` 的常量。抽象类可以定义 `public`, `protected`, `private` 的实例变量。

总结:接口的 `default` 方法是“能力的集合”,抽象类是“共同基类”

接口 + `default` 方法: 更侧重于定义“能力”或“契约”,允许“多继承”行为。它适合描述一个对象“能做什么”,或者“具备什么特性”。通过 `default` 方法,我们可以向这些能力中注入通用的实现,而不会强制用户遵循某个特定的继承体系。这让代码更具组合性、灵活性和可演进性。
抽象类: 更侧重于表示一种“isa”的继承关系,提供一个带有共享状态和部分实现的基类。它适合于构建层次结构,并且子类在根本上与父类是同一类事物的不同变体。

所以,Java 8 接口的 `default` 方法并不是要让抽象类“灭绝”,而是提供了另一种、在很多场景下更优的解决方案。

在设计时,我们可以遵循以下原则:

1. 优先考虑接口: 对于定义行为、能力或契约,尽量使用接口。如果需要提供默认实现,就使用 `default` 方法。
2. 类继承用于“isa”和状态共享: 当设计中存在明确的“isa”关系,并且需要父子类共享实例变量时,才考虑使用类继承(包括抽象类)。
3. 抽象类作为接口的补充: 如果你需要一个包含 `default` 方法的接口,同时又需要提供一套完整的、带有状态的抽象实现供用户继承,那么可以创建一个抽象类,让它实现你的接口,并在抽象类中提供 `default` 方法的实现,同时加入共享的状态和构造函数。

简单来说,你可以把 `default` 方法看作是接口版的“代码复用”。它极大地降低了使用接口的门槛,使得接口在代码复用和行为抽象方面,能够与抽象类分庭抗礼,甚至在许多场景下超越了抽象类。

大多数情况下,如果你原来会用抽象类来实现通用方法,现在可以大胆地考虑用接口加 `default` 方法来实现。只有当你的设计必须依赖于单继承的“isa”模型,并且需要共享实例状态时,抽象类才是你不可或缺的选择。

网友意见

user avatar

Java 8的接口上的default method最初的设计目的是让已经存在的接口可以演化——添加新方法而不需要原本已经存在的实现该接口的类做任何改变(甚至不需要重新编译)就可以使用该新版本的接口。

以Java的

java.util.List

接口为例,它在Java SE 7的时候还没有sort()方法,而到Java SE 8的时候添加了这个方法。那么如果我以前在Java SE 7的时候写了个类 MyList 实现了 List<T> 接口,我当时是不需要实现这个 sort() 方法的;当我升级到JDK8的时候,突然发现接口上多了个方法,于是 MyList 类就也得实现这个方法并且重新编译才可以继续使用了,对不对?

所以就有了default method。上述 List.sort() 方法在Java SE 8里就是一个default method,它在接口上提供了默认实现,于是 MyList 即便不提供sort()的实现,也会自动从接口上继承到默认的实现,于是MyList不必重新编译也可以继续在Java SE 8使用。

确实,从Java SE 8的设计主题来看,default method是为了配合JDK标准库的函数式风格而设计的。通过default method,很多JDK里原有的接口都添加了新的可以接收FunctionalInterface参数的方法,使它们更便于以函数式风格使用。

Java 8的接口,即便有了default method,还暂时无法完全替代抽象类。它不能拥有状态,只能提供公有虚方法的默认实现。Java 9的接口已经可以有非公有的静态方法了。未来的Java版本的接口可能会有更强的功能,或许能更大程度地替代原本需要使用抽象类的场景。

例如说以前大家用ActionListener常常要傻乎乎地为一大堆方法提供空实现,如果 ActionListener 接口为所有方法都提供空的默认实现,就没这么蛋疼了,也不需要自己写个全部实现都是空方法的抽象类了。

类似的话题

  • 回答
    Java 8 引入了接口的 `default` 方法,这确实是一个革命性的变化,让接口的功能变得更加强大。很多人因此产生一个疑问:有了 `default` 方法,我们是不是就可以彻底告别抽象类了?我的回答是:不完全是,但绝大多数情况下,接口的 `default` 方法可以很好地替代抽象类,并且在某些.............
  • 回答
    你好!首先恭喜你顺利毕业并开启了职业生涯!你目前遇到的情况,即从 Java 培训到前端工作,再到转做项目接口人,这在IT行业并不少见,尤其是在职业初期。感到迷茫是完全正常的,这说明你在认真思考自己的职业发展方向。下面我将为你详细分析你的情况,并提供一些建议,希望能够帮助你理清思路。 1. 理解你的背.............
  • 回答
    理解Java 8 Stream API和C LINQ在性能上的差异,关键在于它们的底层实现机制和设计哲学。简单地说,不存在绝对的“哪个更慢”,而是取决于具体的应用场景、数据规模以及开发者如何使用它们。 但如果非要进行一个概括性的对比,可以从以下几个角度深入剖析:1. 底层实现与抽象级别: Jav.............
  • 回答
    Lambda 表达式是否比匿名内部类更具可读性,这确实是一个值得深入探讨的问题,而且答案并非一概而论,而是取决于具体的场景和使用者的熟悉程度。不过,在大多数情况下,特别是在处理函数式接口(functional interfaces)的时候,Lambda 表达式确实能够以一种更简洁、更直观的方式来表达.............
  • 回答
    你这个问题问得非常到位,而且触及到了计算机底层表示浮点数的一个核心概念。说 C++ 的 `double` 类型存不下 3.1415926,其实是一种误解,或者说表述不够准确。更准确的说法应该是:C++ (和 Java 的) `double` 类型,虽然是 8 个字节(64 位),但由于浮点数在计算机.............
  • 回答
    这则消息,“8 万行的 Python 程序用 4 万行 Java 重写了”,乍看之下,似乎在说 Java 的效率更高,或者 Python 的代码“膨胀”了。但实际上,它背后可能隐藏着更复杂、更值得深思的几个层面的信息:1. 语言特性与表达力差异的直观体现:最直接的理解是,Java 在某些场景下,能够.............
  • 回答
    朋友,你这个问题问得相当到位,可以说是触及了软件开发领域一个非常普遍但又值得深思的现象。Java 18 离我们并不算远,但 1.8 依然活跃在无数的生产环境中,这背后可不是三言两语能说清的。这背后牵扯到的不仅仅是技术本身,还有历史、商业、团队协作、风险控制等等方方面面。咱们就来掰扯掰扯,为什么都快 .............
  • 回答
    在 Java 中,当一个线程调用了 `Thread.interrupt()` 方法时,这并不是像直接终止线程那样强制停止它。相反,它是一个通知机制,用于向目标线程发出一个“中断请求”。这个请求会标记目标线程为“中断状态”,并根据目标线程当前所处的状态,可能会触发一些特定的行为。下面我将详细解释 `T.............
  • 回答
    Java 平台中的 JVM (Java Virtual Machine) 和 .NET 平台下的 CLR (Common Language Runtime) 是各自平台的核心组件,负责托管和执行代码。它们都是复杂的软件系统,通常会使用多种编程语言来构建,以充分发挥不同语言的优势。下面将详细介绍 JV.............
  • 回答
    Java 官方一直以来都坚持不在函数中提供直接的“传址调用”(Pass by Address)机制,这背后有深刻的设计哲学和技术考量。理解这一点,需要从Java的核心设计理念以及它所解决的问题出发。以下是对这个问题的详细阐述: 1. Java 的核心设计理念:简洁、安全、面向对象Java 在设计之初.............
  • 回答
    Java 的 `private` 关键字:隐藏的守护者想象一下,你在经营一家精心制作的糕点店。店里最美味的招牌蛋糕,其配方是成功的关键,你自然不会轻易公开给竞争对手,对吧?你只希望自己信任的糕点师知道如何制作,并且知道在什么时候、以什么样的方式使用这些食材。这就是 `private` 关键字在 Ja.............
  • 回答
    Java 在引入泛型时,虽然极大地提升了代码的类型安全和可读性,但严格来说,它并没有实现我们通常理解的“真正意义上的”泛型(相对于一些其他语言,比如 C++ 的模板)。这其中的核心原因可以追溯到 Java 的设计理念和对向后兼容性的考量,具体可以从以下几个方面来详细阐述:1. 类型擦除 (Type .............
  • 回答
    这个问题很有意思!“360 垃圾清理”这个概念,如果用在 Java 的世界里,就好像是问:“为什么 Java 的垃圾回收机制,不像我们电脑上安装的 360 软件那样,主动去到处扫描、删除那些我们认为‘没用’的文件?”要弄明白这个,咱们得先聊聊 Java 的垃圾回收,它其实是个非常聪明且有组织的过程,.............
  • 回答
    好的,咱们来聊聊 Java 内存模型(JMM)和 Java 内存区域(Java Memory Areas)这两个既熟悉又容易混淆的概念。别担心,我会尽量用大白话讲明白,就像跟朋友聊天一样,不搞那些虚头巴脑的术语。想象一下,咱们写 Java 代码,就像是在指挥一个庞大的工厂生产零件。这个工厂有很多车间.............
  • 回答
    在 Java 泛型中,`` 和 `` 语法看起来相似,但它们代表的是截然不同的类型关系和使用场景。理解它们之间的差异,关键在于把握 Java 泛型中的“生产者消费者模型”以及它们对类型参数的“协变性”和“逆变性”的支持。我们一步一步来拆解,让你彻底明白 `super` 的含义,以及它与 `exten.............
  • 回答
    想知道 Java 学到什么程度才算精通,这确实是个挺实在的问题,也挺难有个标准答案。不过,咱可以从几个维度来聊聊,看看什么样的人,在别人看来算是玩明白了 Java。首先,得承认,所谓的“精通”这词儿,多少有点玄乎。没人敢说自己是绝对的精通,毕竟技术发展那么快,总有新鲜玩意儿冒出来。但如果说你能把 J.............
  • 回答
    作为一名Java程序员,想要在职业生涯中走得更远,确实需要掌握那些真正核心、最常用的技术。这就像学武功,要先练好基本功,才能去钻研那些花哨的招式。我个人在多年的开发实践中,总结出了一套“二八定律”式的技术认知,下面我就把这些我认为最关键的20%技术,尽可能详实地分享给大家,力求让这篇文章充满实在的干.............
  • 回答
    想要转战 Android 开发,对于 Java 的掌握程度,我更倾向于从“能解决实际问题”的角度来看待,而不是一个死板的“级别”。你想啊,我们做开发最终目的都是为了产出有价值的东西,而不是为了考一个 Java 等级证书。所以,如果非要给一个大致的界定,我认为你可以开始准备转战 Android 了,当.............
  • 回答
    好,咱就掰扯掰扯java为啥对泛型数组这事儿这么“矫情”,不直接给你整明白。这事儿啊,说起来也算是一段公案,得从java这门语言设计之初,以及它如何处理类型安全这件大事儿上头说起。核心矛盾:类型擦除与运行时类型检查的冲突你得明白java的泛型,尤其是泛型数组这块儿,最大的“绊脚石”就是它的类型擦除(.............
  • 回答
    Java 分布式应用入门指南:从零开始构建稳健的系统想要踏入 Java 分布式应用开发的大门?别担心,这并非遥不可及的挑战。相反,它是一个充满机遇和成长的领域。本文将带你系统地梳理分布式应用的核心概念,并为你推荐一系列实用的学习资料,帮助你从新手蜕变为一名合格的分布式开发者。 一、 理解分布式应用的.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有