M
Mike Schilling
What do you think the following fragment prints?:
List l = new ArrayList();
l.add(l);
System.out.println(l.hashCode());
Trick question. The result is an infinite recursion (i.e. a stack
overflow), since the hash code for a List is calculated from the hash codes
of its members.
Likewise, the following causes an infinite recursion, since lists are
defined to be equal when they have the same members.
List l1 = new ArrayList();
List l2 = new ArrayList();
l1.add(l2);
l2.add(l1);
System.out.println(l1.equals(l2));
These cases are simple to spot, but the same situation could occur with any
cyclic graph of objects that compute hash code and/or equality based on the
values of their members. Also, equals() and hashCode() are often called
implicitly, for instance when adding an object to a HashSet or HashMap.
I don't see any way this could be addressed other than by keeping track of
which objects have already been processed by the current hashCode() or
equals() call, and there's no way to do that without changing the method
signatures, to, say, add a Set:
in Object:
public final int hashCode() {
return hashCode(new HashSet());
}
public final int hashCode(Set processed)
{
if (processed.contains(this)) {
return System.identityHashCode(this);
}
else {
processed.add(this);
return this.calculateHashCode(processed);
}
}
public int calculateHashCode(processed) {
return return System.identityHashCode(this);
}
with calculateHashCode(Set) being the method that classes can override and
hashCode(Set) the method List et al. call on their members. And that is a
pipe dream, since it isn't possible to make this sort of incompatible change
at this late date.
List l = new ArrayList();
l.add(l);
System.out.println(l.hashCode());
Trick question. The result is an infinite recursion (i.e. a stack
overflow), since the hash code for a List is calculated from the hash codes
of its members.
Likewise, the following causes an infinite recursion, since lists are
defined to be equal when they have the same members.
List l1 = new ArrayList();
List l2 = new ArrayList();
l1.add(l2);
l2.add(l1);
System.out.println(l1.equals(l2));
These cases are simple to spot, but the same situation could occur with any
cyclic graph of objects that compute hash code and/or equality based on the
values of their members. Also, equals() and hashCode() are often called
implicitly, for instance when adding an object to a HashSet or HashMap.
I don't see any way this could be addressed other than by keeping track of
which objects have already been processed by the current hashCode() or
equals() call, and there's no way to do that without changing the method
signatures, to, say, add a Set:
in Object:
public final int hashCode() {
return hashCode(new HashSet());
}
public final int hashCode(Set processed)
{
if (processed.contains(this)) {
return System.identityHashCode(this);
}
else {
processed.add(this);
return this.calculateHashCode(processed);
}
}
public int calculateHashCode(processed) {
return return System.identityHashCode(this);
}
with calculateHashCode(Set) being the method that classes can override and
hashCode(Set) the method List et al. call on their members. And that is a
pipe dream, since it isn't possible to make this sort of incompatible change
at this late date.