在Java中什么时候应该使用接口?

71

在Java中,明确何时使用接口的一个好例子将是理想的,并且适用的任何特定规定。


2
从Dan之前发布的所有问题来看,似乎他只是逐字逐句地发布作业/考试问题。 - Pyrolistical
令人惊讶的是,这些不是考试问题或其他...今天才发现了这个网站,所以想在脑海中有些事情需要回答。感谢大家的回复,真的很有帮助,指引我朝着正确的方向前进。 - Julio
1
已经有非常非常多的问题了。你找到搜索框了吗?以防万一有人之前问过类似的问题。 - Thorbjørn Ravn Andersen
2
因为基于观点而关闭?好吧,那我想我可以随时使用接口,因为这是基于观点的,软件架构也是基于观点的。这里完全是一片混乱。 - Chryfi
12个回答

97

一个好的查看位置是集合框架。

java.util.List //interface

java.util.ArrayList //Concrete class
java.util.LinkedList //Concrete class

你可以编写这样的代码:

List l = new ArrayList();

l.add(..)
//do something else.

如果在将来您想要使用LinkedList或者自己的实现了List接口的AwesomeList来更改此实现,您只需要更改第一行为:

List l = new MyAwesomeList();
or
List l = new LinkedList();
其余的代码将会继续执行。

17
我知道这是一个旧的帖子,但这真的让我非常清楚。+1 - Josh
2
这是理解接口的简单而最佳方式。 - arun8
非常感谢!这真的很清晰,很有帮助 :) - Chen Ni
2
为什么List不能是抽象类而不是接口? - Gopala Krishnan

45
使用接口来定义应用程序编程契约(蓝图、接口),第三方供应商必须完全遵守和实现。这样,最终用户只需根据API契约编写代码,就可以轻松地在不改变代码的情况下切换具体实现。 JDBC API是一个很好的例子。它几乎只由接口组成。具体的实现是作为"JDBC驱动程序"提供的。这使您能够独立于数据库(DB)供应商编写所有的JDBC代码。只需更改JDBC驱动程序,而不需要更改任何Java代码(除了任何硬编码的DB特定SQL代码),您就可以随时切换DB供应商。
另一个例子是Jakarta EE API,它也由许多接口和抽象类组成。具体的实现是由"Jakarta EE应用服务器"(如WildFly、GlassFish、Liberty、TomEE等)提供的。这使您能够将Web应用程序存档(WAR)部署到任何应用服务器上,而无需更改任何Java代码(除非是特定于服务器的XML配置文件),以便在需要切换运行时时使用。

另请参阅:


JDBC API 是一个过时设计的例子,这是不是事实?里面有很多接口是没人用的。DB 供应商通常会提供一些 javax.sql 的实现,但几乎从不使用实现连接池的库,通常只需要一个 Driver 实现。现在真的有必要拥有 StatementPreparedStatement 吗?也许可以通过使用泛型来减少 getXX()updateXXX() 方法的数量,从而更好地设计 ResultSet 接口... - Janez Kuhar

21

接口在程序中的使用通常是为了应对变化,即在设计中需要弯曲的地方以及预期发生变化的地方。

实现本身具有脆弱性:很容易出故障。这就是为什么子类化并不总是最佳解决方案,长而复杂的方法也一般不是好主意,因为它们会在自身实现某些复杂行为时产生问题。

接口比实现更加灵活,可以处理程序设计上更多的压力。

通过在程序中引入接口,您可以在接口的变化点插入不同的实现,从而引入了程序的变化点。接口的主要目的是抽象,“做什么”和“怎么做”分离。

为了安全地使用接口,有一个重要的规则需要牢记,那就是Liskov替换原则 [UncleBob, Wikipedia]。虽然像Java这样的语言编译器会确保语法正确(正确的参数数量、类型等),LSP涉及到语义。简而言之,LSP要求接口的每个实现都必须(也)正确地表现自己,以便真正可替代如上所述。


谢谢你的回答! 对于其他人来说,UncleBob的链接已经失效,因为他有一个新的网站/域名。然而这篇文章还是被托管在他的Google驱动器上。可以通过在他的文章页面上搜索“Liskov”来找到它。 - TheAtomicOption

12

来自 Oracle 文档 page

如果满足以下条件,请考虑使用接口:

  1. 您希望不相关的类实现您的接口。例如,许多不相关的对象可以实现 Serializable 接口。
  2. 您想指定特定数据类型的行为,但不关心谁实现其行为。
  3. 您想利用类型的多重继承。

查看与代码示例相关的 SE 问题,并已有良好答案。

一个接口是否仅仅包含正确的方法?

接口和抽象类之间的区别是什么?

我该如何解释接口和抽象类的区别?


4

当您需要多个实现相同行为时,可以使用接口。这是一个接口的示例,对象可以实现它来显示它们都可以被序列化为XML。

public interface Xmlizable
{
    public String toXML();
}

那么你可以将“Xmlizable”接口传递到只关心该接口的方法中。


1
在这个例子中,您可以在将其传递给公共代码之前调用 toXML - Tom Hawtin - tackline

4
这个答案与coolest_head的基本相同,只是更加明确地传达了其有用性。
正如coolest_head所解释的那样,当您将来可能想要切换程序的可能子组件时,接口非常有用。它们还允许您更轻松地分离程序结构各部分的关注点,因为使用接口可以确保某些不相关的类等仅对程序的其他部分不可见。
例如,假设您想读取任意数据并打印它,就像这样:
SomeReader someReader = new SomeReader();
String data = someReader.readLine();
System.out.println(data);

这里没有什么花哨的东西,对吧?但是,虽然这个例子很简单,它已经与SomeReader类绑定在一起了。这意味着您对该类所做的所有更改都必须被传播到使用该类的类中 - 尤其是如果您重构了一些内部部分!相反,您应该这样做

IMyReader reader = new SomeReader();
System.out.println(reader.readLine());

你已经接近成功了 - 现在打印代码不再关心具体的实现细节,只关注接口所暴露出来的部分。通常这就足够了,因为现在你只需要切换一个 new 语句,你就可以得到新的实现,而且仍然能按照预期工作(只要实现类遵守了接口契约!)。当你需要多次使用同一个特定对象时,这尤其方便 - 在此我只使用了一次,但实际上,如果你使用列表等数据结构,你通常会对同一个列表进行多个操作。

因此,为了让这个例子更加突出,下面是你的代码可能会变成的样子:

public class RowPrinter {

    private final IMyReader reader;

    public RowPrinter(IMyReader reader) {
        this.reader = reader;
    }

    public void print() {
        IMyReader reader = getReader();
        System.out.println(reader.readLine());
    }

    protected IMyReader getReader() {
        return reader;
    }
}

注意构造函数部分吗?那就是控制反转,让我告诉你,那是一种很酷的软件工程技术。从我的经验来看,它可以帮助你解决很多问题,无论是从一个数据库产品切换到另一个,还是使代码中的某些部分线程安全。或者您只想给某个类添加日志记录层,使用实现与包装类相同接口的装饰器就可以轻松实现。这仅仅是个开始。
虽然简单的例子可以让您正确入门,但接口带来了许多好处,这些好处通常并不明显。虽然Java中的接口是一种语言结构,但它们实际上更像是一种编程范式,而不仅仅是单一语言的特性,在某些语言中,如果找到正确的方法来模拟接口,确实会有益处。

4
请查看JDK集合教程,思考集合。你想到了什么?它可能是有序的,也可能是无序的,可能有重复,也可能没有。

因此,Collection是一个具有List(有序)和Set(无序)子接口的接口。现在有许多关于列表的问题,它应该同步/还是不同步,它应该是链表还是不是等等。每个“行为”都将有自己的接口/抽象类。

当您想要在集合中指定“某些”行为时,就需要抽象类。例如,所有集合(集合/列表等)都可以具有“toString”表示,这仅需要遍历元素(有序/无序)并将它们字符串化。该行为可以存在于“AbstractCollection”等中。

如果您遵循JDK集合的层次结构,则学习接口和抽象类的绝佳场所:)


4

3
基本上,当需要“省略”一些实现细节时,可以选择接口和抽象类。接口通常是更好的选择,因为客户端类可以实现任意数量的接口,但它们只能有一个超类(正如他们所说,“继承是一种稀缺资源”)。
为什么您要选择抽象类或接口?因为有时,当您编写算法时,您并不关心特定子步骤的执行方式,只需根据某种契约完成即可。例如,Collections API 中的 List 就是一个接口 - 通常,当您使用 List 时,您并不真正关心它是将东西保存在数组中,还是在节点的链接列表中,或者以其他某种方式进行存储。只要它按照您放置它们的顺序存储您放入其中的东西,您就会感到满意。
然后我们有 AbstractList:一个实现 List 的抽象类,它提供了几乎所有完整的 List 需要的实现 - 要创建自己的 List 实现,只需扩展 AbstractList 并填写一些方法即可。这是抽象类是一个很好的选择的一个典型例子 - 当您想要提供一个几乎完整的实现,只缺少一些需要由客户端代码填补的空白时。
提示:如果您创建一个仅包含抽象方法的抽象类,则可能应将其制作为接口。

3

远程通信中的接口:

接口还可以用于定义在系统不同部分之间进行通信的“协议”,可能通过远程调用实现。因此,接口仅定义了可以调用什么、使用哪些参数以及调用后会返回什么内容。客户端使用接口,服务器实现具体的实际代码。

附注:

在Java中,您只能从一个类继承(扩展),但是您可以实现多个接口,因此有时需要在需要多重继承的情况下使用接口,并且当您决定不使用组合而选择继承时也需要使用接口。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接