I'm want to figure out what's the pros and cons of these two methods.
I tried to find out about that here and on some other sites, but I haven't found this particular information.
import java.util.HashMap;
import java.util.Map;
public class Maps {
private Map<Integer, String> map = new HashMap<>();
private HashMap<Integer, String> map2 = new HashMap<>();
private Map<Integer, String> map3 = new HashMap<>();
private void initialization() {
map.put(1, "Hello");
map.put(2, "everyone!");
map.put(3, "Nice to");
map.put(4, "meet you");
}
public static void main(String[] args) {
Maps exe = new Maps();
exe.initialization();
exe.map2.putAll(exe.map);
exe.map3 = (Map<Integer, String>) exe.map2.clone();
System.out.println(exe.map2.values());
System.out.println(exe.map3.values());
}
}
I get the same result, but I'm interested what the situation I should use one or the other method.
Map.putAll() adds all the mappings from the argument to the existing map on which it is invoked. Where the target map already has mappings for the same keys, the original mappings will be replaced. Mappings for all other keys will be retained.
In general, one must assume that Map.clone() will throw a CloneNotSupportedException, as that is the behavior inherited from class Object, and the Map interface does not specify different behavior. However, HashMap in particular provides a clone() method that creates a new, shallow copy of the map on which it is invoked. Note well that there is a big difference between creating a new map, as HashMap.clone() does, and adding entries to an existing map, as Map.putAll() does, though it is of less significance in the case where the target map of the putAll() is initially empty.
When you want to create a copy of a Map, it is usually best to choose an appropriate implementation class and instantiate it via a constructor that accepts the source map as a parameter. Such a constructor is not guaranteed to be present, but it is strongly recommended for general-purpose Map implementations, and every such implementation in the Java standard library offers one. Instantiating a new, empty, map and then using putAll() to populate it is another option. Using clone() safely requires that you know that you're using a Map implementation that supports it, so it is rare that you can safely clone() but cannot instantiate a suitable map normally.
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).
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.
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 .
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;
}