I was just wondering why java.util.Scanner implements java.util.Iterator?
Scanner
implements the remove method and throws an UnsupportedOperationException.
But shouldn't a class, when implementing an interface, fulfill the contract of the interface?
What is the use of implementing iterator
and adding a method that throws an exception?
Why not just avoid the implementation of the interface and keep it simple?
One can argue that it is defined so that the class which might extend Scanner
could implement the method, like AbstractList has an add method that throws an UnsupportedOperationException
. But AbstractList
is an abstract
class, whereas Scanner
is a final
class.
Is this not a bad design practice?
Scanner is not an iterator in the sense of traversing a collection. But the idea of a Scanner is to supply it input to be "scanned", which in a sense is iterating over something (the characters in a String ).
It allows us to traverse the collection, access the data element and remove the data elements of the collection.
An Iterator is an object that can be used to loop through collections, like ArrayList and HashSet. It is called an "iterator" because "iterating" is the technical term for looping. To use an Iterator, you must import it from the java.
Scanner is a class in java. util package used for obtaining the input of the primitive types like int, double, etc. and strings.
For an official answer, see the Collections Framework Overview. Scroll down to the bottom for Design Goals.
To keep the number of core interfaces small, the interfaces do not attempt to capture such subtle distinctions as mutability, modifiability, and resizability. Instead, certain calls in the core interfaces are optional, enabling implementations to throw an
UnsupportedOperationException
to indicate that they do not support a specified optional operation. Collection implementers must clearly document which optional operations are supported by an implementation.
As previous answers have pointed out, the Collections framework could uphold Liskov Substition by decomposing its interfaces into numerous, smaller interfaces. That approach was considered and rejected, in order to minimize the number of core interfaces in the framework.
I'd say yes, it's a design flaw. The flaw is within Iterator
. This issue could be thrown in the same category as attempting to create an immutable Collection
implementation.
It violates the Interface Segregation Principle and forces the developers to include a corner case into the JavaDocs (the infamous UnsupportedOperationException
) to avoid violating the Liskov Subsitution Principle. You'll find this in Collection#remove
methods aswell.
I believe design could be improved by decomposing the interface, segregating hasNext()
and next()
into a new (immutable) interface and letting the (mutable) Iterator
interface derive from it :
interface Traversable<E> {
boolean hasNext();
E next();
}
interface Iterator<E> extends Traversable<E> {
void remove();
}
final class Scanner implements Traversable<String> {
}
Better names could definitely be used. Please don't down this post due to my bad naming choices.
Scanner
implement Iterator
in the first place?Scanner
is not an iterator in the sense of traversing a collection. But the idea of a Scanner
is to supply it input to be "scanned", which in a sense is iterating over something (the characters in a String
).
I can see why Scanner
would implement Iterator
(you were asking for a use case). For example, if you wanted to create your own Iterable
type to iterate over a String
specifying a delimiter:
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);
}
But this would have also worked if the JDK supported a Traversable
type and allowed enhanced loops to accept Traversable
items, since removing from a collection in this fashion may throw a ConcurrentModificationException
, which leads to using an iterator instead.
So is it good design? Nope. It violates ISP and results in cluttered contracts. This is simply a giagantic code smell. The real problem is the language's lack of support for immutability, which should allow developers to specify whether a behavior should mutate state, allowing behavioral contracts to be stripped of their mutability. Or something along those lines..
The JDK is filled with stuff like this (bad design choices, such as exposing length
for arrays and attempts at an ImmutableMap
I mentioned above), and changing it now would result in breaking code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With