JDK-6454574 : (coll) Support object comparison by identity
  • Type: Enhancement
  • Component: core-libs
  • Sub-Component: java.util:collections
  • Affected Version: 6
  • Priority: P5
  • Status: Open
  • Resolution: Unresolved
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2006-07-31
  • Updated: 2012-10-08
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Description
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.

Comments
EVALUATION This is another attempt to improve equivalence relations in Java. See also: 6270657: (coll) remove/contains and "Equators" other than .equals() 4771660: (coll) Comparator, Comparable, Identity, and Equivalence 4269596: (coll) Wanted: A way to customize the equals/hashCode algorithm Perhaps enlightenment will occur after reading http://home.pipeline.com/~hbaker1/ObjectIdentity.html It's too late to add proper support for Value types to Java. Object identity and creation are non-overridable low-level concepts in Java. They cannot be hidden when they become implementation details. Perhaps this should go back to "specification", to be closed Will Not Fix?
20-11-2006

EVALUATION It's unfortunate that identity has to be implemented with equals(). Operator overloading, perhaps including for ==, is under consideration. In the meantime, I cannot imagine adding identifies(Object) and identityHashCode() to Object. They will interact with too many people's code. Solution 1 is therefore not possible. An entirely library-based scheme such as Solution 2 is plausible, so I am reassigning this request to java.util. I have changed the title from "Add object identity overridability in java.lang.Object".
20-11-2006