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?
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:
hashCode
of the key. Store value under that hashCodeget:
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;
}