You can make an unmodifiable map whose map values are also unmodifiable by extending AbstractMap.
To implement an unmodifiable map, the programmer needs only to extend this class and provide an implementation for the entrySet method, which returns a set-view of the map's mappings. Typically, the returned set will, in turn, be implemented atop AbstractSet. This set should not support the add or remove methods, and its iterator should not support the remove method.
package mcve;
import java.util.*;
public final class UnmodifiableMapOfMaps<K, L, V> extends AbstractMap<K, Map<L, V>> {
// Optionally Map<? extends K, ? extends Map<? extends L, ? extends V>>
// but it would make the code a lot more difficult to read.
private final Map<K, Map<L, V>> map;
public UnmodifiableMapOfMaps(Map<K, Map<L, V>> map) {
this.map = Objects.requireNonNull(map);
}
// Additionally override size(), get(), etc., entirely optionally.
// Doing so would merely be an optimization since AbstractMap
// implements those methods via the entrySet() iterator.
@Override
public Set<Entry<K, Map<L, V>>> entrySet() {
return new AbstractSet<Entry<K, Map<L, V>>>() {
@Override
public int size() {
return map.size();
}
@Override
public Iterator<Entry<K, Map<L, V>>> iterator() {
return new Iterator<Entry<K, Map<L, V>>>() {
private final Iterator<Entry<K, Map<L, V>>> it = map.entrySet().iterator();
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public Entry<K, Map<L, V>> next() {
// Return the Entry as an immutable Entry with an
// unmodifiableMap as the value.
Entry<K, Map<L, V>> entry = it.next();
K key = entry.getKey();
Map<L, V> val = Collections.unmodifiableMap(entry.getValue());
return new SimpleImmutableEntry<>(key, val);
}
};
}
};
}
}
The abstract collections are really great classes to be familiar with because they make a lot of problems like this fairly trivially solvable without writing a new container from scratch.
There is no way to make an immutable object in Java, aside from writing an immutable object. Java has no feature like e.g. const in C++.
One way to do this without actually instantiating a new object is to write interfaces as follows, then return the ImmutableFoo any time you don't want to expose the setter:
interface ImmutableFoo {
int getValue();
}
interface MutableFoo extends ImmutableFoo {
void setValue(int value);
}
class Foo implements MutableFoo {
private int value;
public Foo(int value) { this.value = value; }
@Override public int getValue() { return value; }
@Override public void setValue(int value) { this.value = value; }
}
Otherwise, if Foo is a class you are unable to change, you have to write something like the following:
class FooAccessor {
private final Foo foo;
public FooAccessor(Foo foo) { this.foo = Objects.requireNonNull(foo); }
public int getValue() { return foo.getValue(); }
}
Either of those could be used e.g. in combination with the unmodifiable AbstractMap example.