In order to achieve better immutability, should my API return java.lang.Iterable<T>
or java.util.Enumeration<E>
? Since Iterable's iterator has a remove() method, one could say Enumeration is the right choice, but the javadoc says "New implementations should consider using Iterator in preference to Enumeration".
I'm aware about unmodifiable collections, but in that case we still end up with collections that expose add and remove methods.
Any other options in Java?
3 Answers 3
As Brian says (and your reference to the docs), Iterable
s are much more common and what programmers are used to. Thus, use Iterable
- it also allows to use the "foreach" statement, which does not support Enumeration
.
As the unmodifiable collections are part of the base JDK, I would say that add/remove methods in the collection, and remove methods in the iterator, are exposed is just how it is in Java.
It is instead common to simply document the API sufficiently, and throw UnsupportedOperationException
where the user violates the API.
I would suggest the implementation of Tulains.
However, throw an UnsupportedOperationException
in remove
instead of doing nothing. This communicates the interface correctly: you return an unmodifiable collection.
This is also the behaviour of standard JDK classes (for instance emptySet()
and unmodifiableList()
from the Collections
class).
For completeness, the implementation (with sufficient rep, I would have just commented on Tulains answer):
/* This is unmodifiable */
public class MyList<T> implements Iterable<T> {
@Override
public Iterator<T> iterator() {
return new Iterator<T>() {
@Override
public boolean hasNext() {
return false; // TODO: to implement
}
@Override
public T next() {
return null; // TODO: to implement
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
Note that I also moved the Iterator
to an inner class to separate the collection and its iterator. This is also how it is done, for instance, for the class Collections.UnmodifiableList
.
Iterable<T>
doesn't have a remove()
method. Iterator<E>
does.
If you return an Enumeration the client would have to iterate it like this:
while(list.hasMoreElements()){
e=nextElement();
}
If you want client code to be able to iterate it like this:
for(Element e; list){
//do something with e
}
and keep it inmmutable at the same time, you should extend Iterator
and Iterable
, make remove()
throw IllegalStateException
if called, and return an object of that type:
public class MyList<T> implements Iterable<T>, Iterator<T> {
@Override
public Iterator<T> iterator() {
return this;
}
@Override
public boolean hasNext() {
return false; // to implement
}
@Override
public T next() {
return null; // to implement
}
@Override
public void remove() {
throw new IllegalStateException("I'm inmutable");
}
public static void main(String[] args) {
MyList<String> l = new MyList<String>();
for (String s: l){
System.out.println(s);
}
}
}
You could return an object of type MyList.
-
As noted in the other answers,
remove()
should throw an exception, not do nothing. Otherwise a good answer.user949300– user94930009/14/2016 16:01:47Commented Sep 14, 2016 at 16:01 -
@user949300 You are right. I changed the answer.Tulains Córdova– Tulains Córdova09/14/2016 16:10:18Commented Sep 14, 2016 at 16:10
-
Thanks! I've updated the question to explain the Iterable's iterator has the remove() method.juliaaano– juliaaano09/16/2016 09:09:59Commented Sep 16, 2016 at 9:09
I would normally expect an Iterable
. It's a member of the Collections framework, and as such returning an Iterable
will allow you to play nicely with other components.
I take your point re. immutability, though. There isn't a particularly nice means of returning a read-only collection. You could use Collections.unmodifiableList()
but that won't provide a compile time check. The same applies to buidling a defensive copy.
Returning a Collection<? extends ElementType>
if you're at liberty to return a collection rather than an iterator, will give you the compile-time safety. There's some more related discussion here
-
Well, the wildcard-approach only prevents adding and setting elements, but not removing if I recall correctly. And it does not clearly communicate the intent of being immutable which is why some static analysis tools flag it as an issue (e.g. Sonar squid:S1452). See also the accepted answer to stackoverflow.com/q/22815023/2513200Hulk– Hulk05/17/2016 12:22:32Commented May 17, 2016 at 12:22
-
I certainly agree with you that's not a perfect solution (including the concept of 'intent'). I don't think there's a perfect intuitive solution, unfortunatelyBrian Agnew– Brian Agnew05/17/2016 12:26:30Commented May 17, 2016 at 12:26
-
Also, it does not even really prevent adding - you can still add
null
(admittedly, that's probably a minor issue in most szenarios, but...).Hulk– Hulk05/17/2016 12:31:14Commented May 17, 2016 at 12:31 -
@BrianAgnew Good insight with Producer Extends approach, but still didn't help much compared to the other options. Thanks.juliaaano– juliaaano05/17/2016 20:29:31Commented May 17, 2016 at 20:29
Iterable<T>
doesn't have aremove()
method.Iterator<E>
does.