我只是想知道为什么java.util.Scanner实施java.util.Iterator?
Scanner
实施remove方法并抛出UnsupportedOperationException。
但是,在实现接口时,不应该是一个类来完成接口的合同吗?
实现iterator
并添加引发异常的方法有什么用?
为什么不避免接口的实现并保持简单?
有人可以争辩说,它被定义为可以扩展Scanner
的类可以实现该方法,例如AbstractList有add方法抛出UnsupportedOperationException
。但AbstractList
是abstract
类,而Scanner
是final
类。
这不是一个糟糕的设计实践吗?
答案 0 :(得分:8)
我说是,Iterator
有一个设计缺陷,并将其抛入与尝试创建不可变Collection
实现相同的类别。
它违反Interface Segregation Principle并强制开发人员在corner case(臭名昭着的UnsupportedOperationException
)中加入JavaDocs以避免违反Liskov Subsitution Principle。您也可以在Collection#remove
方法中找到它。
我相信设计可以通过分解界面,将hasNext()
和next()
分隔成一个新的(不可变的)接口并让(可变的)Iterator
接口派生出来来改进设计:< / p>
interface Traversable<E> {
boolean hasNext();
E next();
}
interface Iterator<E> extends Traversable<E> {
void remove();
}
final class Scanner implements Traversable<String> {
}
绝对可以使用更好的名字。由于我的命名选择不当,请不要忽略这篇文章。
Scanner
首先实施Iterator
? Scanner
不是迭代器。但是Scanner
的想法是将输入提供为&#34; 扫描&#34;这在某种意义上是迭代某些东西( String
)中的字符。
我可以看到为什么Scanner
会实现Iterator
(您要求用例)。例如,如果您想创建自己的Iterable
类型来迭代指定分隔符的String
:
class ScannerWrapper implements Iterable<E> {
public Scanner scanner;
public ScannerWrapper(Scanner scanner) {
this.scanner = scanner;
}
public Iterator<String> iterator() {
return scanner;
}
}
Scanner scanner = new Scanner("one,two,three");
scanner.useDelimiter(",");
ScannerWrapper wrapper = new ScannerWrapper(scanner);
for(String s : wrapper) {
System.out.println(s);
}
但是如果JDK支持Traversable
类型并允许增强循环接受Traversable
项,这也会有效,因为以这种方式从集合中删除可能会抛出ConcurrentModificationException
,这导致使用迭代器。
那么好的设计呢?不。它违反了ISP,导致合同混乱。这只是一种典型的代码味道。真正的问题是语言缺乏对不变性的支持,这应该允许开发人员指定行为是否应该改变状态,允许行为合同被剥夺其可变性。或者沿着这些方向的东西..
JDK充满了这样的东西(糟糕的设计选择,例如为数组公开length
并尝试上面提到的ImmutableMap
),现在更改它会导致代码破坏。
答案 1 :(得分:3)
因为实现迭代器允许在只能使用只读迭代器的地方使用扫描器。
此外,它确实实施了合同。从Iterator文档(强调我的):
删除() 从底层集合中删除此迭代器返回的最后一个元素(可选操作)。
答案 2 :(得分:0)
这不是一种受支持的方法。为了避免以这种方式实现它,需要一个没有remove方法的类似接口。创建多个类似接口只是为了避免抛出NotImplementedException
的方法?
任何使用Scanner
的人都知道(或很快就会知道)remove()
方法无法使用,所以它确实没有实际效果。它也可能是一个无操作,因为 项有效删除,只是因为remove()
,但是抛出异常可能更清楚。
答案 3 :(得分:0)
要获得正式答案,请参阅Collections Framework Overview。向下滚动到设计目标。
为了保持核心接口的数量较少,接口不会 试图捕捉可变性这样微妙的区别, 可修改性和可恢复性。相反,核心中的某些调用 接口是可选,使实现能够抛出
UnsupportedOperationException
表示他们不支持a 指定的可选操作。收集实施者必须清楚 记录实施支持哪些可选操作。
正如之前的答案所指出的那样,Collections框架可以通过将其接口分解为众多更小的接口来维护Liskov Substition。考虑并拒绝了这种方法,以便最大限度地减少框架中核心接口的数量。