This question already has answers here:
ArrayList as key in HashMap
(9 answers)
Closed 6 years ago.
Tired up with trying to resolve the problem with this code:
public class MapTest {
static class T{
static class K{}
}
static Map<List<T.K>, List<String>> map = new HashMap<>();
static List<String> test(List<T.K> list, String s){
List<String> l = map.get(list);
if (l == null){
l = new ArrayList<String>();
System.out.println("New value()");
map.put(list, l);
}
l.add(s);
return l;
}
public static void main(String s[]){
ArrayList<T.K> list = new ArrayList<T.K>();
test(list, "TEST");
list.add(new T.K());
List<String> l = test(list, "TEST1");
System.out.println(l.size());
}
}
It should create a new list-value for the map only once, but output is as follows:
New value
New value
1
it is something wrong happen with hashcode of the list after I insert value in it.
I expect "new value" show up only once, and size will be 2, not 1.
is it just JVM problem or something more general?
mine one is Oracle JVM 1.8.0_65
The hashcode of the list object changes when you put an item in it. You can see how the hashcode is calculated in the ArrayList.hashCode documentation.
In general, using a mutable object as the key for a map isn't going to work well. Per the Map documentation:
Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map.
Thus, when you add the list to the map a second time, the map doesn't see it as being "equal" to the first list (since it isn't according to .equals), so it adds it again.
If you want a map where keys are looked up by identity rather than by value, you can use the IdentityHashMap class.
Related
I am learning how to code in Java and struggling with this concept.
I need to write a method that takes a char argument which exists within the Objects that are in a map.
It needs to return a currently non existing Set of Objects that contain this char value as a new Set after iterating over the map to find these objects.
The following is my current code which doesn't get my desired outcome, I believe the problem is in the if statement. I have done lots of different combinations of equals() and containsValue on the object and map and cannot seem to get it to return true.
public Set findObj(char aChar)
{
Set<String> objSet = new HashSet();
for (Object Obj: map1.values())
{
if (map1.values().contains(aChar))
{
//if true add to objSet
}
}
return objSet;
}
If it helps the Map is
Map<String,Obj> map1= new HashMap<>();
and the Object is created by a class and contains a 3 variables, 2 string and one being the char value I am trying to find within each iteration of the map.
you are correct the problem is mainly in your if statement, since you're iterating over the objects in the map, you should check if the current object contains the string not whether or not the map as a whole contains it
public Set findObj(char aChar) {
Set<String> objSet = new HashSet();
for (Object c : map1.values()) {
if (c.toString().indexOf(aChar) >= 0) {
//if true add to objSet
}
}
return objSet;
}
This question already has answers here:
HashMap with multiple values under the same key
(21 answers)
Closed 4 years ago.
public static void main(String[] args) {
// write your code here
LinkedHashMap<String,String>category1=new LinkedHashMap();
category1.put("action","die hard");
Scanner s=new Scanner(System.in);
String answer=s.nextLine();
if (category1.containsKey(answer))
System.out.println(category1.get("action"));
if (category1.containsValue(answer))
System.out.println(category1.keySet());
How to get the key when the user answer with it's specific value, and how to add more values to one key?
1. The map collection, does not support multiple values under the same key, it will override whatever was stored there before.
2. However, you can change it from <String,String> to <String,List<String>>, thus gaining the ability to accumulate the answers from the client into the list. The key will refer to only one object, the list of Strings, but the list itself can hold many values.
3. In order to add more Strings to the list, you will need to retrieve the list by the desired key, and then add your new String to it.
Here is some code that implements the idea:
private void test(){
Map<String, List<String>> categories = new HashMap<>();
String answerFromClient = "Some text";
List<String> actionAnswers = categories.get("action");
if (actionAnswers == null){
actionAnswers = new ArrayList<>();
actionAnswers.add(answerFromClient);
categories.put("action",actionAnswers);
}
else{
actionAnswers.add(answerFromClient);
}
}
This question already has answers here:
In Java, what is a shallow copy?
(11 answers)
Closed 4 years ago.
String [] keys0 =a.keySet(). toArray (new String[a.size()]);
Map< String , TreeSet <Integer>> Sub_hash=new HashMap< String, TreeSet <Integer>>();
for(ko = 0 ; ko < keys0 . length ; ko ++) {
Set<Integer> so = test.get( keys0[ko] );
System.out.println(""+so);
for(ko1 = ko+1 ; ko1 < keys0.length; ko1++) {
Set<Integer> so1 = test.get( keys0[ko1] );
boolean tr=so1.retainAll(so);
if(tr && so1.size()>=2) {
Sub_hash. put(keys0[ko]+keys0[ko1], (TreeSet<Integer>) so1);
System.out.println(""+Sub_hash.size()+""+Sub_hash);
}
}}
this is my second post and i dont know how to post neatly the requirement is i have a hash map with keys and values where i need to compare with one key in the map with another key in the map and retain the values and put the result in the sub_hash map but the problem is the original values of the map are changing as the values of the map are updated by the method retainAll();
but when the iteration comes to second key the values are changed completely but the comparison goes like this 2-3,2-4,2-5,2-6,2-7...etc but
as the values are changed result is completely erroneous so is there any chance to make it constant like some variable final to the hashmap.
Here's your problem:
Set<Integer> so1 = test.get( keys0[ko1] );
boolean tr=so1.retainAll(so);
You don't want to modify values in test. You want to modify a copy instead:
Set<Integer> so1 = new TreeSet<>(test.get( keys0[ko1] ));
boolean tr=so1.retainAll(so);
A Map (or any collection) can only contain references to objects, not the objects themselves. When you put a copy of a reference into a collection or get a reference from a collection, you are copying just the reference, not the underlying object.
When you get a set from a Map, and you modify the set, there is only one copy of that set.
NOTE: The Map is not altered when you alter a Set in it.
If you want a copy of a collection, you have to do this explicitly.
I have a map like this with several million entries:
private final Map<String, SomeItem> tops = new HashMap<>();
I need to get list of values, which could be done by calling java.util.Map values() method.
Is Collection of values created every time I call values() method or is it pre-computed from performance perspective?
As my Map has several millions elements, I do not want to create new list object every time values() is called.
Below is the copied implementation of Map.values() in java.util.HashMap:
public Collection<V> values() {
Collection<V> vs = values;
if (vs == null) {
vs = new Values();
values = vs;
}
return vs;
}
This clearly shows that the value collection isn't created unless necessary. So, there should not be additional overhead caused by calls to values()
One important point here may be: It does not matter!
But first, referring to the other answers so far: The collection that is returned there is usually "cached", in that it is lazily created, and afterwards, the same instance will be returned. For example, considering the implementation in the HashMap class:
public Collection<V> values() {
Collection<V> vs;
return (vs = values) == null ? (values = new Values()) : vs;
}
This is even specified (as part of the contract, as an implementation specification) in the documentation of the AbstractMap class (which most Map implementations are based on) :
The collection is created the first time this method is called, and returned in response to all subsequent calls. No synchronization is performed, so there is a slight chance that multiple calls to this method will not all return the same collection.
But now, one could argue that the implementation might change later. The implementation of the HashMap class could change, or one might switch to another Map implementation that does not extend AbstractMap, and which is implemented differently. The fact that it is currently implemented like this is (for itself) no guarantee that it will always be implemented like this.
So the more important point (and the reason why it does not matter) is that the values() method is indeed supposed to return a collection view. As stated in the documentation of the Map interface :
The Map interface provides three collection views, which allow a map's contents to be viewed as a set of keys, collection of values, or set of key-value mappings.
and specifically, the documentation of the Map#values() method :
Returns a Collection view of the values contained in this map. The collection is backed by the map, so changes to the map are reflected in the collection, and vice-versa.
I cannot imagine a reasonable way of implementing such a view that involves processing all values of the Map.
So for example, imagine the implementation in HashMap was like this:
public Collection<V> values() {
return new Values();
}
Then it would return a new collection each time that it was called. But creating this collection does not involve processing the values at all.
Or to put it that way: The cost of calling this method is independent of the size of the map. It basically has the cost of a single object creation, regardless of whether the map contains 10 or 10000 elements.
As others have mentioned you can see this by looking at the code. You can also code up a quick example to prove it to yourself. The code below will print true 10 times as the object identity will always be the same for values.
public static void main(String[] args) {
Map<String, String> myMap = new HashMap();
Collection<String> lastValues = myMap.values();
for (int i=0; i < 10; i++) {
System.out.println(lastValues == myMap.values());
lastValues = myMap.values();
}
}
The following code will print true the first time and then false the next 9 times.
public static void main(String[] args) {
Map<String, String> myMap = new HashMap();
Collection<String> lastValues = myMap.values();
for (int i=0; i < 10; i++) {
System.out.println(lastValues == myMap.values());
lastValues = myMap.values();
myMap = new HashMap();
}
}
One more suggestion after reading this thread, if the Map tops declared contents are not changed - you could use google guava ImmutableMap object. For more info- UnmodifiableMap (Java Collections) vs ImmutableMap (Google)
I have a fully working version of MineSweeper implemented in Java. However, I am trying to add an additional feature that updates a Map to store the indexes of the locations of the mines within a 2D array. For example, if location [x][y] holds a mine, I am storing a linked list containing x and y, which maps to a boolean that is true to indicate that the space holds a mine. (This feature is seemingly trivial, but I am just doing this to practice with Collections in Java.)
My relevant private instance variables include:
public Class World{ ...
private LinkedList<Integer> index;
private Map<LinkedList<Integer>, Boolean> revealed;
"index" is the list to be stored in the map as the key for each boolean.
In my constructor I have:
public World(){ ...
tileArr = new Tile[worldWidth][worldHeight];
revealed = new TreeMap<LinkedList<Integer>, Boolean>();
index = new LinkedList<Integer>();
... }
Now, in the method in which I place the mines, I have the following:
private void placeBomb(){
int x = ran.nextInt(worldWidth); //Random stream
int y = ran.nextInt(worldHeight); //Random stream
if (!tileArr[x][y].isBomb()){
tileArr[x][y].setBomb(true);
index.add(x); //ADDED COMPONENT
index.add(y);
revealed.put(index, true);
index.remove(x);
index.remove(y); //END OF ADDED COMPONENT
} else placeBomb();
}
Without the marked added component my program runs fine, and I have a fully working game. However, this addition gives me the following error.
Exception in thread "main" java.lang.ClassCastException: java.util.LinkedList
cannot be cast to java.lang.Comparable
If anyone could help point out where this error might be, it would be very helpful! This is solely for additional practice with collections and is not required to run the game.
There are actually about 3 issues here. One that you know about, one that you don't and a third which is just that using LinkedList as a key for a map is clunky.
The ClassCastException happens because TreeMap is a sorted set and requires that every key in it implement the Comparable interface, or else you have to provide a custom Comparator. LinkedList doesn't implement Comparable, so you get an exception. The solution here could be to use a different map, like HashMap, or you could write a custom Comparator.
A custom Comparator could be like this:
revealed = new TreeMap<List<Integer>, Boolean>(
// sort by x value first
Comparator.comparing( list -> list.get(0) )
// then sort by y if both x values are the same
.thenComparing( list -> list.get(1) )
);
(And I felt compelled to include this, which is a more robust example that isn't dependent on specific elements at specific indexes):
revealed = new TreeMap<>(new Comparator<List<Integer>>() {
#Override
public int compare(List<Integer> lhs, List<Integer> rhs) {
int sizeComp = Integer.compare(lhs.size(), rhs.size());
if (sizeComp != 0) {
return sizeComp;
}
Iterator<Integer> lhsIter = lhs.iterator();
Iterator<Integer> rhsIter = rhs.iterator();
while ( lhsIter.hasNext() && rhsIter.hasNext() ) {
int intComp = lhsIter.next().compareTo( rhsIter.next() );
if (intComp != 0) {
return intComp;
}
}
return 0;
}
});
The issue that you don't know about is that you're only ever adding one LinkedList to the map:
index.add(x);
index.add(y);
// putting index in to the map
// without making a copy
revealed.put(index, true);
// modifying index immediately
// afterwards
index.remove(x);
index.remove(y);
This is unspecified behavior, because you put the key in, then modify it. The documentation for Map says the following about this:
Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map.
What will actually happen (for TreeMap) is that you are always erasing the previous mapping. (For example, the first time you call put, let's say x=0 and y=0. Then the next time around, you set the list so that x=1 and y=1. This also modifies the list inside the map, so that when put is called, it finds there was already a key with x=1 and y=1 and replaces the mapping.)
So you could fix this by saying something like either of the following:
// copying the List called index
revealed.put(new LinkedList<>(index), true);
// this makes more sense to me
revealed.put(Arrays.asList(x, y), true);
However, this leads me to the 3rd point.
There are better ways to do this, if you want practice with collections. One way would be to use a Map<Integer, Map<Integer, Boolean>>, like this:
Map<Integer, Map<Integer, Boolean>> revealed = new HashMap<>();
{
revealed.computeIfAbsent(x, HashMap::new).put(y, true);
// the preceding line is the same as saying
// Map<Integer, Boolean> yMap = revealed.get(x);
// if (yMap == null) {
// yMap = new HashMap<>();
// revealed.put(x, yMap);
// }
// yMap.put(y, true);
}
That is basically like a 2D array, but with a HashMap. (It could make sense if you had a very, very large game board.)
And judging by your description, it sounds like you already know that you could just make a boolean isRevealed; variable in the Tile class.
From the spec of a treemap gives me this:
The map is sorted according to the natural ordering of its keys, or by a Comparator provided at map creation time, depending on which constructor is used.
The Java Linkedlist can not be compared just like that. You have to give it a way to compare them or just use another type of map, that does not need sorting.