Question

Collections.unmodifiableList(...) returns a new instance of a static inner class UnmodifiableList. Other unmodifiable collections classes are constructed same way.

Were these classes public, one had two advantages:

  • ability to indicate a more specific return value (such as UnmodifiableList), so an API user wouldn't come to the idea of modifying that collection;
  • ability to check during runtime if a List is instanceof UnmodifiableList.

So, were there any advantages not to make those classes public?

EDIT: No definitely convincing arguments were presented, so I choose the most upvoted answer.

Was it helpful?

Solution

I think both advantages are there but are not that useful. The main problems remain the same: UnmodifiableList still is a List and thus all the setters are available and the underlying collections still are modifiable. Making the class UnmodifiableList public would add to the illusion of being unmodifiable.

The nicer way would be for the compiler to help, but for that the collection class hierarchies would have to changed a lot. E.g., the collection API of Scala is way more advanced in that respect.

A disadvantage would be the introduction of at least three additional classes / interfaces into the API. Because of them not being that useful, I think leaving them out of the API is a good choice.

OTHER TIPS

Personally I completely agree with you. At the core of the problem is that fact that Java's generics are not covariant, which, in turn, is because Java's collections are mutable.

It is not possible for Java's type system to codify a type that seems to have mutators is actually immutable. Imagine if we were to start designing some solution:

interface Immutable //marker for immutability

interface ImmutableMap<K, V> extends Map<K, V>, Immutable

But then ImmutableMap is a subclass of Map, and hence Map is assignable from ImmutableMap so any method which returns such an immutable Map:

public ImmutableMap<K, V> foo();

can be assigned to a Map and can therefore be mutated at compile time:

Map<K, V> m = foo();
m.put(k, v); //oh dear

So, you can see that the addition of this type has not actually prevented us from doing anything bad. I think for this reason a judgement was made that it did not have enough to offer.


A language like scala has declaration-site variance annotations. That is, you could specify a type as being covariant (and hence immutable) as Scala's Map is (actually it's covariant in its V parameter). Hence your API can declare whether its return type is mutable or immutable.

As another aside, Scala lets you declare intersection types so that you don't even need to create the ImmutableXYZ interface as a separate entity, you could specify a method to return:

def foo : XYZ with Immutable

But then scala has a proper type system, whereas Java does not

If it important for you to check if the list was created with Collections.unmodifiableList then you can create an instance and ask for the class. Now you you can compare this class with the class of any list.

private static Class UNMODIFIABLE_LIST_CLASS = 
    Collections.unmodifiableList( new ArrayList() ).getClass();
...
if( UNMODIFIABLE_LIST_CLASS == listToTest.getClass() ){
    ...
} 

The answer to the why is quite simple: at the time, in 1998, efficient design was a bit flanky. People thought about it it wasn't apparently a priority. But there was no true, deep thinking about it.

If you want to use such a mechanism, use Guava's ImmutableList/Set/Map/...

They are explicitly Immutable and a good practice when using that library is not to return a List for instance but an ImmutableList. So you will know that a List/Set/Map/... is immutable.

Example:

private final ImmutableList constants = ...;
public final ImmutableList<String> getConstants() {
  return constants;
}

About the design itself of UnmodifiableXxx, one could have done the following:

public static final class UnmodifiableXxx implements Xxx { // don't allow extend
  // static if inside Collections
  UnmodifiableXxx (Xxx backend) { // don't allow direct instanciation
    ...
  }
  ...
}
  • ability to indicate a more specific return value (such as UnmodifiableList), so an API user wouldn't come to the idea of modifying that collection;

In a proper API, this should already be documented in the javadoc of the method returning the unmodifiable list.

  • ability to check during runtime if a List is instanceof UnmodifiableList.

Such a need indicates that the actual problem lies somewhere else. It's a flaw in the code design. Ask yourself, have you ever had the need to check if a List is an instance of ArrayList or LinkedList? Whether it's an ArrayList, LinkedList or UnmodifiableList is clearly a decision which is to be made during code write time, not during code run time. If you're encountering problems because you're attempting to modify an UnmodifiableList (for which the API developer may have very good reasions which should be already documented), then it's rather your own fault, not a runtime fault.

All with all, it makes no sense. The Collections#unmodifiableXXX(), synchronizedXXX() and checkedXXX() do in any way not represent concrete implementations. They are all just decorators which can be applied regardless of the underlying concrete implementation.

Suppose UnmodifiableList was a public class. I suspect that it would lull programmers into a false sense of security. Remember, UnmodifiableList is a view of a modifiable List. This means that the contents of an UnmodifiableList can still change via any changes made to its underlying List. A naive programmer may not understand this nuance and may expect instances of UnmodifiableList to be immutable.

I think the answer is because the method form properly knows about the generics used and requires no extra programming to pass this information through, whilst the class form would require more messing about. The method form for unmodifiableMap has two floating generic arguments, which it maps to both the generic arguments of the return type and of the passed argument.

public static <K,V> Map<K,V> unmodifiableMap(Map<? extends K, ? extends V> m) {
    return new UnmodifiableMap<K,V>(m);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top