Tại sao CompareTo trên Enum cuối cùng trong Java?


93

Một enum trong Java thực thi Comparablegiao diện. Nó sẽ được tốt đẹp để ghi đè Comparable's compareTophương pháp, nhưng ở đây nó được đánh dấu như là cuối cùng. Thứ tự tự nhiên mặc định trên Enum's compareTolà thứ tự được liệt kê.

Có ai biết tại sao một enums Java có hạn chế này không?


Có một lời giải thích rất hay trong Java hiệu quả - Phiên bản thứ 3 trong Mục 10 (xử lý bằng = (), nhưng trong Mục 14, họ cho biết Vấn đề với CompareTo () là như nhau). Tóm lại: Nếu bạn mở rộng một lớp có thể khởi tạo (như enum) và thêm một thành phần giá trị, bạn không thể bảo toàn hợp đồng bằng (hoặc so sánh).
Christian H. Kuhn

Câu trả lời:


121

Để có tính nhất quán, tôi đoán ... khi bạn nhìn thấy một enumkiểu, bạn biết một thực tế rằng thứ tự tự nhiên của nó là thứ tự mà các hằng được khai báo.

Để giải quyết vấn đề này, bạn có thể dễ dàng tạo của riêng mình Comparator<MyEnum>và sử dụng nó bất cứ khi nào bạn cần một thứ tự khác:

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

Bạn có thể sử dụng Comparatortrực tiếp:

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

hoặc sử dụng nó trong các bộ sưu tập hoặc mảng:

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

Thêm thông tin:


Bộ so sánh tùy chỉnh chỉ thực sự hiệu quả khi cung cấp Enum vào Bộ sưu tập. Nó không giúp ích nhiều nếu bạn muốn so sánh trực tiếp.
Martin OConnor

7
Có, nó có. mới MyEnumComparator.compare (enum1, enum2). Et voilá.
Bombe

@martinoconnor & Bombe: Tôi đã kết hợp nhận xét của bạn vào câu trả lời. Cảm ơn!
Zach Scrivena

MyEnumComparatorkhông có trạng thái, nó chỉ nên là một singleton, đặc biệt nếu bạn đang làm những gì @Bombe đề xuất; thay vào đó, bạn sẽ làm điều gì đó như MyEnumComparator.INSTANCE.compare(enum1, enum2)để tránh không cần thiết tạo đối tượng
kbolino

2
@kbolino: Sự kiện so sánh có thể là một lớp lồng nhau bên trong lớp enum. Nó có thể được lưu trữ trong một LENGTH_COMPARATORtrường tĩnh trong enum. Bằng cách đó, sẽ dễ dàng tìm thấy bất kỳ ai đang sử dụng enum.
Lii

39

Cung cấp một triển khai mặc định của CompareTo sử dụng thứ tự mã nguồn là tốt; khiến nó cuối cùng là một bước sai lầm của Sun. Thứ tự đã tính đến thứ tự khai báo. Tôi đồng ý rằng trong hầu hết các tình huống, nhà phát triển chỉ có thể sắp xếp thứ tự các phần tử của họ một cách hợp lý, nhưng đôi khi người ta muốn mã nguồn được tổ chức theo cách làm cho khả năng đọc và bảo trì là điều tối quan trọng. Ví dụ:


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

Thứ tự trên có vẻ tốt trong mã nguồn, nhưng không phải là cách tác giả tin rằng CompareTo sẽ hoạt động. Hành vi CompareTo mong muốn là có thứ tự theo số byte. Thứ tự mã nguồn có thể làm cho điều đó xảy ra làm suy giảm tổ chức của mã.

Là một khách hàng của một bảng liệt kê, tôi không thể quan tâm hơn đến cách tác giả tổ chức mã nguồn của họ. Tuy nhiên, tôi muốn thuật toán so sánh của họ có ý nghĩa nào đó. Sun đã đặt những người viết mã nguồn vào một ràng buộc một cách không cần thiết.


3
Đồng ý, tôi muốn enum của tôi có thể có một thuật toán kinh doanh có thể so sánh được thay vì một đơn đặt hàng có thể bị phá vỡ nếu ai đó không chú ý.
TheBakker

6

Các giá trị liệt kê được sắp xếp logic chính xác theo thứ tự chúng được khai báo. Đây là một phần của đặc tả ngôn ngữ Java. Do đó, theo đó các giá trị liệt kê chỉ có thể được so sánh nếu chúng là thành viên của cùng một Enum. Đặc tả muốn đảm bảo thêm rằng thứ tự có thể so sánh được trả về bởi CompareTo () giống với thứ tự mà các giá trị đã được khai báo. Đây là định nghĩa của một phép liệt kê.


Như Thomas Paine đã nói rõ trong ví dụ của mình, ngôn ngữ chỉ có thể sắp xếp theo cú pháp, không phải ngữ nghĩa. Bạn nói, các mục được sắp xếp một cách hợp lý, nhưng theo cách tôi hiểu về enum, các mục được đóng gói theo cách logic.
Bondax

2

Một lời giải thích có thể là compareTonên nhất quán với equals.

equalsđối với enums nên phù hợp với bình đẳng danh tính ( ==).

Nếu compareTovị trí không phải là cuối cùng, có thể ghi đè nó bằng một hành vi không phù hợp equals, điều này sẽ rất phản trực quan.


Mặc dù được khuyến nghị rằng CompareTo () nhất quán với equals (), nhưng không cần thiết.
Christian H. Kuhn

-1

Nếu bạn muốn thay đổi thứ tự tự nhiên của các phần tử enum, hãy thay đổi thứ tự của chúng trong mã nguồn.


Yup, đó là những gì tôi đã viết trong mục gốc :)
neu242

Có nhưng bạn không thực sự giải thích lý do tại sao bạn muốn ghi đè so sánhTo (). Vì vậy, kết luận của tôi là bạn đang cố gắng thực hiện Something Bad ™ và tôi đang cố gắng chỉ cho bạn một cách đúng đắn hơn.
Bombe

Tôi không hiểu tại sao tôi phải sắp xếp các mục nhập bằng tay khi máy tính làm điều đó tốt hơn tôi nhiều.
neu242

Trong các phép liệt kê, giả định rằng bạn đã sắp xếp các mục theo cách cụ thể đó là có lý do. Nếu không có lý do gì thì tốt hơn hết bạn nên sử dụng <enum> .toString (). So sánhTo (). Hoặc có thể một cái gì đó khác hoàn toàn, như có thể là một Bộ?
Bombe

Lý do điều này được đưa ra là tôi có một Enum với {isocode, countryname}. Nó được sắp xếp theo cách thủ công trên tên quốc gia, hoạt động như dự định. Điều gì sẽ xảy ra nếu tôi quyết định tôi muốn sắp xếp trên isocode từ bây giờ?
neu242
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.