Search code examples
dictionaryequalskey-valuehashcodepojo

How to get the value of a map from a key which is a POJO


I got the following function

Map<MyClass, String> someFunction() {
    Map<MyClass, String> result = new HashMap<>();
    return result.put(new MyClass("someString"), "someOtherString"));
}

The implementation of MyClass looks like the following:

public class MyClass{

    String string;

    public MyClass(String string) {
        this.string = string;
    }

    public void setString(String string) {
        this.string = string;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((string== null) ? 0 : string.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        MyClass other = (MyClass) obj;
        if (string== null) {
            if (other.string!= null) {
                return false;
            }
        } else if (!string.equals(other.string)) {
            return false;
        }
        return true;
    }

}

In my test I am doing the following:

@Test
public void test() {
    Map<MyClass, String> outcome = classUnderTest.someFunction();

    assertThat(outcome.get(new MyClass("someString")), is("someOtherString"));
}

But this test fails, because actual is null. If I try the following:

assertThat(outcome.keySet(), hasItem(MY_CLASS));

this also fails, telling me, that these are different intantiations. I even tried to debug my test, but it never reaches the equals method. Can you tell me what is happening here?


Solution

  • Are you sure, that your method doesn't modify the objecT? I think, that someFunction replaces the string in MyClass. That causes that your object of MyClass return another hashCode.

    A HashMap works like that:

    put:

    • compute hashCode of the key. Store value under that hashCode

    get:

    • compute hashCode of the key. Search for a value with that hashCode. If there is a value, then maybe call equals.

    So: never use mutable values as key! Otherwise, you may lose your data (or make it difficult to resolve)

    Just try to execute this test, it should be green

    import static org.hamcrest.Matchers.is;
    import static org.junit.Assert.assertThat;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import org.junit.Test;
    
    public class SomeTest {
    
        Map<MyClass, String> someFunction() {
            Map<MyClass, String> result = new HashMap<>();
            result.put(new MyClass("someString"), "someOtherString");
            return result;
        }
    
        @Test
        public void test() {
            Map<MyClass, String> outcome = someFunction();
    
            assertThat(outcome.get(new MyClass("someString")), is("someOtherString"));
        }
    
        public static class MyClass {
    
            String string;
    
            public MyClass(String string) {
                this.string = string;
            }
    
            public void setString(String string) {
                this.string = string;
            }
    
            @Override
            public int hashCode() {
                final int prime = 31;
                int result = 1;
                result = prime * result + ((string == null) ? 0 : string.hashCode());
                return result;
            }
    
            @Override
            public boolean equals(Object obj) {
                if (this == obj) {
                    return true;
                }
                if (obj == null) {
                    return false;
                }
                if (getClass() != obj.getClass()) {
                    return false;
                }
                MyClass other = (MyClass) obj;
                if (string == null) {
                    if (other.string != null) {
                        return false;
                    }
                } else if (!string.equals(other.string)) {
                    return false;
                }
                return true;
            }
    
        }
    }
    

    but if you modify MyClass object after it was added to Map, the test became red:

    Map<MyClass, String> someFunction() {
        Map<MyClass, String> result = new HashMap<>();
        MyClass key = new MyClass("someOldString");
        result.put(key, "someOtherString");
        key.setString("someString");
        return result;
    }