Java接口中的可选方法


120

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

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


您指的是哪种方法?我在JavaDoc或源代码中找不到它
dcpomero 2012年


Answers:


232

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

Java语言要求接口中的每个方法都由该接口的每个实现来实现。期。这条规定没有例外。说“收藏是一个例外”,这表明对这里的实际情况非常模糊。

重要的是要认识到符合接口有两个层次:

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

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

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

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

为了详细说明问题,假设您想在层次结构中添加可修改性的概念。您需要四个新接口:ModifiableCollection,ModifiableSet,ModifiableList和ModifiableMap。以前简单的层次结构现在变成了混乱的层次结构。另外,您需要一个新的Iterator接口以用于不可修改的Collection,其中不包含remove操作。现在,您可以取消UnsupportedOperationException吗?不幸的是没有。

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

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

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

现在我们有多达二十个左右的接口和五个迭代器,并且几乎可以肯定的是,实际上仍然有一些集合不能完全适合任何一个接口。例如,Map返回的集合视图是自然的仅删除集合。另外,有些集合会根据其值拒绝某些元素,因此我们仍然没有消除运行时异常。

说完所有内容后,我们认为通过提供很少的一组核心接口(可能会抛出运行时异常)来回避整个问题,这是合理的工程折衷方案。

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

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

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


30
为+1 There are no exceptions to this rule。想知道为什么这个答案没有被标记为接受。其他人很好,但您付出的远远不够。
xyz 2012年

9
“ Java语言要求接口中的每种方法都必须由该接口的每种实现来实现。期限。此规则没有例外。” 除了...有的时候。:-) Java 8接口可以指定默认的方法实现,因此,在Java 8中……接口的每个实现都必须实现接口中的每个方法,这并不是真的,至少在一定意义上不是这样在具体的类中编码实现。
DaBlick

1
@DaBlick当我说“由每个实现实现”时,我并不是说所说的方法实现必须驻留在实现类的源代码中。即使在Java 8之前,也可以继承接口方法的实现,甚至可以继承自未实现该接口的类。例如:创建FooRunnable使用public方法实现的对象void run()。现在,创建一个类Barextends Fooimplements Runnable无overiding run。它仍然实现该方法,尽管是间接的。同样,默认方法实现仍然是实现。
劳伦斯·贡萨尔维斯

道歉。我并不是想在学究上过于挑剔,而是要引起人们对可能与原始帖子有关的Java 8功能的关注。在Java 8中,您现在可以选择不使用ANY超类或子类编码的实现。这(IMHO)开启了设计模式的新世界,其中包括一些在禁止多重继承的情况可能带来一些挑战的情况下可能适用的设计模式。我认为这将催生出一组新的非常有用的设计模式。\
DaBlick

3
@AndrewS,因为在Java 8 remove中提供了默认实现。如果您不实现它,那么您的类将获得默认实现。您提到的其他两种方法没有默认实现。
劳伦斯·贡萨尔维斯

27

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

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

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

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

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


更新:

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

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

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


我认为与其说“不搞乱”,不如说是“这就是事实”。

@pst:我相信设计师在最初实现它时的想法,但是我无法确定。我认为任何其他方法都只会造成混乱,但是再次-可能是错误的。我要在此处说明的要点是:此示例是一个例外,不是通常的例外-尽管有时可能有用-在一般情况下-如有可能应避免使用。
阿米特2012年

12
“不必实现它(它可能只创建一个抛出...的方法)”。那就是实现方法。
罗恩侯爵

1
由于不幸的是这个答案已经被接受,我建议重写一下。正如EJP已经指出的那样,“ 通常,实现类应该(并且将)实现所有方法”具有误导性。
阿尔贝托2012年

2
该“可选”,在收集装置的实现类没有实现它” -这是普通的假通过。‘没有实现’你的意思是别的东西。
djechlin

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
  }
}

现在我还没有doSomethingElse()实现,将其留给子类自由实现。那是可选的。

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

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


我可以吻你,我的朋友。
微型

16

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

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


我从来没有真正理解过可选的javadocs是什么意思。我相信他们的意思是您所说的。但大多数方法是通过标准可选: new Runnable ( ) { @ Override public void run ( ) { throw new UnsupportedOperationException ( ) ; } };
Emory

这似乎并不适用于可选的方法,而是说,例如,add((T)null)可以在一个案件,但有效期另一个。也就是说,本文仅讨论可选的异常/行为和参数(“元素限制” ...“不合格元素” ...“标记为可选的异常”),并且不涉及可选方法

9

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

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

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


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”。

对不起,英语不好。


5

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

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

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

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

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

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

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

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

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

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类提供了一种方式,使流畅的风格,而不会导致客户端代码崩溃返回null结果客户端代码的机制。


4

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

public boolean  add(E e) {

        throw new UnsupportedOperationException();
    } 

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


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中有效。


是的,对不起,我忘了提这个……现在应该编辑。
Thorben Kuck

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);
}

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



0

Oracle Java Collections教程:

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.