/ / Perché Scanner implementa Iterator <String>? - java, oop, design-patterns, java.util.scanner

Perché Scanner implementa Iterator <String>? - java, oop, design-patterns, java.util.scanner

Mi stavo chiedendo perché java.util.Scanner attrezzi java.util.Iterator?

Scanner implementa il rimuovere metodo e lancia un UnsupportedOperationException.

Ma non dovrebbe una classe, quando si implementa un'interfaccia, soddisfare il contratto dell'interfaccia?

Qual è l'uso dell'attuazione iterator e aggiungendo un metodo che genera un'eccezione?

Perché non solo evitare l'implementazione dell'interfaccia e mantenerla semplice?

Si può sostenere che è definito in modo che la classe possa estendersi Scanner potrebbe implementare il metodo, come AbstractList ha un Inserisci metodo che lancia un UnsupportedOperationException. Ma AbstractList è un abstract classe, mentre Scanner è un final classe.

Non è una cattiva pratica progettuale?

risposte:

6 per risposta № 1

Direi , Iterator ha un difetto di progettazione e lo lancia nella stessa categoria del tentativo di creare un immutabile Collection implementazione.

Viola il Interfaccia Segregation Principle e costringe gli sviluppatori a includere a caso d'angolo dentro JavaDocs (il famigerato UnsupportedOperationException) per evitare di violare il Principio di sub-concessione di Liskov. Lo troverai dentro Collection#remove anche i metodi.

Credo che la progettazione potrebbe essere migliorata scomponendo l'interfaccia, separando hasNext() e next() in una nuova interfaccia (immutabile) e lasciando che il (mutabile) Iterator l'interfaccia deriva da esso:

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

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

final class Scanner implements Traversable<String> {

}

Nomi migliori potrebbero sicuramente essere usati. Si prega di non buttar giù questo post a causa delle mie cattive scelte di denominazione.

Scanner non è un iteratore nel senso di attraversare una collezione. Ma l'idea di a Scanner è quello di fornire input per essere "digitalizzata", che in un certo senso è iterando su qualcosa (i personaggi di a String).

Posso capire perché Scanner implementerebbe Iterator (stavi chiedendo un caso d'uso). Ad esempio, se si voleva creare il proprio Iterable digitare per scorrere su a String specificando un delimitatore:

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);
}

Ma questo avrebbe funzionato anche se il JDK supportasse un Traversable digita e accetta i loop potenziati Traversable oggetti, dal momento che rimuovere da una collezione in questo modo può lanciare un ConcurrentModificationException, che porta invece a usare un iteratore.

Conclusione

Quindi è un buon design? No. Violare ISP e risultati in contratti ingombri. Questo è semplicemente un odore di codice giagantico. Il vero problema è la mancanza di supporto per l'immutabilità del linguaggio, che dovrebbe consentire agli sviluppatori di specificare se un comportamento dovrebbe mutare di stato, consentendo ai contratti comportamentali di essere privati ​​della loro mutevolezza o qualcosa del genere ..

Il JDK è pieno di cose come questa (scelte progettuali sbagliate, come l'esposizione length per matrici e tentativi di a ImmutableMap Ho menzionato sopra), e cambiarlo ora si tradurrebbe in codice di rottura.


3 per risposta № 2

Poiché l'iteratore di implementazione consente di utilizzare lo scanner ovunque sia possibile utilizzare un iteratore di sola lettura.

Inoltre implementa il contratto. Dalla documentazione di Iterator (sottolineatura mia):

rimuovere() Rimuove dalla raccolta sottostante l'ultimo elemento restituito da questo iteratore (operazione opzionale).


0 per risposta № 3

Non è solo un metodo supportato. Per evitare di implementarlo in questo modo richiederebbe un'interfaccia simile senza un metodo di rimozione. È un buon design creare più interfacce simili solo per evitare un metodo che genera NotImplementedException?

Chiunque usi un Scanner sa (o lo saprà presto) che il remove() il metodo non può essere usato, quindi non ha davvero alcun effetto pratico, potrebbe anche essere un no-op, dal momento che effettivamente l'oggetto è rimosso, solo non dovuto a remove(), ma è probabilmente più chiaro lanciare un'eccezione.


0 per risposta № 4

Per una risposta ufficiale, vedere il Panoramica del framework delle collezioni. Scorri verso il basso per Obiettivi di progettazione.

Per mantenere piccolo il numero di interfacce core, ille interfacce no tentare di cogliere tali sottili distinzioni come la mutevolezza, modificabilità e resizability. Invece, alcune chiamate nel nucleo le interfacce sono opzionale, abilitando le implementazioni per lanciare un UnsupportedOperationException per indicare che non supportano a operazione opzionale specificata. Gli esecutori della raccolta devono chiaramente documentare quali operazioni opzionali sono supportate da un'implementazione.

Come hanno indicato le risposte precedenti, ilLa struttura delle collezioni potrebbe sostenere Liskov Substition decomponendo le sue interfacce in numerose interfacce più piccole. Tale approccio è stato considerato e respinto, al fine di ridurre al minimo il numero di interfacce principali nel framework.