Automatic generation of the "compareTo" method

12,734 views
Skip to first unread message

stlecho

unread,
Mar 1, 2010, 3:04:36 AM3/1/10
to Project Lombok
Does Lombok allow to automatically generate the "compareTo" method,
for instance by adding the @compareTo annotation.

Reinier Zwitserloot

unread,
Mar 1, 2010, 3:41:51 AM3/1/10
to project-lombok
Nope; I'm not entirely sure what lombok should generate for the content of compareTo.

--Reinier Zwitserloot



On Mon, Mar 1, 2010 at 9:04 AM, stlecho <stl...@gmail.com> wrote:
Does Lombok allow to automatically generate the "compareTo" method,
for instance by adding the @compareTo annotation.

--
You received this message because you are subscribed to the Google
Groups group for http://projectlombok.org/

To post to this group, send email to project...@googlegroups.com
To unsubscribe from this group, send email to
project-lombo...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/project-lombok?hl=en

Roel Spilker

unread,
Mar 1, 2010, 4:33:19 AM3/1/10
to Project Lombok
Stlecho,

Can you show us a before and after image to clarify what you mean? For
example:

Before @Foo

class X {
// code
// code
// code
// code
// code
// code
}

After @Foo

@Foo class X {
// code
}

Where both the before and after image would execute the same.

Roel

Michael Lorton

unread,
Mar 1, 2010, 1:14:36 PM3/1/10
to project...@googlegroups.com
As a default, you could compare every Comparable member -- like the default equals. 

M. 

Sent from my iPhone

Tuure Laurinolli

unread,
Mar 1, 2010, 1:44:50 PM3/1/10
to project...@googlegroups.com
On 2010-03-01 22:14, Michael Lorton wrote:
> As a default, you could compare every Comparable member -- like the
> default equals.

Yes, but in which order, and do you think it would really be OK to
exclude non-Comparable members (that still affect equals())?

v6ak

unread,
Mar 2, 2010, 3:12:00 PM3/2/10
to Project Lombok
I think that default order should be the order of variables in
declaration. I also think that non-excluded non-comparable members
should cause an error.

On Mar 1, 7:44 pm, Tuure Laurinolli <tuure.laurino...@indagon.com>
wrote:

Michael Lorton

unread,
Mar 2, 2010, 3:20:47 PM3/2/10
to project...@googlegroups.com
You could order the variables that way, but it seems unlikely to produce the intuitively correct answer.  How about requiring the variables to be listed?

M.

Daniel Yokomizo

unread,
Mar 13, 2010, 6:03:59 PM3/13/10
to project...@googlegroups.com
Hi,

In the simplest scenario It could be something like this:

@CompareTo
class Foo implements Comparable<Foo>{
private String s;
private int i;
}

//becomes

class Foo implements Comparable<Foo>{
private String s;
private int i;

public int compareTo(Foo that) {
int order = (this.s == that.s) ? 0 : (this.s == null) ? -1 :
(that.s == null) ? +1 : this.s.compareTo(that.s);
order = (order != 0) ? order : (this.i == that.i) ? 0 :
(this.i < that.i) ? -1 : +1;
return order;
}
}

Also would be nice if @Data implied in @CompareTo iff it's Comparable.

OTOH I see a few issues with this approach:

1. Some fields aren't comparable. Particularly collections and arrays.
2. null handling: null is smaller or larger.
3. Some fields shouldn't matter for comparison, but using different
fields for equals and compareTo is (probably) always a bug, so we need
to keep these in sync.
4. Sometimes the default comparison is not what we want.

A few solutions:

1. Introduce an utility class with a bunch of overloaded compare(X,
X), compare(X, X, Comparator<X>), deepCompare(X, X) and deepCompare(X,
X, Comparator<Object>) (i.e. arbitrary nesting is handled by
recursion, the comparator is used when dealing with elements).
2 & 4. An additional @CompareWith(value = Class<Comparator>,
nullOrder = NullOrder /* enum with NULL_IS_LESSER and
NULL_IS_GREATER*/) can deal with these issues, but it would require
some additional design (e.g. do we cache the Comparator instances
using a ThreadLocal?).
3. This points to making this feature a part of @EqualsAndHashCode
and @Data when the underlying class is Comparable.

Best regards,
Daniel Yokomizo

P.S.: This discussion makes clearer to me that Lombok features can be
divided in two broad categories:
1. Language extensions or macros (e.g. Cleanup, Synchronized, SneakyThrows).
2. Creating stuff derived from the internal structure of a class
(e.g. Data, EqualsAndHashCode, ToString). This category is very
similar to how "deriving" works in Haskell and Lombok can be seen as
the Java equivalent of DrIFT
(http://repetae.net/computer/haskell/DrIFT/).

Condor

unread,
Aug 26, 2015, 8:59:27 AM8/26/15
to Project Lombok, daniel....@gmail.com
This discussion seems dead, but IMHO this would be a really useful to add to Lombok!

Here is my take on the subject:

I suggest not only creating a class-level @CompareTo annotation, but also a field-level @Compare annotation (analogous to @Getter and @Setter). With the field-level @Compare you would be able to specify ordering and null handling. It could even be used as field selection when @CompareTo is not specified.

Field order:
The @Compare annotation should have an "order" attribute to specify the field order. If not specified I would suggest using the ordering in which the fields are defined.

Lists and arrays are not comparable:
The solution to implement multiple overloaded compares seems too far fetched. To simplify things I would suggest to just ignore non-comparable fields (and make this clear in the documentation).
Maybe we could even support a "comparator" attribute in the @Compare annotation to specify how the field should be compared.

Null handling:
Being able to specify a NullOrder is a definite requirement. This should however not be specified at the class-level (@CompareTo annotation) but at the field level (@Compare annotation).

Field selection for CompareTo:
I do not agree that the fields for equals and compareTo should always be the same. In my code they are almost always different (equals compares all fields except detail lists and compareTo only compares the fields of the primary key).
So IMHO @CompareTo should definitely not be part of @EqualsAndHashCode, but only of @Data.

Default comparison not always preferred:
I don't see this as a problem. You can always specify an "include" or "exclude" in the @CompareTo annotation or use the @Compare annotations on the fields directly.


Op zondag 14 maart 2010 00:03:59 UTC+1 schreef Daniel Yokomizo:

Reinier Zwitserloot

unread,
Aug 28, 2015, 6:13:07 AM8/28/15
to project-lombok
It sounds like your classes do not adhere to spec. The following must always be true for any class:

* if a.equals(b), then a.compareTo(b) == 0, and in reverse:

* if a.compareTo(b) == 0, then a.equals(b).

* if a.equals(b), then a.hashCode() == b.hashCode() (but the reverse does not have to hold; equal hashCodes do not imply equality).


Failure to adhere to this gives you some very nasty surprises if your use instances of your class as a key in for example a TreeMap.

In general you're not going to be able to guarantee that you hold to the spec unless compareTo and equals() and hashCode() _ALL_ calculate based on the exact same fields.



That's what's kinda hairy about this; if @EqualsAndHashCode did not exist we'd just make a @CompareTo annotation and have per-field settings available, but the @EqAHC exclusion/inclusion list should share itself with @CompareTo, so this now gets bundled up with our intent to un-stringly-typed-ify @EqAHC.


How about we deprecate @EqAHC and create a new annotation to take its place.

@Ordering


@Ordering is like @EqAHC now, except it does not have 'of' or 'exclude', and 'onParam' becomes 'onEqualsParam'. It gains 'comparable = true' as well as 'onCompareToParam'.

To configure individual fields, instead, you annotate that field with @Ordering.Conf. THis can be used to include a field (which excludes all other non-included fields) or exclude it (trying to include some field and exclude another is an error; it's either all includes, or all excludes). Here you can also configure where null sorts, ordering (int-based; all fields with the same int order by appearance in the source file.


If comparable=true, then the handler adds 'implements Comparable<SelfType>' to the implements list if it isn't already there.

If comparable=true, the handler generates a compareTo method that compares each field (except fields whose name starts with $, and static fields are also skipped), in order, using some default behaviour (null is 'smaller' than any other value). Compiler errors will simply be generated if a field isn't comparable (we won't even bother checking, as that's hard (requires resolution), we just assume .compareTo() will work, and we'll have special code for all primitives. Presence of an array generates a lombok-sourced error (we shall not be comparing arrays, too convoluted...).


.Conf settings:

'include = true'; if any field is so annotated, ONLY fields with include = true are included. This goes for all 3 method (equals, hashCode, and compareTo).

'exclude = true'; excludes the field. It is an error if both @CompareTo.Settings(include = true) as well as @CompareTo.Settings(exclude = true) shows up, even on different fields.

'compareWith = "foobar"' - passes the job of comparing this field to the method foobar, which is assumed to be static, internal to this class, and take 2 parameters; our field, and the other field, and return an int (negative/0/positive, same as normal compareTo method).

'nullOrder = NullOrder.NULL_IS_MAX' - sorts nulls as if they are larger than non-null, vs the default behaviour of null sorting as smaller than anything else (default is NullOrder.NULL_IS_MIN; another option is NULL_IS_ERR) We could debate if NULL_IS_ERR should be the default value instead.

'reverse = true' - reverses this field; objects where this field is largest sort as smaller and vice versa.

'order = (int)' - by default the order of any field is 0. Lower-ordered fields are compared first, and if multiple fields have the same order int, they are compared in the order as they show up in the source file itself. (by default, then, ALL fields have order 0 and thus the whole thing is compared in the order of appearance in the source file). The same order is used for equals and hashCode.


 --Reinier Zwitserloot

--
You received this message because you are subscribed to the Google Groups "Project Lombok" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-lombo...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reinier Zwitserloot

unread,
Aug 28, 2015, 6:14:24 AM8/28/15
to project-lombok
Lets continue discussion of this topic over at this issue:



 --Reinier Zwitserloot
Reply all
Reply to author
Forward
0 new messages