为什么Scanner会实现Iterator ?

我只是想知道为什么java.util.Scanner实现java.util.Iterator ?

Scanner实现remove方法并抛出UnsupportedOperationException 。

但是,在实现接口时,不应该是一个类,履行接口的契约?

实现iterator和添加抛出exception的方法有什么用?

为什么不避免接口的实现并保持简单?

可以认为它被定义为可以扩展Scanner的类可以实现该方法,例如AbstractList有一个抛出UnsupportedOperationException的add方法。 但是AbstractList是一个abstract类,而Scanner是一个final类。

这不是一个糟糕的设计实践吗?

我会说Iterator有一个设计缺陷,并将其抛入与尝试创建不可变Collection实现相同的类别。

它违反了接口隔离原则,并强制开发人员将一个角落案例包含在JavaDocs (臭名昭着的UnsupportedOperationException )中,以避免违反Liskov Subsitution Principle 。 你会在Collection#remove #remove方法中找到它。

我相信设计可以通过分解接口,将hasNext()next()分隔成一个新的(不可变的)接口并让(可变的) Iterator接口从它派生来改进:

 interface Traversable { boolean hasNext(); E next(); } interface Iterator extends Traversable { void remove(); } final class Scanner implements Traversable { } 

绝对可以使用更好的名字。 由于我的命名选择不当,请不要关闭这篇文章。

为什么Scanner实现Iterator

在遍历集合的意义上, Scanner不是迭代器。 但是Scanner的想法是提供它被“ 扫描 ”的输入,这在某种意义上迭代某些东西(字符串中的String )。

我可以看到为什么Scanner会实现Iterator (你要求用例)。 例如,如果要创建自己的Iterable类型以迭代指定分隔符的String

 class ScannerWrapper implements Iterable { public Scanner scanner; public ScannerWrapper(Scanner scanner) { this.scanner = scanner; } public Iterator 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尝试),现在更改它会导致代码破坏。

因为实现迭代器允许在可以使用只读迭代器的任何地方使用扫描器。

此外,它确实实施合同。 从Iterator文档(强调我的):

remove()从底层集合中移除此迭代器返回的最后一个元素(可选操作)

它不是一种受支持的方法。 为了避免以这种方式实现它,需要一个没有remove方法的类似接口。 创建多个类似的接口只是为了避免抛出NotImplementedException的方法,这是一个好的设计吗?

任何使用Scanner人都知道(或者很快就会知道)不能使用remove()方法,因此它实际上没有实际效果。 它也可能是一个无操作,因为有效的项删除,只是因为remove() ,但是抛出exception可能更清楚。

有关官方答案,请参阅“ 集合框架概述” 。 向下滚动到设计目标的底部。

为了保持较小的核心接口数量,接口不会尝试捕获可变性,可修改性和可重新定义等细微差别。 相反,核心接口中的某些调用是可选的 ,使实现能够抛出UnsupportedOperationException以指示它们不支持指定的可选操作。 集合实现者必须清楚地记录实现支持哪些可选操作。

正如之前的答案所指出的那样,Collections框架可以通过将其接口分解为众多更小的接口来维护Liskov Substition。 考虑并拒绝了这种方法,以便最小化框架中核心接口的数量。