K Erlandsson
K Erlandsson

Reputation: 13696

How can I implement an iterable class and expose a truly immutable API?

I want to implement a immutable class that is Iterable in Java. I do it the following way:

public final class MyIterable implements Iterable<Integer> {

    private final Collection<Integer> items;

    public MyIterable(Collection<Integer> items) {
        this.items = Collections.unmodifiableCollection(new LinkedList<Integer>(items));
    }

    @Override
    public Iterator<Integer> iterator() {
        return items.iterator();
    }
}

This is all good, I cannot change anything in my instances. However, my class still exposes an API that indicates that its internals could be modified through the remove() method on the iterator:

MyIterable myIterable = new MyIterable(Arrays.asList(1, 2, 3));
for (Iterator<Integer> it = myIterable.iterator(); it.hasNext();) {
    it.remove(); // I do not want to expose this even 
                 // though I know it will throw an error at runtime.
    it.next();
}

Is there some way that I can avoid exposing this remove method and hence have my class exposing a truly immutable API? Ideal would be implementing something like ReadOnlyIterable instead, but something like that does not seem to be available in Java.

Upvotes: 0

Views: 212

Answers (5)

corsiKa
corsiKa

Reputation: 82579

It would seem to indicate it by virtue of its method signatures, but those methods are specifically documented to say "No, we do not supply those behaviors!"

In order to carry this through, you would have to also document that you follow identical behaviors to UnmodifiableList.

It is the responsibility of developers to know what the libraries they use do, and the responsibility of library creators to make that information available.

Bottom line is, Iterable is baked into the language (the for(a:b) loop) and while you could create your own interface that is ReadOnlyIterable it simply won't be as robust.

The problem with this approach is that you sacrifice compile-time integrity. In other words, someone can compile code that uses the remove() method, and they won't find out until runtime that it doesn't work. This means if they happen to not test properly, you won't find out until it's in production that the method shouldn't be used.

You could perhaps use annotations and warnings to mitigate this - if they listen to warnings, or use an IDE that tells them about warnings, they'll find out much earlier. But you can't rely on users to do the right thing. That's why you have to throw an exception instead of doing nothing.

Upvotes: 3

Natix
Natix

Reputation: 14257

You can follow Guava's approach, which is defining an abstract subclass of Iterator that guarantees that the remove() method is not supported and will always throw an UnsupportedOperationException.

This is the source of UnmodifiableIterator. Note that the remove() method is final:

public abstract class UnmodifiableIterator<E> implements Iterator<E> {
  /** Constructor for use by subclasses. */
  protected UnmodifiableIterator() {}

  /**
   * Guaranteed to throw an exception and leave the underlying data unmodified.
   *
   * @throws UnsupportedOperationException always
   */
  @Override
  public final void remove() {
    throw new UnsupportedOperationException();
  }
}

Now, in any immutable iterable, you can change the return type of iterator() method from

public Iterator iterator() { ... }

to:

public UnmodifiableIterator iterator() { ... }

This is possible thanks to covariant return types introduced in Java 5. Now this gives the client a statically typed confidence that the iterator is surely immutable.

However, you should still provide a JavaDoc for the method that emphasizes this behaviour, because the return type can still easily be overlooked, if the client doesn't intentionally look for it.

Upvotes: 2

OldCurmudgeon
OldCurmudgeon

Reputation: 65859

One alternative you could use (and I add this more for completeness than as a recommendation) is to offer an Enumeration. See here for details.

Sadly you will not be able to use the new for loop on it but it is, by definition, immutable.

Here's an example:

class E implements Enumeration<Integer> {
  int i = 0;

  @Override
  public boolean hasMoreElements() {
    return true;
  }

  @Override
  public Integer nextElement() {
    return ++i;
  }

}

Upvotes: 1

Awesomedude8888
Awesomedude8888

Reputation: 49

Use the try statement so when it throws an error you can deal with it using a null System.out.print().

Upvotes: -1

If you want to have your class to be Iterable then no. You must deal with it. You should add JavaDoc, and throw proper exception when a develoer that use the API call remove().

Upvotes: 1

Related Questions