Can an Array Be Used as a Hashmap Key

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 HashMaps, 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()

Sample Image

ArrayList's hashCode()

Sample Image

Sample Image

HashMap Implementation

The HashMap 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

Sample Image

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



Leave a reply



Submit