为什么 Scanner 实现 Iterator<String>?为什么首先要实施?ScannerIterator结论

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

Scanner实现 remove 方法并引发不受支持的操作异常

但是,在实现接口时,类难道不应该满足接口的约定吗?

实现和添加引发异常的方法有什么用?iterator

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

有人可能会争辩说,它的定义是为了让可能扩展的类可以实现该方法,就像 AbstractList 有一个 add 方法,该方法会引发 .但是是一个类,而是一个类。ScannerUnsupportedOperationExceptionAbstractListabstractScannerfinal

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


答案 1

我会说是的,这是一个设计缺陷。该漏洞位于 。此问题可能与尝试创建不可变实现属于同一类别。IteratorCollection

它违反了接口隔离原则,并迫使开发人员在JavaDocs(臭名昭著)中包含一个角落案例,以避免违反Liskov Subsitution原则。您也可以在Collection#remove methods中找到它。UnsupportedOperationException


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

interface Traversable<E> {
    boolean hasNext();
    E next();
}

interface Iterator<E> extends Traversable<E> {
    void remove();
}

final class Scanner implements Traversable<String> {

}

绝对可以使用更好的名字。请不要因为我糟糕的命名选择而关闭这篇文章。

为什么首先要实施?ScannerIterator

Scanner不是遍历集合意义上的迭代器。但是 a 的想法是为其提供要“扫描”的输入,从某种意义上说,这是在迭代某些内容(a 中的字符)。ScannerString

我能理解为什么会实现(你要求一个用例)。例如,如果要创建自己的类型以循环访问指定的分隔符:ScannerIteratorIterableString

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 支持某个类型并允许增强的循环接受项目,这也有效,因为以这种方式从集合中删除可能会抛出一个 ,这会导致使用迭代器。TraversableTraversableConcurrentModificationException

结论

那么这是好的设计吗?不。它违反了ISP并导致合同混乱。这只是一种吉加甘特代码的气味。真正的问题是该语言缺乏对不可变性的支持,这应该允许开发人员指定行为是否应该改变状态,从而允许行为契约被剥夺其可变性。或类似的东西。

JDK充满了这样的东西(糟糕的设计选择,例如公开数组和我上面提到的尝试),现在更改它会导致代码中断。lengthImmutableMap


答案 2

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

此外,它确实实现了合同。来自迭代器文档(强调我的):

remove() 从基础集合中删除此迭代器返回的最后一个元素(可选操作)。