How to override equals(), hashcode() and compareTo() for a HashSet - java

I am trying to override the mentioned methods for my HashSet:
Set<MyObject> myObjectSet = new HashSet<MyObject>();
MyObject:
public class MyObject implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
int number;
Map<String,String> myMap;
public MyObject(String name, int number, Map<String,String> myMap) {
this.name = name;
this.number = number;
this.myMap = myMap;
}
[...]
}
How do I override the hashcode(), equals() and compareTo() method?
Currently I have the following:
public int hashCode () {
return id.hashCode();
}
// override the equals method.
public boolean equals(MyObject s) {
return id.equals(s.id);
}
// override compareTo
public int compareTo(MyObject s) {
return id.compareTo(s.id);
}
I read that comparing by id is not enough this is object is a persistent entity for the DB (see here).
The name and number aren't unique across all objects of this type.
So how should I override it?
Do I also need to compare the hashMap inside it?
I am confused. The only unique thing about the object is the the map myMap which gets populated later in the lifecycle.
How do I check for its equality?
Based on all the responses I have changed the methods to the following
#Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MyComplexObj myComplexObj = (MyComplexObj) o;
return myMap != null ? myMap.equals(myComplexObj.myMap) : myComplexObj.myMap == null;
}
#Override
public int hashCode() {
return myMap != null ? myMap.hashCode() : 0;
}
public int compareTo(MyComplexObj o) {
return myMap.compareTo(o.getMyMap()));
}
This fails at the compareTo method, "this method is undefined for the type Map

The basic question here is "How can you determine if two objects are equal to each other?"
This is a simple question for simple objects. However, it becomes increasingly difficult with even slightly more complex objects.
As stated in the original question:
The only unique thing about the object is the the map myMap which gets populated later in the lifecycle.
Given two instances of the type MyObject, the member variables myMap must be compared with each other. This map is of type Map<String, String>. A few questions immediately come to mind:
How do the keys & values define equality?
(does a key=value pair need to be compared as a unit?)
(or should only the values be compared to each other?)
How does the order of the keys in the map affect equality?
(should keys in the list be sorted, so that A-B-C is equivalent to B-C-A?)
(or does 1-2-3 mean something different than 3-2-1?)
Does upper/lower case make any different to the equality of the values?
Will these objects ever be stored in some kind of Java HashSet or Java TreeSet?
(do you need to store the same object several times in the same collection?)
(or should objects with equal hashcodes only be stored once?)
Will these objects ever require sorting as part of a list or Java Collection?
How should the comparison function arrange non-equal objects in a list?
(how should key order determine if an object will come earlier or later in a list?)
(how should values determine order, especially if several values are different?)
Answers to each of these questions will vary between applications. In order to keep this applicable to a general audience, the following assumptions are being made:
To maintain a deterministic comparison, keys will be sorted
Values will be considered to be case-sensitive
Keys and values are inseparable, and will be compared as a unit
The Map will be flattened into a single String, so results can be compared easily
The beauty of using equals(), hashCode(), and compareTo() is that once hashCode() is implemented properly, the other functions can be defined based on hashCode().
Considering all of that, we have the following implementation:
#Override
public boolean equals(final Object o)
{
if (o instanceof MyObject)
{
return (0 == this.compareTo(((MyObject) o)));
}
return false;
}
#Override
public int hashCode()
{
return getKeyValuePairs(this.myMap).hashCode();
}
// Return a negative integer, zero, or a positive integer
// if this object is less than, equal to, or greater than the other object
public int compareTo(final MyObject o)
{
return this.hashCode() - o.hashCode();
}
// The Map is flattened into a single String for comparison
private static String getKeyValuePairs(final Map<String, String> m)
{
final StringBuilder kvPairs = new StringBuilder();
final String kvSeparator = "=";
final String liSeparator = "^";
if (null != m)
{
final List<String> keys = new ArrayList<>(m.keySet());
Collections.sort(keys);
for (final String key : keys)
{
final String value = m.get(key);
kvPairs.append(liSeparator);
kvPairs.append(key);
kvPairs.append(kvSeparator);
kvPairs.append(null == value ? "" : value);
}
}
return 0 == kvPairs.length() ? "" : kvPairs.substring(liSeparator.length());
}
All the critical work is being done inside of hashCode(). For sorting, the compareTo() function only needs to return a negative/zero/positive number -- a simple hashCode() diff. And the equals() function only needs to return true/false -- a simple check that compareTo() equals zero.
For further reading, there is a famous dialogue by Lewis Carroll on the foundations of logic, which touches on the basic question of equality:
https://en.wikipedia.org/wiki/What_the_Tortoise_Said_to_Achilles
And, in regard to even simple grammatical constructs, there is a fine example of two "equal" sentences at the start of chapter 6, "Pig and Pepper", from Alice in Wonderland:
The Fish-Footman began by producing from under his arm a great letter, and this he handed over to the other, saying, in a solemn tone, "For the Duchess. An invitation from the Queen to play croquet." The Frog-Footman repeated, in the same solemn tone, "From the Queen. An invitation for the Duchess to play croquet." Then they both bowed low and their curls got entangled together.

compareTo() is relevant to sorting. It has no relevance to a HashSet or HashMap.
A properly working equals() and hashCode() are vital for members of hash-based collections. Read their specifications in the Javadoc for Object.
Possibly the definitive recommendations for implementing these are in Joshua Bloch's Effective Java. I recommend reading the relevant chapter -- it's easily Google-able. There's no point in trying to paraphrase it all here.
One thing that may have escaped your notice, is that your field myMap has a working equals() and hashCode() of its own, so you don't have to do anything special with it. If you can guarantee that none of the fields are null, a reasonable hashCode() would be (following Bloch's system):
public int hashCode() {
int result = 44; // arbitrarily chosen
result = 31 * result + (int) (id ^ (id >>> 32));
result = 31 * result + name.hashCode();
result = 31 * result + number;
result = 31 * result + myMap.hashCode();
return result;
}
(You'll need more code if any of these could be null)
Pretty much all IDEs will automatically generate both equals() and hashcode(), using all the fields in the class. They'll use something very similar to Bloch's recommendations. Hunt around the UI. You'll find it.
Another alternative is to use Apache ReflectionUtils, which allows you to simply use:
#Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
#Override
public boolean equals(final Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
This works out which fields to use at runtime, and applies Bloch's methods.

This is what intellij default option gives
import java.util.Map;
public class MyObject {
String name;
int number;
Map<String,String> myMap;
#Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MyObject myObject = (MyObject) o;
if (number != myObject.number) return false;
if (name != null ? !name.equals(myObject.name) : myObject.name != null) return false;
return myMap != null ? myMap.equals(myObject.myMap) : myObject.myMap == null;
}
#Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + number;
result = 31 * result + (myMap != null ? myMap.hashCode() : 0);
return result;
}
}
But, since you said
The only unique thing about the object is the the map myMap which gets
populated later in the lifecycle.
I would just keep myMap and skip both name and number (But this begs the question, why would you include a redundant data- name and number in all the elements of your collection?)
Then it becomes
import java.util.Map;
public class MyObject {
String name;
int number;
Map<String,String> myMap;
#Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MyObject myObject = (MyObject) o;
return myMap != null ? myMap.equals(myObject.myMap) : myObject.myMap == null;
}
#Override
public int hashCode() {
return myMap != null ? myMap.hashCode() : 0;
}
}
Keep in mind that, there are other ways too for the equals and hashcode methods. For example, Here are the options that intelliJ gives for code generation
To Answer Further question about CompareTo
Unlike Equals and Hashcode, here is no contract exist between compareTo and any other behaviors. You don't really need to do anything with compareTo until you want to make use of it for say, sorting. To read more about CompareTo Why should a Java class implement comparable?

If you want to make myMap implements comparable, and any other methods that you want, create decorator that implement comparable interface and delegate all other methods to enclosing myMap instance.
public class ComparableMap implements Map<String, String>, Comparable<Map<String, String>> {
private final Map<String, String> map;
public ComparableMap(Map<String, String> map) {
this.map = map;
}
#Override
public int compareTo(Map<String, String> o) {
int result = 0;
//your implementation based on values on map on you consider one map bigger, less or as same as another
return result;
}
#Override
public boolean equals(Object obj) {
return map.equals(obj);
}
#Override
public int hashCode() {
return map.hashCode();
}
// map implementation methods
#Override
public int size() {
return map.size();
}
#Override
public boolean isEmpty() {
return map.isEmpty();
}
#Override
public boolean containsKey(Object key) {
return map.containsKey(key);
}
#Override
public boolean containsValue(Object value) {
return map.containsValue(value);
}
#Override
public String get(Object key) {
return map.get(key);
}
#Override
public String put(String key, String value) {
return map.put(key, value);
}
#Override
public String remove(Object key) {
return map.remove(key);
}
#Override
public void putAll(Map<? extends String, ? extends String> m) {
map.putAll(m);
}
#Override
public void clear() {
map.clear();
}
#Override
public Set<String> keySet() {
return map.keySet();
}
#Override
public Collection<String> values() {
return map.values();
}
#Override
public Set<Entry<String, String>> entrySet() {
return map.entrySet();
}
}
You may use this map in anywhere where you use myMap
public class MyObject implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
int number;
ComparableMap myMap;
public MyObject(String name, int number, Map<String, String> myMap) {
this.name = name;
this.number = number;
this.myMap = new ComparablemyMap(myMap);
}
#Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MyComplexObj myComplexObj = (MyComplexObj) o;
return myMap != null ? myMap.equals(myComplexObj.myMap) : myComplexObj.myMap == null;
}
#Override
public int hashCode() {
return myMap != null ? myMap.hashCode() : 0;
}
public int compareTo(MyComplexObj o) {
return myMap.compareTo(o.getMyMap())); //now it works
}
}

Related

Optimize two lists with shared objects

I have the following two lists:
List<Animal> AllAnimals;
List<Animal> AnimalsWithEyes;
Since there are a lot of animals in the world and there are a lot of the same animals, objects, in both lists, as many have eyes, it would be kind of cool if there were a way to optimize the memory efficiency by not having duplicate objects cluttering up the RAM.
As an additional problem, the Animals in AllAnimals are ordered alphabetically, meaning that we can't just say that the first block of indexes is animals with eyes.
Any ideas on how Java can support such behavior?
Perhaps you should consider using java.util.Set and java.util.TreeSet for natural ordering(if needed be). The Set interface specification ensures, that no two equal objects exist in a Set.
Override Animal#equals, Animal#hashcode and implements Comparable interface. For example:-
public class Animal implements Comparable<Animal>{
private final String name;
private final boolean hasEyes;
public Animal(String name, boolean eyes){
this.name = name;
this.hasEyes = eyes;
}
public String getName() {
return name;
}
public boolean isHasEyes() {
return hasEyes;
}
#Override
public int hashCode() {
int hash = 5;
hash = 29 * hash + Objects.hashCode(this.name);
hash = 29 * hash + (this.hasEyes ? 1 : 0);
return hash;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Animal other = (Animal) obj;
if (this.hasEyes != other.hasEyes) {
return false;
}
if (!Objects.equals(this.name, other.name)) {
return false;
}
return true;
}
#Override
public int compareTo(Animal o) {
//....compareTo implementation.
}
}
Finally "intersections" between 2 List<Animal> consolidated into a Set<Animal>.
Set<Animal> s = new TreeSet<>();
s.addAll(allAnimals);
s.addAll(animalWithEyes);
The Set#addAll implementation ensure no duplicate Animal exist.
*** Or you could use project Lombok annotations to generate equals, hashcode and Comparable implementations.

Overridden hashCode() function is not called by any of HashMap methods

I have overridden hashCode() and equals() method in my class Supplier given below.
public class Supplier {
private final String name;
public Supplier(String name) {
this.name = name;
}
public String getName() {
return name;
}
#Override
public int hashCode() {
char[] charArray = name.toCharArray();
int sumOfchars = 0;
for (char element : charArray) {
sumOfchars += element;
}
return 51 * sumOfchars;
}
#Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (getClass() != o.getClass()) {
return false;
}
final Supplier other = (Supplier) o;
return this.name.equals(other.name);
}
}
Objects of this class are added to a HashMap with name field as Key.
Supplier s1 = new Supplier("supplierA");
Supplier s2 = new Supplier("supplierB");
Map<String, Supplier> supplierMap = new HashMap<>();
supplierMap.put(s1.getName(), s1);
supplierMap.put(s2.getName(), s2);
supplierMap.containsKey("supplierA"));
But, when I put() or get() an element my overridden hashCode() method is not called. Same is the case for equals() when I use contains(Key key). I thought HashMap internally calls hashCode() in case of put and get(). And equals is called in case of contains(). Kindly throw some light on this.
When you put something in a HashMap, the hashCode() method is called on the key, not the value. So in this case, it's the hashCode from String that gets called on s1.getName() and s2.getName().
You're using java.lang.String value of 'name' as your key - hence the hashcode method on your object will not be called.
If you were to do Map<Supplier, Object> or something else then it would be called.

Customizing equals method as per the use case

I have a class defined as
class Book{
String author;
String title;
int id;
public boolean equals(Object o){
return id == ((Book)o).id;
}
public int hashCode{...}
}
In most of the cases uniqueness of the Books is determined by id, hence works properly. In one particular case, I want to merge two lists based on author and title value. I cannot directly use a Set and add the second list to the Set as comparison will happen on ids and not on author/title value. Only way for me is to have two nested for loops to compare each object's value.
List<Book> list1=...;
List<Book> list2 = ...;
Iterator<Book> iterator = list1.iterator();
while(iterator.hasNext()){
Book b1 = iterator.next();
for(Book b2:list2){
if(b1.getAuthor().equals(b2.getAuthor()) && b1.getTitle().equals(b2.getTitle())){
iterator.remove();
}
}
}
list2.addAll(list1);
Is there any way where we can override the equals method as per the use case (similar to Comparator where we can change the sort algorithm)?
Instead just have customized equals method that will check the author value and somehow following works
set.addAll(list2);
You can do something liek that with closures, but it is not overriding as such. The problem you have is this is a O(N*M) time complexity which is not idea. A better approach is O(N) is
Map<String, Book> books = new LinkedHashMap<>();
for (Book book : list1) books.put(book.author+"/"+book.title, book);
for (Book book : list2) books.remove(book.author+"/"+book.title);
list2.addAll(books.values());
For closures, you need a few functions I couldn't find.
static class MapStream<K, V> {
final Map<K, V> map;
final Function<V, K> func;
MapStream(Iterable<V> values, Function<V, K> func) {
map = new LinkedHashMap<>();
this.func = func;
addAll(values);
}
private void addAll(Iterable<V> values) {
for (V value : values)
map.put(func.apply(value), value);
}
public MapStream<K, V> removeAll(Iterable<V> values) {
for (V value : values) {
map.remove(func.apply(value));
}
return this;
}
public Collection<V> values() {
return map.values();
}
}
public static <T> Function<T, String> and(Function<T, String> func1, Function<T, String> func2) {
return (T t) -> func1.apply(t) + "\uffff" + func2.apply(t);
}
public static void main(String... ignored) {
List<Book> list1 = new ArrayList<>();
List<Book> list2 = new ArrayList<>();
Function<Book, String> commonKey = and((Book b) -> b.author, (Book b) -> b.title);
list2.addAll(new MapStream<>(list1, commonKey).removeAll(list2).values());
}
You can see that with some support you can see something with closures.
In general you need to externalize equals and hashCode methods. Therefore you could have something like:
class MyModelClass {
private EqualsImpl<MyModelClass> equalsImpl;
public MyModelClass(EqualsImpl<MyModelClass> equalsImpl) {
super();
this.equalsImpl = equalsImpl;
}
#Override
public boolean equals(Object obj) {
return equalsImpl.equals(this, obj);
}
#Override
public int hashCode() {
return equalsImpl.hashCode(this);
}
}
interface EqualsImpl<C> {
public boolean equals(C obj1, Object obj2);
public int hashCode(C obj);
}
If I understand your need correctly, you can use a TreeSet with a custom comparator:
TreeSet<Book> set = new TreeSet<Book>(new Comparator<Book>() {
public int compare(Book o1, Book o2) {
int r = o1.author.compareTo(o2.author);
if (r != 0)
return r;
return o1.title.compareTo(o2.title);
}
});
set.add(...);
SortedSet guarantees uniqueness, but it doesn't make use of equals() or hashcode(). Instead, it uses the comparator (or natural ordering) for determining equality.
Create a custom List which decorates the List.add() (or List.addAll()) using a Comparator like the TreeSet
Just in addition to answer of #Aniket Thakur (+1).
Yes, I also recommend you to use Comparator. You should define Comparator per your use case and use collections that work with comparators, e.g. TreeSet, TreeMap. This is the clearest way to achieve what you need: separation of comparison logic from model class itself.
public boolean equals(Object o){
return (author.equals(((Book)o).author) && title.equals(((Book)o).title));
}
why can't you override the equals method as above.
You can do
import java.util.Comparator;
public class Book {
String author;
String title;
int id;
public boolean equals(Object o) {
return id == ((Book) o).id;
}
//getters and setters
//other methods
}
class BookComparator implements Comparator<Book>{
#Override
public int compare(Book o1, Book o2) {
if(o1.getAuthor() == o2.getAuthor() && o1.getTitle() == o2.getTitle())
return 0;
return 1;
}
}
and then you can do something like
Set<Book> lSet = new TreeSet<Book>(new BookComparator());
lSet.addAll(list1);
lSet.addAll(list2);

Java HashMap containsKey returns false for existing object

I have a HashMap for storing objects:
private Map<T, U> fields = Collections.synchronizedMap(new HashMap<T, U>());
but, when trying to check existence of a key, containsKey method returns false.
equals and hashCode methods are implemented, but the key is not found.
When debugging a piece of code:
return fields.containsKey(bean) && fields.get(bean).isChecked();
I have:
bean.hashCode() = 1979946475
fields.keySet().iterator().next().hashCode() = 1979946475
bean.equals(fields.keySet().iterator().next())= true
fields.keySet().iterator().next().equals(bean) = true
but
fields.containsKey(bean) = false
What could cause such strange behavioure?
public class Address extends DtoImpl<Long, Long> implements Serializable{
<fields>
<getters and setters>
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + StringUtils.trimToEmpty(street).hashCode();
result = prime * result + StringUtils.trimToEmpty(town).hashCode();
result = prime * result + StringUtils.trimToEmpty(code).hashCode();
result = prime * result + ((country == null) ? 0 : country.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;
Address other = (Address) obj;
if (!StringUtils.trimToEmpty(street).equals(StringUtils.trimToEmpty(other.getStreet())))
return false;
if (!StringUtils.trimToEmpty(town).equals(StringUtils.trimToEmpty(other.getTown())))
return false;
if (!StringUtils.trimToEmpty(code).equals(StringUtils.trimToEmpty(other.getCode())))
return false;
if (country == null) {
if (other.country != null)
return false;
} else if (!country.equals(other.country))
return false;
return true;
}
}
You shall not modify the key after having inserted it in the map.
Edit : I found the extract of javadoc in Map :
Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map.
Example with a simple wrapper class:
public static class MyWrapper {
private int i;
public MyWrapper(int i) {
this.i = i;
}
public void setI(int i) {
this.i = i;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
return i == ((MyWrapper) o).i;
}
#Override
public int hashCode() {
return i;
}
}
and the test :
public static void main(String[] args) throws Exception {
Map<MyWrapper, String> map = new HashMap<MyWrapper, String>();
MyWrapper wrapper = new MyWrapper(1);
map.put(wrapper, "hello");
System.out.println(map.containsKey(wrapper));
wrapper.setI(2);
System.out.println(map.containsKey(wrapper));
}
Output :
true
false
Note : If you dont override hashcode() then you will get true only
As Arnaud Denoyelle points out, modifying a key can have this effect. The reason is that containsKey cares about the key's bucket in the hash map, while the iterator doesn't. If the first key in your map --disregarding buckets -- just happens to be the one you want, then you can get the behavior you're seeing. If there's only one entry in the map, this is of course guaranteed.
Imagine a simple, two-bucket map:
[0: empty] [1: yourKeyValue]
The iterator goes like this:
iterate over all of the elements in bucket 0: there are none
iterate over all the elements in bucket 1: just the one yourKeyValue
The containsKey method, however, goes like this:
keyToFind has a hashCode() == 0, so let me look in bucket 0 (and only there). Oh, it's empty -- return false.
In fact, even if the key stays in the same bucket, you'll still have this problem! If you look at the implementation of HashMap, you'll see that each key-value pair is stored along with the key's hash code. When the map wants to check the stored key against an incoming one, it uses both this hashCode and the key's equals:
((k = e.key) == key || (key != null && key.equals(k))))
This is a nice optimization, since it means that keys with different hashCodes that happen to collide into the same bucket will be seen as non-equal very cheaply (just an int comparison). But it also means that changing the key -- which will not change the stored e.key field -- will break the map.
Debugging the java source code I realized that the method containsKey checks two things on the searched key against every element in the key set:
hashCode and equals; and it does it in that order.
It means that if obj1.hashCode() != obj2.hashCode(), it returns false (without evaluating obj1.equals(obj2). But, if obj1.hashCode() == obj2.hashCode(), then it returns obj1.equals(obj2)
You have to be sure that both methods -may be you have to override them- evaluate to true for your defined criteria.
Here is SSCCE for your issue bellow. It works like a charm and it couldn't be else, because your hashCode and equals methods seem to be autogenerated by IDE and they look fine.
So, the keyword is when debugging. Debug itself can harm your data. For example somewhere in debug window you set expression which changes your fields object or bean object. After that your other expressions will give you unexpected result.
Try to add all this checks inside your method from where you got return statement and print out their results.
import org.apache.commons.lang.StringUtils;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class Q21600344 {
public static void main(String[] args) {
MapClass<Address, Checkable> mapClass = new MapClass<>();
mapClass.put(new Address("a", "b", "c", "d"), new Checkable() {
#Override
public boolean isChecked() {
return true;
}
});
System.out.println(mapClass.isChecked(new Address("a", "b", "c", "d")));
}
}
interface Checkable {
boolean isChecked();
}
class MapClass<T, U extends Checkable> {
private Map<T, U> fields = Collections.synchronizedMap(new HashMap<T, U>());
public boolean isChecked(T bean) {
return fields.containsKey(bean) && fields.get(bean).isChecked();
}
public void put(T t, U u) {
fields.put(t, u);
}
}
class Address implements Serializable {
private String street;
private String town;
private String code;
private String country;
Address(String street, String town, String code, String country) {
this.street = street;
this.town = town;
this.code = code;
this.country = country;
}
String getStreet() {
return street;
}
String getTown() {
return town;
}
String getCode() {
return code;
}
String getCountry() {
return country;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + StringUtils.trimToEmpty(street).hashCode();
result = prime * result + StringUtils.trimToEmpty(town).hashCode();
result = prime * result + StringUtils.trimToEmpty(code).hashCode();
result = prime * result + ((country == null) ? 0 : country.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;
Address other = (Address) obj;
if (!StringUtils.trimToEmpty(street).equals(StringUtils.trimToEmpty(other.getStreet())))
return false;
if (!StringUtils.trimToEmpty(town).equals(StringUtils.trimToEmpty(other.getTown())))
return false;
if (!StringUtils.trimToEmpty(code).equals(StringUtils.trimToEmpty(other.getCode())))
return false;
if (country == null) {
if (other.country != null)
return false;
} else if (!country.equals(other.country))
return false;
return true;
}
}

Java HashMap not finding key, but it should

I have a strange issue occuring in my application, I will quickly explain global architecture and then my problem in depth.
I use a service to populate a HashMap<DomainObject,Boolean> coming from my database (JPA driven) which is in turn returned to my view, via an EJB remote method call (using Apache Wicket). In this part, I add a new DomainObject to the map returned in order to store any new value from my end user.
The problem occurs when the user hit the "add" button in its browser, I try to retrieve the newly created item in my map, but it fails. By playing with the debugger I face the following things.
Assuming HashMap<DomainObject, Boolean> map and DomainObject do are the two variables interesting I have the following results in the debugger
map.keySet(); gives me an object corresponding to do (even the #whatever simili-reference is identical), hashcode() on both objects returns similar value and equals() between the two returns true
map.containsKey(do); returns false
map.get(do); returns null, weird because my key seems to be in the map.
Assuming my newly created item is the first key enumerated by keySet(), I do the following :
map.get(new ArrayList(map.keySet()).get(0)), and it returns null.
If it can help, by attaching breakpoints to my DomainObject.equals() and DomainObject.hashcode() methods I found that map.get() is only calling hashcode() and not equals().
The only workaround I found is to recreate a new map on top of the existing one new HashMap(map), in this new map, I have no problem at all looking up an object by its key.
I hope someone here can give my a pointer on what happens, thanks.
Environment used :
Sun Java 1.6.0_26 x64 under OS X 10.7.1
OpenJDK 1.6.0_18 x64 under Debian 6.0.2 (2.6.32)
Apache Wicket 1.4.17
Oracle Glassfish 3.1.1
JBoss Hibernate 3.6.5
DomainObject code :
public class AssetComponentDetailTemplate extends BaseEntite<Long> {
public enum DataType {
TXT,
DATE,
INT,
JOIN,
LIST,
COULEURS,
REFERENCE
}
public enum Tab {
IDENTITE,
LOCALISATION,
CYCLE_DE_VIE,
FINANCE,
RESEAU,
DETAIL
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false)
private String name;
#Column(nullable = false)
#Enumerated(EnumType.STRING)
private DataType dataType;
private Integer classNameId;
private Long orderId;
private Long nextAssetComponentDetailTemplateId;
private String unit;
#Enumerated(EnumType.STRING)
private Tab tab;
#Column(nullable = false)
private Long uniqueOrganizationId;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "idAssetComponentDetailTemplate", insertable = false, updatable = false)
private List<AssetComponentDetailJoin> assetComponentDetailJoins;
private Boolean mandatory = false;
public AssetComponentDetailTemplate() {
}
public Long getId() {
return id;
}
public void setId(final Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public DataType getDataType() {
return dataType;
}
public void setDataType(final DataType dataType) {
this.dataType = dataType;
}
public Integer getClassNameId() {
return classNameId;
}
public void setClassNameId(final Integer classNameId) {
this.classNameId = classNameId;
}
public Long getUniqueOrganizationId() {
return uniqueOrganizationId;
}
public void setUniqueOrganizationId(final Long uniqueOrganizationId) {
this.uniqueOrganizationId = uniqueOrganizationId;
}
public Long getNextAssetComponentDetailTemplateId() {
return nextAssetComponentDetailTemplateId;
}
public void setNextAssetComponentDetailTemplateId(final Long nextAssetComponentDetailTemplateId) {
this.nextAssetComponentDetailTemplateId = nextAssetComponentDetailTemplateId;
}
public String getUnit() {
return unit;
}
public void setUnit(final String unit) {
this.unit = unit;
}
public Tab getTab() {
return tab;
}
public void setTab(final Tab tab) {
this.tab = tab;
}
public Long getOrder() {
return orderId;
}
public void setOrder(final Long order) {
this.orderId = order;
}
public Boolean isMandatory() {
return mandatory;
}
#Override
public String toString() {
return name;
}
#Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final AssetComponentDetailTemplate that = (AssetComponentDetailTemplate) o;
if (classNameId != null ? !classNameId.equals(that.classNameId) : that.classNameId != null) {
return false;
}
if (dataType != that.dataType) {
return false;
}
if (id != null ? !id.equals(that.id) : that.id != null) {
return false;
}
if (name != null ? !name.equals(that.name) : that.name != null) {
return false;
}
if (nextAssetComponentDetailTemplateId != null ?
!nextAssetComponentDetailTemplateId.equals(that.nextAssetComponentDetailTemplateId) :
that.nextAssetComponentDetailTemplateId != null) {
return false;
}
if (orderId != null ? !orderId.equals(that.orderId) : that.orderId != null) {
return false;
}
if (tab != that.tab) {
return false;
}
if (uniqueOrganizationId != null ? !uniqueOrganizationId.equals(that.uniqueOrganizationId) :
that.uniqueOrganizationId != null) {
return false;
}
if (unit != null ? !unit.equals(that.unit) : that.unit != null) {
return false;
}
return true;
}
#Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + (dataType != null ? dataType.hashCode() : 0);
result = 31 * result + (classNameId != null ? classNameId.hashCode() : 0);
result = 31 * result + (orderId != null ? orderId.hashCode() : 0);
result = 31 * result +
(nextAssetComponentDetailTemplateId != null ? nextAssetComponentDetailTemplateId.hashCode() : 0);
result = 31 * result + (unit != null ? unit.hashCode() : 0);
result = 31 * result + (tab != null ? tab.hashCode() : 0);
result = 31 * result + (uniqueOrganizationId != null ? uniqueOrganizationId.hashCode() : 0);
return result;
}
[This basically expands on Jesper's answer but the details may help you]
Since recreating the map using new HashMap(map) is able to find the element I am suspecting that the hashCode() of the DomainObject changed after adding it to the Map.
For example if your DomainObject looks the following
class DomainObject {
public String name;
long hashCode() { return name.hashCode(); }
boolean equals(Object other) { /* compare name in the two */'
}
Then
Map<DomainObject, Boolean> m = new HashMap<DomainObject, Boolean>();
DomainObject do = new DomainObject();
do.name = "ABC";
m.put(do, true); // do goes in the map with hashCode of ABC
do.name = "DEF";
m.get(do);
The last statement above will return null. Because the do object you have inside the map is under the bucket of "ABC".hashCode(); there is nothing in the "DEF".hashCode() bucket.
The hashCode of the Objects in map should not change once added to map. The best way to ensure it is that the fields on which hashCode depends must be immutable.
Is your DomainObject class immutable? Does it have properly implemented hashCode and equals methods?
Note that you will get into trouble if your DomainObject class is not immutable and you change the state of the object while it is in the map in a way that would change the result of calling hashCode or equals.
hashCode must be implemented in such a way that it returns the same value for two objects whenever equals returns true when comparing these objects. See the API documentation of java.lang.Object.hashCode() for detailed information.
Here is your clue:
hashcode() on both objects returns similar value
For the objects to be considered equal, their hash codes shouldn't just be similar, they must be identical.
If two objects have different hash codes, then as far as the container is concerned the objects are different. There's no need to even call equals().
From the Javadoc:
The general contract of hashCode is:
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 I were you, I'd take a close look at DomainObject.hashcode() and DomainObject.equals() to see what's causing the contract to be broken.
map.get(do) returning null could be easily explained by assuming that the Boolean value for that key might be null but map.containsKey(do) returning false would require do's hashCode to be different at the time of calling containsKey(do) to it's hashCode at the time of retrieving it from the keySet.
To see what's happening, you could (temporarily) use a more verbose implementation of HashMap...
Maybe something like this:
public class VerboseHashMap<K, V> implements Map<K, V> {
private transient final static Logger logger = Logger.getLogger(VerboseHashMap.class);
private HashMap<K, V> internalMap = new HashMap<K, V>();
public boolean containsKey(Object o) {
logger.debug("Object HashCode: " + o.hashCode());
logger.debug("Map contents:");
for (Entry<K, V> entry : internalMap.entrySet()) {
logger.debug(entry.getKey().hashCode() + " - " + entry.getValue().toString());
}
return internalMap.containsKey(o);
}
public V get(Object key) {
logger.debug("Object HashCode: " + key.hashCode());
logger.debug("Map contents:");
for (Entry<K, V> entry : internalMap.entrySet()) {
logger.debug(entry.getKey().hashCode() + " - " + entry.getValue().toString());
}
return internalMap.get(key);
}
}
You'd need to map all the other requirements of the Map interface to your internalMap as well.
Note: This code is not intended for production, nor is it in any way performance oriented, nice or unsmelly....
2nd note (after seeing your code): To use your domain-object as a key for your hashMap, you should only use the immutable parts of your object for hashCode and equals (in this case the id-value). Else lazy-loading further values would change the hashCode...
In Response to your comment:
public class Demo extends TestCase {
public void testMap() {
Map<DomainObject, String> map = new HashMap<DomainObject, String>();
DomainObject sb = new DomainObject();
map.put(sb, "Some value");
System.out.println(map.containsKey(sb));
sb.value = "Some Text";
System.out.println(map.containsKey(sb));
}
private static class DomainObject {
public String value = null;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((value == null) ? 0 : value.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;
DomainObject other = (DomainObject) obj;
if (value == null) {
if (other.value != null)
return false;
} else if (!value.equals(other.value))
return false;
return true;
}
}
}
prints
true
false
The HashCode for the key is computed at the time of putting it into the map.

Categories