Lợi thế của một enum Java so với một lớp với các trường cuối cùng tĩnh công khai là gì?


147

Tôi rất quen thuộc với C # nhưng bắt đầu làm việc nhiều hơn với Java. Tôi dự kiến ​​sẽ học được rằng enum trong Java về cơ bản tương đương với những người trong C # nhưng rõ ràng đây không phải là trường hợp. Ban đầu, tôi rất hào hứng khi biết rằng các enum Java có thể chứa nhiều phần dữ liệu có vẻ rất thuận lợi ( http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html ). Tuy nhiên, kể từ đó, tôi đã tìm thấy rất nhiều tính năng bị thiếu trong C #, chẳng hạn như khả năng dễ dàng gán một phần tử enum một giá trị nhất định và do đó khả năng chuyển đổi một số nguyên thành enum mà không cần nỗ lực nhiều ( tức là Chuyển đổi giá trị số nguyên để khớp với Enum Java ).

Vì vậy, câu hỏi của tôi là: có bất kỳ lợi ích nào cho các enum Java đối với một lớp với một loạt các trường cuối cùng tĩnh công khai không? Hay nó chỉ cung cấp cú pháp nhỏ gọn hơn?

EDIT: Hãy để tôi rõ ràng hơn. Lợi ích của các enum Java đối với một lớp với một loạt các trường cuối cùng tĩnh công khai cùng loại là gì? Ví dụ, trong ví dụ về các hành tinh ở liên kết đầu tiên, lợi thế của enum so với một lớp có các hằng số công khai này là gì:

public static final Planet MERCURY = new Planet(3.303e+23, 2.4397e6);
public static final Planet VENUS = new Planet(4.869e+24, 6.0518e6);
public static final Planet EARTH = new Planet(5.976e+24, 6.37814e6);
public static final Planet MARS = new Planet(6.421e+23, 3.3972e6);
public static final Planet JUPITER = new Planet(1.9e+27, 7.1492e7);
public static final Planet SATURN = new Planet(5.688e+26, 6.0268e7);
public static final Planet URANUS = new Planet(8.686e+25, 2.5559e7);
public static final Planet NEPTUNE = new Planet(1.024e+26, 2.4746e7);

Theo như tôi có thể nói, câu trả lời của caskish là người duy nhất thỏa mãn điều này.


4
@Bohantic: Nó có thể không phải là một bản sao, vì OP chỉ đề cập đến public static finalcác trường, có thể là các giá trị được nhập và không nhất thiết phải intlà s.
casifer

1
@Shahzeb Khó. Rõ ràng sử dụng enums thay vì hằng chuỗi là một ý tưởng TUYỆT VỜI và hơn cả được khuyến khích. Loại an toàn, không cần các chức năng tiện ích tĩnh, v.v. hoàn toàn không có lý do để sử dụng chuỗi thay thế.
Voo

1
@Voo Có tôi biết sẽ có những bất đồng. Và đây là một trong 49 giây. Enums là tuyệt vời (và tôi yêu chúng và sử dụng chúng rất thường xuyên) nhưng bạn cần loại an toàn nào khi khai báo một hằng hoặc sử dụng nó. Đó là một quá mức cần thiết để tạo enum mỗi khi bạn cần khai báo một hằng cho chuỗi ký tự.
Shahzeb

4
@Shahzeb Nếu bạn có một biến duy nhất, chắc chắn sử dụng một chuỗi, không có nhiều điều có thể xảy ra ở đây (một giá trị duy nhất là vô nghĩa như một tham số). Nhưng lưu ý rằng chúng ta đang nói về hằng số S, vì vậy bây giờ chúng ta đang nói về việc có thể chuyển chúng vào các chức năng, v.v. Chúng ta có cần loại saftey không? Chà, không, nhưng sau đó hầu hết mọi người coi các kiểu "mọi thứ void*" là kiểu tốt và nó có thể ngăn chặn các lỗi (đặc biệt là nếu truyền nhiều hơn một tham số enum / chuỗi!). Ngoài ra, nó đặt các hằng số vào không gian tên của riêng chúng, v.v ... Trái với điều đó, không có lợi thế thực sự khi chỉ có các biến đơn giản.
Voo

3
@Bohantic: Tôi không thấy thế nào. Với ints, không có loại an toàn vì người ta có thể vượt qua bất kỳ giá trị nào. Mặt khác, các đối tượng đánh máy không khác gì enum về độ an toàn của loại.
casifer

Câu trả lời:


78

Về mặt kỹ thuật, người ta thực sự có thể xem enum là một lớp với một loạt các hằng số được gõ, và đây thực tế là cách các hằng số enum được thực hiện trong nội bộ. enumTuy nhiên, việc sử dụng một phương thức hữu ích ( Enum javadoc ) mà nếu không bạn sẽ phải tự thực hiện, chẳng hạn như Enum.valueOf.


14
Ngoài ra còn có .values()để lặp lại danh sách các giá trị.
h3xStream

1
Đây có vẻ là câu trả lời đúng, mặc dù nó không thỏa mãn lắm. Theo tôi, việc Java bổ sung hỗ trợ cho enum chỉ là cú pháp nhỏ gọn hơn và truy cập vào các phương thức Enum.
Craig W

7
@Craig bản năng của bạn là đúng - đây là một câu trả lời rất tệ, bởi vì nó hoàn toàn bỏ lỡ mục đích của enums. Xem ý kiến ​​của tôi dưới câu hỏi cho một phần lý do tại sao.
Bohemian

1
@Bohantic: Tôi không "bỏ lỡ mục đích" của enums - Tôi sử dụng chúng mọi lúc. Xem phản hồi của tôi để bình luận của bạn ở trên.
casifer

1
Phương thức Enum.valuesOf không trả về cùng một đối tượng nếu được gọi hai lần?
Emre Aktürk

104
  1. Loại an toàn và giá trị an toàn.
  2. Đảm bảo singleton.
  3. Khả năng xác định và ghi đè các phương thức.
  4. Khả năng sử dụng các giá trị trong các switchtuyên casebố tuyên bố mà không cần trình độ.
  5. Tích hợp tuần tự các giá trị thông qua ordinal().
  6. Tuần tự hóa theo tên không theo giá trị, cung cấp một mức độ chứng minh trong tương lai.
  7. EnumSetEnumMapcác lớp học.

19
Nói xong, mỗi lần tôi đặt mã vào Enum tôi đều hối hận.
Hầu tước Lorne

4
Tại sao bạn lại hối hận? Tôi chưa bao giờ đào
glglgl

2
@glglgl Trở thành việc đặt mã dành riêng cho ứng dụng vào nơi mà tôi cảm thấy nó không thực sự thuộc về, điều đó thực sự chỉ xác định một tập hợp các giá trị. Nếu tôi có nó để làm lại, tôi sẽ đưa nó vào một trong vô số switchtuyên bố đó là động lực ban đầu để sử dụng một Enum.
Hầu tước Lorne

72

Không ai đề cập đến khả năng sử dụng chúng trong các switchtuyên bố; Tôi cũng sẽ ném nó vào.

Điều này cho phép các enum phức tạp tùy ý được sử dụng một cách sạch sẽ mà không sử dụng instanceof, các ifchuỗi có khả năng gây nhầm lẫn hoặc các giá trị chuyển mạch không chuỗi / int. Ví dụ kinh điển là một máy trạng thái.


Dù sao, Bạn đã không đề cập đến bất kỳ lợi ích nào từ enums so với trường tĩnh, Bạn có thể sử dụng đủ loại trong câu lệnh chuyển đổi với trường tĩnh. Op Cần các chức năng thực sự hoặc sự khác biệt về nước hoa
Genaut

@Genaut Lợi ích là enums có nhiều chức năng hơn một chuỗi hoặc int - câu hỏi là về sự khác biệt, mà tôi đã cung cấp. OP đã nhận thức được enum là gì và không ai khác đã đề cập đến các câu lệnh chuyển đổi khi tôi đăng bài này 4,5 năm trước, và ít nhất một vài người đã tìm thấy nó cung cấp thông tin mới ¯_ () _ /
Dave Newton

44

Ưu điểm chính là loại an toàn. Với một tập hợp các hằng số, bất kỳ giá trị nào thuộc cùng loại nội tại đều có thể được sử dụng, gây ra lỗi. Với một enum chỉ có thể sử dụng các giá trị áp dụng.

Ví dụ

public static final int SIZE_SMALL  = 1;
public static final int SIZE_MEDIUM = 2;
public static final int SIZE_LARGE  = 3;

public void setSize(int newSize) { ... }

obj.setSize(15); // Compiles but likely to fail later

đấu với

public enum Size { SMALL, MEDIUM, LARGE };

public void setSize(Size s) { ... }

obj.setSize( ? ); // Can't even express the above example with an enum

3
các lớp cũng là loại an toàn ...: / (giả sử trường tĩnh là loại của lớp container)
h3xStream

Bạn vẫn có thể chuyển cho nó một giá trị không hợp lệ nhưng đó sẽ là lỗi thời gian biên dịch dễ phát hiện hơn nhiều.
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz

2
Bạn có thể gọi setSize(null)trong ví dụ thứ hai của mình, nhưng nó có khả năng thất bại sớm hơn nhiều so với lỗi của ví dụ đầu tiên.
Jeffrey

42

Có ít nhầm lẫn. Lấy Fontví dụ. Nó có một hàm tạo lấy tên của Fontbạn muốn, kích thước và kiểu của nó ( new Font(String, int, int)). Cho đến hôm nay tôi không thể nhớ nếu phong cách hoặc kích thước đi trước. Nếu Fontđã sử dụng một enumcho tất cả các phong cách khác nhau của nó ( PLAIN, BOLD, ITALIC, BOLD_ITALIC), constructor của nó sẽ như thế nào Font(String, Style, int), ngăn chặn bất kỳ sự nhầm lẫn. Thật không may, enumkhông có mặt khi Fontlớp được tạo và do Java phải duy trì khả năng tương thích ngược, chúng tôi sẽ luôn bị bối rối bởi sự mơ hồ này.

Tất nhiên, đây chỉ là một đối số cho việc sử dụng enumthay vì public static finalhằng số. Enums cũng hoàn hảo cho singletons và thực hiện hành vi mặc định trong khi cho phép tùy chỉnh sau này (IE mẫu chiến lược ). Một ví dụ về cái sau là java.nio.file' OpenOptionStandardOpenOption: nếu một nhà phát triển muốn tạo ra cái không chuẩn của riêng mình OpenOption, anh ta có thể.


Trường hợp 'Phông chữ' của bạn vẫn còn mơ hồ nếu hai yêu cầu tương tự được yêu cầu. Câu trả lời chính tắc cho vấn đề đó là điều mà nhiều ngôn ngữ gọi là Thông số được đặt tên . Điều đó hoặc tốt hơn hỗ trợ chữ ký phương pháp IDE.
aaaaaa

1
@aaaaaa Tôi chưa thấy nhiều trường hợp một nhà xây dựng sẽ lấy hai cái giống nhau enummà không sử dụng varargs hoặc a Setđể lấy số lượng tùy ý của chúng.
Jeffrey

@aaaaaa Vấn đề chính với các tham số được đặt tên, nó phụ thuộc vào chi tiết triển khai (tên của tham số). Tôi có thể làm một giao diện interface Foo { public void bar(String baz); }. Ai đó làm cho một lớp mà gọi bar: someFoo.bar(baz = "hello");. Tôi thay đổi chữ ký của Foo::barđể public void bar(String foobar). Bây giờ người được gọi someFoosẽ cần sửa đổi mã của họ nếu họ vẫn muốn nó hoạt động.
Jeffrey

Tôi cũng không nhớ đã nhìn thấy các loại enum liên tiếp, nhưng nghĩ rằng một loại phổ biến có thể là DAY_OF_WEEK hoặc một cái gì đó tương tự. Và điểm hay về giao diện - đã không nghĩ về điều đó. Cá nhân tôi sẽ đưa vấn đề đó qua sự mơ hồ lan rộng gây ra bởi các tham số không tên, đòi hỏi phải có sự hỗ trợ IDE mạnh mẽ hơn. Tôi hiểu rằng đó là một cuộc gọi phán xét, tuy nhiên, việc phá vỡ các thay đổi API là điều cần xem xét nghiêm túc.
aaaaaa

26

Có nhiều câu trả lời hay ở đây, nhưng không có câu trả lời nào cho thấy có các triển khai / lớp giao diện API được tối ưu hóa cao dành riêng cho enums :

Các lớp cụ thể enum này chỉ chấp nhận các Enumthể hiện ( EnumMapchỉ chấp nhận Enums dưới dạng các khóa) và bất cứ khi nào có thể, chúng trở lại biểu diễn nhỏ gọn và thao tác bit trong quá trình thực hiện.

Điều đó có nghĩa là gì?

Nếu Enumloại của chúng tôi không có nhiều hơn 64 phần tử (hầu hết các Enumví dụ thực tế sẽ đủ điều kiện cho điều này), thì việc triển khai sẽ lưu trữ các phần tử trong một longgiá trị duy nhất , mỗi Enumtrường hợp được đề cập sẽ được liên kết với một bit dài 64 bit này long. Thêm một phần tử vào một EnumSetchỉ đơn giản là đặt bit thích hợp thành 1, loại bỏ nó chỉ là đặt bit đó thành 0. Kiểm tra xem một phần tử có trong Setchỉ là một bài kiểm tra bitmask! Bây giờ bạn phải yêu Enums cho điều này!


1
Tôi đã biết về hai điều này trước đây, nhưng tôi chỉ học được rất nhiều từ What does this mean?phần của bạn . Tôi biết có một sự phân chia trong việc thực hiện ở kích thước 64, nhưng tôi thực sự không biết tại sao
Christopher Rucinski

15

thí dụ:

public class CurrencyDenom {
   public static final int PENNY = 1;
 public static final int NICKLE = 5;
 public static final int DIME = 10;
public static final int QUARTER = 25;}

Giới hạn của hằng java

1) Không an toàn loại : Trước hết, nó không an toàn về loại; bạn có thể gán bất kỳ giá trị int hợp lệ nào cho int eg 99 mặc dù không có đồng xu nào đại diện cho giá trị đó.

2) Không in ấn có ý nghĩa : giá trị in của bất kỳ hằng số này sẽ in giá trị số của nó thay vì tên đồng tiền có ý nghĩa, ví dụ như khi bạn in NICKLE, nó sẽ in "5" thay vì "NICKLE"

3) Không có không gian tên : để truy cập vào hằng số tiền tệ, chúng ta cần phải đặt tiền tố tên lớp, ví dụ: MoneyDenom.PENNY thay vì chỉ sử dụng PENNY mặc dù điều này cũng có thể đạt được bằng cách sử dụng nhập tĩnh trong JDK 1.5

Lợi thế của enum

1) Enums trong Java là loại an toàn và có không gian tên riêng. Điều đó có nghĩa là enum của bạn sẽ có một loại ví dụ "Tiền tệ" trong ví dụ bên dưới và bạn không thể gán bất kỳ giá trị nào ngoài chỉ định trong Hằng số Enum.

public enum Currency {PENNY, NICKLE, DIME, QUARTER};

Currency coin = Currency.PENNY; coin = 1; //compilation error

2) Enum trong Java là loại tham chiếu như lớp hoặc giao diện và bạn có thể định nghĩa hàm tạo, phương thức và biến trong java Enum, làm cho nó mạnh hơn Enum trong C và C ++ như trong ví dụ tiếp theo của loại Enum Java.

3) Bạn có thể chỉ định các giá trị của hằng số enum tại thời điểm tạo như ví dụ dưới đây: enum công khai Tiền tệ {PENNY (1), NICKLE (5), DIME (10), QUARTER (25)}; Nhưng để làm việc này, bạn cần xác định một biến thành viên và hàm tạo vì PENNY (1) thực sự đang gọi một hàm tạo chấp nhận giá trị int, xem ví dụ bên dưới.

public enum Currency {
    PENNY(1), NICKLE(5), DIME(10), QUARTER(25);
    private int value;

    private Currency(int value) {
            this.value = value;
    }
}; 

Tham khảo: https://javarevisited.blogspot.com/2011/08/enum-in-java-example-tutorial.html


11

Lợi ích đầu tiên của enums, như bạn đã nhận thấy, là sự đơn giản cú pháp. Nhưng điểm chính của enums là cung cấp một bộ hằng số nổi tiếng, theo mặc định, tạo thành một phạm vi và giúp thực hiện phân tích mã toàn diện hơn thông qua kiểm tra an toàn loại & giá trị.

Những thuộc tính của enums giúp cả lập trình viên và trình biên dịch. Ví dụ: giả sử bạn thấy một hàm chấp nhận một số nguyên. Số nguyên đó có nghĩa là gì? Những loại giá trị bạn có thể vượt qua? Bạn không thực sự biết ngay lập tức. Nhưng nếu bạn thấy một hàm chấp nhận enum, bạn biết rất rõ tất cả các giá trị có thể bạn có thể truyền vào.

Đối với trình biên dịch, enums giúp xác định một phạm vi các giá trị và trừ khi bạn gán các giá trị đặc biệt cho các thành viên enum, chúng nằm trong phạm vi từ 0 trở lên. Điều này giúp tự động theo dõi các lỗi trong mã thông qua kiểm tra an toàn loại và hơn thế nữa. Ví dụ: trình biên dịch có thể cảnh báo bạn rằng bạn không xử lý tất cả các giá trị enum có thể có trong câu lệnh chuyển đổi của bạn (nghĩa là khi bạn không có defaulttrường hợp và chỉ xử lý một trong số N giá trị enum). Nó cũng cảnh báo bạn khi bạn chuyển đổi một số nguyên tùy ý thành enum vì phạm vi giá trị của enum nhỏ hơn số nguyên và do đó có thể gây ra lỗi trong hàm không thực sự chấp nhận số nguyên. Ngoài ra, việc tạo bảng nhảy cho công tắc trở nên dễ dàng hơn khi các giá trị từ 0 trở lên.

Điều này không chỉ đúng với Java mà còn đối với các ngôn ngữ khác có kiểm tra kiểu nghiêm ngặt. C, C ++, D, C # là những ví dụ điển hình.


4

Một enum là cuối cùng, với một hàm tạo riêng, tất cả các giá trị của nó là cùng loại hoặc một kiểu con, bạn có thể nhận được tất cả các giá trị của nó bằng cách sử dụng values(), lấy name()hoặc ordinal()giá trị của nó hoặc bạn có thể tra cứu một enum theo số hoặc tên.

Bạn cũng có thể định nghĩa các lớp con (mặc dù cuối cùng về mặt ý tưởng, một số thứ bạn không thể làm theo cách nào khác)

enum Runner implements Runnable {
    HI {
       public void run() {
           System.out.println("Hello");
       }
    }, BYE {
       public void run() {
           System.out.println("Sayonara");
       }
       public String toString() {
           return "good-bye";
       }
    }
 }

 class MYRunner extends Runner // won't compile.

4

Lợi ích của enum:

  1. Enums là loại an toàn, trường tĩnh không
  2. Có một số lượng giá trị hữu hạn (không thể vượt qua giá trị enum không tồn tại. Nếu bạn có các trường lớp tĩnh, bạn có thể mắc lỗi đó)
  3. Mỗi enum có thể có nhiều thuộc tính (trường / getters) được gán - đóng gói. Ngoài ra một số phương pháp đơn giản: YEAR.toSeconds () hoặc tương tự. So sánh: Colors.RED.getHex () với Colors.toHex (Colors.RED)

"chẳng hạn như khả năng dễ dàng gán một phần tử enum một giá trị nhất định"

enum EnumX{
  VAL_1(1),
  VAL_200(200);
  public final int certainValue;
  private X(int certainValue){this.certainValue = certainValue;}
}

"và do đó, khả năng chuyển đổi một số nguyên thành một enum mà không cần một nỗ lực đáng kể" Thêm một phương thức chuyển đổi int thành enum để thực hiện điều đó. Chỉ cần thêm HashMap tĩnh <Integer, EnumX> chứa enum java ánh xạ .

Nếu bạn thực sự muốn chuyển đổi ord = VAL_200.ordinal () trở lại val_200, chỉ cần sử dụng: EnumX.values ​​() [ord]


3

Một sự khác biệt quan trọng khác là trình biên dịch java xử lý static finalcác trường thuộc kiểu nguyên thủyChuỗi dưới dạng chữ. Nó có nghĩa là các hằng số trở thành nội tuyến. Nó tương tự như C/C++ #definetiền xử lý. Xem câu hỏi SO này . Đây không phải là trường hợp với enums.



2

Ưu điểm lớn nhất là enum Singletons dễ viết và an toàn cho chủ đề:

public enum EasySingleton{
    INSTANCE;
}

/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
     private volatile DoubleCheckedLockingSingleton INSTANCE;

     private DoubleCheckedLockingSingleton(){}

     public DoubleCheckedLockingSingleton getInstance(){
         if(INSTANCE == null){
            synchronized(DoubleCheckedLockingSingleton.class){
                //double checking Singleton instance
                if(INSTANCE == null){
                    INSTANCE = new DoubleCheckedLockingSingleton();
                }
            }
         }
         return INSTANCE;
     }
}

cả hai đều tương tự nhau và nó tự xử lý serialization

//readResolve to prevent another instance of Singleton
    private Object readResolve(){
        return INSTANCE;
    }

hơn


0

Tôi nghĩ enumkhông thể final, bởi vì trong trình biên dịch mui xe tạo ra các lớp con cho mỗi enummục.

Thêm thông tin từ nguồn


Trong nội bộ, chúng không phải là cuối cùng, bởi vì - như bạn nói - bên trong có thể được phân lớp. Nhưng than ôi, bạn không thể tự phân lớp chúng, ví dụ để mở rộng nó với các giá trị riêng.
glglgl

0

Có rất nhiều lợi thế của enum được đăng ở đây, và tôi đang tạo ra những enum như vậy ngay bây giờ như được hỏi trong câu hỏi. Nhưng tôi có một enum với 5-6 lĩnh vực.

enum Planet{
EARTH(1000000, 312312321,31232131, "some text", "", 12),
....
other planets
....

Trong các loại trường hợp này, khi bạn có nhiều trường trong enum, rất khó để hiểu giá trị nào thuộc về trường nào khi bạn cần xem hàm tạo và bóng mắt.

Lớp với các static finalhằng số và sử dụng Buildermẫu để tạo các đối tượng như vậy làm cho nó dễ đọc hơn. Nhưng, bạn sẽ mất tất cả các lợi thế khác của việc sử dụng enum, nếu bạn cần chúng. Một bất lợi của các lớp học đó là, bạn cần phải thêm các Planetđối tượng thủ công vào list/setcácPlanets.

Tôi vẫn thích enum trên lớp như thế, như values()có ích và bạn không bao giờ biết nếu bạn cần đến chúng để sử dụng trong switchhoặc EnumSethoặc EnumMaptrong tương lai :)


0

Lý do chính: Enums giúp bạn viết mã có cấu trúc tốt trong đó ý nghĩa ngữ nghĩa của các tham số rõ ràng và được gõ mạnh vào thời gian biên dịch - vì tất cả các lý do khác đã đưa ra.

Quid pro quo: trong Java ngoài hộp, một loạt các thành viên của Enum là cuối cùng. Điều đó thường tốt vì nó giúp đánh giá sự an toàn và kiểm tra, nhưng trong một số trường hợp, đó có thể là một nhược điểm, ví dụ nếu bạn đang mở rộng mã cơ sở hiện tại có lẽ từ thư viện. Ngược lại, nếu cùng một dữ liệu trong một lớp có các trường tĩnh, bạn có thể dễ dàng thêm các phiên bản mới của lớp đó vào thời gian chạy (bạn cũng có thể cần phải viết mã để thêm chúng vào bất kỳ Iterable nào bạn có cho lớp đó). Nhưng hành vi này của Enums có thể được thay đổi: sử dụng sự phản chiếu, bạn có thể thêm thành viên mới trong thời gian chạy hoặc thay thế các thành viên hiện có, mặc dù điều này có lẽ chỉ nên được thực hiện trong các tình huống chuyên biệt khi không có giải pháp thay thế: đó là giải pháp hack và có thể gây ra sự cố không mong muốn, xem câu trả lời của tôi trênTôi có thể thêm và xóa các phần tử liệt kê khi chạy trong Java không .

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.