Java接口中的可选方法

mjs*_*sey 110 java methods interface

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

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

Lau*_*ves 219

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

Java语言要求接口中的每个方法都由该接口的每个实现实现.期.这条规定没有例外.说"收藏是一个例外"表明对这里真正发生的事情有一个非常模糊的理解.

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

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

  2. 实际上履行合同.也就是说,实现是否按照界面中的文档说它应该做什么?

    编写良好的接口将包括文档,准确解释实现的期望.您的编译器无法为您检查.您需要阅读文档,并按照他们的意思行事.如果你没有按照合同所说的那样做,那么就编译器而言,你将拥有接口的实现,但它将是一个有缺陷/无效的实现.

在设计Collections API时,Joshua Bloch决定不再使用非常细粒度的接口来区分不同的集合变体(例如:可读,可写,随机访问等),他只有非常粗略的接口集,主要是 Collection,List,SetMap,然后记录某些操作为"可选".这是为了避免由细粒度界面引起的组合爆炸.来自Java Collections API Design FAQ:

为了说明血腥细节中的问题,假设您想要将可修改性的概念添加到层次结构中.您需要四个新接口:ModifiableCollection,ModifiableSet,ModifiableList和ModifiableMap.以前简单的层次结构现在是一个混乱的层次结构.此外,您需要一个新的Iterator接口,用于不可修改的集合,不包含remove操作.你现在可以取消UnsupportedOperationException吗?不幸的是.

考虑数组.它们实现了大部分List操作,但没有删除和添加.它们是"固定大小"的列表.如果要在层次结构中捕获此概念,则必须添加两个新接口:VariableSizeList和VariableSizeMap.您不必添加VariableSizeCollection和VariableSizeSet,因为它们与ModifiableCollection和ModifiableSet相同,但您可以选择添加它们以保持一致性.此外,您还需要一种不支持添加和删除操作的新型ListIterator,以及不可修改的List.现在我们有十到十二个接口,再加上两个新的Iterator接口,而不是原来的四个接口.我们完了吗?没有.

考虑日志(例如错误日志,审计日志和可恢复数据对象的日志).它们是自然的仅附加序列,支持除remove和set(replace)之外的所有List操作.它们需要一个新的核心接口和一个新的迭代器.

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

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

当一切都说完了之后,我们觉得通过提供一小组可以引发运行时异常的核心接口来回避整个问题是一个合理的工程折衷方案.

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

注意,因为UnsupportedOperationException是一个RuntimeException你可以从任何方法实现中抛出它,就编译器而言.例如,您可以从实现中抛出它Collection.size().但是,这样的实现会违反合同,因为文档Collection.size()并未说明这是允许的.

旁白:Java的Collections API使用的方法有点争议(但现在可能比首次引入时更少).在完美的世界中,接口具有可选操作,而是使用细粒度接口.问题是Java既不支持推断的结构类型也不支持交集类型,这就是为什么在集合的情况下尝试"正确的方式"变得非常笨拙的原因.

  • +1对于"此规则没有例外".想知道为什么这个答案没有被标记为已被接受.其他人都很好,但你给了绰绰有余. (27认同)
  • "Java语言要求接口中的每个方法都由该接口的每个实现实现.期间.此规则没有例外." 除了...当有.:-) Java 8接口可以指定一个默认的方法实现,因此,在Java 8中......接口的每个方法都必须通过接口的每个实现来实现,这至少不是你必须的意义所在.编码conrete类中的实现. (6认同)
  • @DaBlick当我说“由每个实现实现”时,我并不是说所述方法实现必须驻留在实现类的源代码中。即使在 Java 8 之前,人们也可以继承接口方法的实现,甚至可以从未实现该接口的类继承。例如:创建不使用公共方法“void run()”实现“Runnable”的“Foo”。现在创建一个类“Bar”,它“扩展 Foo”并“实现 Runnable”,而不覆盖“run”。它仍然实现该方法,尽管是间接的。同样,默认方法实现仍然是一个实现。 (3认同)
  • @AndrewS因为在Java 8中`remove`被赋予了默认实现.如果您没有实现它,那么您的类将获得默认实现.您提到的其他两种方法没有默认实现. (3认同)

ami*_*mit 26

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

但是,如果我们想到一个方法,它的实现是一个简单的异常抛出'非实现'(就像Collection接口中的一些方法),那么Collection接口在这种情况下是例外,而不是常规情况.通常,实现类应该(并将)实现所有方法.

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

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

如果这样Collection做是为了防止凌乱的继承树,这将使程序员痛苦 - 但在大多数情况下,这种范式不建议,如果可能应该避免.


更新:

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

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

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

  • "不必实现它(它可能只是创建一个抛出的方法......)".那*是*实现方法. (12认同)
  • 集合中的"可选"意味着实现类不必实现它." - 这显然是错误的.通过"不必实现"你的意思是别的. (2认同)

S.R*_*R.I 19

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
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

但是,如果您正在谈论Collection接口,正如其他人所说,它们是一个例外.如果某些方法未实现并且您调用它们,则它们可能会抛出 UnsupportedOperationException异常.


MBy*_*ByD 16

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

某些集合实现对它们可能包含的元素有限制.例如,某些实现禁止null元素,并且一些实现对其元素的类型有限制.尝试添加不合格的元素会引发未经检查的异常,通常是NullPointerException或ClassCastException.试图查询不合格元素的存在可能会引发异常,或者它可能只是返回false; 一些实现将展示前一种行为,一些将展示后者.更一般地,尝试对不合格的元素进行操作,其完成不会导致将不合格的元素插入到集合中,可以在实现的选择中抛出异常或者它可以成功.此类异常在此接口的规范中标记为"可选".


Mic*_*rry 9

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

  • 可能是空白的(空方法.)
  • 可能只是扔UnsupportedOperationException(或类似)

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


Tho*_*uck 7

好吧,这个话题已经解决了……是的……但是想想,缺少一个答案。我正在谈论接口的“默认方法”。例如,假设您将有一个用于关闭任何内容的类(例如析构函数或其他东西)。假设它应该有 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!");
    }
}
Run Code Online (Sandbox Code Playgroud)

现在会发生什么,例如,如果您在名为“Test”的类中实现它,编译器将完全可以执行以下操作:

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

与输出:

first ... closing ... and finally!
Run Code Online (Sandbox Code Playgroud)

或者

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

    @Override
    public void doLast() {
        System.out.println("done!");
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

first ... closing ... done!
Run Code Online (Sandbox Code Playgroud)

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

希望我现在的回答没有完全错误。

祝大家有美好的一天!

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


Won*_*son 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;
}
Run Code Online (Sandbox Code Playgroud)

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

对不起,该死的英语。


sco*_*ttb 5

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

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

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

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

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

“第三个声明”表示必须始终实现抽象接口方法,并且在 Java 8+ 中仍然如此。但是,在 Java Collections Framework 中,可以在契约中将一些抽象接口方法描述为“可选”。

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

public SomeReturnType optionalInterfaceMethodA(...) {
    throw new UnsupportedOperationException();
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

在这个新环境中,Java 集合框架可以重写为:

public interface List<E> {
    :
    :
    default public boolean add(E element) {
        throw new UnsupportedOperationException();
    }
    :
    :
}
Run Code Online (Sandbox Code Playgroud)

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

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

3.返回Optional结果的方法

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

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