为什么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。 考虑并拒绝了这种方法,以便最小化框架中核心接口的数量。