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.