views:

216

answers:

13

Hello, everyone!

As far as I know, things such as SortedMap or SortedSet, use compareTo (rather than equals) on Comparable<?> types for checking equality (contains, containsKey).

But what if certain types are equatable by concept, but not comparable?
(Hash codes, memory addresses, ...)

I have to declare a Comparator<?> and override the method int compareTo(T o1, To2). OK, I can return 0 for instances which are considered equal. But, for unqeual instances, what do I return when an order is not evident?

Is the approach of using SortedMap or SortedSet on equatable but (by concept) not comparable types good anyway?

Thank you!

EDIT:
I don't want to store things sorted, but would I use "usual" Map and Set, I couldn't "override" the equality-behavior.

EDIT 2:
Why I can't just override equals(...):
I need to alter the equality-behavior of a foreign class. I can't edit it.

EDIT 3:
Just think of .NET: They have IEquatable interface which cat alter the equality-behavior without touching the comparable behavior.

EDIT 4:
Can't I just make compareTo return 0 for equal and 1 for non-equal instances? What's the big problem? I've dome some tests, it seems that SortedMap/SortedSet call compareTo on a pair of instances once. Yes, the order would not make sense, but why should it be my problem? I don't need the order. *I just need altered equality-behavior. Sadly most people just can't understand this.
NOTE: The concept of returning 1 for non-equal instances now was proven wrong.

EDIT 5:
Altering equality-behavior of foreign classes is a bad concept? Sure? I don't think so: Why then am I allowed to alter comparison-behavior of foreign classes using Comparator?

EDIT 6:
Thanks to Mark Peters and waxwing for the idea of wrapping the key type in a custom class. This way, I can override equals and hashCode, thus altering the equality-behavior.

A: 

You cannot sort objects if they are not comparable; how would you know which of the two objects should come first if they are not comparable? So there is no way to put objects that are not comparable in a SortedMap or SortedSet. (Why would you want to? Use a different kind of Map or Set).

The equals() method in Java is defined in class Object, and since all classes extend Object, all objects have an equals() method. You have to take care that you override and implement equals() correctly in your classes if you want to be able to tell if two objects are equal.

If you want to put objects in a hash-based collection (such as HashMap or HashSet) you must also override hashCode() and you must make sure that hashCode() and equals() are implemented the right way (see the documentation of those methods in class Object for details on how to do that).

Jesper
+10  A: 

No, using SortedMap or SortedSet on equatable but not comparable types is a horrible idea. If they're not comparable intrisically or through a comparator, they should not be used in a SortedSet. Sorted implies there is ordering, meaning you can compare two items to see which is "less".

Just use a HashMap/Set.

Edit to your Edit #2

If you can't properly override equals, somewhere you've got a very bad design. You would need to give more info into what you're trying to accomplish.

Edit to your Edit #3

In Java, modifying equals does not change the comparable behaviour. You don't need an interface to accomplish that.

Edit to your Edit #4

NO, YOU CANNOT JUST RETURN 1 FOR NON-EQUAL ELEMENTS!!

SortedSets use the comparison to find your element in the set. The comparable interface has specific requirements. The one that you're breaking is that if A.compareTo(B) > 0, then necessarily B.compareTo(A) < 0. You're breaking that which would make it impossible to find elements in your set afterwords.

public static void main(String[] args) throws Exception {
    SortedSet<MyClass> set = new TreeSet<MyClass>();
    MyClass one = new MyClass(1);
    set.add(one);
    set.add(new MyClass(2));
    set.add(new MyClass(3));
    System.out.println(set.contains(one));
}
private static class MyClass implements Comparable<MyClass> {
    private final int data;
    private MyClass(int data) { this.data = data; }
    public int compareTo(MyClass o) { return (data == o.data ? 0 : 1); }
}

This code prints false, so obviously your comparator has broken the semantics of a Set.

Mark Peters
Yes, I would need that interface to alter equality of external types. But, hence I don't have such interface for that, I was going to use Comparable.
java.is.for.desktop
**Thank you very much for proving my approach inconsistent! If not you, I was going to use it!** ;) But can you please explain, why the code from my answer **works**? I changed the order of insertions and `contains`-queries in my code, but it still gives valid results.
java.is.for.desktop
Ok, I got it, in my example (now deleted) I had only one insertion. Thank you again! **You where the only one who proved the inconsistency of my approach, instead of arguing philosophically why it eventually is not optimal.**
java.is.for.desktop
I give "right" answer to `waxing` because he provided the wrapper-idea. You also did, but not in an answer so I can't mark a comment as correct. You are "credited" in EDIT 6 of my question. Thanks again!
java.is.for.desktop
+6  A: 

It looks like you don't want/need to sort the elements.

In that case, maybe you can use HashMap and HashSet instead? There is no point of using SortedMap and SortedSet if you don't need it to be sorted.

ryanprayogo
Sorry, you got me wrong: As I mentioned multiple times in my question, I need to alter the equality-behavior of a foreign class. Can't do that with HashMap/HashSet.
java.is.for.desktop
@java.is.for.desktop: Sure you can. You just can't do that with HashMap or HashSet *out of the box*. But unlike your evil foreign class, HashMap and HashSet aren't `final` classes, so you can subclass them and do whatever you want. Or, to be more elegant, build your new implementation atop `AbstractMap` and `AbstractSet`.
Daniel Pryden
+1  A: 

I don't want to store things sorted, but would I use "usual" Map and Set, I couldn't "override" the equality-behavior.

If you don't want to store your elements sorted, then why are you using a sorted collection?

In order to maintain a sorted collection, an insert operation (typically) has O(log n) complexity to place the element in the correct place. If you don't need sorting, then this is wasteful, as you could be using a hash-based collection (HashMap, HashSet) which would give you O(1) insertion time.

matt b
+1  A: 

Is the approach of using SortedMap or SortedSet on equatable but (by concept) not comparable types good anyway?

No. The point of these collections is to allow you to sort the objects in them, if the objects don't have a natural sorting order then what is the point of putting them in a sorted collection?

You should override the equals() and hashcode() methods and use the standard Map/Set classes instead.

Paolo
Sorry, can't override equals and hashCode. What's the point of Comparable interface? It's to allow to alter the comparison-behavior of foreign classes: those who **do not** override `compareTo` by themselves andn those you can't change.
java.is.for.desktop
@java.is.for.desktop The point of the Comparable interface is to give objects a natural ordering for use in Sorted Collections and not just to be used for establishing equality.
Poindexter
The point of Comparable is to allow a class to define a standard semantic for comparing one instance to another. It's not designed to allow a user to change the "natural sorting behaviour" of a class. If you want to define some other, non-natural comparison, then you implement a Comparator, in which you can encapsulate your comparison routines. Comparable is meant explicitly for "natural ordering", which is, by definition, dictated by the creator of the class.
ptomli
But hence SortedMap and SortedSet use `compareTo` rather than `equals` for their methods `contains` and `containsKey`, why not alter compareTo?
java.is.for.desktop
If you provide a Comparator, it will use that instead. Also, read the SortedSet javadoc, it does explain why the interface does what it does. In particular, the ordering as defined by the comparator in use, be that user provided or by using the Comparable interface of its entries, must be consistent with equals, or the general contract of Set cannot be maintained. To answer your question "why not alter compareTo", by providing a Comparator, you are doing just that, for the particular Set you are using.
ptomli
A: 

As others have said, if there is no natural order a SortedXXX isn't really an option. However, assuming you just want some kind of consistent way of listing the elements, if you just consider the fields that you are using for equality testing, as these constitute the "primary key", and come up with some sort of numerical or alphabetical ordering around them that might suit your purpose.

Trevor Tippins
+1  A: 

If you need to override hashCode but can't, I think you're looking at extending HashMap or writing your own.

sblundy
This looks like the right solution for the OP. Define a class `CustomEqualityMap` extending `AbstractMap`, and use it to build whatever behavior you want.
Daniel Pryden
A: 

Just use a (custom) Comparator provided to the implementation of Sorted[Set|Map] when you create it...

The javadocs would tend to suggest this: All keys inserted into a sorted map must implement the Comparable interface (or be accepted by the specified comparator).

SortedSet<MyObject> s = new TreeSet<MyObject>(new Comparator<MyObject>() {
    @Override
    public int compare(T o1, T o2) {
        // your very specific, fancy dancy code here
    }
});

http://java.sun.com/javase/6/docs/api/

ptomli
Yes, that's what I was going to do. But I asked for consistency of such concept.
java.is.for.desktop
-1. This simply will not work if you don't know how to compare the two. You can't *just* return 0 on equality. You also need to consistently return other values when they're not equal.
Mark Peters
If the class he's stashing into these collections are using the same internal elements for comparison in equals and hashCode, you can try to imply a consistent basis by doing something like 'return o1.equals(o2) ? 0 : o1.hashCode() - o2.hashCode();'. He's trying to apply comparable to something that doesn't naturally exhibit it (in the way he wants). You have to assume he knows what he's comparing... Or at least you have to pretend :) Using hashCode is not great, but it is often using the same fields as equals, it might just work for him.
ptomli
@ptomli: There is no way to use hashCode to get a provably consistent set. On any collision, the comparator would think they're equal and the set semantics would be broken.
Mark Peters
A: 

I am not sure if I see your point (I think you're trying to solve a problem from the wrong direction on), but if you after all just want a Set or Map which maintains insertion order, then use LinkedHashSet or LinkedHashMap respectively.


Update: as per your quote:

I need to alter the equality-behavior of a foreign class. I can't edit it.

Inside a sorted set/map? Then use a TreeSet or TreeMap which you construct with a custom Comparator. E.g.

SortedSet<String> set = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);

(which constructs a set of Strings ordered in case insensitive order).

See also:

BalusC
@BalusC: I think the problem that the OP is having is about *what* to return from the `Comparator` in the case that it's comparing 2 objects that can't be ordered.
ryanprayogo
@ryan: The OP just has to come up with a fullworthy [SSCCE](http://sscce.org) and to describe both the actual and expected result in detail instead of coming up with a vague and ranty story wherein he seem to shift the cause of the whole problem to Java API :)
BalusC
A: 

I don't want to store things sorted, but would I use "usual" Map and Set, I couldn't "override" the equality-behavior.

You are trying to override the equals() method of an unknown type. You are thinking about the problem wishing you had the IEquatable interface. If you must use SortedSet/SortedMap then provide a Comparator like @ptomli mentions in his answer.

Using HashMap/HashSet instead seems to be a good suggestion. Have you looked at those?

Kelly French
+1  A: 

It's not clear, but it could be that all you're trying to do is get a Collection of somethings, using the same semantics of Set/Map, but with somethings that don't adequately implement Object.equals.

In that case I suggest you subclass AbstractSet or AbstractMap and override AbstractCollection.contains to use your version of equals.

It's not something I'd recommend, but your question doesn't actually make it clear what you're trying to achieve.

See http://java.sun.com/javase/6/docs/api/java/util/AbstractSet.html and http://java.sun.com/javase/6/docs/api/java/util/AbstractCollection.html#contains(java.lang.Object)

ptomli
+8  A: 

Consider wrapping your foreign class inside your own instead.

public class Foreign {
  // undesired equals() and hashCode() implementation
}


public class ForeignWrapper {
   private Foreign foreign;

   public ForeignWrapper(Foreign foreign) {
      this.foreign = foreign;
   }

   public void equals() {
       // your equals implementation, using fields from foreign
   }

   public int hashCode() {
       // your hashCode implementation, using fields from foreign
   }

}

Then add new ForeignWrapper(foreign) to the standard HashSet / HashMap. Not applicable in all situations, but maybe in yours.

waxwing
Good idea! I'll try it.
java.is.for.desktop
+1  A: 

If memory is not a big problem subclass HashMap and HashSet to take an Equality class

interface Equality<T>//Defines the equality behavior
{
   int hashCode(T t);//Required, always make sure equals = true => same hashCode
   boolean areEqual(T t,Object t2);
}
class EqualWrapper<T>//Wraps object and equality for the HashMap/Set
{
   T object;
   Equality<T> equal;
   int hashCode(){return equal.hashCode(object);}
   boolean equals(Object o){return equal.areEqual(object,o);}

}
class MySet<T>extends AbstractSet<T>
{
   private HashSet<EqualWrapper<T> > internalSet = new HashSet<T>();
   private Equality<T> equal;
   public MySet(Equality<T> et){equal = et;}
   // TODO implement abstract functions to wrapp 
   // objects and forward them to 
   // internalSet  
}

This way you can define your own equality behavior. Strange that it is missing from the jre

josefx
Good idea, but I'll go with the slightly more simple suggested solution to wrap the foreign class in a wrapper class and override `equals` and `hashCode` there.
java.is.for.desktop