I have a class with a for-loop within the equals/hashCode:
class User {
private List<Task> tasks;
private ZonedDateTime date;
#Override
public int hashCode() {
int hash = 17;
hash = 31 * hash + (date != null ? date() : 0);
for (var task : tasks) {
hash = 31 * hash + task.hashCode();
}
return hash;
}
#Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
final User other = (User) obj;
if (tasks.size() != other.tasks.size()) return false;
// needed?
for (int i = 0; i < tasks.size(); i++) {
if (!tasks.get(i).equals(other.tasks.get(i))) {
return false;
}
}
return Objects.equals(timeStamp, other.timeStamp) && Objects.equals(tasks, other. tasks);
}
}
I am used to have this version (version 2) of equals/hashCode, which is shorter and faster:
#Override
public int hashCode() {
return Objects.hash(date, tasks);
}
#Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
final User other = (User) obj;
return Objects.equals(timeStamp, other.timeStamp) && Objects.equals(tasks, other. tasks);
}
Can I replace the former equals/hashCode with the version 2 without worrying about correctness?
Are both versions return the same result?
To sum up:
for typcial List implementation we can use version 2 instead of version 1.
One additional question related to this:
Will version 2 be also valid, if the property task is not a List but a Stream? (Stream<Task> tasks).
It depends on the specific List implementation.
Let's look at what Object.equals does:
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
So it checks for a couple of trivial cases, then calls a.equals(b), which means that it will call the equals method of your list. But if you're using some custom List or just some list that doesn't compare the elements one by one, then the two implementation will be different.
For any sane implementation, equals should iterate over the elements and compare each one using equals. This is what AbstractList does.
Also note that your hash code will probably change between implementations.
Version 2 will work just fine, though it will return slightly different hash codes.
Related
I want the FeatureEntryKey used as a Map Key, which depends on the two strings. I remember by default String values can handle equality well, so there is no need to auto-generate the two methods here. Is that true?
public class FeatureEntryKey implements Comparable<FeatureEntryKey> {
private String my_key;
private String my_pos;
public FeatureEntryKey(String key, String pos) {
my_key = key;
my_pos = pos;
}
String getKey() {
return my_key;
}
String getPos() {
return my_pos;
}
#Override
public int compareTo(FeatureEntryKey entryKey) {
int key = my_key.compareTo(entryKey.my_key);
return key == 0 ? this.my_pos.compareTo(entryKey.my_pos) : key;
}
/*
* (non-Javadoc)
*
* #see java.lang.Object#hashCode()
*/
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((my_key == null) ? 0 : my_key.hashCode());
result = prime * result + ((my_pos == null) ? 0 : my_pos.hashCode());
return result;
}
/*
* (non-Javadoc)
*
* #see java.lang.Object#equals(java.lang.Object)
*/
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
FeatureEntryKey other = (FeatureEntryKey) obj;
if (my_key == null) {
if (other.my_key != null)
return false;
} else if (!my_key.equals(other.my_key))
return false;
if (my_pos == null) {
if (other.my_pos != null)
return false;
} else if (!my_pos.equals(other.my_pos))
return false;
return true;
}
}
No; you don't have to, but you really should.
It is strongly recommended (though not required) that natural orderings be consistent with equals. This is so because sorted sets (and sorted maps) without explicit comparators behave "strangely" when they are used with elements (or keys) whose natural ordering is inconsistent with equals. In particular, such a sorted set (or sorted map) violates the general contract for set (or map), which is defined in terms of the equals method.
However, that has nothing to do with whether or not String can handle equality well. That solely has to do with the fact that you're implementing Comparable; you're not tacitly expected to override either equals or hashCode when implementing Comparable.
If you planned on using this in a collection where either equality or hashing were a factor, then you would want to override these, just to ensure that the behavior you get is the behavior you expect.
I'm using Java 6. I can't figure out why "retainAll" is not correctly computing the intersection of two sets. I have
for (ProductDto dtoProd : dto.getProducts())
{
System.out.println("dtoProd:" + dtoProd.getId());
} // for
for (ProductDto princProd : principal.getProducts())
{
System.out.println("princProd:" + princProd.getId());
} // for
dto.getProducts().retainAll(principal.getProducts());
Despite the fact I observe through my System.out's that I have the same products in both sets, after, the last call, my "dto.getProducts()" is empty. This is the relevant object's id and hashcode methods, if that matters ....
#Override
public int hashCode()
{
return this.id != null ? this.id.hashCode() : 0;
}
#Override
public boolean equals(Object obj)
{
boolean ret = false;
if (obj instanceof ProductDto)
{
final ProductDto other = (ProductDto) obj;
ret = (this.id == other.getId() || (this.id != null && this.id.equals(other.getId())));
}
return ret;
}
and here is the System.out info
dtoProd:777
dtoProd:778
dtoProd:110074257z
princProd:777
princProd:777SB
princProd:110074257z
princProd:110074258z
princProd:110074259z
princProd:6161
princProd:778
What else do I need to do to compute the correct intersection?
I have a bean with 4 attributes:
user
institutionId
groupId
postingDate
I use Eclipse to generate equals and hashcode but the resulting code is not pretty. Is there a compact way to do the same? Assuming I want equals & hashcode to use all the attributes or a subset of them.
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((groupId == null) ? 0 : groupId.hashCode());
result = prime * result + ((institutionId == null) ? 0 : institutionId.hashCode());
result = prime * result + ((postingDate == null) ? 0 : postingDate.hashCode());
result = prime * result + ((user == null) ? 0 : user.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;
ManGroupKey other = (ManGroupKey) obj;
if (groupId == null) {
if (other.groupId != null)
return false;
} else if (!groupId.equals(other.groupId))
return false;
if (institutionId == null) {
if (other.institutionId != null)
return false;
} else if (!institutionId.equals(other.institutionId))
return false;
if (postingDate == null) {
if (other.postingDate != null)
return false;
} else if (!postingDate.equals(other.postingDate))
return false;
if (user == null) {
if (other.user != null)
return false;
} else if (!user.equals(other.user))
return false;
return true;
}
In Java 7
public int hashCode() {
return Objects.hash(groupId, institutionId, postingDate, user);
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
// cast to correct class
Target o = (Target)obj;
return Objects.equals(groupId, o.groupId) &&
Objects.equals(institutionId, o.institutionId) &&
Objects.equals(postingDate, o.postingDate) &&
Objects.equals(user, o.user);
}
You could compact the code down, but the odds are far higher that you would introduce bugs than that you would do anything useful. All the parts of the equals and hash code method are there for a reason.
If it's bothering you most IDEs have a folding editor, just click the little yellow box (usually) and all the contents of the method get hidden away.
Instead of using the eclipse generated code, you can use Apache-common-langs(http://commons.apache.org/proper/commons-lang/) class HashCodeBuilder and EqualsBuilder to do this:
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this);
}
hashCode:
Either:
#Override
public int hashCode() {
return Objects.hash(user, institutionId, groupId, postingDate);
}
Or:
#Override
public int hashCode() {
int result = 17;
result = 31 * result + Objects.hashCode(user);
result = 31 * result + Objects.hashCode(institutionId);
result = 31 * result + Objects.hashCode(groupId);
result = 31 * result + Objects.hashCode(postingDate);
return result;
}
Equals:
public boolean equals(Object obj){
if (obj == this){
return true;
}
if (! (obj instanceof ManGroupKey)){
return false;
}
ManGroupKey other = (ManGroupKey) obj;
return Objects.equals(user, other.user)
&& Objects.equals(institutionId, other.institutionId)
&& Objects.equals(groupId, other.groupId)
&& Objects.equals(postingDate, other.postingDate);
}
You can at least remove one level of nesting by removing the other.x != null check.
Comparing a value in this way: x.equals(y) will always return false when y is null.
Aside from that: the .equals() method is a good example where a bit of reflection can be handy, possible extracted out into a generic utility method. All you have to do is run through the different fields and see if they're equal in the two objects, that can be done in a few lines.
Obviously that is only feasible when you actually want to compare each field (or you'll have to add some additions to it which let you choose the fields).
I think the library, that can suite you is apache common. It provides EqualsBuilder and HashCodeBuilder classes, that do exactly what you are looking for.
Consider this question for details: Apache Commons equals/hashCode builder
Here are some code snippets:
public class Bean{
private String name;
private int length;
private List<Bean> children;
#Override
public int hashCode(){
return new HashCodeBuilder()
.append(name)
.append(length)
.append(children)
.toHashCode();
}
#Override
public boolean equals(final Object obj){
if(obj instanceof Bean){
final Bean other = (Bean) obj;
return new EqualsBuilder()
.append(name, other.name)
.append(length, other.length)
.append(children, other.children)
.isEquals();
} else{
return false;
}
}
}
I have a POJO that contains hashcode() and equals() method that I have overridden , but my query is that what about If i make hashcode() method comment then in collection lets say in a hashmap when I am storing the user defined objects then what impact would it have...and another thing is that if I make equals method as a comment then what Impact would it have If I try to enter duplicate records will it store duplicate records twice!
public class Employee {
String name,job;
int salary;
public Employee(String n , String j, int t )
{
this.name= n;
this.job=j;
this.salary= t;
}
/* #Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((job == null) ? 0 : job.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + salary;
return result;
}*/
/*#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Employee other = (Employee) obj;
if (job == null) {
if (other.job != null)
return false;
} else if (!job.equals(other.job))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (salary != other.salary)
return false;
return true;
}
*/
#Override
public int hashCode()
{
return name.hashCode()+job.hashCode()+salary;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
{
return true;
}
// make sure o can be cast to this class
if (obj == null || obj.getClass() != getClass())
{
// cannot cast
return false;
}
Employee e = (Employee) obj;
return this.name.equals(e.name)&&this.job.equals(e.job)&&this.salary==e.salary;
}
#Override
public String toString() {
return name+"\t" +"\t"+ job +"\t"+ salary;
}
}
If you leave out equals, it will use the Object.equals method, which is true only if both objects are the same instance.
Thus, duplicate objects won't be added, but duplicate records may be added, if you add the same data through different object instances.
Once you comment something out, it isn't compiled and won't have any effect on the rest of your program.
Also, not overriding the Object.equals() method means that equals() will only return true when the two objects are the same instance. For example:
int[] data = data; //data to create object
mObject o1 = new mObject(data); //new object from data
mObject o2 = new mObject(data); //another object from the same data
System.out.println(o1.equals(o2)); //prints false even though o1 and o2 contain the same information.
No matter what you do or don't comment out, the same object (address-wise) can't be added twice to a set. (For any sane definition of equals() and hashCode()).
When you have two objects o1 and o2 that "mean" the same thing (have the same data):
If you comment out hashCode(), you (almost always) will be able to add both to the same HashSet, even if equals() says that the two objects are the same (or as keys to the same HashMap). This is because all the hash-based data structures compare objects by hashcode first then by equals().
If you comment out equals() without commenting hashCode(), all collections will behave as if the objects are not equal. This is because even the hash-based comparisons check equals() after checking hashCode(), since hash collisions can occur even in proper implementations of hashCode() and equals().
I have a class Odp. I want to use TreeSet to keep a sorted collection of Odp objects. However, I've been having problems.
public class OdpStorage {
private TreeSet<Odp> collection = new TreeSet<Odp>();
public addOdp(Odp o) {
return collection.add(o);
}
public int size() {
return collection.size();
}
}
collection.add(Odp o) is supposed to do nothing if it's already in the tree, right? Somehow, this unit test fails:
OdpStorage ts = new OdpStorage();
Odp ftw = new Odp("LOL");
Odp ktr = new Odp("OMG");
ts.addOdp(ftw);
ts.addOdp(ftw); //should do nothing
ts.addOdp(ftw); //should do nothing
ts.addOdp(ftw); //should do nothing
ts.addOdp(ktr);
assertEquals(2, ts.size());
The assertion fails. It expects 2, but the return value is 5. Why? Could the odp.equals() function be messed up?
Similarly, calling collection.contains(o) fails, even when the there is an object in the set X for which o.equals(X) returns true.
The .equals() function of Odp: (generated by Eclipse)
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Odp))
return false;
Gene other = (Odp) obj;
if (sequence == null) {
if (other.sequence != null)
return false;
} else if (!sequence.equals(other.sequence))
return false;
return true;
}
compareTo:
/**
* this = g0
* if they are equal, g1 is presumed to come first
*
* #return -1 if g0 comes before g1; 1 if g0 comes after g1
*/
#Override
public int compareTo(Odp g1) {
if (sequence.length() < g1.getSeq().length()) {
return -1;
}
else if (sequence.length() > g1.getSeq().length()) {
return 1;
}
if (sequence.compareTo(g1.getSeq()) < 0) {
return -1;
}
return 1;
}
hashCode() is not overridden. Problem?
UPDATE
hashCode() is as follows:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((sequence == null) ? 0 : sequence.hashCode());
return result;
}
But that still doesn't solve the problem.
Your compareTo implementation never returns 0. It should return 0 when the object instances are equal.
It appears that your collection.add(o) is failing to find the object in the backing TreeMap. Does your Odp implement Comparable or are you setting a default Comparable on your TreeSet whose compare method you have implemented? If so, you will need to ensure that your compareTo (for the Comparable), or your Comparator compare method will return 0 if the objects passed in are equals.
EDIT (in response to your comment to the original post):
It is recommended that you override HashCode() whenever you override equals()
EDIT2 in response to your compareTo implementation:
If g0 and g1 are equal, you should return 0. This is the root of the problem.
Mate cleanup your equals, its got too many if/elses. replace it with a nice do/while with lots of condition tests. If all the tests pass then reutrn true...Yes its got "goto" statements but its very easy to read and even easier to insert new conditions as necessary without lots of nesting. Nesting if/elses is evil. Using "elses" is evil and almost always never needed.
#Override
public boolean equals(final Object object) {
boolean equals = false;
do {
if (this == object) {
equals = true;
break;
}
if (false == super.equals(object)) {
break;
}
final DocumentView view = Unsafe.cast(object);
if (false == this.document.equals(view.document)) {
break;
}
if (this.revision != view.revision) {
break;
}
if (false == this.user.equals(view.user)) {
break;
}
if (false == this.timestamp.equals(view.timestamp)) {
break;
}
equals = true;
} while (false);
return equals;
}