为什么用Java在Enum final上进行compareTo?


93

Java中的枚举实现了该Comparable接口。覆盖ComparablecompareTo方法会很不错,但是这里将其标记为final。在默认的自然秩序EnumcompareTo是所列出的顺序。

有谁知道为什么Java枚举有此限制?


在有效Java-第三版的第10条中有一个很好的解释(处理equals(),但在第14条中,他们告诉问题compareTo()是相同的)。简而言之:如果扩展可实例化的类(例如枚举)并添加值组件,则无法保留equals(或compareTo)协定。
Christian H. Kuhn

Answers:


121

为了保持一致性,我想...当您看到一个enum类型时,您就知道一个事实,即它的自然顺序就是常量的声明顺序。

要解决此问题,您可以轻松创建自己的Comparator<MyEnum>应用程序,并在需要其他顺序时使用它:

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
    }
}

您可以Comparator直接使用:

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

或在集合或数组中使用它:

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

更多信息:


自定义比较器仅在将Enum提供给Collection时才真正有效。如果您想进行直接比较,并没有太大帮助。
马丁·奥康纳

7
是的,它确实。新的MyEnumComparator.compare(enum1,enum2)。等等。
孟买

@martinoconnor&Bombe:我已将您的评论纳入答案。谢谢!
Zach Scrivena,09年

由于MyEnumComparator没有状态,因此应该只是一个单例,特别是如果您正在执行@Bombe建议的操作;相反,您可以做一些事情MyEnumComparator.INSTANCE.compare(enum1, enum2)以避免不必要的对象创建
kbolino

2
@kbolino:比较器事件可能是enum类中的嵌套类。它可以存储在LENGTH_COMPARATOR枚举的静态字段中。这样,对于使用枚举的任何人来说都很容易找到。
Lii

39

提供使用源代码顺序的compareTo的默认实现是可以的;使它最终成为Sun的失误。序号已经说明了申报顺序。我同意,在大多数情况下,开发人员可以按逻辑顺序排列其元素,但有时人们希望以一种使可读性和维护性至高无上的方式来组织源代码。例如:


  //===== 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");

上面的顺序在源代码中看起来不错,但不是作者认为compareTo应该起作用的方式。所需的compareTo行为是按字节数排序。可能发生的源代码排序会降低代码的组织性。

作为枚举的客户,我不介意作者如何组织其源代码。我确实希望它们的比较算法具有某种意义。Sun不必要地将源代码编写者置于束缚之中。


3
同意,我希望我的枚举能够具有与业务类似的算法,而不是如果有人不注意就可以取消的订单。
TheBakker

6

枚举值根据声明的顺序在逻辑上精确地排序。这是Java语言规范的一部分。因此,得出的结论是,枚举值只有在属于相同枚举的成员时才能进行比较。规范希望进一步保证compareTo()返回的可比顺序与声明值的顺序相同。这是枚举的确切定义。


正如托马斯·潘恩(Thomas Paine)在其示例中明确指出的那样,该语言只能通过语法进行排序,而不能通过语义进行排序。您说的是,这些项目是逻辑顺序排列的,但是按照我对枚举的理解,这些项目是通过逻辑方式封装的。
Bondax

2

一种可能的解释是 compareTo应该与equals

equals因为枚举应与身份相等一致(==)。

如果compareTo在哪里是非最终的,则可以用与不一致的行为来覆盖它equals,这将是非常违反直觉的。


虽然建议compareTo()与equals()一致,但这不是必需的。
克里斯蒂安·H·库恩,

-1

如果要更改枚举元素的自然顺序,请在源代码中更改其顺序。


是的,那是我在原始条目中写的:)
neu242

是的,但是您并没有真正解释为什么要覆盖compareTo()。因此,我的结论是您正在尝试做某事Bad™,而我正在向您展示一种更正确的方法。
孟买

我不明白为什么计算机要比我做的好得多,所以我必须手工对条目进行排序。
neu242

在枚举中,假设您出于某种原因以特定方式对条目进行了排序。如果没有理由,最好使用<enum> .toString()。compareTo()。还是完全不同的东西,例如Set?
孟买

出现此问题的原因是我有一个带有{isocode,countryname}的枚举。它是按照国家名称手动排序的,可以按预期工作。如果我决定从现在开始我要对isocode排序怎么办?
neu242
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.