Почему Scanner реализует Iterator <String>?

Мне просто интересно, почему java.util.Scanner реализует java. util.Iterator?

Scanner реализует метод remove и выдает UnsupportedOperationException.

Но не должен ли класс при реализации интерфейса выполнить контракт интерфейса?

Что такое использование iterator и добавление метода, который генерирует исключение?

Почему бы не просто избежать реализации интерфейса и сохранить его простым?

Можно утверждать, что он определен так, чтобы класс, который мог бы расширять Scanner, мог реализовать метод, например AbstractList имеет add метод, который вызывает UnsupportedOperationException. Но AbstractList является классом abstract, тогда как Scanner является классом final.

Является ли это плохой проектной практикой?

Ответ 1

Я бы сказал, да, это недостаток дизайна. Это Iterator, который имеет недостаток. Эта проблема может быть отнесена к той же категории, что и попытка создать неизменную реализацию Collection.

Это нарушает принцип сегрегации интерфейса и вынуждает разработчиков включать angular случай в JavaDocs (печально известный UnsupportedOperationException), чтобы избежать нарушения Принцип замещения Лискова. Вы найдете это также в Collection#remove методах.


Я полагаю, что дизайн может быть улучшен путем разложения интерфейса, разделения hasNext() и next() на новый (неизменяемый) интерфейс и предоставления возможности (изменяемого) интерфейса Iterator получить его:

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 состоит в том, чтобы предоставить его вход для "сканирования", которое в некотором смысле перебирает что-то (символы в 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, что приводит к использованию итератора вместо этого.

Заключение

Так это хороший дизайн? Нет. Это нарушает провайдера и приводит к загроможденным контрактам. Это просто гигантский запах кода. Настоящей проблемой является отсутствие поддержки неизменяемости языка, что должно позволить разработчикам указать, должно ли поведение изменять состояние, позволяя лишать поведенческие контракты их изменчивости. Или что-то в этом роде..

JDK заполнен такими вещами (плохой выбор дизайна, такой как выставление length для массивов и попытки ImmutableMap, о котором я упоминал выше), и изменение его сейчас приведет к нарушению кода.

Ответ 2

Поскольку реализация итератора позволяет использовать сканер везде, где может использоваться только итератор, доступный только для чтения.

Кроме того, он реализует контракт. Из документации Iterator (акцент мой):

удалить() Удаляет из базовой коллекции последний элемент, возвращаемый этим итератором (дополнительная операция).

Ответ 3

Это просто не поддерживаемый метод. Чтобы избежать его реализации, для этого потребуется аналогичный интерфейс без метода удаления. Хорошая конструкция для создания нескольких аналогичных интерфейсов, чтобы избежать метода, который бросает NotImplementedException?

Любой, кто использует Scanner, знает (или скоро знает), что метод remove() не может быть использован, поэтому он действительно не имеет практического эффекта. Это также может быть no-op, так как эффективно удаляется элемент , просто не из-за remove(), но, вероятно, более ясный для исключения исключений.

Ответ 4

Официальный ответ см. в Обзор коллекций.. Прокрутите вниз до уровня Цели дизайна.

Чтобы уменьшить количество интерфейсов ядра, интерфейсы не попытаться уловить такие тонкие различия, как изменчивость, изменчивость и изменчивость. Вместо этого некоторые вызовы в ядре интерфейсы являются необязательными, что позволяет реализациям UnsupportedOperationException, чтобы указать, что они не поддерживают указанная дополнительная операция. Сборщики должны четко документ, в котором дополнительные операции поддерживаются реализацией.

Как уже отмечалось в предыдущих ответах, структура коллекций может поддерживать Liskov Substation, разлагая свои интерфейсы на многочисленные, меньшие интерфейсы. Этот подход был рассмотрен и отклонен, чтобы свести к минимуму количество основных интерфейсов в рамках.