127

据我了解,如果您在 java 中实现接口,则该接口中指定的方法必须由实现所述接口的子类使用。

我注意到在某些接口(例如 Collection 接口)中,有一些方法被注释为可选,但这究竟是什么意思?它让我有点吃惊,因为我认为接口中指定的所有方法都是必需的?

4

13 回答 13

243

这里的答案似乎有很多混乱。

Java 语言要求接口中的每个方法都由该接口的每个实现来实现。时期。这条规定没有例外。说“收藏是一个例外”意味着对这里真正发生的事情的理解非常模糊。

重要的是要意识到有两个级别的符合接口:

  1. Java 语言可以检查什么。这几乎可以归结为:每种方法都有一些实现吗?

  2. 实际履行合同。也就是说,实现是否按照接口中的文档所说的去做?

    编写良好的接口将包括文档,准确解释实现的预期。您的编译器无法为您检查。您需要阅读文档,并按照他们所说的去做。如果您不按照合同的规定执行,那么就编译器而言,您将拥有接口的实现,但这将是有缺陷/无效的实现。

在设计集合 API 时,Joshua Bloch 决定不再使用非常细粒度的接口来区分集合的不同变体(例如:可读、可写、随机访问等),而是只使用非常粗略的接口集,主要是 Collection, List,SetMap, 然后将某些操作记录为“可选”。这是为了避免细粒度接口导致的组合爆炸。来自Java 集合 API 设计常见问题解答

为了详细说明问题,假设您想将可修改性的概念添加到层次结构中。您需要四个新接口:ModifiableCollection、ModifiableSet、ModifiableList 和 ModifiableMap。以前简单的等级制度现在变成了杂乱无章的等级制度。此外,您需要一个新的 Iterator 接口来与不可修改的集合一起使用,该接口不包含删除操作。现在你可以取消 UnsupportedOperationException 了吗?不幸的是没有。

考虑数组。它们实现了大多数 List 操作,但没有删除和添加。它们是“固定大小”的列表。如果你想在层次结构中捕捉这个概念,你必须添加两个新接口:VariableSizeList 和 VariableSizeMap。您不必添加 VariableSizeCollection 和 VariableSizeSet,因为它们与 ModifiableCollection 和 ModifiableSet 相同,但为了保持一致性,您可以选择添加它们。此外,您需要一种新的不支持添加和删除操作的 ListIterator 来配合不可修改的 List。现在我们有多达十个或十二个接口,加上两个新的迭代器接口,而不是我们原来的四个。我们完了吗?不。

考虑日志(例如可恢复数据对象的错误日志、审计日志和日志)。它们是自然的仅追加序列,支持除删除和设置(替换)之外的所有列表操作。它们需要一个新的核心接口和一个新的迭代器。

与不可修改的集合相比,不可变集合又如何呢?(即,客户无法更改的集合,并且永远不会因任何其他原因而更改)。许多人认为这是最重要的区别,因为它允许多个线程同时访问一个集合而无需同步。将此支持添加到类型层次结构需要另外四个接口。

现在我们有多达 20 个左右的接口和 5 个迭代器,而且几乎可以肯定,在实践中仍然会出现不完全适合任何接口的集合。例如, Map 返回的集合视图是自然的仅删除集合。此外,有些集合会根据它们的值拒绝某些元素,所以我们仍然没有消除运行时异常。

当一切都说完了,我们觉得这是一个合理的工程妥协,通过提供一组非常小的可以引发运行时异常的核心接口来回避整个问题。

当 Collections API 中的方法被记录为“可选操作”时,这并不意味着您可以将方法实现留在实现中,也不意味着您可以使用空的方法主体(一方面,许多他们需要返回一个结果)。相反,这意味着一个有效的实现选择(仍然符合合同)是抛出一个UnsupportedOperationException.

请注意,因为UnsupportedOperationExceptionis aRuntimeException您可以从任何方法实现中抛出它,就编译器而言。例如,您可以从Collection.size(). 但是,这样的实现会违反合同,因为文档Collection.size()没有说这是允许的。

另外:Java 的 Collections API 使用的方法有些争议(不过,现在可能比首次引入时少)。在一个完美的世界里,接口不会有可选的操作,而是使用细粒度的接口。问题是 Java 既不支持推断的结构类型也不支持交集类型,这就是为什么尝试以“正确的方式”做事最终在集合的情况下变得极其笨拙。

于 2012-05-13T22:13:54.087 回答
30

为了编译接口的实现(非抽象)类 - 必须实现所有方法。

但是,如果我们认为一个方法的实现是一个简单的异常抛出作为“未实现”(如接口中的某些方法Collection),那么Collection接口在这种情况下是异常,而不是常规情况。通常,实现类应该(并且将)实现所有方法。

集合中的“可选”意味着实现类不必“实现”(根据上面的术语)它,它只会抛出 NotSupportedException)。

一个很好的例子 -add()不可变集合的方法 - 具体将只实现一个除了抛出之外什么都不做的方法NotSupportedException

如果这样Collection做是为了防止混乱的继承树,那会让程序员很痛苦——但在大多数情况下,不建议使用这种范例,如果可能的话应该避免使用。


更新:

从 java 8 开始,引入了默认方法

这意味着,一个接口可以定义一个方法——包括它的实现。
添加此功能是为了允许向接口添加功能,同时仍支持不需要新功能的代码片段的向后兼容性。

请注意,该方法仍然由声明它的所有类实现,但使用接口的定义。

于 2012-05-13T14:51:32.393 回答
20

Java 中的接口只是声明实现类的协定。该接口中的所有方法都必须实现,但实现类可以自由地将它们保留为未实现,即空白。作为一个人为的例子,

interface Foo {
  void doSomething();
  void doSomethingElse();
}

class MyClass implements Foo {
  public void doSomething() {
     /* All of my code goes here */
  }

  public void doSomethingElse() {
    // I leave this unimplemented
  }
}

现在我没有doSomethingElse()实现,让我的子类可以自由实现。那是可选的。

class SubClass extends MyClass {
    @Override
    public void doSomethingElse() {
      // Here's my implementation. 
    }
}

但是,如果您谈论的是 Collection 接口,正如其他人所说,它们是一个例外。如果某些方法未实现而您调用它们,它们可能会引发 UnsupportedOperationException异常。

于 2012-05-13T14:57:32.400 回答
16

Collection 接口中的可选方法意味着该方法的实现允许抛出异常,但无论如何都必须实现。如文档中所述

一些集合实现对它们可能包含的元素有限制。例如,一些实现禁止空元素,而一些实现对其元素的类型有限制。尝试添加不合格的元素会引发未经检查的异常,通常是 NullPointerException 或 ClassCastException。尝试查询不合格元素的存在可能会引发异常,或者它可能只是返回 false;一些实现会表现出前一种行为,而另一些会表现出后者。更一般地,尝试对不合格元素执行操作,该不合格元素的完成不会导致将不合格元素插入集合中,这可能会引发异常,也可能会成功,这取决于实现的选择。此类例外被标记为“可选”

于 2012-05-13T14:50:40.643 回答
10

必须实现所有方法才能编译代码(除了那些default在 Java 8+ 中实现的方法),但实现不必做任何功能上有用的事情。具体来说,它:

  • 可能为空白(一个空方法。)
  • 可能只是扔一个UnsupportedOperationException(或类似的)

后一种方法通常在集合类中采用 - 所有方法仍然实现,但如果在运行时调用,有些方法可能会抛出异常。

于 2012-05-13T14:48:44.077 回答
7

在 Java 8 及更高版本中,这个问题的答案仍然有效,但现在更加细微。

首先,来自已接受答案的这些陈述仍然正确:

  • 接口旨在指定它们在契约中的隐含行为(实现类必须遵守的行为规则声明才能被认为是有效的)
  • 合同(规则)和实施(规则的程序化编码)之间存在区别
  • 必须始终实现接口中指定的方法(在某些时候)

那么,Java 8 中新的细微差别是什么?当谈到“可选方法”时,以下任何一种现在都很合适:

1. 一种方法,其实现在合同上是可选的

“第三条声明”表示必须始终实现抽象接口方法,这在 Java 8+ 中仍然适用。但是,就像在 Java 集合框架中一样,可以在合同中将一些抽象接口方法描述为“可选”。

在这种情况下,实现接口的作者可以选择不实现该方法。然而,编译器将坚持实现,因此作者将此代码用于特定实现类中不需要的任何可选方法:

public SomeReturnType optionalInterfaceMethodA(...) {
    throw new UnsupportedOperationException();
}

在 Java 7 和更早的版本中,这确实是唯一一种“可选方法”,即如果没有实现,就会抛出 UnsupportedOperationException 的方法。这种行为必须由接口契约指定(例如,Java 集合框架的可选接口方法)。

2.一个默认方法,其重新实现是可选的

Java 8 引入了默认方法的概念。这些方法的实现可以由接口定义本身提供。通常只有在方法体可以使用其他接口方法(即“原语”)编写时才可能提供默认方法,并且何时this可以表示“其类已实现该接口的该对象”。

默认方法必须满足接口的约定(就像任何其他接口方法实现必须一样)。因此,在实现类中指定接口方法的实现由作者自行决定(只要行为适合他或她的目的)。

在这个新环境中,Java Collections Framework可以重写为:

public interface List<E> {
    :
    :
    default public boolean add(E element) {
        throw new UnsupportedOperationException();
    }
    :
    :
}

这样,add()如果实现类没有提供自己的新行为,则“可选”方法具有抛出 UnsupportedOperationException 的默认行为,这正是您希望发生的并且符合 List 的约定。如果作者正在编写一个不允许将新元素添加到 List 实现的类,则实现add()是可选的,因为默认行为正是需要的。

在这种情况下,上面的“第三条语句”仍然成立,因为接口本身已经实现了方法。

3.返回Optional结果的方法

最后一种新的可选方法只是一个返回Optional. 该类Optional提供了一种更加面向对象的方式来处理null结果。

在流畅的编程风格中,例如在使用新的 Java Streams API 编码时常见的那种,任何时候的 null 结果都会导致程序崩溃并出现 NullPointerException。该类Optional提供了一种将空结果返回到客户端代码的机制,该机制可以启用流畅的样式,而不会导致客户端代码崩溃。

于 2016-12-22T23:22:26.813 回答
5

事实上,我受到 SurfaceView.Callback2 的启发。我认为这是官方的方式

public class Foo {
    public interface Callback {
        public void requiredMethod1();
        public void requiredMethod2();
    }

    public interface CallbackExtended extends Callback {
        public void optionalMethod1();
        public void optionalMethod2();
    }

    private Callback mCallback;
}

如果您的类不需要实现可选方法,只需“实现回调”。如果您的类需要实现可选方法,只需“实现 CallbackExtended”。

对不起,糟糕的英语。

于 2015-03-11T19:14:45.620 回答
4

如果我们通过 grepCode 中的AbstractCollection.java的代码,它是所有集合实现的祖先类,它将帮助我们理解可选方法的含义。这是 AbstractCollection 类中 add(e) 方法的代码。add(e) 方法根据集合接口是可选的

public boolean  add(E e) {

        throw new UnsupportedOperationException();
    } 

可选方法意味着它已经在祖先类中实现,并在调用时抛出 UnsupportedOperationException。如果我们想让我们的集合可修改,那么我们应该覆盖集合接口中的可选方法。

于 2013-02-11T07:08:49.497 回答
4

好吧,这个话题已经解决了......是的..但是想想,一个答案是缺失的。我在谈论接口的“默认方法”。例如,假设您将有一个用于关闭任何东西(如析构函数或其他东西)的类。假设它应该有3种方法。我们称它们为“doFirst()”、“doLast()”和“onClose()”。

所以我们说我们希望该类型的任何对象至少实现“onClose()”,但另一个是可选的。

您可以使用接口的“默认方法”来实现这一点。我知道,大多数时候这会否定接口的原因,但如果你正在设计一个框架,这可能很有用。

因此,如果您想以这种方式实现它,它将如下所示

public interface Closer {
    default void doFirst() {
        System.out.print("first ... ");
    }
    void onClose();
    default void doLast() {
        System.out.println("and finally!");
    }
}

现在会发生什么,例如,如果您在一个名为“Test”的类中实现它,编译器将完全可以使用以下内容:

public class TestCloser implements Closer {
    @Override
    public void onClose() {
        System.out.print("closing ... ");
    }
}

输出:

first ... closing ... and finally!

或者

public class TestCloser implements Closer {
    @Override
    public void onClose() {
        System.out.print("closing ... ");
    }

    @Override
    public void doLast() {
        System.out.println("done!");
    }
}

输出:

first ... closing ... done!

所有组合都是可能的。任何带有“默认”的东西都可以实现,但不能,但是任何没有“默认”的东西都必须实现。

希望我现在回答不是完全错误的。

祝大家有美好的一天!

[edit1]:请注意:这只适用于 Java 8。

于 2016-07-31T19:02:26.553 回答
1

我正在寻找一种实现回调接口的方法,因此实现可选方法是必要的,因为我不想为每个回调实现每个方法。

因此,我没有使用接口,而是使用了一个具有空实现的类,例如:

public class MyCallBack{
    public void didResponseCameBack(String response){}
}

你可以像这样设置成员变量CallBack,

c.setCallBack(new MyCallBack() {
    public void didResponseCameBack(String response) {
        //your implementation here
    }
});

然后这样称呼它。

if(mMyCallBack != null) {
    mMyCallBack.didResponseCameBack(response);
}

这样,您不必担心每次回调都实现每个方法,而只需覆盖您需要的方法。

于 2015-10-27T21:19:19.120 回答
0

虽然它没有回答 OP 的问题,但值得注意的是,从 Java 8 开始,向接口添加默认方法实际上是可行的。放置在接口的方法签名中的default关键字将导致类可以选择覆盖该方法,但不需要它。

于 2015-07-03T00:22:06.030 回答
0

Oracle 的 Java 集合教程:

为了保持核心集合接口的数量易于管理,Java 平台没有为每个集合类型的每个变体提供单独的接口。(这样的变体可能包括不可变、固定大小和仅附加。)相反,每个接口中的修改操作都被指定为可选的——给定的实现可能选择不支持所有操作。如果调用了不受支持的操作,则集合将引发UnsupportedOperationException。实现负责记录它们支持哪些可选操作。Java 平台的所有通用实现都支持所有可选操作。

于 2018-12-04T08:39:37.410 回答
0

我想对接口中的“可选”方法做一些澄清,请记住这篇文章已经有将近 10 年的历史了。

正如已经提到的(尤其是@amit),从 Java 8 开始,接口可以有default方法。这是您可以解决此问题的一种方法,因为实现类不需要实现默认方法(因此您可以从实现的角度调用“可选”)。然而,事实上,一个实现类仍然可以调用这个方法,因为接口正在定义它。因此,实际上,它不是可选行为,因为该类已经包含接口强加的(默认)行为。我有一个更好的选择,我稍后会提到。

我要说明的第二点是,在绝大多数情况下实施“什么都不做”方法是一个糟糕的想法。“什么都不做”方法会引入副作用,这些副作用在大多数情况下弊大于利,而且您可能永远不会意识到这些副作用。作为一个例子,我想用 JUnit 测试来说明这一点。如果您使用一堆“什么都不做”测试方法创建一个测试类,框架将执行这些方法并将它们标记为通过,而实际上没有执行任何测试。这是一个非常危险的副作用。将此场景扩展到您可能创建的包含“什么都不做”方法的公共库。客户端可能不知道这些空实现,并且可能在不知道它们不做任何事情的情况下调用这些方法。我可以举例说明更多的例子,但这不是我回答的重点。话虽如此,实现“什么都不做”方法并不是“可选”行为的示例。该方法首先实现的事实已经证明它不是可选的(“什么都不做”行为与不存在的行为不同)。

无论您使用什么版本的 Java,解决此问题的最佳方法是遵循 SOLID 原则中的“I”:接口隔离原则。使用 ISP,您可以在单独的接口中分离可选和必需的方法。假设您需要创建一个Calculator并且您打算使用一个CalculatorOperations接口。但是这个界面的操作比你需要的要多,因为你的计算器只需要使用四种基本的数学运算(加法、减法、乘法、除法)。ISP 声明不应强迫任何客户端依赖它不使用的方法。因此,处理这个问题的最好方法是将CalculatorOperations接口分成两个或多个接口;例如,BasicOperationsAdvancedOperations. 一旦您通过使用不同的接口将必需的功能与可选功能分离,您可以让您的类实现所需的接口,或者更好的是,在需要时使用依赖注入 (DI) 来注入所需的行为(甚至删除它)。例如,使用 DI 可以让您通过简单地单击按钮(即 Windows 计算器)来显示和隐藏操作。

我知道计算器操作接口是一个微不足道的例子,但关键是 ISP 是使用 Java 中的接口明确区分必需操作和可选操作的最佳方法。

于 2021-09-23T18:34:50.207 回答