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

When should I use an interface in java?

在Java中精确使用接口的一个很好的例子将是理想的,适用于任何特定的规则。


值得一看的是集合框架。

1
2
3
4
java.util.List //interface

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

所以你可以这样写代码:

1
2
3
4
List l = new ArrayList();

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

如果将来您希望使用上述LinkedList或您自己的AwesomeList which implements List接口更改实现,那么您所要做的就是将第一行更改为:

1
2
3
List l = new MyAwesomeList();
or
List l = new LinkedList();

其余的代码将继续执行。


使用接口定义"第三方"供应商必须完全遵守和实施的应用程序编程合同(蓝图、接口)。这样,最终用户就可以根据API契约进行编码,并且可以轻松地在不更改代码的情况下切换具体实现"在引擎盖下"。

JDBCAPI是一个很好的例子。它几乎只存在接口。具体实现作为"JDBC驱动程序"提供。这使您能够独立于数据库(DB)供应商编写所有JDBC代码。只要您想切换DB供应商,就可以更改JDBC驱动程序而不更改任何Java代码行(除了任何硬编码的特定于B的SQL代码)。

另一个例子是JavaEE API,它也包含相当多的接口和抽象类。具体的实现提供为"JavaEE应用服务器"、"ServelPosits"等,如Sun GalasFISH、Apache Tomcat等。这使得您可以将WebApvices(WAR)部署到任何您喜欢的Java Web服务器。


在程序中预期波动的地方、预期变化的地方、设计需要弯曲的地方都需要接口。

从这个意义上说,实现是脆弱的:它很容易中断。这就是为什么子类化并不总是最好的解决方案,正如实现一些复杂行为的长循环方法通常是一个坏主意一样。

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

通过在程序中引入接口,您实际上引入了一些变化点,在这些变化点上您可以为该接口插入不同的实现。接口的主要目的是抽象,将"什么"与"如何"分离开来。

一个重要的规则要记住,这样做是安全的利斯科夫替代原则[Unbebob,维基百科]。虽然像Java这样的语言编译器会确保语法上一切都是有序的(正确的参数、类型、……),LSP处理语义。简而言之,LSP说接口的每一个实现都必须(也)正确地表现自己,才能像上面描述的那样真正具有可替换性。


从Oracle文档页

如果出现以下情况,请考虑使用接口:

  • 您期望不相关的类实现您的接口。例如,许多不相关的对象可以实现可序列化的接口。
  • 您希望指定特定数据类型的行为,但不关心谁实现了它的行为。
  • 您希望利用类型的多重继承。
  • 用已经有很好答案的代码示例来看看相关的SE问题。

    一个接口是否比正确的方法更重要?

    接口和抽象类有什么区别?

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


    试着理解战略设计模式


    在链接文本中查看JDK集合教程。想想收藏品。你怎么想的?可以订购,也可以不订购,也可以有副本。

    所以集合是一个具有列表(有序)和设置(无序)为子接口的接口。现在有很多关于列表的问题,如果它是同步的/或不同步的,如果它是链接的列表或不同步的,等等。每个"行为"都有自己的接口/抽象类。

    当需要在集合中指定"某些"行为时,需要使用抽象类。例如,所有集合(集合/列表等)都可以有一个"ToString"表示,它只是迭代元素(有序/非有序)并将它们串接。这种行为可以出现在"抽象集合"等中。

    如果您遵循JDK集合的层次结构,那么它是了解接口和抽象类的一个很好的地方:)


    这个答案基本上和最酷的大脑是一样的,只是在传达有用性时更明确一点。

    正如最酷的头所解释的,当您将来可能想要切换程序的可能子组件时,接口是有用的。它们还允许您更容易地分离程序结构各个部分的关注点,因为通过接口,您可以确保某些非相关类等对程序的其他部分不可见。

    例如,假设您希望读取任意数据并打印它,如下所示:

    1
    2
    3
    SomeReader someReader = new SomeReader();
    String data = someReader.readLine();
    System.out.println(data);

    这里没什么特别之处,对吧?但是,虽然这个例子很简单,但它已经绑定到SomeReader类,这意味着对该类所做的所有更改都必须传播到您使用该类的类中,特别是在重构某些内部部分时!相反,你想这样做

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

    现在打印代码不再关心具体的实现,只关心接口所暴露的部分。这通常就足够了,因为现在您只需切换一条new语句,就可以得到新的实现,以及任何仍按预期工作的实现(只要接口的契约在实现类中得到尊重!)。当您最终多次使用某个特定对象时,这特别方便-这里我只使用一次,但实际上,如果您使用的是例如列表,那么通常要执行多少个相同的列表操作?

    所以,为了真正地打破这个例子的比例,下面是您的代码最终可能会变成的样子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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中的接口是一种语言构造,但实际上它们更像是一种编程范式,而不仅仅是单个语言的特性,在某些语言中,如果正确地理解接口,那么仿真接口真的是有益的。


    基本上,当需要"忽略"一些实现细节时,可以在接口和抽象类之间进行选择。接口通常是更好的选择,因为客户机类可以实现任意数量的接口,但它们只能有一个超类("继承是一种稀缺的资源",正如他们所说)。

    为什么要抽象类或接口?因为有时候,当你写一个算法的时候,你并不关心它的一个子步骤是如何完成的,只是它是根据某种契约完成的。集合API就是一个例子,其中list是一个接口——通常,当您使用List时,您不会真正关心它是在数组中保存东西,还是在节点的链接列表中保存东西,或者以其他方式保存东西。只要它把你放在里面的东西按你放在那里的顺序放好,你就会很高兴。

    然后我们有了AbstractList:一个实现List的抽象类,它提供了几乎所有成熟List需要的实现,要创建自己的List实现,您所要做的就是扩展AbstractList并填充一些方法。这是一个很好的例子,当抽象类是一个很好的选择时——当你想要提供一个几乎完全的实现,这只是缺少一些需要由客户机代码填充的空白。

    提示:如果您创建一个只包含抽象方法的抽象类,那么您可能应该创建它的接口。


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

    1
    2
    3
    4
    public interface Xmlizable
    {
        public String toXML();
    }

    然后您可以将"可xmlizable"接口传递给只关心该接口的方法。


    远程通信接口:

    接口还可以用来定义一个商定的"协议",用于在系统的不同部分之间通信,可能通过远程调用。因此,接口只定义可以调用的内容、参数以及调用后返回的内容。例如,客户机使用接口,服务器实现具体的实际代码。

    边注:

    在爪哇中,只能从一个类中继承(扩展),但可以实现多个接口,因此有时需要使用多个继承的接口,并且在决定不使用构造函数继承时使用接口。


    OOP的一个基本原则是信息隐藏:隐藏实现细节,只向调用者显示基本服务的描述。

    Java必须为这个目标构建:接口和抽象类。您可以通过调用接口中定义的"可用方法",定义接口并编写代码,使其仅依赖于接口。

    信息隐藏既可用于读取外部类(从某种意义上说是外部的,它在您正在编写的模块之外),这样您就可以定义您需要的方法,而无需推断其具体的实现类型,或者在定义可在类外使用的数据类型时,这是一个典型的示例,例如C像前面提到的那样,集合API或J2EE。

    接口和抽象类都提供了这些细节——但是有两个主要的区别:接口支持多个继承,而抽象类可以保存基本实现。为了最大限度地提高效率,在定义接口时,还应该使用有意义的默认实现定义一个抽象类。如果接口的用户不需要扩展任何其他类,那么它可以扩展这个抽象类,否则它需要从接口实现所有方法。另一方面,确保不要直接读取这个抽象类,接口应该足够抽象。


    每当您计划在某个开发点用另一个类替换一个实现类时,都要使用接口。

    我还建议至少在更严肃的项目中使用继承接口中的所有类的接口后端:不幸的是,我不再有链接,但是语言Java的开发者曾经说过,包括类继承是设计语言时最大的错误。

    这些参数非常好:使用适当的设计,总是可以用接口继承来替换类继承,并且在代码维护方面获得了很多好处。与类继承相比,保存自然类型关系(例如,从几何图形("正方形是矩形")更容易。