-
Enhancement
-
Resolution: Unresolved
-
P5
-
None
-
6
-
Cause Known
-
x86
-
windows_xp
A DESCRIPTION OF THE REQUEST :
Currently in Java, the concept of object equality and object identity are intimately related, but only one, object equality, is overridable. The default implementation of java.lang.Object's "boolean equals(Object that)" is to use object identity, specifically, the "==" operator, to determine object equality. If a class author chooses to change this behavior, then he has that option by overriding equals and providing his own implementation.
No such option is available for object identity, because the "==" operator is not overridable. This becomes problematic when using persistent objects (JDO-persisted objects, JPA Entity classes, or other, POJO-based persistent objects) combination with collections or maps that enforce or utilize uniqueness among its elements or keys & values, respectively.
JUSTIFICATION :
Due to the lack of overridability of object identity, users of persistent objects are required to override equals, changing its semantics from that of "object equality" to "transient and persistent object identity" so that collections, maps, and other classes dependent on object equality can behave as expected for the user. Further, since the author is overriding equals, he should also override hashCode, using only the fields comprising the identity of the object.
See the JDO 2.0 & EJB 3.0 JPA specifications for discussions of this issue.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
===========================
Solution 1: Add methods to java.lang.Object & enhance collections, etc
===========================
1.1. Add to java.lang.Object the following methods:
public boolean identifies(Object that) {
return this == that;
}
public int identityHashCode() {
return System.identityHashCode(this);
}
1.2. Allow collections, maps, and other uniqueness-enforcing JDK classes the ability to be told to use identity or equality in their enforcement of uniqueness. This would render java.util.IdentityHashMap to be a bonafide general purpose Map implementation, or at least much closer to it. This entails a slight modification of the collection & map interface contracts to allow the user to specify whether object equality or object indentity is used.
For java.util.Collection, two methods could be added:
void setUsingEquality(boolean useEquality);
boolean isUsingEquality();
For java.util.Map, four methods could be added:
void setKeyUsingEquality(boolean useEquality);
boolean isKeyUsingEquality();
void setValueUsingEquality(boolean useEquality);
boolean isValueUsingEquality();
The modified contract would default these properties to true for backward compatibility.
===========================
Solution 2: Define new interfaces
===========================
2.1 Define new interface in package java.util
public interface UniquenessStrategy {
boolean areUnique(Object a, Object b);
}
and classes
public class DefaultUniquenessStrategy implements UniquenessStrategy {
public boolean areUnique(Object a, Object b) {
return a == null ? b != null : !a.equals(b);
}
}
public class IdentityUniquenessStrategy
implements UniquenessStrategy
{
public boolean areUnique(Object a, Object b) {
return a != b;
}
}
2.2 Add methods to java.util.Collection
void setUniquenessStrategy(UniquenessStrategy s);
UniquenessStrategy getUniquenessStrategy();
and to java.util.Map
void setKeyUniquenessStrategy(UniquenessStrategy s);
UniquenessStrategy getKeyUniquenessStrategy();
void setValueUniquenessStrategy(UniquenessStrategy s);
UniquenessStrategy getValueUniquenessStrategy();
The default UniquenessStrategy for all three uses would be DefaultUniquenessStrategy except for java.util.IdentityHashMap, which would default to IdentityUniquenessStrategy.
===========================
Solution 3: Some variation on the above solutions
===========================
This is just here to emphasize that if any proposal here doesn't work, it shouldn't kill the whole request.
ACTUAL -
Persistent classes are required to fuse the concepts of object equality and object identity into one and the same concept by overriding equals and using the object's identity fields (or persistence provider's object identity) to implement equals and hashCode.
---------- BEGIN SOURCE ----------
package com.example.domain;
import com.acme.persistor.*;
public class Person {
protected long id == System.currentTimeMillis(); // persistent identity field
protected int bar;
protected int blaz;
public Person(int bar, int blaz) {
setBar(bar);
setBlaz(blaz);
}
// setters/getters here for bar, blaz
// this is the developer's **desired** equals method
public boolean desiredEquals(Object that) {
if (this == that) return true;
if (that == null) return false;
if (!(that instanceof Person)) return false;
Person thatPerson = (Person) that;
return
this.bar == thatPerson.bar
&& this.blaz == that.blaz;
}
// this is the developer's **desired** hashCode method
public int desiredHashCode() {
return bar ^ blaz;
}
// This is the **required** equals method
// so this instances of this class
// can be used predictably in a Set, for example.
public boolean equals(Object that) {
if (this == that) return true;
if (that == null) return false;
if (!(that instanceof Person)) return false;
Person thatPerson = (Person) that;
return this.id == thatPerson.id;
}
// This is abiding by the recommendation that equals
// and this method be implemented similarly.
public int hashCode() {
return id;
}
public static void test() {
Person p1 = new Person(1, 1);
Person p2 = new Person(1, 1);
Set set = new HashSet();
set.add(p1);
set.add(p2);
assert !set.contains(p2);
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
No known workaround.
Currently in Java, the concept of object equality and object identity are intimately related, but only one, object equality, is overridable. The default implementation of java.lang.Object's "boolean equals(Object that)" is to use object identity, specifically, the "==" operator, to determine object equality. If a class author chooses to change this behavior, then he has that option by overriding equals and providing his own implementation.
No such option is available for object identity, because the "==" operator is not overridable. This becomes problematic when using persistent objects (JDO-persisted objects, JPA Entity classes, or other, POJO-based persistent objects) combination with collections or maps that enforce or utilize uniqueness among its elements or keys & values, respectively.
JUSTIFICATION :
Due to the lack of overridability of object identity, users of persistent objects are required to override equals, changing its semantics from that of "object equality" to "transient and persistent object identity" so that collections, maps, and other classes dependent on object equality can behave as expected for the user. Further, since the author is overriding equals, he should also override hashCode, using only the fields comprising the identity of the object.
See the JDO 2.0 & EJB 3.0 JPA specifications for discussions of this issue.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
===========================
Solution 1: Add methods to java.lang.Object & enhance collections, etc
===========================
1.1. Add to java.lang.Object the following methods:
public boolean identifies(Object that) {
return this == that;
}
public int identityHashCode() {
return System.identityHashCode(this);
}
1.2. Allow collections, maps, and other uniqueness-enforcing JDK classes the ability to be told to use identity or equality in their enforcement of uniqueness. This would render java.util.IdentityHashMap to be a bonafide general purpose Map implementation, or at least much closer to it. This entails a slight modification of the collection & map interface contracts to allow the user to specify whether object equality or object indentity is used.
For java.util.Collection, two methods could be added:
void setUsingEquality(boolean useEquality);
boolean isUsingEquality();
For java.util.Map, four methods could be added:
void setKeyUsingEquality(boolean useEquality);
boolean isKeyUsingEquality();
void setValueUsingEquality(boolean useEquality);
boolean isValueUsingEquality();
The modified contract would default these properties to true for backward compatibility.
===========================
Solution 2: Define new interfaces
===========================
2.1 Define new interface in package java.util
public interface UniquenessStrategy {
boolean areUnique(Object a, Object b);
}
and classes
public class DefaultUniquenessStrategy implements UniquenessStrategy {
public boolean areUnique(Object a, Object b) {
return a == null ? b != null : !a.equals(b);
}
}
public class IdentityUniquenessStrategy
implements UniquenessStrategy
{
public boolean areUnique(Object a, Object b) {
return a != b;
}
}
2.2 Add methods to java.util.Collection
void setUniquenessStrategy(UniquenessStrategy s);
UniquenessStrategy getUniquenessStrategy();
and to java.util.Map
void setKeyUniquenessStrategy(UniquenessStrategy s);
UniquenessStrategy getKeyUniquenessStrategy();
void setValueUniquenessStrategy(UniquenessStrategy s);
UniquenessStrategy getValueUniquenessStrategy();
The default UniquenessStrategy for all three uses would be DefaultUniquenessStrategy except for java.util.IdentityHashMap, which would default to IdentityUniquenessStrategy.
===========================
Solution 3: Some variation on the above solutions
===========================
This is just here to emphasize that if any proposal here doesn't work, it shouldn't kill the whole request.
ACTUAL -
Persistent classes are required to fuse the concepts of object equality and object identity into one and the same concept by overriding equals and using the object's identity fields (or persistence provider's object identity) to implement equals and hashCode.
---------- BEGIN SOURCE ----------
package com.example.domain;
import com.acme.persistor.*;
public class Person {
protected long id == System.currentTimeMillis(); // persistent identity field
protected int bar;
protected int blaz;
public Person(int bar, int blaz) {
setBar(bar);
setBlaz(blaz);
}
// setters/getters here for bar, blaz
// this is the developer's **desired** equals method
public boolean desiredEquals(Object that) {
if (this == that) return true;
if (that == null) return false;
if (!(that instanceof Person)) return false;
Person thatPerson = (Person) that;
return
this.bar == thatPerson.bar
&& this.blaz == that.blaz;
}
// this is the developer's **desired** hashCode method
public int desiredHashCode() {
return bar ^ blaz;
}
// This is the **required** equals method
// so this instances of this class
// can be used predictably in a Set, for example.
public boolean equals(Object that) {
if (this == that) return true;
if (that == null) return false;
if (!(that instanceof Person)) return false;
Person thatPerson = (Person) that;
return this.id == thatPerson.id;
}
// This is abiding by the recommendation that equals
// and this method be implemented similarly.
public int hashCode() {
return id;
}
public static void test() {
Person p1 = new Person(1, 1);
Person p2 = new Person(1, 1);
Set set = new HashSet();
set.add(p1);
set.add(p2);
assert !set.contains(p2);
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
No known workaround.
- relates to
-
JDK-6270657 (coll) remove/contains and "Equators" other than .equals()
- Open
-
JDK-4771660 (coll) Comparator, Comparable, Identity, and Equivalence
- Open
-
JDK-4171916 myArray.equals() does not check content equality
- Closed
-
JDK-4269596 (coll) Wanted: A way to customize the equals/hashCode algorithm
- Closed
-
JDK-4905919 RFE: Operator overloading
- Closed