What should go into equals()
?
There are fields that define the identity of an object and there are fields that define its properties. The serial number of your phone is part of its identity, its colour or battery charge percentage is just a property.
equals()
should only depend on fields that define identity, and that is independent of whether the fields are accessible from the outside or not, it comes from the semantics of the fields themselves.
So the question should really be: is there any reason to hide the identity of an object from its clients? And if the answer seems to be "yes", the next, more fundamental question is: is there any reason to identify the objects in the first place? After all, there are lots of objects out there that don't require to have an identity beyond the trivial.
There are several possible problems that arise from it, here's one example.
Example of problem
public class Foo {
private Bar hidden;
private String accessible;
@Override
public boolean equals( Object ob ) {
return hidden.equals(((Foo)ob).hidden);
}
@Override
public int hashCode() {
//Let's not forget that we MUST override hashCode() if we override equals() :)
}
}
Now image a naive client wants to put these into a TreeSet
. The class isn't Comparable
but nothing to worry, we can write a comparator for it:
public class FooComparator implements Comparator<Foo> {
public int compare( Foo first, Foo second ) {
return first.getAccessible().compareTo( second.getAccessible() );
}
}
Job done, great!
Except poor client unwittingly violated the contract of TreeSet
as the comparator provided isn't consistent with equals()
.
How to work around this?
Let's suppose that despite knowing all this, you still want your own equals()
that depends on hidden fields. Because you want to put them in a HashSet
for example.
You can then do this:
public class Foo {
private Bar hidden;
private String accessible;
private final Helper helper = new Helper();
Helper helper() {
return helper;
}
class Helper {
@Override
public boolean equals( Object ob ) {
// you can access "hidden" from here
}
@Override
public int hashCode() {
// you can access "hidden" from here
}
public Foo foo() {
return Foo.this;
}
}
}
And you put your Helper
objects in the HashSet
instead of Foo
. Although Helper
s visibility is package-private, that's still acceptable in most cases, as this level is not part of the public API.