Java接口中的可选方法

Optional Methods in Java Interface

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

我注意到在某些接口(例如Collection接口)中,有一些方法被注释为可选方法,但这到底是什么意思? 我觉得接口中指定的所有方法都需要吗?


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

Java语言要求接口中的每种方法都必须由该接口的每种实现来实现。期。这条规定没有例外。说"集合是个例外",这表明对这里的实际情况非常模糊。

重要的是要意识到接口的符合性分为两个层次:

  • Java语言可以检查的内容。这几乎可以归结为:每个方法都有一些实现吗?

  • 实际履行合同。也就是说,实现是否执行界面中的文档所述的内容?

    编写良好的界面将包括文档,确切解释实现的期望。您的编译器无法为您检查。您需要阅读文档,然后按他们说的做。如果您不遵守合同规定,那么就编译器而言,您将拥有该接口的实现,但这将是有缺陷/无效的实现。

  • 在设计Collections API时,约书亚·布洛赫(Joshua Bloch)决定,除了拥有非常细粒度的界面来区分不同的集合变体(例如:可读,可写,随机访问等)之外,他只会拥有非常粗糙的接口集,主要是
    CollectionListSetMap,然后将某些操作记录为"可选"。这是为了避免细粒度接口导致组合爆炸。来自Java Collections API设计常见问题解答:

    To illustrate the problem in gory detail, suppose you want to add the
    notion of modifiability to the Hierarchy. You need four new
    interfaces: ModifiableCollection, ModifiableSet, ModifiableList, and
    ModifiableMap. What was previously a simple hierarchy is now a messy
    heterarchy. Also, you need a new Iterator interface for use with
    unmodifiable Collections, that does not contain the remove operation.
    Now can you do away with UnsupportedOperationException? Unfortunately
    not.

    Consider arrays. They implement most of the List operations, but not
    remove and add. They are"fixed-size" Lists. If you want to capture
    this notion in the hierarchy, you have to add two new interfaces:
    VariableSizeList and VariableSizeMap. You don't have to add
    VariableSizeCollection and VariableSizeSet, because they'd be
    identical to ModifiableCollection and ModifiableSet, but you might
    choose to add them anyway for consistency's sake. Also, you need a new
    variety of ListIterator that doesn't support the add and remove
    operations, to go along with unmodifiable List. Now we're up to ten or
    twelve interfaces, plus two new Iterator interfaces, instead of our
    original four. Are we done? No.

    Consider logs (such as error logs, audit logs and journals for
    recoverable data objects). They are natural append-only sequences,
    that support all of the List operations except for remove and set
    (replace). They require a new core interface, and a new iterator.

    And what about immutable Collections, as opposed to unmodifiable ones?
    (i.e., Collections that cannot be changed by the client AND will never
    change for any other reason). Many argue that this is the most
    important distinction of all, because it allows multiple threads to
    access a collection concurrently without the need for synchronization.
    Adding this support to the type hierarchy requires four more
    interfaces.

    Now we're up to twenty or so interfaces and five iterators, and it's
    almost certain that there are still collections arising in practice
    that don't fit cleanly into any of the interfaces. For example, the
    collection-views returned by Map are natural delete-only collections.
    Also, there are collections that will reject certain elements on the
    basis of their value, so we still haven't done away with runtime
    exceptions.

    When all was said and done, we felt that it was a sound engineering
    compromise to sidestep the whole issue by providing a very small set
    of core interfaces that can throw a runtime exception.

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

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

    另外:Java的Collections API使用的方法有些争议(但是现在可能比第一次引入时要少)。在理想情况下,接口将没有可选的操作,而将使用细粒度的接口。问题是Java不支持推断的结构类型或交集类型,这就是为什么尝试以"正确的方式"做事最终变得非常笨拙的原因。


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

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

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

    一个很好的例子-不可变集合的add()方法-具体将只实现一个只抛出NotSupportedException的方法

    Collection的情况下,这样做是为了防止混乱的继承树,这会使程序员感到痛苦-但在大多数情况下,不建议使用此范例,应尽可能避免。

    更新:

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

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

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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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(),将其留给子类自由实现。那是可选的。

    1
    2
    3
    4
    5
    6
    class SubClass extends MyClass {
        @Override
        public void doSomethingElse() {
          // Here's my implementation.
        }
    }

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


    Collection接口中的可选方法意味着允许该方法的实现引发异常,但是无论如何都必须实现它。如文档中所指定:

    Some collection implementations have restrictions on the elements that
    they may contain. For example, some implementations prohibit null
    elements, and some have restrictions on the types of their elements.
    Attempting to add an ineligible element throws an unchecked exception,
    typically NullPointerException or ClassCastException. Attempting to
    query the presence of an ineligible element may throw an exception, or
    it may simply return false; some implementations will exhibit the
    former behavior and some will exhibit the latter. More generally,
    attempting an operation on an ineligible element whose completion
    would not result in the insertion of an ineligible element into the
    collection may throw an exception or it may succeed, at the option of
    the implementation. Such exceptions are marked as"optional" in the
    specification for this interface.


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

    • 可以为空白(空方法)。
    • 可能只抛出UnsupportedOperationException(或类似的)

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


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

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

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

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

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

    "第三条陈述"说,抽象接口方法必须始终实现,并且在Java 8+中仍然如此。但是,就像在Java Collections Framework中一样,可以在合同中将某些抽象接口方法描述为"可选"。

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

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

    在Java 7和更早的版本中,这实际上是唯一的一种"可选方法",即,如果未实现,则抛出UnsupportedOperationException的方法。该行为必须由接口协定(例如,Java Collections Framework的可选接口方法)指定。

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

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

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public interface List<E> {
        :
        :
        default public boolean add(E element) {
            throw new UnsupportedOperationException();
        }
        :
        :
    }

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

    在这种情况下,上面的"第三条语句"仍然适用,因为该方法已在接口本身中实现。

    3.返回Optional结果的方法

    最后一种新的可选方法就是返回Optional的方法。 Optional类提供了一种处理null结果的绝对面向对象的方法。

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


    嗯,这个话题已经解决了……是的,但是,想想,一个答案不见了。我在说接口的"默认方法"。
    例如,假设您将有一个用于关闭任何东西的类(例如析构函数或类似的东西)。假设它应该有3种方法。我们称它们为" doFirst()"," doLast()"和" onClose()"。

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

    您可以使用接口的"默认方法"来实现这一点。我知道,这在大多数时候都可以消除接口的原因,但是如果您正在设计框架,这可能会很有用。

    因此,如果您想以这种方式实现它,它将看起来如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public interface Closer {
        default void doFirst() {
            System.out.print("first ...");
        }
        void onClose();
        default void doLast() {
            System.out.println("and finally!");
        }
    }

    现在将发生什么,例如,如果您在一个名为" Test"的类中实现了该编译器,则可以使用以下代码:

    1
    2
    3
    4
    5
    6
    public class TestCloser implements Closer {
        @Override
        public void onClose() {
            System.out.print("closing ...");
        }
    }

    输出:

    1
    first ... closing ... and finally!

    要么

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class TestCloser implements Closer {
        @Override
        public void onClose() {
            System.out.print("closing ...");
        }

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

    输出:

    1
    first ... closing ... done!

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

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

    祝大家有美好的一天!

    [edit1]:请注意:这仅在Java 8中有效。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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"。

    对不起,英语不好。


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

    1
    2
    3
    4
    public boolean  add(E e) {

            throw new UnsupportedOperationException();
        }

    可选方法意味着它已经在祖先类中实现,并且在调用时引发UnsupportedOperationException。如果我们想使集合可修改,那么我们应该重写集合接口中的可选方法。


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

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

    1
    2
    3
    public class MyCallBack{
        public void didResponseCameBack(String response){}
    }

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

    1
    2
    3
    4
    5
    c.setCallBack(new MyCallBack() {
        public void didResponseCameBack(String response) {
            //your implementation here
        }
    });

    然后这样称呼它。

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

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


    Oracle Java Collections教程:

    To keep the number of core collection interfaces manageable, the Java platform doesn't provide separate interfaces for each variant of each collection type. (Such variants might include immutable, fixed-size, and append-only.) Instead, the modification operations in each interface are designated optional — a given implementation may elect not to support all operations. If an unsupported operation is invoked, a collection throws an UnsupportedOperationException. Implementations are responsible for documenting which of the optional operations they support. All of the Java platform's general-purpose implementations support all of the optional operations.


    尽管它不能回答OP的问题,但值得注意的是,从Java 8开始,向接口添加默认方法实际上是可行的。放置在接口的方法签名中的default关键字将导致一个类可以选择重写该方法,但不要求这样做。