0

I have a Map as follows:

Map<String,Map<String,Object>>

Given a key,I would be returned the Map.
What is the recommended way to make the returned Map immutable.
This essentially means that:
Map is immutable (no edits/deletes/add)
Underlying Object are immutable (no edits)

We have no option to make Object immutable .

IUnknown
  • 9,301
  • 15
  • 50
  • 76
  • Possible duplicate of [Returning an unmodifiable map](https://stackoverflow.com/questions/7066618/returning-an-unmodifiable-map) – Ousmane D. May 24 '17 at 13:09
  • 1
    There's no way to specify that an object is immutable, other than writing an immutable object. As I understand your question, you are actually not really asking about making the map immutable, you're asking about making the objects *in* the map immutable too. Is that correct? Or to put it another way, you basically want something like C++ `const`, but that does not exist in Java. You have to write a proxy which wraps the object and doesn't provide setters or throws exceptions. Using interfaces helps. – Radiodef May 24 '17 at 13:18
  • 1
    What do you actually need the immutable `Object` _for_? Without knowing that, it is hard to offer any useful advice. – Rook May 24 '17 at 13:34

3 Answers3

1

What you have to do is make a deep copy of the returning Map.

Java HashMap - deep copy

freedev
  • 25,946
  • 8
  • 108
  • 125
1

You can make each map unmodifiable, then make the whole map unmodifiable.

public Map<String,Map<String,Object>> makeUnModifiable(Map<String,Map<String,Object>> a){
  for(Entry<String, Map<String, Object>> e:a.entrySet()){
    e.setValue(Collections.unmodifiableMap(e.getValue()));
  }
  return Collections.unmodifiableMap(a);
}

The real problem is that you still provide access to the value in the map. You could create copies of those objects, but that doesnt solve the problem. Either make the object class immutable or create a Wrapper class for them which disables the setter methods.

f1sh
  • 11,489
  • 3
  • 25
  • 51
  • That would not make the objects themselves unmodifiable as required in the question. – RealSkeptic May 24 '17 at 13:11
  • @RealSkeptic that's true. That can only be done in a very limited number of ways, each needing to know the object class. – f1sh May 24 '17 at 13:31
1

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.

Radiodef
  • 37,180
  • 14
  • 90
  • 125