【问题标题】:Why is compareTo on an Enum final in Java?为什么 compareTo 在 Java 中的 Enum final 上?
【发布时间】:2010-10-05 21:48:47
【问题描述】:

Java 中的枚举实现了Comparable 接口。覆盖ComparablecompareTo 方法会很好,但在这里它被标记为final。 EnumcompareTo 上的默认自然顺序是列出的顺序。

有人知道为什么 Java 枚举有这个限制吗?

【问题讨论】:

  • 在 Effective Java - 3rd Edition 的第 10 条中有一个很好的解释(处理 equals(),但在第 14 条中他们告诉了 compareTo() 的问题是一样的)。简而言之:如果您扩展一个可实例化的类(如枚举)并添加一个值组件,则无法保留 equals(或 compareTo)协定。
  • @ChristianH.Kuhn 这听起来正是您不会想要这个限制的论据!大多数人在添加 value 组件时都希望添加 compareTo 方法,正是因为他们想要保留 equals/compareTo 合约。

标签: java enums comparable compareto


【解决方案1】:

我猜为了一致性...当您看到 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);    

更多信息:

【讨论】:

  • 自定义比较器仅在将枚举提供给集合时才真正有效。如果您想进行直接比较,它并没有多大帮助。
  • 是的,确实如此。新的 MyEnumComparator.compare(enum1, enum2)。等等。
  • @martinoconnor & Bombe:我已将您的 cmets 纳入答案。谢谢!
  • 由于MyEnumComparator 没有状态,它应该只是一个单例,特别是如果你正在做@Bombe 建议的事情;相反,你会做类似MyEnumComparator.INSTANCE.compare(enum1, enum2) 的事情来避免不必要的对象创建
  • @kbolino:比较器可以是枚举类中的嵌套类。它可以存储在枚举中的LENGTH_COMPARATOR 静态字段中。这样,任何使用枚举的人都可以轻松找到。
【解决方案2】:

提供使用源代码排序的 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】:

枚举值根据它们声明的顺序在逻辑上精确排序。这是 Java 语言规范的一部分。因此,枚举值只能在它们是同一个 Enum 的成员时进行比较。规范希望进一步保证 compareTo() 返回的可比较顺序与声明值的顺序相同。这就是枚举的定义。

【讨论】:

  • Thomas Paine 在他的示例中明确指出,语言只能按句法排序,而不能按语义排序。你说,项目是在逻辑上排序,但我理解枚举的方式,项目是通过逻辑方式封装
【解决方案4】:

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

并且枚举的equals 应该与身份相等(==)一致。

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

【讨论】:

  • 虽然建议 compareTo() 与 equals() 一致,但没有必要。
【解决方案5】:

如果您想更改枚举元素的自然顺序,请在源代码中更改它们的顺序。

【讨论】:

  • 是的,这就是我在原始条目中写的:)
  • 是的,但您并没有真正解释为什么要覆盖 compareTo()。所以我的结论是你正在尝试做一些坏事™,而我试图向你展示一个更正确的方法。
  • 当计算机做得比我好得多时,我不明白为什么我必须手动对条目进行排序。
  • 在枚举中,假设您出于某种原因以特定方式对条目进行排序。如果没有理由最好使用 .toString().compareTo()。或者可能是完全不同的东西,比如一个集合?
  • 出现这种情况的原因是我有一个带有 {isocode,countryname} 的枚举。它是按国家名称手动排序的,按预期工作。如果我决定从现在开始按 isocode 排序怎么办?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-01-29
  • 1970-01-01
  • 1970-01-01
  • 2015-12-27
  • 2018-07-22
相关资源
最近更新 更多