Given a Map, how can I make an immutable map using Guava? I know one way through Collections.unmodifiableMap but is there any other way using Guava?
Map<String, String> realMap = Maps.newHashMap();
realMap.put("A", "B");
// any other alternative?
Map<String, String> unmodifiableMap = Collections.unmodifiableMap(realMap);
I am populating my realMap with some entries and then I need to make it Immutable so that no one can modify it. I am just trying to see if there is any alternative using Guava? I did some search but I cannot find it.
You're looking for ImmutableMap.copyOf():
ImmutableMap<String, String> immutableMap = ImmutableMap.copyOf(realMap);
Keep in mind that, as opposed to unmodifiableMap() which only creates a wrapper to its argument, this actually copies it to a new map. That can mean a slight performance hit, but it also means there's no possibility of the map being modified accidentally through realMap.
Related
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).
I'm trying Kotlin and I've encountered a small problem that I can't resolve.
When I have the following construction I can put elements into the map:
val map = HashMap<String, String>()
map["asd"] = "s"
map.put("34", "354")
However when I create a map with the Map interface I can only read them, what I'm doing wrong ?
val map: Map<String, String> = HashMap<String, String>();
map.put("24", "34") //error
map["23"] = "23" //error
Or maybe I'm confusing something about interfaces in Kotlin ?
In the first example map gets the type of HashMap,
in the second example you cast it to the Interface Map.
Map is a readonly map, there is no put/set, see here
In order to be able to edit the map, you should use MutableMap
When working with kotlin collections, one important consideration is that, kotlin categorizes its collections as mutable and immutable. this is in contrast to java, where no such categorization exists.
In kotlin for most collections you have a base interface which only supports read-only methods. In your case Map<K,V is an example of that, from the docs
Methods in this interface support only read-only access to the map;
read-write access is supported through the MutableMap interface.
this is the reason for error when you try to modify the map after val map: Map<String, String> = HashMap<String, String>();, even though the actual object is of type HashMap<String,String>, but the map reference is of type Map<String,String>, which will only provide read only operation.
Now if you use a class which implements MutableMap<K,V> then you can put values in map as well. this is the case with val map = HashMap<String, String>(), since here type of map is HashMap<K,V>, which extends MutableMap<K,V> and hence is mutable.
I am having trouble getting a separate copy of my HashMaps. By that I mean, once I have made a copy of the original, making a change to one does not change the other.
I have two HashMaps in this format:
HashMap<String, List<String> one = new HashMap<String, List<String>();
HashMap<String, List<String> two = new HashMap<String, List<String>();
I call the following function below (getTabSetDifferences) passing in one and two, as expected if there are some differences, those values will be removed from the HashMap
and it'll be different than before it was passed in for the test.
I want them to remain unchanged, so tried passsing in:
getTabSetDifferences((HashMap)one.clone(), (HashMap)two.clone())
This still changed the originals, so i created two more hashmaps in the same format, and cloned one and two to them, I used the new hashmaps to pass
in, and the original was still changed.
I then tried:
HashMap<String, List<String>> holdOne = new HashMap<String, List<String>>();
holdOne.putAll(one);
HashMap<String, List<String>> Holdtwo = new HashMap<String, List<String>>();
holdTwo.putAll(two);
Now I can do something like:
holdTwo.remove(key);
and the original is not changed, but if i call the method with holdOne and holdTwo it still changes the original one and two hashmaps, shouldn't they remain?
The method is working, and finding the differences i want, and is returned. But I still need the original two hashmaps to be as they were, but no matter which
way I call, what ever changes are made to holdOne and holdTwo changes the originals. Is that the expected behavior? If so, what is the proper way
to get a copy of a hashmap that is not tied to it.
getTabSetDifferences(holdOne, holdTwo);
public HashMap<String, List<String>> getTabSetDifferences(HashMap<String, List<String>> hmMain, HashMap<String, List<String>> hmSecond) {
HashMap<String, List<String>> hmDifferences = new HashMap<String, List<String>>();
for (Map.Entry<String, List<String>> entry : hmMain.entrySet()) {
if(hmSecond.containsKey(entry.getKey())) {
entry.getValue().removeAll(hmSecond.get(entry.getKey()));
if (entry.getValue().size() > 0)
hmDifferences.put(entry.getKey(), entry.getValue());
}
else {
hmDifferences.put(entry.getKey(), entry.getValue());
}
}
return hmDifferences;
}
The clone method doesn't do a deep copy.
You have 2 options.
create a deep copy method.
Use one of the Map implementations from the java.util.concurrent package like copyOnWrite
I suspect you are only copying the keys/values. This will not create copies of the lists.
Perhaps Guava's MultiMap is what you want?
If you copy the list as a list (i.e. copy it at list scope, rather than some lower level implementation), then the pointer behavior will be seen.... However if you copy from one list into a new list, then those string objects are independant.
Java's clone method should not be use in expectation that it will return distinct , deep copies of an object - immutability is not a central concept to the way clone works.
I agree with the above comment : either use a multimap in a library like guava, or google collections, or simply be very careful about your copying, and only copy at the primitive levels, (don't ever copy a collection and expect it to be independent) unless you've tested this explicitly .
I have written some code that works fine but I am confused about the correct way to declare a TreeMap.
If SortedMap is a subinterface of Map then is it okay to just use Map if the code is working okay? Is SortedMap even necessary if TreeMap works fine with Map?
Should it be:
private Map<String, List <Bus>> map = new TreeMap<String, List <Bus>>();
or
private SortedMap<String, List <Bus>> map = new TreeMap<String, List <Bus>>();
Thanks.
Sorry this is so basic - I am new to Java.
I've used SortedMap to inform others that it is already sorted. Using Map is OK too.
private Map<String, List <Bus>> busTimetable = new TreeMap<String, List <Bus>>();
Unless you have a good reason, always use the highest level interface you can.
The answer to your question depends on your usage. By default, you should simply program to the data type's interface (i.e. Map). If SortedMap provides methods that you will be using that aren't declared in Map, then program to SortedMap.
If you need to use specific SortedMap methods (like firstKey()/lastKey()/whatever...), it is mandatory to declare your reference as SortedMap. Otherwise, Map is the one I'd choose, if I'm only planning on using it as a Map, so I'll be able so switch implementations without any other change in the code.
I agree with other commenters that you use SortedMap if you use methods that aren't in vanilla Map. Also use SortedMap if you have use in an iterator or a for-each loop if they implicitly rely on sorted input.
If neither of these cases are true, you should also think about if you only need a vanilla Map, a HashMap may be a better choice. HashMap has O(1) access; TreeMap does not.
It depends upon your requirement and design whenever possible use the highest level of abstraction that is Map. The reason is say you are creating a service and it consume list of data and produce an output in a Map.
some client may expect for sorted order of the data in the map and some other client may just need the data in the insertion order of the map if you use a specific interface SortedMap ; this kind of scenarion you can not handle using one service and you will end up creating two different api becuase one who is expecting sorted order you can just return an implementaion of TreeMap and one for insertion order you can use LinkedHashMap. So it's about how flexible is your program.
As I understand it, there are a couple of ways (maybe others as well) to create a shallow copy of a Map in Java:
Map<String, Object> data = new HashMap<String, Object>();
Map<String, Object> shallowCopy;
// first way
shallowCopy = new HashMap<String, Object>(data);
// second way
shallowCopy = (Map<String, Object>) ((HashMap<String, Object>) data).clone();
Is one way preferred over the other, and if so, why?
One thing worth mentioning is that the second way gives an "Unchecked Cast" warning. So you have to add #SuppressWarnings("unchecked") to get around it, which is a little irritating (see below).
#SuppressWarnings("unchecked")
public Map<String, Object> getDataAsMap() {
// return a shallow copy of the data map
return (Map<String, Object>) ((HashMap<String, Object>) data).clone();
}
It's always better to copy using a copy constructor. clone() in Java is broken (see SO: How to properly override clone method?).
Josh Bloch on Design - Copy Constructor versus Cloning
If you've read the item about cloning in my book, especially if you read between the lines, you will know that I think clone is deeply broken. [...] It's a shame that Cloneable is broken, but it happens.
Bloch (who by the way, designed and implemented the Collection framework) even went further in saying that he only provides the clone() method just "because people expect it". He does NOT actually recommend using it at all.
I think the more interesting debate is whether a copy constructor is better than a copy factory, but that's a different discussion altogether.
Neither of the two: the constructor that you are referring to is defined for the HashMap implementation of a Map, (as well as for others) but not for the Map interface itself (for example, consider the Provider implementation of the Map interface: you will not find that constructor).
On the other hand it is not advisable to use the clone() method, as explained by Josh Bloch.
In respect of the Map interface (and of your question, in which you ask how to copy a Map, not a HashMap), you should use Map#putAll():
Copies all of the mappings from the specified map to this map
(optional operation). The effect of this call is equivalent to that of
calling put(k, v) on this map once for each mapping from key k to
value v in the specified map.
Example:
// HashMap here, but it works for every implementation of the Map interface
Map<String, Object> data = new HashMap<String, Object>();
Map<String, Object> shallowCopy = new HashMap<String, Object>();
shallowCopy.putAll(data);
Copy a map without knowing its implementation:
static final Map shallowCopy(final Map source) throws Exception {
final Map newMap = source.getClass().newInstance();
newMap.putAll(source);
return newMap;
}