UnmodifiableMap (Java Collections) vs ImmutableMap (Google) [duplicate] - java

This question already has answers here:
Immutable vs Unmodifiable collection [duplicate]
(11 answers)
Closed 8 years ago.
Context
I need to return a reference to a map that I'm using for a data cache, and I'd like to make sure nobody can modify their reference.
Question
I've seen lots of references to UnmodifiableMap and ImmutableMap online, but I don't see anything comparing/contrasting them. I figure there is a good reason that Google/Guava created their own version - can someone tell me what it is?

An unmodifiable map may still change. It is only a view on a modifiable map, and changes in the backing map will be visible through the unmodifiable map. The unmodifiable map only prevents modifications for those who only have the reference to the unmodifiable view:
Map<String, String> realMap = new HashMap<String, String>();
realMap.put("A", "B");
Map<String, String> unmodifiableMap = Collections.unmodifiableMap(realMap);
// This is not possible: It would throw an
// UnsupportedOperationException
//unmodifiableMap.put("C", "D");
// This is still possible:
realMap.put("E", "F");
// The change in the "realMap" is now also visible
// in the "unmodifiableMap". So the unmodifiableMap
// has changed after it has been created.
unmodifiableMap.get("E"); // Will return "F".
In contrast to that, the ImmutableMap of Guava is really immutable: It is a true copy of a given map, and nobody may modify this ImmutableMap in any way.
Update:
As pointed out in a comment, an immutable map can also be created with the standard API using
Map<String, String> immutableMap =
Collections.unmodifiableMap(new LinkedHashMap<String, String>(realMap));
This will create an unmodifiable view on a true copy of the given map, and thus nicely emulates the characteristics of the ImmutableMap without having to add the dependency to Guava.

Have a look at ImmutableMap JavaDoc: doc
There is information about that there:
Unlike Collections.unmodifiableMap(java.util.Map), which is a view of a separate map which can still change, an instance of ImmutableMap contains its own data and will never change. ImmutableMap is convenient for public static final maps ("constant maps") and also lets you easily make a "defensive copy" of a map provided to your class by a caller.

Guava Documentation
The JDK provides Collections.unmodifiableXXX methods, but in our opinion, these can be unwieldy and verbose; unpleasant to use everywhere you want to make defensive copies unsafe: the returned collections are only truly immutable if nobody holds a reference to the original collection inefficient: the data structures still have all the overhead of mutable collections, including concurrent modification checks, extra space in hash tables, etc.

ImmutableMap does not accept null values whereas Collections.unmodifiableMap() does. In addition it will never change after construction, while UnmodifiableMap may. From the JavaDoc:
An immutable, hash-based Map with reliable user-specified iteration order. Does not permit null keys or values.
Unlike Collections.unmodifiableMap(java.util.Map), which is a view of a separate map which can still change, an instance of ImmutableMap contains its own data and will never change. ImmutableMap is convenient for public static final maps ("constant maps") and also lets you easily make a "defensive copy" of a map provided to your class by a caller.

Related

Java unmodifiableMap can be replaced with 'Map.copyOf' call

I'm new to Java and I recently learnt that somtimes it's important to deepcopy a Collection and make an unmodifiable view of it so that the data inside remains safe and unchanged.
When I try to practice this(unmodifiableMap2), I get a warning from IDEA that
unmodifiableMap Can be replaced with 'Map.copyOf' call
That's weird for me because I think unmodifiableMap is not only a copy of the underlying map. Besides, when I try to create the same unmodifiableMap in another way(unmodifiableMap1), the warning doesn't pop up!
How should I understand this behavior of IDEA ?
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class test {
public static void main(String[] args) {
Map<Integer, Integer> map = new HashMap<>();
map.put(1,1);
map.put(2,2);
Map<Integer, Integer> map1 = new HashMap<>(map);
Map<Integer, Integer> unmodifiableMap1 = Collections.unmodifiableMap(map1);
Map<Integer, Integer> unmodifiableMap2 = Collections.unmodifiableMap(new HashMap<>(map););
}
}
Map.copyOf() makes a copy of the given Map instance, but it requires that no value in the map is null. Usually, this is the case, but it is not a strict requirement for a Map in general.
java.util.Collections.unmodifiableMap() just wraps a reference to the given Map instance. This means that the receiver is unable to modify the map, but modifications to the original map (that one that was the argument to unmodifiableMap()) are visible to the receiver.
Assuming we have two threads, one iterates over the unmodifiable map, while the other modifies the original one. As a result, you may get a ConcurrentModificationException for an operation on the unmodifiable map … not funny to debug that thing!
This cannot happen with the copy created by Map.copyOf(). But this has a price: with a copy, you need two times the amount of memory for the map (roughly). For really large maps, this may cause memory shortages up to an OutOfMemoryError. Also not fun to debug!
In addition, just wrapping the existing map is presumably much faster than copying it.
So there is no best solution in general, but for most scenarios, I have a preference for using Map.copyOf() when I need an unmodifiable map.
The sample in the question did not wrap the original Map instance, but it makes a copy before wrapping it (either in a line of its own, or on the fly). This eliminates the potential problem with the 'under-the-hood' modification, but may bring the memory issue.
From my experience so far, Map.copyOf( map ) looks to be more efficient than Collections.unmodifiableMap( new HashMap( map ) ).
By the way: Map.copyOf() returns a map that resembles a HashMap; when you copy a TreeMap with it, the sort order gets lost, while the wrapping with unmodifiableMap() keeps the underlying Map implementation and therefore also the sort order. So when this is important, you can use Collections.unmodifiableMap( new TreeMap( map ) ), while Map.copyOf() does not work here.
An unmodifiable map using an existing reference to a map is perfectly fine, and there are many reasons you might want to do this.
Consider this class:
class Foo {
private final Map<String, String> fooMap = new HashMap<>();
// some methods which mutate the map
public Map<String, String> getMap() {
return Collections.unmodifiableMap(fooMap);
}
}
What this class does is provide a read-only view of the map it encapsulates. The class can be sure that clients who consume the map cannot alter it, they can just see its contents. They will also be able to see any updates to the entries if they keep hold of the reference for some time.
If we had tried to expose a read-only view by copying the map, it would take time and memory to perform the copy and the client would not see any changes because both maps are then distinct instances - the source and the copy.
However in the case of this:
Collections.unmodifiableMap(new HashMap<>(map));
You are first copying the map into a new hash map and then passing that copy into Collections.unmodifiableMap. The result is effectively constant. You do not have a reference to the copy you created with new HashMap<>(map), and nor can you get one*.
If what you want is a constant map, then Map.copyOf is a more concise way of achieving that, so IntelliJ suggests you should use that instead.
In the first case, since the reference to the map already exists, IntelliJ cannot make the same inference about your intent so it gives no such suggestion.
You can see the IntelliJ ticket for this feature if you like, though it doesn't explain why the two are essentially equivalent, just that they are.
* well, you probably could via reflection, but IntelliJ is assuming that you won't
Map.copyOf(map) is fully equivalent to Collections.unmodifiableMap(new HashMap<>(map)).
Neither does any kind of deep copying. But it's strictly shorter to do Maps.copyOf(map).

Is the Access Time Complexity of a Map Created by Map.ofEntries() the Same as HashMap which is o(1)? [duplicate]

This question already has answers here:
How to get a immutable collection from java HashMap?
(5 answers)
Closed 2 years ago.
I wanted to create an immutable hashMap inline using the new factory method Map.ofEntries() in Java 9, for example:
Map<Integer, String> map = Map.ofEntries(
Map.entry(1, "One"),
Map.entry(2, "Two"),
Map.entry(3, "Three"));
Then to my surprise, I found I could not create an immutable hashMap the same way! For example, the following code would not work.
HashMap<Integer, String> map = HashMap.ofEntries( //not work
Map.entry(1, "One"),
Map.entry(2, "Two"),
Map.entry(3, "Three"));
Then when I want to check what type of map is returned by the factory method, I found the following note:
Callers should make no assumptions about the identity of the returned instances.
So my question is, is the access time complexity of an immutable map the same as a hashMap which is o(1)? If not, how to create a map that is both immutable and access o(1) at the same time? It would be best if it can be created inline.
Mutability or immutability are not directly related to the complexity of the access operation in a Map. For instance, a HashMap will always be O(1) for the get() operation, whereas a TreeMap will be O(log n). It's the implementing class of the Map interface that determines the complexity of the operations.
Besides it's always been possible to create unmodifiable maps, because we can make any Map of any concrete type immutable after we put items on it, like this:
Map<Integer, String> immutableMap = Collections.unmodifiableMap(mutableMap);
To be clear, HashMap.ofEntries() won't work because the ofEntries() method is static and defined in the Map interface, not in any of its implementing classes.
And you should not worry about being unable to declare the type of a map as HashMap or some other concrete class, anyway the best practice is to declare the map as being of a Map interface.
Also, if you were using a version older than Java 9 and don't mind using an external library, you can use ImmutableMap from Guava:
Map<Integer, String> immutableMap = ImmutableMap.of(key1, val1, key2, val2);
Perhaps reading this article will clarify things a bit more.

How do I ask for a parameter to be a mutable Map?

I have a function that accepts a Map of parameters, but adds to them before using them:
public String doRequest(String endpoint, Map<String, String> parameters) {
parameters.put("limit", "50");
...
}
It's convenient to create this map using Guava's ImmutableMap.of():
doRequest("/comments", ImmutableMap.of("filter", "true"));
However, this throws a java.lang.UnsupportedOperationException at runtime.
Two questions:
1) Can I declare doRequest in a way that for example HashMap and TreeMap would be fine but using ImmutableMap would be a compile time error?
2) From inside doRequest, how would I detect that it's an ImmutableMap, so I could run params = Maps.newHashMap(params)?
Well, the fact an ImmutableMap is a Map is bad subtyping and a violation of the LSP - this is you paying for it. A subtype not being able to fulfil its parent type's contract... this is ImmutableMap inheriting from map being bad OOP (since Map specifies a put) and Java not providing strong enough constructs to enforce those guarantees.
As for a solution - either document that you're going to mutate the Map passed in, or as an alternative don't mutate the Map that's passed in - instead return a new ImmutableMap yourself.
1) Can I declare doRequest in a way that for example HashMap and TreeMap would be fine but using ImmutableMap would be a compile time error?
No. As ImmutableMap implements Map, it can't be a compile time error. The problem is actually with Map.put, which allows to throw. It'd better to have a Map without put and an MutableMap interface... but there's nothing what anyone could do about it in Java as all the software around relies on it.
2) From inside doRequest, how would I detect that it's an ImmutableMap, so I could run params = Maps.newHashMap(params)?
You'd better do it always as there's also Collections.unmodifiableMap and actually everyone could implement a Map refusing put.
If you're concerned with performance, then you'll need an instanceof check.

I need an immutable key-value structure that retains insertion order

I want to find something like ImmutableLinkedHashMap<> in Guava library.
I need to use an immutable key-value data structure with an insertion order.
So, what should I use?
I am not sure I am understanding exactly what you are after, but if it is a really immutable Map, you mght want to look at ImmutableMap
As mentioned in the doc:
An immutable, hash-based Map with reliable user-specified iteration order. Does not permit null keys or values.
Unlike Collections.unmodifiableMap(java.util.Map<? extends K, ? extends V>), which is a view of a separate map which can still change, an instance of ImmutableMap contains its own data and will never change. ImmutableMap is convenient for public static final maps ("constant maps") and also lets you easily make a "defensive copy" of a map provided to your class by a caller
E.g, you could use it in a similar fashion:
Map<Integer, String> m = ImmutableMap.of(5,"Five",6,"Six",7,"Seven");
Hope this is what you were after.
First create a LinkedHashMap and then use ImmutableMap.copyOf(linkedHashMap) to create an immutable copy which will have the same ordering as the original map.

Is static initialized unmodifiableCollection.get guaranteed immutable?

Is static initialized unmodifiableCollection.get guaranteed immutable?
For:
static final Map FOO =
Collections.unmodifiableMap(new HashMap());
Can multiple threads use method get and not run into problems?
Even through items in FOO cannot be added/removed, what's stopping the get method from manipulating FOO's internal state for caching purposes, etc. If the internal state is modified in any way then FOO can't be used concurrently. If this is the case, where are the true immutable collections in java?
Given the specific example:
static final Map FOO = Collections.unmodifiableMap(new HashMap());
Then FOO will be immutable. It will also never have any elements. Given the more general case of:
static final Map BAR = Collections.unmodifiableMap(getMap());
Then whether or not this is immutable is entirely dependent on whether or not someone else can get to the underlying Map, and what type of Map it is. For example, if it is a LinkedHashMap then the underlying linked list could be modified by access order, and could change by calling get(). The safest way (using non-concurrent classes) to do this would be:
static final Map BAR = Collections.unmodifiableMap(new HashMap(getMap()));
The javadocs for HashMap imply that so long as you make no structural changes to the map, then it is safe to use it concurrently, so this should be safe for any of the accessors that you can use, that is getting the various sets and iterating over them and get() should then be safe.
If you can use the concurrent classes, then you could also do:
static final Map BAR = Collections.unmodifiableMap(new ConcurrentHashMap(getMap());
This will be explicitly safe to use from multiple threads, since ConcurrentHashMap is explicitly multi-thread access safe. The internal state might be mutable, but the externally visible state will not be, and since the class is guaranteed to be threadsafe, we can safely consider it to be externally immutable.
At the risk of sounding like I'm on an advertising spree, use the Google Immutable Collections and be done with it.
Actually a good question. Think WeakHashMap - that can change without having a mutation operation called on it. LinkedHashMap in access-order mode is much the same.
The API docs for HashMap state:
Note that this implementation is not
synchronized. If multiple threads
access a hash map concurrently, and at
least one of the threads modifies the
map structurally, it must be
synchronized externally. (A structural
modification is any operation that
adds or deletes one or more mappings;
merely changing the value associated
with a key that an instance already
contains is not a structural
modification.)
Presumably that should be if and only if. That means that get does not need to be synchronised if the HashMap is 'effectively immutable'.
There is no true immutable map in Java SDK. All of the suggested Maps by Chris are only thread safe. The unmodifiable Map is not immutable either since if the underlying Map changed there will ConcurrentModificationException as well.
If you want the truly immutable map, use ImmutableMap from Google Collections / Guava.
I would suggest for any threaded operation to use ConcurrentHashMap or HashTable, both are thread-safe.
Whether a getter on the returned map happens to twiddle with some internal state is unimportant, as long as the object honors its contract (which is to be a map that cannot be modified). So your question is "barking up the wrong tree".
You are right to be cautious of UnmodifiableMap, in the case where you do not have ownership and control over the map it wraps. For example
Map<String,String> wrapped = new HashMap<String,String>();
wrapped.add("pig","oink");
Map<String,String> wrapper = Collections.unmodifiableMap(wrapped);
System.out.println(wrapper.size());
wrapper.put("cow", "moo"); // throws exception
wrapped.put("cow", "moo");
System.out.println(wrapper.size()); // d'oh!

Categories

Resources