Java接口中的可选方法
从我的理解,如果你在java中实现一个接口,在该接口中指定的方法必须由实现该接口的子类使用。
我注意到,在一些接口,如集合接口有方法被评论为可选,但究竟是什么意思? 它给我一点点,因为我认为界面中指定的所有方法将是必需的?
这里的答案似乎有很多混乱。
Java语言要求接口中的每个方法都由该接口的每个实现来实现。 期。 这条规定没有例外。 要说“集合是一个例外”,表明对这里发生的事情非常模糊的理解。
认识到有一个界面符合两个层次是很重要的:
-
Java语言可以检查什么 这几乎可以归结为:是否有一些实现每个方法?
-
实际履行合同。 也就是说,该实现是否执行界面中的文档说明它应该?
编写良好的接口将包括解释准确地从实现中期望的文档。 你的编译器不能为你检查。 你需要阅读文档,并做他们所说的。 如果你没有做合同中说的话,那么就编译器而言,你将会有一个接口的实现,但是这将是一个有缺陷/无效的实现。
当deviseCollections API时,Joshua Bloch认为,不是有非常细致的接口来区分不同的集合变体(例如:可读,可写,随机访问等),他只有非常粗糙的接口集合Collection
, List
, Set
和Map
,然后将某些操作logging为“可选”。 这是为了避免细粒度界面导致的组合爆炸。 从Java Collections APIdeviseFAQ :
为了详细说明这个问题,假设你想把可修改的概念添加到Hierarchy中。 您需要四个新的接口:ModifiableCollection,ModifiableSet,ModifiableList和ModifiableMap。 以前是一个简单的层次结构现在是一个混乱的异构。 此外,您还需要一个新的Iterator接口,用于不可修改的Collections,它不包含remove操作。 现在你可以抛弃UnsupportedOperationException了吗? 不幸的是,
考虑数组。 他们执行大部分列表操作,但不能删除和添加。 他们是“固定大小”列表。 如果要在层次结构中捕获这个概念,则必须添加两个新接口:VariableSizeList和VariableSizeMap。 您不必添加VariableSizeCollection和VariableSizeSet,因为它们与ModifiableCollection和ModifiableSet相同,但为了一致性,您可以select添加它们。 另外,您还需要一个不支持添加和删除操作的新ListIterator类,以及不可修改的List。 现在我们最多可以有十个或十二个接口,再加上两个新的Iterator接口,而不是我们原来的四个接口。 我们完了吗? 没有。
考虑日志(例如错误日志,可恢复数据对象的审计日志和日志)。 它们是自然附加序列,除了删除和设置(replace)以外,它们都支持所有的列表操作。 他们需要一个新的核心接口和一个新的迭代器。
那么不可变的集合,而不是不可修改的集合呢? (即客户不能改变的集合,因其他原因决不会改变)。 许多人认为这是所有最重要的区别,因为它允许多个线程同时访问集合,而不需要同步。 将此支持添加到types层次结构中需要四个以上的接口。
现在我们有二十个左右的接口和五个迭代器,而且几乎可以肯定的是,在实践中仍然存在一些不适合任何接口的集合。 例如,Map返回的collection-views是自然的只删除集合。 此外,有些集合会根据它们的值拒绝某些元素,所以我们仍然没有废除运行时exception。
当所有的事情都说完之后,我们觉得通过提供一小组可以抛出运行时exception的核心接口来回避整个问题是一个妥善的工程方面的妥协。
当Collections API中的方法被logging为“可选操作”时,并不意味着您可以将方法实现留在实现中,也不意味着您可以使用空方法体(一方面,许多他们需要返回结果)。 相反,这意味着一个有效的实现select(仍然符合合同)是抛出一个UnsupportedOperationException
。
请注意,由于UnsupportedOperationException
是一个RuntimeException
,因此可以从任何方法实现中抛出,就编译器而言。 例如,你可以从Collection.size()
的实现中抛出它。 然而,这样的实现会违反合同,因为Collection.size()
的文档没有说这是允许的。
另外:Java的Collections API使用的方法有点争议(但是现在可能比第一次引入的时候要less)。 在一个完美的世界里,接口不会有可选的操作,细粒度的接口将被使用。 问题在于,Java不支持推断的结构types或交集types,这就是为什么试图以“正确的方式”做事情最终在集合的情况下变得非常笨重的原因。
为了编译一个接口的实现(非抽象)类 – 所有的方法都必须实现。
但是 ,如果我们想到一个方法,它的实现是一个简单的exception,抛出一个“未实现”(就像在Collection
接口中的某些方法一样),那么Collection
接口在这种情况下是例外情况,而不是常规情况。 通常 ,实施课程应该(并将)实施所有方法。
集合中的“可选”意味着实现类不必“执行”(根据上面的术语),它只会抛出NotSupportedException
)。
一个很好的例子add()
方法为不可变集合 – 具体将只实现一个方法,除了抛出NotSupportedException
在Collection
的情况下,它是为了防止混乱的inheritance树,这将使程序员悲惨 – 但在大多数情况下,这种范例不build议,应尽可能避免。
更新:
从java 8开始,引入了一个默认的方法 。
这意味着,一个接口可以定义一个方法 – 包括其实现。
这是为了允许向接口添加function而添加的,同时还支持不需要新function的代码片段的向后兼容性。
请注意,该方法仍由所有声明它的类实现,但使用接口的定义。
Collection接口中的可选方法意味着该方法的实现允许抛出exception,但仍然必须实现。 按照文档中的规定:
一些集合实现对它们可能包含的元素有限制。 例如,有些实现禁止使用null元素,有些则对元素types有限制。 尝试添加不合格的元素将引发未检查的exception,通常为NullPointerException或ClassCastException。 试图查询不合格元素的存在可能会引发exception,也可能仅返回false; 一些实现将展现前者的行为,一些将展现后者。 更一般地说,尝试对不合格的元素进行操作,其完成不会导致将不合格的元素插入到集合中可能会引发exception,或者可能会成功执行。 这个例外在这个接口的规范中被标记为“可选的”。
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
exception。
所有的方法都必须实现代码才能编译(除了那些在Java 8+中的default
实现),但是实现并不需要做任何有用的function。 具体来说,它:
- 可能是空白的(一个空的方法)
- 可能只是抛出一个
UnsupportedOperationException
(或类似的)
后一种方法通常在集合类中被采用 – 所有的方法仍然被实现,但是如果在运行时被调用,一些方法可能会抛出一个exception。
如果我们通过grepCode中的AbstractCollection.java代码,它是所有集合实现的祖先类,它将帮助我们理解可选方法的含义。 以下是AbstractCollection类中add(e)方法的代码。 根据collections界面添加(e)方法是可选的
public boolean add(E e) { throw new UnsupportedOperationException(); }
可选方法意味着它已经在祖先类中实现了,并且在调用时抛出UnsupportedOperationException。 如果我们想让我们的集合可以修改,那么我们应该重写collection接口中的可选方法。
事实上,我受到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; }
如果你的类不需要实现可选的方法,只是“实现callback”。 如果你的类需要实现可选的方法,只需“实现CallbackExtended”。
对不起狗屎英语。
那么,这个话题已经被提出…是的..但是,想想,一个答案是失踪。 我在谈论接口的“默认方法”。 例如,让我们想象一下,你将有一个closures任何东西的类(比如析构函数或其他东西)。 假设它应该有3种方法。 我们称之为“doFirst()”,“doLast()”和“onClose()”。
所以我们说我们想让这个types的任何对象至less实现“onClose()”,但是另一个是可选的。
你可以认识到,使用接口的“默认方法”。 我知道,这大部分时间都是否定界面的原因,但如果你正在devise一个框架,这可能是有用的。
所以如果你想以这种方式来实现它,它会看起来如下
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!
所有组合都是可能的。 任何有“默认”的东西都可以实现,但是不能,但是没有必要实现。
希望我现在回答并不完全错误。
祝大家有个美好的一天!
请注意:这只适用于Java 8。
在Java 8及更高版本中,这个问题的答案仍然有效,但现在更加细微。
首先,从接受的答案这些陈述仍然是正确的:
- 接口是为了在合同中指定它们的隐式行为(一个为了被视为有效而实现的类必须遵守的行为规则声明)
- 合同(规则)和实施(规则的程序编码)之间是有区别的,
- 在接口中指定的方法必须总是被实现(在某个时刻)
那么,Java 8中的新特性又是什么呢? 当谈到“可选方法”时,以下任何一种现在都适用:
1.一个方法的实现是合同上可选的
“第三条语句”说抽象接口方法必须总是被实现,这在Java 8+中仍然是真实的。 但是,与Java集合框架一样,可以在合同中将某些抽象接口方法描述为“可选”。
在这种情况下,执行界面的作者可以select不执行该方法。 然而编译器会坚持一个实现,所以作者使用这个代码来实现特定实现类中不需要的任何可选方法:
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实现的类,那么add()
的实现是可选的,因为默认行为正是需要的。
在这种情况下,上面的“第三条语句”仍然成立,因为该方法已经在接口本身中实现了。
3.返回Optional
结果的方法
最后一种新的可选方法就是返回一个Optional
的方法。 Optional
类提供了一个更加面向对象的处理null
结果的方法。
在stream畅的编程风格中,例如在使用新的Java Streams API进行编码时常见的types,任何一点的空结果都会导致程序崩溃并导致NullPointerExceptionexception。 Optional
类提供了一种机制,可以将空结果返回给客户端代码,这样可以使stream畅样式不会导致客户端代码崩溃。
虽然它没有回答OP的问题,但值得注意的是,从Java 8向接口添加默认方法实际上是可行的 。 放置在接口的方法签名中的default
关键字将导致类有一个覆盖方法的选项,但不需要它。
我正在寻找一种方法来实现callback接口,所以实现可选的方法是必要的,因为我不想为每个callback实现每个方法。
所以,我没有使用接口,而是使用了一个空实现的类,例如:
public class MyCallBack{ public void didResponseCameBack(String response){} }
而且你可以像这样设置成员variablesCallBack,
c.setCallBack(new MyCallBack() { public void didResponseCameBack(String response) { //your implementation here } });
然后像这样调用它。
if(mMyCallBack != null) { mMyCallBack.didResponseCameBack(response); }
这样,你就不用担心每个callback实现每个方法,而只是覆盖你需要的方法。