Why are custom objects not equivalent keys for a HashMap? - java

I'm having trouble using my own class as a key for a HashMap
public class ActorId {
private final int playerId;
private final int id;
ActorId(int playerId, int id) {
this.playerId = playerId;
this.id = id;
}
public boolean equals(ActorId other) {
return this.id == other.id && this.playerId == other.playerId;
}
public int hashCode() {
int hash = 1;
hash = hash * 31 + playerId;
hash = hash * 31 + id;
return hash;
}
public String toString() {
return "#" + playerId + "." + id;
}
public int getPlayerId() {
return playerId;
}
}
Here is a failing JUnit test
import static org.junit.Assert.*;
import java.util.Map;
import org.junit.Test;
public class ActorIdTest {
#Test
public final void testAsMapKey() {
ActorId a = new ActorId(123, 345);
ActorId b = new ActorId(123, 345);
assertTrue(a.equals(b));
assertEquals(a.hashCode(), b.hashCode());
// Works with strings as keys
Map<String, String> map1 = new java.util.HashMap<String, String>();
map1.put(a.toString(), "test");
assertEquals("test", map1.get(a.toString()));
assertEquals("test", map1.get(b.toString()));
assertEquals(1, map1.size());
// But not with ActorIds
Map<ActorId, String> map2 = new java.util.HashMap<ActorId, String>();
map2.put(a, "test");
assertEquals("test", map2.get(a));
assertEquals("test", map2.get(b)); // FAILS here
assertEquals(1, map2.size());
map2.put(b, "test2");
assertEquals(1, map2.size());
assertEquals("test2", map2.get(a));
assertEquals("test2", map2.get(b));
}
}

You need to change
public boolean equals(ActorId other) {
....
}
to
public boolean equals(Object other) {
....
}
Tip of the day: Always use #Override annotation.
If you had used the #Override annotation, the compiler would have caught the error and said:
The method equals(ActorId) of type ActorId must override or implement a supertype method

Your code is correct, but you also need to override the equals method inherited from Object.
Add this to your ActorId class:
#Override
public boolean equals(Object other) {
if(other == null || other.getClass() != getClass())
return false;
return equals((ActorId)other);
}

You definitely must override the method equals(Object), and for certain implementation of a Map (HashMap) it is also necesary that you overrdide the method hashCode().
I had the same problem, and without the custom hashCode implementation the equals method of the class "ActorId" was never called.

By default Java invokes boolean equals(Object obj);
So, you login is correct but if you want to OVERRIDE equals() use Object as a parameter and and check the class by instanceOf or getClass() and do a class casting.
if (obj instanceOf ActorId) {
ActorId other = (ActorId)obj;
... compare fields
}

Related

java: issue while access value from TreeMap in multithreading

I have use TreeMap to store key value.
For key using custom object.
But once I have faced very strange issue, I am not able to get value which I have set earlier(with same key).
below is my code
public final class TestOptions implements Cloneable {
private Map<StorageSystemOptionKey, Object> options = new TreeMap<StorageSystemOptionKey, Object>();
private static final class StorageSystemOptionKey implements Comparable<StorageSystemOptionKey> {
/** Constant used to create hashcode */
private static final int HASH = 31;
private final Class<? extends StorageRepository> storageRepositoryClass;
/** The option name */
private final String name;
private StorageSystemOptionKey(Class<? extends StorageRepository> storageRepositoryClass, String name) {
this.storageRepositoryClass = storageRepositoryClass;
this.name = name;
}
public int compareTo(StorageSystemOptionKey o) {
int ret = storageRepositoryClass.getName().compareTo(o.storageRepositoryClass.getName());
if (ret != 0) {
return ret;
}
return name.compareTo(o.name);
}
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final StorageSystemOptionKey that = (StorageSystemOptionKey) o;
if (!storageRepositoryClass.equals(that.storageRepositoryClass)) {
return false;
}
if (!name.equals(that.name)) {
return false;
}
return true;
}
#Override
public int hashCode() {
int result;
result = storageRepositoryClass.hashCode();
result = HASH * result + name.hashCode();
return result;
}
}
void setOption(Class<? extends StorageRepository> fileSystemClass, String name, Object value) {
options.put(new StorageSystemOptionKey(fileSystemClass, name), value);
}
Object getOption(Class<? extends StorageRepository> fileSystemClass, String name) {
StorageSystemOptionKey key = new StorageSystemOptionKey(fileSystemClass, name);
return options.get(key);
}
boolean hasOption(Class<? extends StorageRepository> fileSystemClass, String name) {
StorageSystemOptionKey key = new StorageSystemOptionKey(fileSystemClass, name);
return options.containsKey(key);
}
public int compareTo(TestOptions other) {
if (this == other) {
return 0;
}
int propsSz = options == null ? 0 : options.size();
int propsFkSz = other.options == null ? 0 : other.options.size();
if (propsSz < propsFkSz) {
return -1;
}
if (propsSz > propsFkSz) {
return 1;
}
if (propsSz == 0) {
return 0;
}
int hash = options.hashCode();
int hashFk = other.options.hashCode();
if (hash < hashFk) {
return -1;
}
if (hash > hashFk) {
return 1;
}
return 0;
}
#Override
public Object clone() {
TestOptions clone = new TestOptions();
clone.options = new TreeMap<StorageSystemOptionKey, Object>(options);
return clone;
}
}
calling method to set and get like
public abstract Class<? extends StorageRepository> getStorageRepositoryClass();
public Class<? extends StorageRepository> getStorageRepositoryClass() {
return MyImpl.class;
}
TestOptions opt =new TestOptions(); // shared accross all Threads
Object getProperty(String name) {
return opt.getOption(getStorageRepositoryClass(), name);
}
void setProperty(String name, Object value) {
opt.setOption(getStorageRepositoryClass(), name, value);
}
Using set and get method in multi-threaded application.
queries:
I am calling set/get in multiple time then also I was not able to get value which was set earlier(same key)
Is this due to Treeset implementation is not synchronized
or problem with hashCode, equals or compareTo method implementation?
On a quick glance your compareTo(), equals() and hashCode() look fine. Note that TreeMap will mostly use compareTo() to find elements so that method needs to be correct (your's looks technically correct).
However, TreeMap and TreeSet (as well as other basic collections and maps) are not thread-safe and thus concurrent modifications can cause all kinds of unexpected behavior. We once had a case where 2 threads were trying to add a single element to a hashmap and the threads ended up in an endless loop because the internal list to resolve clashes produced a cycle (due to the concurrent put).
So either use the ConcurrentXxxx maps and collections or synchronize access to yours.
TreeSet is not synchronized. I belive ConcurrentSkipListMap might be better.
Check also your hashCode, equals implementation

Using an interface as a key in a hashmap

I tried to use an interface as a key in a hashMap in order to have 1 map for multiple types of keys. The following seems to work.
interface Foo {
void function();
}
static class Bar implements Foo {
private int id;
public Bar(int id) {
this.id = id;
}
#Override
public void function() {
System.out.println("this is bar");
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Bar bar = (Bar) o;
return id == bar.id;
}
#Override
public int hashCode() {
return Objects.hash(id);
}
}
public static Map<Foo, Integer> map = new HashMap<>();
static class Baz implements Foo {
String name;
public Baz(String name) {
this.name = name;
}
#Override
public void function() {
System.out.println("this is Baz");
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Baz baz = (Baz) o;
return name.equals(baz.name);
}
#Override
public int hashCode() {
return Objects.hash(name);
}
}
public static void main(String[] args) {
Bar bar = new Bar(123);
Baz baz = new Baz("some name");
map.put(bar, 10);
map.put(baz, 20);
System.out.println(map.get(bar));
}
What I am not sure about is if there is some corner case that would break this map?
Is there a case that having an interface as a key would break down? Could I have done it simpler using generics?
The only thing that's slightly out of the ordinary is that the equals methods have to compare Bar and Baz objects. When a Map only has one type of objects, the check this.getClass() == that.getClass in equals method never returns false. You have implemented this correctly though, so you don't have anything to worry about.
You may get more hash collisions than you expect. Imagine you have two classes that both have an int id field and implement hashCode with Objects.hash(id) - now objects of different classes with the same ID have the same hash code. If this use case is expected, you can perturb the hash in a way unique to each class, for example by mixing a class-specific constant to the hash:
#Override
public int hashCode() {
return Objects.hash(1, id);
}
...
#Override
public int hashCode() {
return Objects.hash(2, name);
}
In theory there could be problems with potentially more hash collisions leading to bad performance due to different implementations of hashCode, so you need to be careful, and test it with the real data. Other than that it is a valid use case.

How to create a Map in java with two keys and a list of values

Wants to create a map with two keys and values as a list.
Could you please help me with a sample code for the same
I think you need something like this:
Firstly you need a POJO which contains your composite key, and to avoid messing up the KeySet inside the map, that pojo must implements equals and hashcode
import java.util.Objects;
public class MyKey {
private final String firstKeyAttr;
private final Integer secondKeyAttr;
public MyKey(String firstKeyAttr, Integer secondKeyAttr) {
this.firstKeyAttr = firstKeyAttr;
this.secondKeyAttr = secondKeyAttr;
}
public String getFirstKeyAttr() {
return firstKeyAttr;
}
public Integer getSecondKeyAttr() {
return secondKeyAttr;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyKey myKey = (MyKey) o;
return Objects.equals(firstKeyAttr, myKey.firstKeyAttr) &&
Objects.equals(secondKeyAttr, myKey.secondKeyAttr);
}
#Override
public int hashCode() {
return Objects.hash(firstKeyAttr, secondKeyAttr);
}
}
And then, you can create a map in this way:
public static void main(String[] args) {
Map<MyKey, List<Object>> myMap = new HashMap<>();
myMap.put(new MyKey("key1", 1), Collections.singletonList(1));
...
}

Java - HashMap cannot use new object as search key

What I am trying to do here is to fill a HashMap with objects Obj using the key Key and when I finish I want to have access to those values according to any any of the possible keys. I have written the following code and what happened is that although the first show really shows the values I want, the second one raises a NullPointerException.
import java.util.*;
public class My{
public static void main(String[] args){
Map<Key,Obj> myMap = new HashMap<Key,Obj>();
Obj ob1 = new Obj("Nick",19);
Obj ob2 = new Obj("George",17);
Key key1 = new Key(1,2);
Key key2 = new Key(2,1);
myMap.put(key1,ob1);
myMap.put(key2,ob2);
myMap.get(key1).show();
myMap.get(new Key(1,2)).show();
}
I can tell that somehow Java cannot tell that new Key(1,2) is equal with key1, but I cannot think of how can I overcome this issue.
public class Obj{
private String name;
private int age;
Obj(String name, int age){
this.name = name;
this.age = age;
}
public void show(){
System.out.println(name + " " + age);
}
}
These are the classes I use
import java.* ;
public class Key{
public int x,y;
Key(int x, int y){
this.x = x;
this.y = y;
}
public boolean equals(Key d){
if ((this.x == d.x)&&(this.y == d.y)){
return true;
}
else{
return false;
}
}
}
You have two problems in your implementation of Key. First, the equals' method signature is wrong - it should be public boolean equals(Object). This error would have been easily noticeable if you had used the #Override annotation. Second, you should also override the hashCode() method:
#Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
Key key = (Key) o;
return x == key.x && y == key.y;
}
#Override
public int hashCode() {
return Objects.hash(x, y);
}

How to define multiple equals() function for a class

I want to override "public boolean equals(Object obj)" function, for name and age, in my class named MyObject whose structure is given below
public class MyObject{
private String name;
private int age;
}
How can i ?
#balusC :
What about this ?
vo = new MyObject() {
public boolean equals(Object obj) {
return ((MyObject)obj).name().equals(this.getName());
}
vo = new MyObject() {
public boolean equals(Object obj) {
return ((MyObject)obj).age() == (this.getAge());
Your question is a bit vague, but if the sole purpose is to have different sorting algorithms depending on what property you'd like to use, then rather use a Comparator.
public class Person {
private String name;
private int age;
public static Comparator COMPARE_BY_NAME = new Comparator<Person>() {
public int compare(Person one, Person other) {
return one.name.compareTo(other.name);
}
}
public static Comparator COMPARE_BY_AGE = new Comparator<Person>() {
public int compare(Person one, Person other) {
return one.age > other.age ? 1
: one.age < other.age ? -1
: 0; // Maybe compare by name here? I.e. if same age, then order by name instead.
}
}
// Add/generate getters/setters/equals()/hashCode()/toString()
}
which you can use as follows:
List<Person> persons = createItSomehow();
Collections.sort(persons, Person.COMPARE_BY_NAME);
System.out.println(persons); // Ordered by name.
Collections.sort(persons, Person.COMPARE_BY_AGE);
System.out.println(persons); // Ordered by age.
As to the actual equals() implementation, I'd rather let it return true when the both Person objects are techically or naturally identical. You can use either a DB-generated PK for this to compare on technical identity:
public class Person {
private Long id;
public boolean equals(Object object) {
return (object instanceof Person) && (id != null)
? id.equals(((Person) object).id)
: (object == this);
}
}
or just compare every property to compare on natural identity:
public class Person {
private String name;
private int age;
public boolean equals(Object object) {
// Basic checks.
if (object == this) return true;
if (object == null || getClass() != object.getClass()) return false;
// Property checks.
Person other = (Person) object;
if (name == null ? other.name != null : !name.equals(other.name)) return false;
if (age != other.age) return false;
// All passed.
return true;
}
}
Don't forget to override hashCode() as well when you override equals().
See also:
Object ordering
Sorting an ArrayList of objects
Overriding equals() and hashCode()
I'm not exactly sure what you're aiming at with this. The general expectation of equals() is that it returns false for null and objects of other classes and performs value equality on the relevant fields of the class in question.
While you can certainly handle String and Integer in the following way:
public boolean equals(Object o) {
if (o == null) return false;
if (o instanceof String) return name.equals(o);
if (o instanceof Integer) return ((Integer)o) == age;
...
}
this breaks the contract for equals so you can't do it (except not without things going wrong in very weird ways).
equals is an equivalence relation, so it has to be reflexive, symmetric and transitive. The symmetric part here is key, since if a.equals(b) then b.equals(a). Both String and Integer won't do that for you.
If you want just helper functions that check whether the name or the age is equals to a given name/age, then you can do that without using equals():
public boolean equalsName(String name) { return name.equals(this.name); }
public boolean equalsAge(int age) { return age == this.age; }
Just keep it short and simple (aka KISS principle): write setters and getters. Something like in the following example:
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public int getAge() {
return age;
}
And then in the method you need to do the check you can write:
Person person = new Person();
if(person.getName().equals("Something")) doThis();
if(person.getAge() == 1337) doThat();
Not sure what you mean by "multiple equals()". If you want compare both your fields, you just need to override the equals method like this,
public boolean equals( Object o )
{
if ( o != null && o instanceof MyObject )
{
MyObject m = (MyObject) o;
if (this.name == null)
return false;
return this.name.eqauls(m.name) && this.age == m.age;
}
return false;
}
/// Compute a hash code for the pair.
public int hashCode()
{
int code = name == null ? 0 : name.hashCode();
return code ^ age;
}
It's a good practice to change hashCode whenever you change equals so HashMap works efficiently with your object.
if you do want to override equals, it should look something like this:
static private <T> boolean checkEquals(T t1, T t2)
{
return (t1 == null) ? (t2 == null) : t1.equals(t2);
}
#Override public boolean equals (Object o)
{
if (o instanceof MyObject)
{
MyObject obj = (MyObject)o;
return checkEquals(this.name, obj.getName())
&& this.age == o.getAge();
}
else
return false;
}
#Override public int hashCode()
{
// implement hashCode
}
You need to override both hashCode() and equals() or neither. And you also should make sure your class is final, otherwise there are potential pitfalls with equals.
public class MyObject {
private String name;
private int age;
#Override
public boolean equals(Object o){
if(o instanceof MyObject){
MyObject otherObject = (MyObject)o;
if(name == null){
return otherObject.name == null && otherObject.age == age;
} else {
return name.equals(otherObject.name) && otherObject.age == age;
}
} else {
return false;
}
}
// When we overriding equals it is a good practice to override hashCode
// for consistecy
#Override
public int hashCode(){
int nameCode = (name == null) ? 0 : name.hashCode();
// See Item 9 in book Effective Java 2nd Edition
return 31 * nameCode + age;
}
}

Categories

Resources