Tại sao chữ Java enum không thể có các tham số kiểu chung?


148

Java enum là tuyệt vời. Thuốc generic cũng vậy. Tất nhiên tất cả chúng ta đều biết những hạn chế sau này vì loại tẩy xóa. Nhưng có một điều tôi không hiểu, Tại sao tôi không thể tạo ra một enum như thế này:

public enum MyEnum<T> {
    LITERAL1<String>,
    LITERAL2<Integer>,
    LITERAL3<Object>;
}

Lần <T>lượt tham số loại chung này có thể hữu ích ở nhiều nơi. Tưởng tượng một tham số kiểu chung cho một phương thức:

public <T> T getValue(MyEnum<T> param);

Hoặc thậm chí trong lớp enum:

public T convert(Object o);

Ví dụ cụ thể hơn # 1

Vì ví dụ trên có vẻ quá trừu tượng đối với một số người, đây là một ví dụ thực tế hơn về lý do tại sao tôi muốn làm điều này. Trong ví dụ này tôi muốn sử dụng

  • Enums, bởi vì sau đó tôi có thể liệt kê một bộ khóa thuộc tính hữu hạn
  • Generics, bởi vì sau đó tôi có thể có loại an toàn ở mức phương thức để lưu trữ các thuộc tính
public interface MyProperties {
     public <T> void put(MyEnum<T> key, T value);
     public <T> T get(MyEnum<T> key);
}

Ví dụ cụ thể hơn # 2

Tôi có một bảng liệt kê các loại dữ liệu:

public interface DataType<T> {}

public enum SQLDataType<T> implements DataType<T> {
    TINYINT<Byte>,
    SMALLINT<Short>,
    INT<Integer>,
    BIGINT<Long>,
    CLOB<String>,
    VARCHAR<String>,
    ...
}

Mỗi enum nghĩa đen rõ ràng sẽ có các thuộc tính bổ sung dựa trên loại chung <T>, đồng thời, là một enum (bất biến, đơn lẻ, liệt kê, v.v.)

Câu hỏi:

Không ai nghĩ về điều này? Đây có phải là một hạn chế liên quan đến trình biên dịch? Xem xét thực tế, từ khóa " enum " được triển khai dưới dạng đường cú pháp, đại diện cho mã được tạo cho JVM, tôi không hiểu giới hạn này.

Ai có thể giải thích điều này cho tôi? Trước khi bạn trả lời, hãy xem xét điều này:

  • Tôi biết các loại chung chung bị xóa :-)
  • Tôi biết có cách giải quyết bằng cách sử dụng các đối tượng Class. Họ là cách giải quyết.
  • Các kiểu chung dẫn đến các kiểu kiểu trình biên dịch được tạo ở bất cứ nơi nào có thể áp dụng (ví dụ: khi gọi phương thức convert ()
  • Loại chung <T> sẽ có trên enum. Do đó, nó bị ràng buộc bởi mỗi chữ của enum. Do đó trình biên dịch sẽ biết, nên áp dụng kiểu nào khi viết một cái gì đó nhưString string = LITERAL1.convert(myObject); Integer integer = LITERAL2.convert(myObject);
  • Điều tương tự áp dụng cho tham số loại chung trong T getvalue()phương thức. Trình biên dịch có thể áp dụng kiểu truyền khi gọiString string = someClass.getValue(LITERAL1)

3
Tôi cũng không hiểu giới hạn này. Gần đây tôi đã tình cờ thấy enum của tôi chứa các loại "Có thể so sánh" khác nhau và với thuốc generic, chỉ có thể so sánh các loại cùng loại mà không có cảnh báo nào cần phải loại bỏ (mặc dù trong thời gian chạy, các loại thích hợp sẽ được so sánh). Tôi có thể loại bỏ những cảnh báo này bằng cách sử dụng một loại bị ràng buộc trong enum để chỉ định loại so sánh nào được hỗ trợ, nhưng thay vào đó tôi phải thêm chú thích SuppressWarnings - không có cách nào khác! Vì so sánh sẽ ném một lớp ngoại lệ dù sao đi nữa, tôi đoán là ổn, nhưng vẫn ...
MetroidFan2002

5
(+1) Tôi đang cố gắng thu hẹp khoảng cách an toàn trong dự án của mình, đã ngừng chết vì giới hạn tùy ý này. Chỉ cần xem xét điều này: biến enumthành thành ngữ "typeafe enum" mà chúng ta đã sử dụng trước Java 1.5. Đột nhiên, bạn có thể có các thành viên enum của bạn tham số hóa. Đó có lẽ là những gì tôi sẽ làm bây giờ.
Marko Topolnik

1
@EdwinDalorzo: Đã cập nhật câu hỏi với một ví dụ cụ thể từ jOOQ , nơi điều này sẽ vô cùng hữu ích trong quá khứ.
Lukas Eder

2
@LukasEder Tôi thấy quan điểm của bạn bây giờ. Trông giống như một tính năng mới mát mẻ. Có lẽ bạn nên đề xuất nó trong danh sách gửi thư dự án, tôi thấy các đề xuất thú vị khác trên enums, nhưng không phải là một đề xuất như của bạn.
Edwin Dalorzo 4/03/2015

1
Hoàn toàn đồng ý. Enum không có thuốc generic bị tê liệt. Trường hợp của bạn # 1 cũng là của tôi. Nếu tôi cần enum chung, tôi từ bỏ JDK5 và triển khai nó theo kiểu Java 1.4 cũ. Cách tiếp cận này cũng có thêm lợi ích : Tôi không bị buộc phải có tất cả các hằng số trong một lớp hoặc thậm chí là gói. Do đó, phong cách gói theo tính năng được thực hiện tốt hơn rất nhiều. Điều này là hoàn hảo chỉ dành cho "enums" giống như cấu hình - các hằng số được trải đều trong các gói theo ý nghĩa logic của chúng (và nếu tôi muốn xem tất cả, tôi có kiểu hiển thị hieararchy).
Tomáš Záluský

Câu trả lời:


50

Điều này hiện đang được thảo luận kể từ Enum tăng cường JEP-301 . Ví dụ được đưa ra trong JEP là, đó chính xác là những gì tôi đang tìm kiếm:

enum Argument<X> { // declares generic enum
   STRING<String>(String.class), 
   INTEGER<Integer>(Integer.class), ... ;

   Class<X> clazz;

   Argument(Class<X> clazz) { this.clazz = clazz; }

   Class<X> getClazz() { return clazz; }
}

Class<String> cs = Argument.STRING.getClazz(); //uses sharper typing of enum constant

Thật không may, JEP vẫn đang phải vật lộn với các vấn đề quan trọng: http://mail.openjdk.java.net/pipermail/amber-spec-experts/2017-May/000041.html


2
Kể từ tháng 12 năm 2018, đã có một số dấu hiệu của cuộc sống xung quanh JEP 301 , nhưng lướt qua cuộc thảo luận đó cho thấy rõ rằng các vấn đề vẫn còn lâu mới được giải quyết.
Pont

11

Câu trả lời là trong câu hỏi:

vì loại tẩy

Không có phương thức nào trong hai phương thức này là có thể, vì kiểu đối số bị xóa.

public <T> T getValue(MyEnum<T> param);
public T convert(Object);

Tuy nhiên, để nhận ra các phương thức đó, bạn có thể xây dựng enum của mình như sau:

public enum MyEnum {
    LITERAL1(String.class),
    LITERAL2(Integer.class),
    LITERAL3(Object.class);

    private Class<?> clazz;

    private MyEnum(Class<?> clazz) {
      this.clazz = clazz;
    }

    ...

}

2
Vâng, tẩy xóa diễn ra vào thời gian biên dịch. Nhưng trình biên dịch có thể sử dụng thông tin loại chung để kiểm tra kiểu. Và sau đó "chuyển đổi" loại chung thành loại phôi. Tôi sẽ viết lại câu hỏi
Lukas Eder

2
Không chắc chắn tôi hoàn toàn làm theo. Đi public T convert(Object);. Tôi đoán phương thức này có thể thu hẹp một loạt các loại khác nhau thành <T>, ví dụ như là một Chuỗi. Cấu trúc của đối tượng String là thời gian chạy - không có gì trình biên dịch làm. Do đó, bạn cần biết loại thời gian chạy, chẳng hạn như String. Class. Hay tôi đang thiếu một cái gì đó?
Martin Alesten

6
Tôi nghĩ rằng bạn đang thiếu thực tế là loại chung <T> nằm trong enum (hoặc lớp được tạo). Các trường hợp duy nhất của enum là nghĩa đen của nó, tất cả đều cung cấp một ràng buộc kiểu chung không đổi. Do đó không có sự mơ hồ về T trong chuyển đổi T công khai (Object);
Lukas Eder

2
Aha! Hiểu rồi. Bạn thông minh hơn tôi :)
Martin Algesten

1
Tôi đoán nó xuống lớp vì một enum được đánh giá theo cách tương tự như mọi thứ khác. Trong java, mặc dù mọi thứ dường như không đổi, nhưng chúng không. Xem xét public final static String FOO;với một khối tĩnh static { FOO = "bar"; }- cũng là hằng số được đánh giá ngay cả khi biết tại thời điểm biên dịch.
Martin Alesten

5

Bởi vì bạn không thể. Nghiêm túc. Điều đó có thể được thêm vào thông số ngôn ngữ. Nó đã không được. Nó sẽ thêm một số phức tạp. Lợi ích đó đối với chi phí có nghĩa là nó không phải là ưu tiên cao.

Cập nhật: Hiện đang được thêm vào ngôn ngữ trong JEP 301: Enums nâng cao .


Bạn có thể giải thích về các biến chứng?
Mr_and_Mrs_D

4
Tôi đã suy nghĩ về điều này trong một vài ngày nay, và tôi vẫn không thấy biến chứng.
Marko Topolnik

4

Có các phương pháp khác trong ENUM sẽ không hoạt động. Sẽ ra saoMyEnum.values() trở lại?

Thế còn MyEnum.valueOf(String name) ?

Đối với valueOf nếu bạn nghĩ rằng trình biên dịch có thể tạo phương thức chung như

công khai tĩnh MyEnum valueOf (Tên chuỗi);

để gọi nó như thế MyEnum<String> myStringEnum = MyEnum.value("some string property"), điều đó cũng không hoạt động. Ví dụ nếu bạn gọi MyEnum<Int> myIntEnum = MyEnum.<Int>value("some string property")? Không thể thực hiện phương thức đó để hoạt động chính xác, ví dụ như ném ngoại lệ hoặc trả về null khi bạn gọi nó như thế MyEnum.<Int>value("some double property")vì xóa kiểu.


2
Tại sao họ không làm việc? Họ chỉ đơn giản là sử dụng các ký tự đại diện ...: MyEnum<?>[] values()MyEnum<?> valueOf(...)
Lukas Eder

1
Nhưng sau đó bạn không thể thực hiện chuyển nhượng như thế này MyEnum<Int> blabla = valueOf("some double property");vì các loại không tương thích. Ngoài ra, bạn muốn trong trường hợp đó nhận được null vì bạn muốn trả về MyEnum <Int> không tồn tại cho tên thuộc tính kép và bạn không thể làm cho phương thức đó hoạt động chính xác vì bị xóa.
dùng1944408

Ngoài ra, nếu bạn lặp qua các giá trị (), bạn sẽ cần sử dụng MyEnum <?> Đây không phải là điều bạn muốn thường xuyên vì ví dụ bạn không thể lặp qua các thuộc tính Int của mình. Ngoài ra, bạn sẽ cần thực hiện nhiều lần truyền mà bạn muốn tránh, tôi sẽ đề nghị tạo ra các enum khác nhau cho mỗi loại hoặc tạo lớp riêng của bạn với các trường hợp ...
user1944408 11/215

Chà, tôi đoán bạn không thể có cả hai. Thông thường, tôi dùng đến việc thực hiện "enum" của riêng mình. Chỉ là nó Enumcó rất nhiều tính năng hữu ích khác, và tính năng này cũng là tùy chọn và cũng rất hữu ích ...
Lukas Eder

0

Thành thật mà nói đây có vẻ như là một giải pháp trong việc tìm kiếm một vấn đề hơn bất cứ điều gì.

Toàn bộ mục đích của java enum là mô hình liệt kê các thể hiện loại chia sẻ các thuộc tính tương tự theo cách cung cấp tính nhất quán và phong phú vượt ra ngoài các biểu diễn Chuỗi hoặc Số nguyên so sánh.

Lấy một ví dụ về một cuốn sách văn bản enum. Điều này không hữu ích hoặc nhất quán:

public enum Planet<T>{
    Earth<Planet>,
    Venus<String>,
    Mars<Long>
    ...etc.
}

Tại sao tôi muốn các hành tinh khác nhau của mình có các chuyển đổi loại chung khác nhau? vấn đề gì nó giải quyết? Liệu nó biện minh cho việc làm phức tạp ngữ nghĩa ngôn ngữ? Nếu tôi cần hành vi này là một enum công cụ tốt nhất để đạt được nó?

Ngoài ra, bạn sẽ quản lý các chuyển đổi phức tạp như thế nào?

ví dụ

public enum BadIdea<T>{
   INSTANCE1<Long>,
   INSTANCE2<MyComplexClass>;
}

Nó đủ dễ dàng String Integerđể cung cấp tên hoặc thứ tự. Nhưng thuốc generic sẽ cho phép bạn cung cấp bất kỳ loại nào. Làm thế nào bạn sẽ quản lý việc chuyển đổi sang MyComplexClass? Bây giờ, bạn tạo ra hai cấu trúc bằng cách buộc trình biên dịch biết rằng có một tập hợp con giới hạn của các loại có thể được cung cấp cho các enum chung và đưa ra sự nhầm lẫn bổ sung cho khái niệm (Generics) dường như đã lảng tránh rất nhiều lập trình viên.


17
Nghĩ về một vài ví dụ mà nó sẽ không hữu ích là một lập luận khủng khiếp rằng nó sẽ không bao giờ hữu ích.
Elias Vasylenko

1
Các ví dụ hỗ trợ quan điểm. Các thể hiện enum là các lớp con của loại (đẹp và đơn giản) và bao gồm cả thuốc generic là một loại giun mà nó có độ phức tạp cho một lợi ích rất mơ hồ. Nếu bạn định hạ bệ tôi thì bạn cũng nên hạ bệ Tom Hawtin bên dưới, người đã nói điều tương tự không quá nhiều từ
nsfyn55

1
@ nsfyn55 Enums là một trong những tính năng ngôn ngữ phức tạp nhất, kỳ diệu nhất của Java. Chẳng hạn, có một tính năng ngôn ngữ khác tự động tạo các phương thức tĩnh. Thêm vào đó, mỗi loại enum đã một thể hiện của một loại chung.
Marko Topolnik

1
@ nsfyn55 Mục tiêu thiết kế là làm cho các thành viên enum trở nên mạnh mẽ và linh hoạt, đó là lý do tại sao họ hỗ trợ các tính năng nâng cao như phương thức cá thể tùy chỉnh và thậm chí các biến thể hiện có thể thay đổi. Chúng được thiết kế để có hành vi chuyên biệt cho từng thành viên để họ có thể tham gia với tư cách cộng tác viên tích cực trong các tình huống sử dụng phức tạp. Trên hết, enum Java được thiết kế để làm cho kiểu thành ngữ liệt kê chung an toàn , nhưng việc thiếu tham số kiểu không may làm cho nó không đạt được mục tiêu ấp ủ nhất đó. Tôi khá tin rằng có một lý do rất cụ thể cho việc đó.
Marko Topolnik

1
Tôi không nhận thấy đề cập của bạn về chống chỉ định ... Java không hỗ trợ nó, chỉ có nó là trang sử dụng, điều này làm cho nó hơi khó sử dụng. interface Converter<IN, OUT> { OUT convert(IN in); } <E> Set<E> convertListToSet(List<E> in, Converter<? super List<E>, ? extends Set<E>> converter) { return converter.convert(in); }Chúng ta phải tự làm việc mỗi lần loại nào được tiêu thụ và loại nào được sản xuất và chỉ định giới hạn tương ứng.
Marko Topolnik

-2

Becasue "enum" là tên viết tắt của liệt kê. Nó chỉ là một tập hợp các hằng số được đặt tên thay cho các số thứ tự để làm cho mã dễ đọc hơn.

Tôi không thấy ý nghĩa dự định của hằng số tham số loại có thể là gì.


1
Trong java.lang.String: public static final Comparator<String> CASE_INSENSITIVE_ORDER. Bạn thấy bây giờ? :-)
Lukas Eder

4
Bạn nói rằng bạn không thấy ý nghĩa của hằng số tham số loại có thể là gì. Vì vậy, tôi đã cho bạn thấy một hằng số tham số kiểu (không phải là một con trỏ hàm).
Lukas Eder

Tất nhiên là không, vì ngôn ngữ java không biết một thứ như con trỏ hàm. Tuy nhiên, không phải là hằng số được tham số hóa, nhưng đó là loại. Và chính tham số kiểu là hằng số, không phải là biến kiểu.
Ingo

Nhưng cú pháp enum chỉ là cú pháp đường. Bên dưới, chúng giống hệt như CASE_INSENSITIVE_ORDER... Nếu Comparator<T>là một enum, tại sao không có nghĩa đen với bất kỳ ràng buộc nào liên quan <T>?
Lukas Eder

Nếu So sánh <T> là một enum, thì thực sự chúng ta có thể có đủ thứ kỳ lạ. Có lẽ một cái gì đó như Integer <T>. Nhưng nó không phải là.
Ingo

-3

Tôi nghĩ bởi vì về cơ bản, Enums không thể được kích hoạt

Bạn sẽ đặt lớp T ở đâu, nếu JVM cho phép bạn làm như vậy?

Bảng liệt kê là dữ liệu được cho là luôn giống nhau hoặc ít nhất là nó sẽ không thay đổi về mặt thực tế.

MyEnum mới <> ()?

Tuy nhiên, cách tiếp cận sau đây có thể hữu ích

public enum MyEnum{

    LITERAL1("s"),
    LITERAL2("a"),
    LITERAL3(2);

    private Object o;

    private MyEnum(Object o) {
        this.o = o;
    }

    public Object getO() {
        return o;
    }

    public void setO(Object o) {
        this.o = o;
    }   
}

8
Không chắc chắn tôi thích setO()phương pháp trên enum. Tôi xem xét các hằng số enums và với tôi ngụ ý bất biến . Vì vậy, ngay cả khi nó có thể, tôi sẽ không làm điều đó.
Martin Alesten

2
Enums được cung cấp trong mã do trình biên dịch tạo ra. Mỗi nghĩa đen thực sự được tạo ra bằng cách gọi các nhà xây dựng tư nhân. Do đó, sẽ có cơ hội chuyển loại chung cho hàm tạo vào thời gian biên dịch.
Lukas Eder

2
Setters trên enums là hoàn toàn bình thường. Đặc biệt là nếu bạn đang sử dụng enum để tạo một singleton (Mục 3 Java hiệu quả của Joshua Bloch)
Preston
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.