Can an array be used as a HashMap key?
It will have to be the same object. A HashMap
compares keys using equals()
and two arrays in Java are equal only if they are the same object.
If you want value equality, then write your own container class that wraps a String[]
and provides the appropriate semantics for equals()
and hashCode()
. In this case, it would be best to make the container immutable, as changing the hash code for an object plays havoc with the hash-based container classes.
EDIT
As others have pointed out, List<String>
has the semantics you seem to want for a container object. So you could do something like this:
HashMap<List<String>, String> pathMap;
pathMap.put(
// unmodifiable so key cannot change hash code
Collections.unmodifiableList(Arrays.asList("korey", "docs")),
"/home/korey/docs"
);
// later:
String dir = pathMap.get(Arrays.asList("korey", "docs"));
Using array as key for hashmap java
Arrays are not suitable as keys in HashMap
s, since arrays don't override Object
's equals
and hashCode
methods (which means two different array instances containing the exact same elements will be considered as different keys by HashMap
).
The alternatives are to use a List<String>
instead of String[]
as the key of the HashMap
, or to use a TreeMap<String[]>
with a custom Comparator<String[]>
passed to the constructor.
How to make HashMap work with Arrays as key?
You cannot do it this way. Both t
and a
will have different hashCode()
values because the the java.lang.Array.hashCode()
method is inherited from Object
, which uses the reference to compute the hash-code (default implementation). Hence the hash code for arrays is reference-dependent, which means that you will get a different hash-code value for t
and a
. Furthermore, equals
will not work for the two arrays because that is also based on the reference.
The only way you can do this is to create a custom class that keeps the boolean
array as an internal member. Then you need to override equals
and hashCode
in such a way that ensures that instances that contain arrays with identical values are equal and also have the same hash-code.
An easier option might be to use List<Boolean>
as the key. Per the documentation the hashCode()
implementation for List
is defined as:
int hashCode = 1;
Iterator<E> i = list.iterator();
while (i.hasNext()) {
E obj = i.next();
hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}
As you can see, it depends on the values inside your list and not the reference, and so this should work for you.
How can I use a string array as key in hash map?
You can create a Map
while using the String
value at each array
index as the key, and an Integer
as the value to keep track of how many times a word appeared.
Map<String,Integer> map = new HashMap<String,Integer>();
Then when you want to increment, you can check if the Map
already contains the key, if it does, increase it by 1, otherwise, set it to 1.
if (occurences.containsKey(word)) {
occurences.put(word, occurences.get(word) + 1);
} else {
occurences.put(word, 1);
}
So, while you are looping over your string array, convert the String
to lower case (if you want to ignore case for word occurrences), and increment the map using the if
statement above.
for (String word : words) {
word = word.toLowerCase(); // remove if you want case sensitivity
if (occurences.containsKey(word)) {
occurences.put(word, occurences.get(word) + 1);
} else {
occurences.put(word, 1);
}
}
A full example is shown below. I converted to words to lowercase to ignore case when using the key in the map, if you want to keep case, remove the line where I convert it to lowercase.
public static void main(String[] args) {
String s = "This this the has dog cat fish the cat horse";
String[] words = s.split(" ");
Map<String, Integer> occurences = new HashMap<String, Integer>();
for (String word : words) {
word = word.toLowerCase(); // remove if you want case sensitivity
if (occurences.containsKey(word)) {
occurences.put(word, occurences.get(word) + 1);
} else {
occurences.put(word, 1);
}
}
for(Entry<String,Integer> en : occurences.entrySet()){
System.out.println("Word \"" + en.getKey() + "\" appeared " + en.getValue() + " times.");
}
}
Which will give me output:
Word "cat" appeared 2 times.
Word "fish" appeared 1 times.
Word "horse" appeared 1 times.
Word "the" appeared 2 times.
Word "dog" appeared 1 times.
Word "this" appeared 2 times.
Word "has" appeared 1 times.
HashMap with List of Objects as a Key
The problem with your code lies in many aspects: using a List as a key, re-using the same references for a second pair and the NewClass
implementation. As others have already pointed out, you're using as a HashMap key an ArrayList
which is a bad design choice because as a mutable object its hashCode is subject to change introducing the risk of losing access to its paired element. Furthermore, what is also hurting you is that the hashCode
and the equals
methods of the ArrayList
are defined on the elements' implementation of said methods.
ArrayList's equals()
ArrayList's hashCode()
HashMap Implementation
TheHashMap
is implemented as a Hash table containing an array of buckets. Each key's hashCode()
is mapped to a bucket (array) index and different keys are allowed to have the same hashcode ([hashCode contract][4]). This is why each bucket can contain multiple key-value pairs arranged via a Linked data structure. So, when you're invoking the get
method, first, a bucket is selected by mapping the key's hashCode to the bucket index, then the target entry is searched by calling the equals()
method on its key.Code Explanation
As shown from the output, after adding the second pair with a different list as the key, we can see that the map's size is still 1. This is because you've used the exact same references (obj1 and obj2) to build both the first and second key, yielding the same hashCode for list1 and list2, as the ArrayList's hashCode is built upon its elements. Once, the second pair is added to the HashMap, its key's hashCode returns the same value of the first key, indexing the same bucket and then replacing the first pair, since the second key equals the first pair's key.Now getting to your question. The situation described would have taken place even if the List's elements had been different references with same values as long as the NewClass
' equals()
method had been defined on the exact three fields passed to the constructor (int, String, String). My guess is that the NewClass' equals()
method hasn't been defined or it confronts different fields. Both equals()
and hashCode
should work on the same set of fields. In fact, if we define your NewClass
as follows, also the third addition will replace the only pair contained within the HashMap.
public class Test {
public static void main(String[] args) {
List<NewClass> list1 = new ArrayList<>();
List<NewClass> list2 = new ArrayList<>();
NewClass obj1 = new NewClass(1, "ddd", "eee@gmail.com");
NewClass obj2 = new NewClass(2, "ccc", "kkk@gmail.com");
list1.add(obj1);
list1.add(obj2);
list2.add(obj1);
list2.add(obj2);
Map<List<NewClass>, Integer> mapClass = new HashMap<>();
mapClass.put(list1, 1234);
mapClass.put(list2, 4567);
System.out.println(mapClass.size());
System.out.println(mapClass.get(list1));
NewClass obj4 = new NewClass(1, "ddd", "eee@gmail.com");
NewClass obj5 = new NewClass(2, "ccc", "kkk@gmail.com");
List<NewClass> list3 = new ArrayList<>();
list3.add(obj4);
list3.add(obj5);
System.out.println(mapClass.get(list3));
System.out.println(list1.hashCode());
System.out.println(list2.hashCode());
System.out.println(list3.hashCode());
}
}
class NewClass {
int id;
String s1, s2;
public NewClass(int id, String s1, String s2) {
this.id = id;
this.s1 = s1;
this.s2 = s2;
}
public int hashCode() {
return Objects.hash(id, s1, s2);
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != getClass()) return false;
NewClass nc = (NewClass) obj;
return nc.id == id && Objects.equals(s1, nc.s1) && Objects.equals(s2, nc.s2);
}
}
Output
Conclusions
In conclusion, as others have already said, you shouldn't be using mutable objects as keys for a HashMap. The changes applied on a key internal state may alter its hashCode, making its paired value untraceable or even worst retrieving another key's value in a very remote scenario. These are some helpful guidelines on how to design a HashMap key:- https://howtodoinjava.com/java/collections/hashmap/design-good-key-for-hashmap/
- https://www.baeldung.com/java-custom-class-map-key
Related Topics
How to Check If My String Is Equal to Null
How to Read JSON File into Java with Simple JSON Library
Efficient Swapping of Elements of an Array in Java
Are "While(True)" Loops So Bad
Convert from List<Completablefuture> to Completablefuture<List>
Why Does String.Valueof(Null) Throw a Nullpointerexception
Differencebetween 'Java', 'Javaw', and 'Javaws'
How to Add a Maven Dependency in Eclipse
What Is More Efficient: System.Arraycopy or Arrays.Copyof
Filter Values Only If Not Null Using Lambda in Java8
How to Redirect to Login Page When Session Is Expired in Java Web Application
Windows Shortcut (.Lnk) Parser in Java
Streaming Large Files in a Java Servlet
Convert a String (Like Testing123) to Binary in Java
How to Update Path Variable Permanently from Windows Command Line
Java - Best Way to Grab All Strings Between Two Strings? (Regex)
How to Fix Ambiguous Type on Method Reference (Tostring of an Integer)