JDK-5045681 : (coll) Collection implementations should support customized comparison logic
  • Type: Enhancement
  • Component: core-libs
  • Sub-Component: java.util:collections
  • Affected Version: 1.4.2
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2004-05-12
  • Updated: 2012-10-08
Related Reports
Relates :  
Relates :  
Relates :  
Description

Name: rmT116609			Date: 05/11/2004


A DESCRIPTION OF THE REQUEST :
Collection interface implementations such as HashMap, Vector, ArrayList use their own logic to compare objects for add(), remove(), contains() & other methods.

JUSTIFICATION :
If a complex object is stored in a vector & then that object needs to be removed by just using its ID, then the solution requires a knowledge of how the Vector class compares the two objects.

In this case, overriding the equals() method of the complex object will not suffice, since the equality check is done on the ID.

Also the documentation for indexOf(Object elem) does not throw much light, since it does not clearly state as to which objects equals() method is called.

In my case (see example below), since ID is a String, I would need to create a wrapper class and override the equals() method in that class.

My question is:  Why all doing this??

It would be much simpler if the collection interface would accept a Comparator (or an interface requiring only an equality check) to specify the comparison logic.


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
I should be able to specify how the collection compares objects during add(), remove(), contains() and other operations. Currently the closest way to specify this is using a Comparator (though only an equality check is needed, and comparator provides a total-ordering).
ACTUAL -
Implementations use their own comparison logic

---------- BEGIN SOURCE ----------
import java.util.Vector;

public class Tester {
    
    public Tester() {
	
    }
    public static void main(String args[]) throws Exception {
	Vector messages = new Vector();
	
	String id = "ID";
	String msg = "Test Message";
	
	Message mess = new Message(id, msg);
	
	messages.add(mess);
	
	//Tries to remove object by ID
	boolean success = messages.remove(id);
	System.out.println("Removal by id: " + success);
	
	//Tries to remove object by reference
	if (!success) {
	    success = messages.remove(mess);
	}
	System.out.println("Removal by reference: " + success);
    }
}

class Message {
    String id;
    String msg;

    public Message(String id, String msg) {
	this.id = id;
	this.msg = msg;
    }

    public String getID() {
	return id;
    }

    public String getMessage() {
	return msg;
    }
    
    // Equals tries to handle case when ID is passed for comparison
    // but is never called.
    // This is because the ID object (String)
    // is tested for equality and not the element in the vector
    public boolean equals(Object a) {
	if (a instanceof Message) {
	    return ((Message) a).getID().equals(id);
	} else if (a instanceof String) {
	    return ((String) a).equals(id);
	} else {
	    return false;
	}
    }

    public int hashCode() {
	return id.hashCode();
    }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
//Using a thin wrapper which overrides the equals() method

import java.util.Vector;

public class Tester {
    
    public Tester() {
	
    }
    public static void main(String args[]) throws Exception {
	Vector messages = new Vector();
	
	String id = "ID";
	String msg = "Test Message";
	
	Message mess = new Message(id, msg);
	
	messages.add(mess);
	
	//Tries to remove object by ID
	boolean success = messages.remove(new ComparisonKey(id));
	System.out.println("Removal by id: " + success);
	
	//Tries to remove object by reference
	if (!success) {
	    success = messages.remove(mess);
	}
	System.out.println("Removal by reference: " + success);
    }
}

class Message {
    String id;
    String msg;

    public Message(String id, String msg) {
	this.id = id;
	this.msg = msg;
    }

    public String getID() {
	return id;
    }

    public String getMessage() {
	return msg;
    }
    
    // Equals tries to handle case when ID is passed for comparison
    // but is never called.
    // This is because the ID object (String)
    // is tested for equality and not the element in the vector
    public boolean equals(Object a) {
	if (a instanceof Message) {
	    return ((Message) a).getID().equals(id);
	} else if (a instanceof ComparisonKey) {
	    return ((ComparisonKey) a).getKey().equals(id);
	} else {
	    return false;
	}
    }

    public int hashCode() {
	return id.hashCode();
    }
}

/**
 * Key to compare the elements
 */
class ComparisonKey {
    String key;
    
    public ComparisonKey(String key) {
	this.key = key;
    }
    
    public String getKey() {
	return key;
    }
    
    public boolean equals(Object a) {
	if (a instanceof Message) {
	    return ((Message) a).getID().equals(key);
	} else if (a instanceof ComparisonKey) {
	    return ((ComparisonKey) a).getKey().equals(key);
	} else {
	    return false;
	}
    }
}
(Incident Review ID: 261455) 
======================================================================

Comments
PUBLIC COMMENTS -
08-09-2004

EVALUATION Doing this in its full generality adds substantially to the weight of the interfaces. It was quite deliberate that we omitted this functionality from the collections framework when it was designed. That said, it would be nice if there were an easier way to make sets of case-independent strings and maps whose keys are case independent strings. ###@###.### 2004-05-28 This is a duplicate of RFE 4771660: Comparator, Comparable, Identity, and Equivalence. I'm leaving this RFE open for now, though, since it has accumulated a number of JDC votes that should not be lost. ###@###.### 2004-08-17
17-08-2004