3

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.

java.is.for.desktop
  • 10,748
  • 12
  • 69
  • 103
  • 3
    If you can't compare them, how can you sort them? – Kevin Jun 18 '10 at 13:15
  • I don't want to sort them, but would I use "usual" Map and Set, I couldn't "override" the equality-behavior. – java.is.for.desktop Jun 18 '10 at 13:16
  • 2
    Not being comparable implies that they don't need to be sorted or that it's non-nonsensical to do so. In that case, what's wrong with HashMap and HashSet? Otherwise, how are they to be ordered? – sblundy Jun 18 '10 at 13:18
  • What do you mean? If you don't use a sorted set, then don't implement comparable and have equals() work as its contract suggests. – Mark Peters Jun 18 '10 at 13:19
  • What do you mean "alter comparison-behaviour of foreign classes"? How do you manage that? – Mark Peters Jun 18 '10 at 14:06
  • @Mark Peters: By passing a custom implementation of `Comparator` to `SortedSet` or `SortedMap`. – java.is.for.desktop Jun 18 '10 at 14:07
  • @java.is.for.desktop: The easiest way to accomplish what you're talking about is to create a new key type that wraps the objects you want to use as keys, and overrides equals and hashCode in that wrapper class. – Mark Peters Jun 18 '10 at 14:09
  • @Mark Peters: Wrapping the key is a good idea! – java.is.for.desktop Jun 18 '10 at 14:29
  • 2
    if you need this often, you can write your own equatable interface and map/set that accept equators. there's no special reason why JDK doesn't include that, other than that they didn't think it's useful to the mass. – irreputable Jun 18 '10 at 17:11

13 Answers13

10

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
  • 80,126
  • 17
  • 159
  • 190
  • 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 Jun 18 '10 at 13:31
  • **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 Jun 18 '10 at 14:18
  • 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 Jun 18 '10 at 14:26
  • 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 Jun 18 '10 at 18:26
10

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
  • 18,547
  • 8
  • 66
  • 82
  • One advantage of this approach is that the wrapper can include not only a cached hash code (which might otherwise be expensive to compute) but also a `long` sequence number and a reference to the oldest instance it knows about which is known to be equal to it. If two objects being compared share the same "oldest-known-equal" object, they can be called equal without further ado. If two different "oldest known equal" objects are found to be equal, the younger can be updated to know about the older. – supercat Dec 17 '13 at 19:42
6

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
  • 11,587
  • 11
  • 51
  • 66
  • 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 Jun 18 '10 at 13:37
  • @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 Jun 18 '10 at 14:25
1

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
  • 138,234
  • 66
  • 282
  • 345
1

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
  • 22,188
  • 6
  • 42
  • 49
  • 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 Jun 18 '10 at 13:39
  • @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 Jun 18 '10 at 13:43
  • 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 Jun 18 '10 at 13:47
  • But hence SortedMap and SortedSet use `compareTo` rather than `equals` for their methods `contains` and `containsKey`, why not alter compareTo? – java.is.for.desktop Jun 18 '10 at 13:47
  • 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 Jun 18 '10 at 13:52
1

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

sblundy
  • 60,628
  • 22
  • 121
  • 123
  • 1
    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 Jun 18 '10 at 14:23
1

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
  • 11,730
  • 4
  • 40
  • 68
1

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
  • 15,506
  • 6
  • 38
  • 63
  • 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 Jun 18 '10 at 18:18
0

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
  • 202,709
  • 46
  • 318
  • 350
0

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
  • 2,827
  • 14
  • 10
0

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
  • 11,730
  • 4
  • 40
  • 68
  • Yes, that's what I was going to do. But I asked for consistency of such concept. – java.is.for.desktop Jun 18 '10 at 13:57
  • 1
    -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 Jun 18 '10 at 13:59
  • 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 Jun 18 '10 at 14:05
  • 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 Jun 18 '10 at 14:52
0

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:

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • 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 Jun 18 '10 at 13:52
  • 1
    @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 Jun 18 '10 at 13:55
0

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?

Community
  • 1
  • 1
Kelly S. French
  • 12,198
  • 10
  • 63
  • 93