What immutable object works with List.contains? - java

I want to create a list of coordinates and be able to check if this list contains a new coordinate.
I have tried implementing Pair, but using List.contains() with a Pair doesn't work, always returning false.
What other objects can I use that I will be able to check against List.contains()?

This is probably because your implementation of Pair doesn't provide an overridden equals method. I was able to reproduce your issue using the following code:
//a plain POJO, in Pair.java
public class Pair<A, B> {
private A a;
private B b;
public Pair(A a, B b) {
this.a = a;
this.b = b;
}
public A getA() {
return a;
}
public B getB() {
return b;
}
}
//... Main.java
public class Main {
public static void main(String[] args) {
List<Pair<Integer, String>> list = new ArrayList<>();
Pair<Integer, String> one = new Pair<>(1, "hello");
Pair<Integer, String> two = new Pair<>(1, "hello");
list.add(one);
System.out.println(list.contains(two));
}
}
This prints out false, because List.contains uses object equality as a test (the default equals method in class Object). With the above code, for example, one.equals(two) evaluates to false, because they're not the same object. To fix this, you have to provide an equals method that looks at each field and compares them individually:
//in class Pair
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Pair<?, ?> pair = (Pair<?, ?>) o;
return Objects.equals(a, pair.a) &&
Objects.equals(b, pair.b);
}
You can delegate the somewhat tedious and error-prone task of writing this code to your IDE. I'm using Intellij, and it's only a matter of clicking Code/Generate/equals() and hashcode() . You don't need hashcode() for this particular case, but it's always a good idea to keep equals() and hashcode() together. Now, when List.contains tries to find an element that is equal to the one you provided, it will use this new and more appropriate method. If you run the main method again, you'll see it evaluates to true.

List.contains checks to see if two objects are equal using the Object.equals method. Therefore, it only works with objects that have an Object.equals method implemented. If you are writing your own Pair class, make sure you give it an equals method.

I think your Pair contains the same value, but the object isn't the same. That's because it doesn't work. You could use a Map instead.
How you can use a map:
Map<String, Integer> map = new HashMap<>();
map.put("Hello", 0); // add key an value to map (Key: "Hello", Value: 0)
map.put("World", 1); // add key an value to map (Key: "World", Value: 1)
if (map.containsKey("World")) { // check if map contains key: "World"
System.out.println(map.get("World")); // try return value by key: returns 1
System.out.println(map.get("world")); // try return value by key: returns null
}
if (map.containsKey("Hello")) { // check if map contains key: "Hello"
System.out.println(map.get("Hello")); // try return value by key: returns 0
System.out.println(map.get("hello")); // try return value by key: returns null
}

Related

Custom key generation and collision in a hashMap

I have a method that is expected to save an object in a hashmap (used as a cache) that has as a key a String.
The objects that are passed in the method have either fields that are “volatile” i.e. they can change on a next refresh from the data store or are the same across all objects except for 3 fields.
Those fields are 2 of type double and 1 field of type String.
What I am doing now is I use:
Objects.hash(doubleField1, doubleField2, theString)
and convert the result to String.
Basically I am generating a key for that object based on the immutable state.
This seems to work but I have the following question:
If we exclude the case that we have 2 objects with the exact same fields (which is impossible) how likely is that I could end up with a collision that won’t be able to be verified properly?
I mean that if I have a hashmap with keys e.g. strings etc if there is a collision on the hashCode the actual value of the key is compared to verify if it is the same object and not a collision.
Would using keys the way I have described create problems in such verification?
Update:
If I have e.g. a hashmap with key a Person and the hashCode is generated using fullName and ssn or dateOfBirth if there is a collision then the hashmap implementation uses equals to verify if it is the actual object being searched for.
I was wondering if the approach I describe could have some issue in that part because I generate the actual key directly
Here is a simple demo for a hashMap key implementation. When retrieving the object I construct the fields piecemeal to avoid any possibility of using cached Strings or Integers. It makes a more convincing demo.
Map<MyKey, Long> map = new HashMap<>();
map.put(new MyKey(10,"abc"), 1234556L);
map.put(new MyKey(400,"aefbc"), 548282L);
int n = 380;
long v = map.get(new MyKey(n + 20, "ae" + "fbc")); // Should get 548282
System.out.println(v);
prints
548282
The key class
class MyKey {
privat eint v;
private String s;
private int hashcode;
public MyKey(int v, String s) {
Objects.requireNonNull(s, "String must be provided");
this.v = v;
this.s = s;
// this class is immutable so no need to keep
// computing hashCode
hashcode = Objects.hash(s,v);
}
#Override
public int hashCode() {
return hashcode;
}
#Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o == null) {
return false;
}
if (o instanceof MyKey) {
MyKey mk = (MyKey)o;
return v == mk.v && s.equals(mk.s);
}
return false;
}
}

Java - test which checks if values of a HashMap are correctly copied to ArrayList returns false

I am supposed to write a method which returns all the values of a HashMap in the form of a List.
For context, here are the classes I am using. The class Question is a quiz-like multi-choice question which has multiple answers mapped in a HashMap with the key values being ( a), b), c), etc).
The class Answer has the following interface:
public class Answer{
private String answerText= "";
private boolean correct;
public Answer(String answerText, boolean correct) {
this.correct = correct;
this.answerText = answerText;
}
...
The class Question has the following interface:
public class Question{
private String text;
private double points;
private HashMap<String, Answer> answers= new HashMap<> ();
public void addAnswer(String id, String text, boolean correct)
answers.put(id, new Answer(text, correct));
}
...
Each question has a text (the actual question), points (the number of points you get for it) and HashMap<String, Answer> answers where String is the key of the answer( a), b), c) and so on), and the Answer is the actual answer (so each key corresponds to one question).
Now, I was given a task to write a method in the class Question which returns all answers in the form of a list. I have been given a JUnit test for this method and here is the code of the test:
#Test
public void testGetListAnswers(){
Question question = new Question("Some question", 2);
question.addAnswer("a", "first answer", true);
question.addAnswer("b", "second answer", false);
List<Answer> answers= question.getListAnswers();
assertAll(
() -> assertEquals(2, answers.size()),
() -> assertTrue(answers.contains(new Answer("first answer", true))),
() -> assertTrue(answers.contains(new Answer("second answer", false))),
() -> assertFalse(answers.contains(new Answer("package", false)))
);
}
And here is the actual method I wrote:
public List<Answer> getListAnswers() {
ArrayList<Answer> list = new ArrayList<>();
for(Map.Entry<String, Answer> map : answers.entrySet()) {
Answer o = map.getValue();
list.add(o);
}
return list;
}
What I tried
I wrote a simple method which uses a for loop to iterate through the set and simply adds every value of the HashMap to an ArrayList.
However, the test fails.
Your method sounds fine.
However the test assertions do
assertTrue(answers.contains(new Answer("first answer", true))
In the List javadocs following is written to the contains method
Returns true if this list contains the specified element. More
formally, returns true if and only if this list contains at least one
element e such that (o==null ? e==null : o.equals(e)).
Since they create a new Answer instance and there is no equals implementation in Answer, the equals from the base Object class is called. This will return false because the basic equals method compares the references.
The equals method for class Object implements the most discriminating possible equivalence relation on objects; that is, for any non-null reference values x and y, this method returns true if and only if x and y refer to the same object (x == y has the value true).
If you implement and override the equals (and hashCode) method in Answer to compare answerText and correct, the tests should pass.
A quick equals (generated by intellij IDEA) could look like
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Answer)) return false;
Answer that = (Answer) o;
return correct == that.correct &&
answerText.equals(that.answerText);
}

Peculiar HashMap Behavior

I was reviewing one of Oracle’s Java Certification Practice Exams when I came across the follow question:
Given:
class MyKeys {
Integer key;
MyKeys(Integer k) {
key = k;
}
public boolean equals(Object o) {
return ((MyKeys) o).key == this.key;
}
}
And this code snippet:
Map m = new HashMap();
MyKeys m1 = new MyKeys(1);
MyKeys m2 = new MyKeys(2);
MyKeys m3 = new MyKeys(1);
MyKeys m4 = new MyKeys(new Integer(2));
m.put(m1, "car");
m.put(m2, "boat");
m.put(m3, "plane");
m.put(m4, "bus");
System.out.print(m.size());
What is the result?
A) 2
B) 3
C) 4
D) Compilation fails
My guess was B because m1 and m3 are equal due to their key references being the same. To my surprise, the answer is actually C. Does put() do something that I am missing? Why wouldn’t "plane" replace "car"? Thank you!
With given definition of class i.e
class MyKeys {
Integer key;
MyKeys(Integer k) {
key = k;
}
public boolean equals(Object o) {
return ((MyKeys) o).key == this.key;
}
}
It will result ans = 4, it has only equal method, if you add definition of hashcode then it will result ans=3
class MyKeys {
Integer key;
MyKeys(Integer k) {
this.key = k;
}
#Override
public boolean equals(Object o) {
return ((MyKeys) o).key == this.key;
}
#Override
public int hashCode(){
return key*key;
}
}
Contract of equal and hashcode:
If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result. If you only override equals() and not hashCode() your class violates this contract.
The problem you will have is with collections where unicity of elements is calculated according to both .equals() and .hashCode(), for instance keys in a HashMap.
If you have two objects which are .equals(), but have different hash codes, you lose!
If we keep this simple, since this is for a Java Certification.
Notice that MyKeys doesn't override hashCode, you know there will be something about it. And I usually try to remember only one thing about Object.hashCode
As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the JavaTM programming language.)
Or in short, every instance will have a distinct hashcode. Meaning that with this code, every new MyKeys will add a new pair in the map.
In reality, this is a bit more complex since the method still return an integer, so the risk of collision is still present (integer doesn't provide infinite amount of values). You can see a bit more about this here.
This explain why the answer is that the map will have a size of 4. Each key inserted is a different instance.
As answered by others, the ans will be 4, reason being not overriding hashcode method.
For a more clear reason, whenever an object is added in hash map, the hashcode of the key is generated, which decides the location of the entry set. The 2 objects m1 and m3 will have different hash codes as the hashcode method is not overridden (usual hashcode behaviour). Different hash code will not create any collision and a new entry is made.
On the contrary, the equals methods is called only after the hashcode method produces the same result, i.e., same hash code.
In case of m2 and m4 also, the 2 objects have different hash codes, hence 2 different entries, with no calling done to the equals method.
Hence, in cases of hashing, it is necessary to overload hashcode method, along with equals.
It will be more clear when we see the implementation of put method of HashMap.
// here hash(key) method is call to calculate hash.
// and in putVal() method use int hash to find right bucket in map for the object.
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
In your code you #Override only equals method.
class MyKeys {
Integer key;
MyKeys(Integer k) {
key = k;
}
public boolean equals(Object o) {
return ((MyKeys) o).key == this.key;
}
}
To achieve output you need to override both hashCode() and equals() method.

retrieval of duplicate keys in hashmap

Since duplicate key overrides the previous key and its corresponding value in hashmap. But if i call get() method and provide previous key as an argument, it returns the overriden value. How it is possible since that key is overriden by new key so it should throw an exception.
//This class is used as a key
Class MapKey
{
public boolean equlas( Object o)
{
return true;
}
}
//This is test class
class MapTest
{
public static void main(String a[])
{
Map m=new Hashmap<Mapkey, String>();
MapKey mk1=new MapKey();
m.put(mk1,"one");
MapKey mk2=new MapKey();
m.put(mk2,"two");
MapKey mk3=new MapKey();
m.put(mk3,"three");
System.out.println(m.get(mk1));
System.out.println(m.get(mk2));
}
}
output: three
three
Since keys are equal so it should get overriden by last key object mk3.
so how is it possible to retrieve value with first or second key object?
Thanks in advance
This is because you have not overrided hashcode method. In equals method you have declared all the objects are equal. But due to default hashcode implementation all equal objects are returning different hashcodes. As hashcodes are used to save values in a hashmap, you are able to get values for old keys mk1 and mk2.
The equals method isn't the most important for HashMap. As the name suggests, the most important method for inserting values into the HashMap is hashCode, not equals. A value is only overwriten in HashMap, if a key k in the map fullfills the following condition: k.hashCode() is equal to the hashCode of the key for inserting an item and the key equals k.
The code you post doesn't come up with
three
three
even if I fix it to clean up the compile errors:
import java.util.*;
class MapKey
{
public boolean equlas( Object o)
{
return true;
}
}
class MapTest
{
public static void main(String a[])
{
Map<MapKey, String> m=new HashMap<MapKey, String>();
MapKey mk1=new MapKey();
m.put(mk1,"one");
MapKey mk2=new MapKey();
m.put(mk2,"two");
MapKey mk3=new MapKey();
m.put(mk3,"three");
System.out.println(m.get(mk1));
System.out.println(m.get(mk2));
}
}
This prints
one
two
Even if I fix the MapKey to override equals:
class MapKey
{
public boolean equlas( Object o)
{
return true;
}
public boolean equals( Object o)
{
return true;
}
}
this prints the same output.
If I implement hashCode:
class MapKey
{
public int hashCode()
{
return 1;
}
public boolean equals( Object o)
{
return true;
}
}
then it will print out
three
three
The default implementations of hashCode and equals are based on the object reference, references to different objects will not be equal. Maps use hashCode to decide which bucket to store an object in, and use equals to differentiate between different objects in the same bucket.
Here you made three different instances so their hashCodes will be different (as long as there isn't a collision, in which case the equals method will get called to decide if the two objects are the same). In this case there isn't a collision, and it's not until hashCode gets overridden that the different mapKey instances are treated as equivalent.

How to make HashMap work with Arrays as key?

I am using boolean arrays as keys for a HashMap. But the problem is HashMap fails to get the keys when a different array is passed as key, although the elements are same. (As they are different objects).
How can I make it work with arrays as keys ?
Here is the code :
public class main {
public static HashMap<boolean[], Integer> h;
public static void main(String[] args){
boolean[] a = {false, false};
h = new HashMap<boolean[], Integer>();
h.put(a, 1);
if(h.containsKey(a)) System.out.println("Found a");
boolean[] t = {false, false};
if(h.containsKey(t)) System.out.println("Found t");
else System.out.println("Couldn't find t");
}
}
Both the arrays a and t contain the same elements, but HashMap doesn't return anything for t.
How do I make it work ?
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.
It is not possible to do this with arrays, as any two different arrays don't compare equals, even if they have the same elements.
You need to map from container class, for example ArrayList<Boolean> (or simply List<Boolean>. Perhaps BitSet would be even more appropriate.
Map implementations relies on key's equals and hashCode methods. Arrays in java are directly extends from Object, they use default equals and hashCode of Object which only compares identity.
If I were you, I would create a class Key
class Key {
private final boolean flag1;
private final boolean flag2;
public Key(boolean flag1, boolean flag2) {
this.flag1 = flag1;
this.flag2 = flag2;
}
#Override
public boolean equals(Object object) {
if (!(object instanceof Key)) {
return false;
}
Key otherKey = (Key) object;
return this.flag1 == otherKey.flag1 && this.flag2 == otherKey.flag2;
}
#Override
public int hashCode() {
int result = 17; // any prime number
result = 31 * result + Boolean.valueOf(this.flag1).hashCode();
result = 31 * result + Boolean.valueOf(this.flag2).hashCode();
return result;
}
}
After that, you can use your key with Map:
Map<Key, Integer> map = new HashMap<>();
Key firstKey = new Key(false, false);
map.put(firstKey, 1);
Key secondKey = new Key(false, false) // same key, different instance
int result = map.get(secondKey); // --> result will be 1
Reference:
Java hash code from one field
Problems
As others have said, Java arrays inherit .hashcode() and .equals() from Object, which uses a hash of the address of the array or object, completely ignoring its contents. The only way to fix this is to wrap the array in an object that implements these methods based on the contents of the array. This is one reason why Joshua Bloch wrote Item 25: "Prefer lists to arrays." Java provides several classes that do this or you can write your own using Arrays.hashCode() and Arrays.equals() which contain correct and efficient implementations of those methods. Too bad they aren't the default implementations!
Whenever practical, use a deeply unmodifiable (or immutable) class for the keys to any hash-based collection. If you modify an array (or other mutable object) after storing it as a key in a hashtable, it will almost certainly fail future .get() or .contains() tests in that hashtable. See also Are mutable hashmap keys a dangerous practice?
Specific Solution
// Also works with primitive: (boolean... items)
public static List<Boolean> bList(Boolean... items) {
List<Boolean> mutableList = new ArrayList<>();
for (Boolean item : items) {
mutableList.add(item);
}
return Collections.unmodifiableList(mutableList);
}
ArrayList implements .equals() and .hashCode() (correctly and efficiently) based on its contents, so that every bList(false, false) has the same hashcode as, and will be equal to every other bList(false, false).
Wrapping it in Collections.unmodifiableList() prevents modification.
Modifying your example to use bList() requires changing just a few declarations and type signatures. It is as clear as, and almost as brief as your original:
public class main {
public static HashMap<List<Boolean>, Integer> h;
public static void main(String[] args){
List<Boolean> a = bList(false, false);
h = new HashMap<>();
h.put(a, 1);
if(h.containsKey(a)) System.out.println("Found a");
List<Boolean> t = bList(false, false);
if(h.containsKey(t)) System.out.println("Found t");
else System.out.println("Couldn't find t");
}
}
Generic Solution
public <T> List<T> bList(T... items) {
List<T> mutableList = new ArrayList<>();
for (T item : items) {
mutableList.add(item);
}
return Collections.unmodifiableList(mutableList);
}
The rest of the above solution is unchanged, but this will leverage Java's built-in type inference to work with any primitive or Object (though I recommend using only with immutable classes).
Library Solution
Instead of bList(), use Google Guava's ImmutableList.of(), or my own Paguro's vec(), or other libraries that provide pre-tested methods like these (plus immutable/unmodifiable collections and more).
Inferior Solution
This was my original answer in 2017. I'm leaving it here because someone found it interesting, but I think it's second-rate because Java already contains ArrayList and Collections.unmodifiableList() which work around the problem. Writing your own collection wrapper with .equals() and .hashCode() methods is more work, more error-prone, harder to verify, and therefore harder to read than using what's built-in.
This should work for arrays of any type:
class ArrayHolder<T> {
private final T[] array;
#SafeVarargs
ArrayHolder(T... ts) { array = ts; }
#Override public int hashCode() { return Arrays.hashCode(array); }
#Override public boolean equals(Object other) {
if (array == other) { return true; }
if (! (other instanceof ArrayHolder) ) {
return false;
}
//noinspection unchecked
return Arrays.equals(array, ((ArrayHolder) other).array);
}
}
Here is your specific example converted to use ArrayHolder:
// boolean[] a = {false, false};
ArrayHolder<Boolean> a = new ArrayHolder<>(false, false);
// h = new HashMap<boolean[], Integer>();
Map<ArrayHolder<Boolean>, Integer> h = new HashMap<>();
h.put(a, 1);
// if(h.containsKey(a)) System.out.println("Found a");
assertTrue(h.containsKey(a));
// boolean[] t = {false, false};
ArrayHolder<Boolean> t = new ArrayHolder<>(false, false);
// if(h.containsKey(t)) System.out.println("Found t");
assertTrue(h.containsKey(t));
assertFalse(h.containsKey(new ArrayHolder<>(true, false)));
I used Java 8, but I think Java 7 has everything you need for this. I tested hashCode and equals using TestUtils.
You could create a class that contains the array. Implements the hashCode() and equals() methods for that class, based on values:
public class boolarray {
boolean array[];
public boolarray( boolean b[] ) {
array = b;
}
public int hashCode() {
int hash = 0;
for (int i = 0; i < array.length; i++)
if (array[i])
hash += Math.pow(2, i);
return hash;
}
public boolean equals( Object b ) {
if (!(b instanceof boolarray))
return false;
if ( array.length != ((boolarray)b).array.length )
return false;
for (int i = 0; i < array.length; i++ )
if (array[i] != ((boolarray)b).array[i])
return false;
return true;
}
}
You can then use:
boolarray a = new boolarray( new boolean[]{ true, true } );
boolarray b = new boolarray( new boolean[]{ true, true } );
HashMap<boolarray, Integer> map = new HashMap<boolarray, Integer>();
map.put(a, 2);
int c = map.get(b);
System.out.println(c);
Probably it is because equals() method for Array returns acts different then you expect. You should think about implementing your own collecting and override equals() and hashCode().
boolean[] t;
t = a;
If you give this, instead of boolean[] t = {false, false};, then you'll get the desired output.
This is because the Map stores the reference as the key, and in your case, though t has the same values, it doesn't have the same reference as a.
Hence, when you give t=a, it'll work.
Its very similar to this:-
String a = "ab";
String b = new String("ab");
System.out.println(a==b); // This will give false.
Both a & b hold the same value, but have different references. Hence, when you try to compare the reference using ==, it gives false.
But if you give, a = b; and then try to compare the reference, you'll get true.
Map uses equals() to test if your keys are the same.
The default implementation of that method in Object tests ==, i.e. reference equality. So, as your two arrays are not the same array, equals always returns false.
You need to make the map call Arrays.equals on the two arrays to check for equality.
You can create an array wrapper class that uses Arrays.equals and then this will work as expected:
public static final class ArrayHolder<T> {
private final T[] t;
public ArrayHolder(T[] t) {
this.t = t;
}
#Override
public int hashCode() {
int hash = 7;
hash = 23 * hash + Arrays.hashCode(this.t);
return hash;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ArrayHolder<T> other = (ArrayHolder<T>) obj;
if (!Arrays.equals(this.t, other.t)) {
return false;
}
return true;
}
}
public static void main(String[] args) {
final Map<ArrayHolder<Boolean>, Integer> myMap = new HashMap<>();
myMap.put(new ArrayHolder<>(new Boolean[]{true, true}), 7);
System.out.println(myMap.get(new ArrayHolder<>(new Boolean[]{true, true})));
}
You could use a library that accepts an external hashing and comparing strategy (trove).
class MyHashingStrategy implements HashingStrategy<boolean[]> {
#Override
public int computeHashCode(boolean[] pTableau) {
return Arrays.hashCode(pTableau);
}
#Override
public boolean equals(boolean[] o1, boolean[] o2) {
return Arrays.equals(o1, o2);
}
}
Map<boolean[], T> map = new TCustomHashMap<boolean[],T>(new MyHashingStrategy());

Categories