104

An enum in Java implements the Comparable interface. It would have been nice to override Comparable's compareTo method, but here it's marked as final. The default natural order on Enum's compareTo is the listed order.

Does anyone know why a Java enums have this restriction?

Lii
  • 11,553
  • 8
  • 64
  • 88
neu242
  • 15,796
  • 20
  • 79
  • 114
  • There’s a very fine explanation in Effective Java - 3rd Edition in Item 10 (dealing with equals(), but in Item 14 they tell the Problem with compareTo() is the same). In short: If you extend an instantiable class (like an enum) and add a value component, you cannot preserve the equals (or compareTo) contract. – Christian H. Kuhn Jul 28 '19 at 11:33
  • @ChristianH.Kuhn that sounds like exactly an argument why you **wouldn't** want this restriction! most people would want to add a compareTo method when they add a value component, exactly because they want to preserve the equals/compareTo contract. – Adam Burley Feb 03 '21 at 22:07

5 Answers5

127

For consistency I guess... when you see an enum type, you know for a fact that its natural ordering is the order in which the constants are declared.

To workaround this, you can easily create your own Comparator<MyEnum> and use it whenever you need a different ordering:

enum MyEnum
{
    DOG("woof"),
    CAT("meow");

    String sound;    
    MyEnum(String s) { sound = s; }
}

class MyEnumComparator implements Comparator<MyEnum>
{
    public int compare(MyEnum o1, MyEnum o2)
    {
        return -o1.compareTo(o2); // this flips the order
        return o1.sound.length() - o2.sound.length(); // this compares length
    }
}

You can use the Comparator directly:

MyEnumComparator comparator = new MyEnumComparator();
int order = comparator.compare(MyEnum.CAT, MyEnum.DOG);

or use it in collections or arrays:

NavigableSet<MyEnum> set = new TreeSet<MyEnum>(comparator);
MyEnum[] array = MyEnum.values();
Arrays.sort(array, comparator);    

Further information:

neu242
  • 15,796
  • 20
  • 79
  • 114
Zach Scrivena
  • 29,073
  • 11
  • 63
  • 73
  • Custom comparators are only really effective when supplying the Enum to a Collection. It doesn't help as much if you want to make a direct comparison. – Martin OConnor Feb 06 '09 at 10:49
  • 7
    Yes, it does. new MyEnumComparator.compare(enum1, enum2). Et voilá. – Bombe Feb 06 '09 at 10:50
  • @martinoconnor & Bombe: I've incorporated your comments into the answer. Thanks! – Zach Scrivena Feb 06 '09 at 10:58
  • Since `MyEnumComparator` has no state, it should just be a singleton, especially if you're doing what @Bombe suggests; instead, you would do something like `MyEnumComparator.INSTANCE.compare(enum1, enum2)` to avoid unnecessary object creation – kbolino Nov 27 '15 at 19:08
  • 2
    @kbolino: The comparator could event be a nested class inside the enum class. It could be stored in a `LENGTH_COMPARATOR` static field in the enum. In that way it would be easy to find for anyone that is using the enum. – Lii Sep 26 '17 at 08:07
  • 2
    I'm confused by the fact that your `compare` method has two `return` statements, one after the other. Please could you explain that? – Sam Nov 26 '19 at 11:37
41

Providing a default implementation of compareTo that uses the source-code ordering is fine; making it final was a misstep on Sun's part. The ordinal already accounts for declaration order. I agree that in most situations a developer can just logically order their elements, but sometimes one wants the source code organized in a way that makes readability and maintenance to be paramount. For example:


  //===== SI BYTES (10^n) =====//

  /** 1,000 bytes. */ KILOBYTE (false, true,  3, "kB"),
  /** 106 bytes. */   MEGABYTE (false, true,  6, "MB"),
  /** 109 bytes. */   GIGABYTE (false, true,  9, "GB"),
  /** 1012 bytes. */  TERABYTE (false, true, 12, "TB"),
  /** 1015 bytes. */  PETABYTE (false, true, 15, "PB"),
  /** 1018 bytes. */  EXABYTE  (false, true, 18, "EB"),
  /** 1021 bytes. */  ZETTABYTE(false, true, 21, "ZB"),
  /** 1024 bytes. */  YOTTABYTE(false, true, 24, "YB"),

  //===== IEC BYTES (2^n) =====//

  /** 1,024 bytes. */ KIBIBYTE(false, false, 10, "KiB"),
  /** 220 bytes. */   MEBIBYTE(false, false, 20, "MiB"),
  /** 230 bytes. */   GIBIBYTE(false, false, 30, "GiB"),
  /** 240 bytes. */   TEBIBYTE(false, false, 40, "TiB"),
  /** 250 bytes. */   PEBIBYTE(false, false, 50, "PiB"),
  /** 260 bytes. */   EXBIBYTE(false, false, 60, "EiB"),
  /** 270 bytes. */   ZEBIBYTE(false, false, 70, "ZiB"),
  /** 280 bytes. */   YOBIBYTE(false, false, 80, "YiB");

The above ordering looks good in source code, but is not how the author believes the compareTo should work. The desired compareTo behavior is to have ordering be by number of bytes. The source-code ordering that would make that happen degrades the organization of the code.

As a client of an enumeration i could not care less how the author organized their source code. I do want their comparison algorithm to make some kind of sense, though. Sun has unnecessarily put source code writers in a bind.

Thomas Paine
  • 411
  • 4
  • 2
  • 4
    Agree, I want my enum to be able to have a business comparable algorythm instead of an order which can be broken if someone doesn't pay attention. – TheBakker Aug 26 '15 at 16:52
7

Enumeration values are precisely ordered logically according to the order they are declared. This is part of the Java language specification. Therefore it follows that enumeration values can only be compared if they are members of the same Enum. The specification wants to further guarantee that the comparable order as returned by compareTo() is the same as the order in which the values were declared. This is the very definition of an enumeration.

Martin OConnor
  • 3,583
  • 4
  • 25
  • 32
  • As Thomas Paine made clear in his example, the language can only order by syntactics, not semantics. You say, the items are _ordered_ logically, but the way i understand enum, the items are _encapsulated_ by logical means. – Bondax Jun 26 '12 at 07:29
3

One possible explanation is that compareTo should be consistent with equals.

And equals for enums should be consistent with identity equality (==).

If compareTo where to be non-final it would be possible to override it with a behaviour which was not consistent with equals, which would be very counter-intuitive.

Lii
  • 11,553
  • 8
  • 64
  • 88
1

If you want to change the natural order of your enum’s elements, change their order in the source code.

Bombe
  • 81,643
  • 20
  • 123
  • 127
  • Yup, that's what I wrote in the original entry :) – neu242 Feb 06 '09 at 10:29
  • Yes but you don’t really explain why you’d want to override compareTo(). So my conclusion was that you are trying to do Something Bad™ and I was trying to show you a more correct way. – Bombe Feb 06 '09 at 10:32
  • I don't see why I should have to sort the entries by hand when computers does that much better than me. – neu242 Feb 06 '09 at 10:35
  • In enumerations it’s assumed that you ordered the entries in that specific way for a reason. If there is no reason you are better off using .toString().compareTo(). Or maybe something different altogether, like maybe a Set? – Bombe Feb 06 '09 at 10:38
  • The reason this came up is that I have an Enum with {isocode,countryname}. It was manually sorted on countryname, which works as intended. What if I decide I want to sort on isocode from now on? – neu242 Feb 06 '09 at 10:44
  • Then you probably shouldn’t use an enum. As I said. – Bombe Feb 06 '09 at 10:50
  • my 2 cents: iso codes change from time to time. You shouldn't store those in code but rather in something data-driven like a config file or a database. – Mr. Shiny and New 安宇 Feb 06 '09 at 15:08