Question

I'm returning an unmodifiable map from a method. How do I make sure that any code that tries to modify the returned map will get a compile time error, as opposed to a run time error?

My class:

public class Foo
{
    private final Map<String, Bar> aMap;

    public Foo()
    {
    ...
    }

    public Map<String, Bar> getMap(){
        return Collections.unmodifiableMap(aMap);
    }
}

I want this to create a compile time error:

Foo foo = new Foo();
Map<String, Bar> m = foo.getMap();
m.put('key',null); // user should be notified that the map is unmodifiable here, not at run time

Can I change the return type? Can I add an appropriate annotation?

Was it helpful?

Solution

You could create a class like

class ReadOnlyMap<K, V> {

    private final Map<K, V> map;

    public ReadOnlyMap(Map<K, V> map) {
       this.map = map;
    }

    public V get(Object key) {
        return map.get(key);
    }

    // also implement size() method
    // and everything else you want to expose
}

and use it as the return type of your method. Then your method would be

public ReadOnlyMap<String, Bar> getMap() {
    return new ReadOnlyMap(aMap);
}

Beware that this won't hinder the caller to mutate the value objects, if they're not instances of immutable classes.

OTHER TIPS

A default Map is modifiable, period.

If you want it to be unmodifiable, you'll have to write your own class that has a Map as variable, implement all methods you need again and pass it on to the map.

Since you don't want the .put() method, you skip that one.

You can create a UnmodifiableMap object that has all read methods of the Map interface but lacks the write methods. This object would wrap the map you want. It would look like :

public class UnmodifiableMap<K, V> {

    private final Map<K, V> map;

    public UnmodifiableMap(final Map<K, V> map) {
        this.map = map;
    }

    public boolean containsKey(final K key) {
        return map.containsKey(key);
    }

    public V get(final K key) {
        return map.get(key);
    }

    // And so on.

}

Beware that the values() and entrySet() methods returns modifiable collections, so you would need to wrap their returned value too.

You can not do it.

An unmodifiable Collection is an implementation choice, not a type.

The best you could do is to document it with javadoc, something like:

/**
 * Gets the map
 * @returns An unmodifiable Map
 */
public Map<String, Bar> getMap(){
    return Collections.unmodifiableMap(aMap);
}

You cannot do it with the standard classes. With the standard classes, the only way that you know that it is unmodifiable is to try to modify it and receive an UnsupportedOperationException.

You can achieve compile-time checking by creating your own UnmodifiableMap implementation that uses a normal Map internally (probably unmodifiable). It would have the usual "read" operations and they would be delegated to the underlying Map. It would not have the usual "write" operations. I would guess that you would want to also have a method that would return the underlying Map (or a copy of it) to enhance interoperability with other code.

This seems to get near what you are asking for. It does not give an error but it does give a warning!

public class Solid {
  // My map has no put.
  public interface Map<K, V> extends java.util.Map<K, V> {
    /**
     * Put to the Map.
     *
     * @deprecated SolidMap is unmodifiable. put() will fail!
     */
    @Deprecated
    @Override
    V put(K key, V value);

  }

  // Could also do this for lists etc.

  // Helper wrappers from each kind of map.
  public class HashMap<K, V> extends java.util.HashMap<K, V> implements Solid.Map<K, V> {
  }

  public class TreeMap<K, V> extends java.util.TreeMap<K, V> implements Solid.Map<K, V> {
  }
  //...

  // Test only.
  public void test() {
    Solid.Map<String, String> sm = new Solid.HashMap<>();
    // Warning that put is deprecated!
    String put = sm.put("Key", "Value");
  }

  public static void main(String args[]) {
    try {
      new Solid().test();
    } catch (Throwable t) {
      t.printStackTrace(System.err);
    }
  }

}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top