I have a hashmap initialized as follows:
Hashmap<String[][], Boolean> tests = new Hashmap<String [][], Boolean>();
I would like to insert into tests without having to initialize the key:
tests.put({{"a"}, {"a"}}, true);
However, Java doesn't seem to let me do this. It works if I do it like this:
String[][] hi = {{"a"}, {"a"}};
tests.put(hi, true);
Is there any way to avoid the latter and get the former working?
Can someone also explain the reasoning behind this error?
Thanks
Yes, you can write like this:
tests.put(new String[][] {{"a"}, {"a"}}, true);
This is often referred to as an anonymous array or a just-in-time array.
In your case you would have to use
tests.put(new String[][]{{"a"}, {"a"}}, true);
because as you noticed {{"a"}, {"a"}}
String[][] hi = {{"a"}, {"a"}};
can be used only while creating reference to array.
You can use
tests.put(new String[][]{{"hello", "goodbye"},{"hi", "bye"}}, true);
This is almost definitely not what you want.
Arrays in Java get their equality and hash code from Object -- which is to say, based on their reference identity. So:
String[] a = { "hello" }; // create one array
String[] b = { "hello" }; // create a different array with the same contents
assert a != b; // the two references are to different objects
assert ! a.equals(b); // they're not equal
assert a.hashCode() != b.hashCode(); // neither are their hashes (probably)
a and b will not be equal, and their hash codes will almost certainly not be equal, since they are different objects. This means that if you use an array as the key to a hash map, you won't be able to retrieve the value using an key but the exact one that you created it with: any other array will have a different hash code and will be non-equal, and therefore won't be considered an equivalent key.
The solution is to replace the String[][] with a List<List<String>>. Lists define equality and hash codes based on their contents, so a list containing [ "hello" ] is equal to any other list containing [ "hello" ]:
List<String> x = Arrays.asList("hello");
List<String> y = Arrays.asList("hello");
assert x != y; // the two lists are different objects
assert x.equals(y); // but they're equal
assert x.hashCode() == y.hashCode(); // and so are their hash codes
Now you can use the lists as keys. Keep in mind that once a list is a key to the map, it's not allowed to change values. Doing so will probably break the hash map, because the list's hash code will have changed, but the map won't know about it, so the map will look for it in the wrong hash bucket.
The easiest options here are:
be sure that nobody else has a reference to that same List object and might change it
create a deep copy of the List before you put it into the map (that is, copy the "inner" lists as well as the "outer" one)
For the second option, it'd be something like:
// copy the outer list
List<List<String>> outerCopy = new ArrayList<List<String>>( originalList );
ListIterator<List<String>> listIterator = outerCopy.listIterator();
while (listIterator.hasNext()) {
// make a copy of the inner list
List<String> innerCopy = new ArrayList<String>( listIterator.next() );
listIterator.set(innerCopy);
}
Related
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 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.
Hi I am trying to save a object in HashMap if it is not exist than if it is exsit I want to control its value with new data. If data change than I want to do sth. else. But whenever I tried to compare new data and hash value I saw they are same on every time . How can I handle with this issue. There is code:
BluetoothLeDevice deviceLe;
private Map<String, byte[]> mMacMap;
byte [] integer0 =new byte[4];
byte[] tempInteger0=new byte[4];
public void addSensor(String macId, BluetoothLeDevice deviceLe) {
byte [] addSensorrecord=deviceLe.getScanRecord();
int j=0;
for(int i=15;i<19;i++)
{
integer0 [j]=addSensorrecord[i];
j++;
}
if (mMacMap.containsKey(macId)) {
tempInteger0 = mMacMap.get(macId);
if(!integer0 .equals(tempInteger0))
{
mMacMap.remove(macId);
mMacMap.put(macId, integer0 );
new SendBLEData().execute(deviceLe);
}
} else {
final byte [] LocalInteger0=new byte[4];
int t=0;
for(int i=15;i<19;i++)
{
LocalInteger0[t]=addSensorrecord[i];
t++;
}
mMacMap.put(macId, LocalInteger0);
new SendBLEData().execute(deviceLe);
}
}
I am guessing, that your problem is here:
!Integer0.equals(tempInteger0))
I think you want to compare two arrays; and you are surprised to find them to be different ... all the time.
Your problem is: equals() on arrays doesn't do a comparison of the array content. In other words: this call to equals() only gives "true" if the arrays you are comparing ... are one and the same, like in:
int a[] = { 1 };
int b[] = a;
int c[] = { 1 };
Here:
a.equals(b) --> true
but
a.equals(c) --> false
When comparing array content matters, then you should use ArrayList instead. Because two ArrayList objects are equal when they contain exactly the same equal elements.
And you see: you are using that equals on arrays to make a decision in your code. So, you either change to ArrayLists; or use Arrays.equals() as user hamsty suggested.
Just a few additions to the already posted answers.
The remove below is not necessary, a simple put will replace the old value
mMacMap.remove(macId);
mMacMap.put(macId, integer0 );
From the javadoc
If the map previously contained a mapping for the key, the old value
is replaced by the specified value. (A map m is said to contain a
mapping for a key k if and only if m.containsKey(k) would return
true.)
Have you considered making bytes 15-19 into a string and adding them onto the maps key? This would eliminate the array compare and make the lookups much faster.
!Integer0.equals(tempInteger0))
is your problem.
Use this to compare the content of arrays:
Arrays.equals(Integer0, tempInteger0)
The problem is the following sequence of events:
macId not in mMacMap, insert new byte[4]; into the map
macId in mMacMap, the array created in the previous step never matches integer0 due to the Array comparison problem mentioned by the other answer, replace macId in the map with a reference to integer0
macId in mMacMap, since the array is a reference to integer0, it will always compare positively and the contents will no longer be updated.
Repeat 3.
Basically caused by these two issues:
Array#equals does not behave intuitively, use the static method Arrays.equals
Java is heavily reference-based, so if you insert something into a map it will not be copied, but simply a new reference is created; this may bite you if you change a shared object afterwards (like the array).
Question: Why does the following print out:
ChildB___Parent of ChildB
ChildB___Parent of ChildB
Instead of what I think it should print out:
ChildA___Parent of ChildA
ChildB___Parent of ChildB
Short Self Contained Generalized Example of Issue:
import java.util.ArrayList;
import java.util.List;
public class StackExchangeQuestion1 {
public static void main(String[] args){
List<String[]> list = new ArrayList();
String[] pair = {"childID","parentID"}; //for readability this gets overwritten
//adding values to list
pair[0] = "ChildA";
pair[1] = "Parent of ChildA";
list.add(pair);
pair[0] = "ChildB";
pair[1] = "Parent of ChildB";
list.add(pair);
//checking values in list
for(int i=0;i<list.size();i++){
pair = list.get(i); //variable reuse for readability
System.out.println(pair[0]+"___"+pair[1]);
}
}}//end class and main
You are adding the same String[] reference twice to your list. Because of this, both elements in your list point to the same object. So, when you overwrote your array, you were really writing to the same piece of memory. The list was referencing this piece of memory twice, so you get 2 identical print statements.
Java will pass all non-primitive values by reference (that means it will pass a pointer to where a non-primitive object is stored in memory.)
If you were storing integers instead like so:
int a = 10;
list.add(a);
a = 20;
list.add(a);
Everything would be fine, because a is an interger, integers are primtive values, and primitive values are passed by value. This means the data is stored in a is copied to the list, not the point in memory a is held at.
Strings and arrays of any sort, however, are non-primitive.
To fix your code then, you need to pass 2 separate references to your list:
String[] pair1 = {"ChildA","Parent of ChildA"};
String[] pare2 = {"ChildB", "Parent of ChildB"};
//adding values to list
list.add(pair1);
list.add(pair2);
Edit:
You'd expressed concerns in comments about verbosity. First, I think your code isn't verbose. But if you're truly concerned about it here's a way to shorten it:
list.add(new String[]{"ChildA","Parent of ChildA"});
list.add(new String[]{"ChildB","Parent of ChildB"});
Because an ArrayList stores references, not objects. The reference pair always refers to the same object; you're simply adding that reference to your list twice.
Your string object array is overriding here. Your last change only will reflect in your string because list will store object's reference. If you want to get your assumed output create new objects every time before you set value.
I am looking for a java data structure similar to an ArrayList that when I do an add or a push with only a value argument an index will be returned for me automatically.
For example:
ArrayList<String> elements = new ArrayList<String>();
String element = "foo";
String elementTwo = "bar";
int index1 = elements.add(element); //note this does not exist, i.e. returns bool in api
int index2 = elements.add(elementTwo);
System.out.println(elements.get(index1)); //would give "foo"
I could see writing a wrapper class around ArrayList that manages a counter that is incremented on every add operation and invoking:
ArrayList.add(int index, E element)
Do you really need to write a wrapper around ArrayList for this? This seems like something simple enough to be provided out of the box somewhere?
Edit:
I need the index (key) to be fixed and unique for this usecase. A map was suggested and I agree with that. Does anyone know of a map implementation that gives you an automatically (uniquely) generated key on a value insert? I am just trying to decide if I need to implement my own wrapper for this.
The element will be added at the end of the list. So you can use elements.size()-1 to get the new elements index.
Note that this will not work reliable if multiple threads are modifying the list at the same time.
EDIT: Also note that it might not be a good idea to use an ArrayLists index as a unique ID because an elements index can change (for example when you remove an element or insert a new one using add(int, Object)). If this is a problem depends on what you want to do with the index: If you only need it for a short time after adding an element and can be sure that the list is not modified in the meantime, there is no problem. In the other case even a method returning the index when calling add(Object) would not help because the index does not get updated in anyway. To prevent this issue you can:
Make sure you never remove elements from the list and never add elements using add(int, Object).
Instead of removing elements you could also set them to null using the method set(int, null). This way no elements index will change.
Use some other data structure like for example a map with a custom ID like helloannalil suggests in his answer.
EDIT 2: I did not find a appropriate, ready to use implementation (but this does not mean there is none, of course). To suggest a good solution, more information on the intended use of the data structure is needed, but here are some ideas and notes:
If the maximum number of elements is not to large, an ArrayList could be used and the elements index represents the ID. As stated above, to remove an element it can be set to null so that no indices are changed. When inserting, positions with null values can be reused.
You can also use one of the two methods show in this answer: https://stackoverflow.com/a/8939049/1347968 (keywords AtomicLong or IdentityHashMap)
Do not depend on the "uniqueness" of Object.hashCode() or System.identityHashCode(Object) as it is not guaranteed (try it by running the example at the bottom of Suns/Oracles Bug #6321873).
Well what I do in that cases (I love ArrayLists) is to get the last index by asking the size of the list:
String thing = "theThing";
List<String> strList = new ArrayList<String>();
strList.add(thing);
int indexOfThing = strList.size() - 1;
I mean, is easier than implement your own List and just works.
if you really want this function, you can use map but not list
Based on your comments and edited question I think you can extend a HashMap for your use like this:
public class MyMap<V> extends HashMap<Integer, V> {
private static final long serialVersionUID = 1L;
public int add(V elem) {
int key = System.identityHashCode(elem);
super.put(key, elem);
return key;
}
}
Then inside your class declare MyMap like this:
private MyMap<String> map = new MyMap<String>();
And then add your elements to MyMap like this:
.....
.....
String element = "foo";
String elementTwo = "bar";
int index1 = map.add(element);
int index2 = map.add(elementTwo);
Now you have index1 and index2 as indices of you inserted strings that you can use or pass around for the lifetime of your application. You can insert or remove elements in MyMap as many times you want but your indices (index1 and index2) will give you back your inserted elements like this:
String elem1 = map.get(index1); // will return "foo"
String elem2 = map.get(index2); // will return "bar"
String thing = "theThing";
List<String> strList = new ArrayList<String>();
strList.add(thing);
int indexOfThing = strList.size() - 1;
If you remove an item, this will no longer work.