Enums có thể được phân lớp để thêm các yếu tố mới?


534

Tôi muốn lấy một enum hiện có và thêm nhiều yếu tố vào nó như sau:

enum A {a,b,c}

enum B extends A {d}

/*B is {a,b,c,d}*/

Điều này có thể có trong Java không?


12
Một lý do để làm điều này là để kiểm tra tình huống có giá trị enum không hợp lệ mà không đưa ra giá trị enum không hợp lệ trong nguồn lõi.
Archimedes Trajano

Có một ví dụ về độ tinh khiết "ngôn ngữ". Tôi nghĩ những gì mong muốn là cho ý tưởng tiết kiệm lao động "bookeep" của một bộ số nguyên tăng tự động như trong C ++ để bạn có thể bắt đầu một bộ mới như một phần mở rộng của bộ cũ bắt đầu từ 1+ giá trị cuối cùng của bộ trước và nếu các mục được đặt tên kế thừa tên từ "tập hợp con chung". Mặc dù java enum có một số điều hay về nó, nhưng nó thiếu phần khai báo số nguyên tự động tăng tự động đơn giản mà C ++ enum cung cấp.
peterk

4
Thực tế, khi bạn mở rộng enum của mình với các giá trị mới, bạn đang tạo không phải lớp con, mà là siêu lớp. Bạn có thể sử dụng các giá trị enum cơ sở ở mọi nơi thay vì enum "mở rộng", nhưng không phải ngược lại, vì vậy theo Nguyên tắc thay thế Liskov, enum mở rộng là siêu lớp của enum cơ sở.
Ilya

@Ilya ... vâng đúng vậy. Tôi chỉ ra rằng câu hỏi có các trường hợp sử dụng trong thế giới thực. Để tranh luận, hãy xem xét một Enum cơ sở của : PrimaryColours; thật hợp lý khi muốn siêu lớp này cho Enum PrimaryAndPastelColoursbằng cách thêm tên màu mới. Liskov vẫn là con voi trong phòng. Vậy tại sao không bắt đầu với một Enum cơ sở của: AllMyColours- Và sau đó người ta có thể phân lớp tất cả các màu thành: PrimaryAndPastelColoursvà sau đó phân lớp này thành: PrimaryColours(giữ nguyên thứ bậc). Java sẽ không cho phép điều đó mặc dù.
sẽ

Câu trả lời:


451

Không, bạn không thể làm điều này trong Java. Ngoài bất cứ điều gì khác,d có lẽ sẽ là một ví dụ của A(đưa ra ý tưởng bình thường là "mở rộng"), nhưng những người dùng chỉ biết về Asẽ không biết về nó - đánh bại quan điểm của một enum là một bộ nổi tiếng các giá trị.

Nếu bạn có thể cho chúng tôi biết thêm về cách bạn muốn sử dụng điều này, chúng tôi có thể đề xuất các giải pháp thay thế.


516
Tất cả các enum ngầm mở rộng java.lang.Enum. Vì Java không hỗ trợ nhiều kế thừa, một enum không thể mở rộng bất cứ thứ gì khác.
givanse

9
Lý do tôi muốn gia hạn là vì tôi muốn có một lớp cơ sở được gọi là IntEnum, trông giống như thế này: stackoverflow.com/questions/1681976/enum-with-int-value-in-java/ . Sau đó, tất cả các enum của tôi có thể mở rộng nó ... trong trường hợp này chỉ được hưởng lợi từ thừa kế và do đó tôi sẽ không phải sao chép mã "enum dựa trên int" này thường xuyên. Tôi mới biết về Java và đến từ C # và tôi hy vọng mình đang thiếu thứ gì đó. Ý kiến ​​hiện tại của tôi là các enum Java là một nỗi đau so với C #.
Tyler Collier

30
@Tyler: C # enums chỉ là tên được liên kết với các số, không có xác nhận tự động hoặc bất cứ điều gì . IMO enums là một bit của Java thực sự tốt hơn C #.
Jon Skeet

21
Không đồng ý với @JonSkeet ở đây. Trong trường hợp sử dụng của tôi, tôi muốn tách tất cả logic khó chịu trong enum lớn của mình và để logic bị ẩn đi, và xác định một enum sạch sẽ mở rộng một logic khác được ẩn đi. Các enum có rất nhiều logic đánh bại ý tưởng có các biến sạch được khai báo để bạn không phải khai báo hàng trăm biến chuỗi tĩnh để một lớp có 5 enum không thể đọc được và quá lớn trong các dòng. Tôi không muốn các nhà phát triển khác quan tâm đến việc sao chép và dán mã an toàn đó cho dự án tiếp theo và thay vào đó mở rộng base_enum ... điều đó có ý nghĩa với tôi ...
mmm

43
@givanse ... không đồng ý với bạn về quan điểm mở rộng ngầm của java.lang.Enum là nguyên nhân của việc không thừa kế vì mọi lớp trong java cũng mặc nhiên thừa kế lớp Object nhưng nó có thể kế thừa một số lớp khác khi nó sẽ đến vào hệ thống phân cấp Object->A->Bthay vìObject->A->B extends Object
mickeymoon

317

Enums đại diện cho một liệt kê đầy đủ các giá trị có thể. Vì vậy, câu trả lời (không có ích) là không.

Như một ví dụ về một vấn đề thực sự mất các ngày trong tuần, ngày cuối tuần và, công đoàn, các ngày trong tuần. Chúng tôi có thể xác định tất cả các ngày trong các ngày trong tuần nhưng sau đó chúng tôi sẽ không thể đại diện cho các thuộc tính đặc biệt cho cả ngày trong tuần và ngày cuối tuần.

Những gì chúng ta có thể làm, là có ba loại enum với ánh xạ giữa các ngày trong tuần / ngày cuối tuần và ngày trong tuần.

public enum Weekday {
    MON, TUE, WED, THU, FRI;
    public DayOfWeek toDayOfWeek() { ... }
}
public enum WeekendDay {
    SAT, SUN;
    public DayOfWeek toDayOfWeek() { ... }
}
public enum DayOfWeek {
    MON, TUE, WED, THU, FRI, SAT, SUN;
}

Ngoài ra, chúng tôi có thể có giao diện kết thúc mở cho ngày trong tuần:

interface Day {
    ...
}
public enum Weekday implements Day {
    MON, TUE, WED, THU, FRI;
}
public enum WeekendDay implements Day {
    SAT, SUN;
}

Hoặc chúng ta có thể kết hợp hai cách tiếp cận:

interface Day {
    ...
}
public enum Weekday implements Day {
    MON, TUE, WED, THU, FRI;
    public DayOfWeek toDayOfWeek() { ... }
}
public enum WeekendDay implements Day {
    SAT, SUN;
    public DayOfWeek toDayOfWeek() { ... }
}
public enum DayOfWeek {
    MON, TUE, WED, THU, FRI, SAT, SUN;
    public Day toDay() { ... }
}

20
Không có vấn đề gì với điều này sao? Một câu lệnh chuyển đổi sẽ không hoạt động trên một giao diện, nhưng nó hoạt động trên một enum thông thường. Không hoạt động với loại chuyển đổi sẽ giết chết một trong những điều đẹp hơn về enums.
Manius

9
Tôi nghĩ rằng có thể có một vấn đề khác với điều này. Không có sự bình đẳng giữa Weekday.MON và DayOfWeek.MON. Đó không phải là lợi ích lớn khác của enums sao? Tôi không có giải pháp nào tốt hơn, chỉ nhận ra điều này khi tôi đang cố gắng tìm câu trả lời tốt nhất. Việc thiếu khả năng sử dụng == buộc tay một chút.
Snekse

2
@Crusader vâng, đó chính xác là sự đánh đổi. Nếu bạn muốn một cái gì đó có thể mở rộng, bạn không thể có các câu lệnh chuyển đổi cố định, nếu bạn muốn một tập hợp các giá trị đã biết cố định mà về mặt phương pháp, bạn không thể có một cái gì đó có thể mở rộng.
djechlin

3
Đi từ enum đến giao diện, bạn cũng mất cuộc gọi tĩnh đến các giá trị (). Điều này làm cho việc tái cấu trúc trở nên khó khăn, đặc biệt nếu bạn quyết định mở rộng enum của mình và thêm giao diện như một rào cản trừu tượng cho một enum đã thiết lập.
Joshua Goldberg

4
Cách tiếp cận này để lấy enum từ một giao diện được sử dụng bởi API Java 1.7, ví dụ java.nio.file.Files.write () lấy một mảng của OpenOption làm đối số cuối cùng. OpenOption là một giao diện, nhưng khi chúng ta gọi hàm này, chúng ta thường truyền hằng số enum StandardOpenOption, có nguồn gốc từ OpenOption. Điều này có lợi thế là có thể mở rộng, nhưng nó cũng có nhược điểm. Việc triển khai bị ảnh hưởng bởi thực tế OpenOption là một giao diện. Nó tạo ra một Hashset <OpenOption> từ mảng đã qua, khi nó có thể tạo ra một Enumset hiệu quả hơn về không gian và thời gian. Và nó không thể sử dụng chuyển đổi.
Klitos Kyriacou

71

Giải pháp được đề xuất cho vấn đề này là mô hình enum mở rộng .

Điều này liên quan đến việc tạo giao diện và sử dụng giao diện nơi bạn hiện đang sử dụng enum. Sau đó làm cho enum thực hiện giao diện. Bạn có thể thêm nhiều hằng số bằng cách làm cho enum mới cũng mở rộng giao diện.


Thật đáng để gọi họ sử dụng một phương thức xuất xưởng trong giao diện. Cách tuyệt vời để chia sẻ chức năng chung giữa các Enums liên quan được đưa ra rằng việc mở rộng không phải là một giải pháp khả thi.
Tim Clemons

8
Bạn có thể cung cấp thêm chi tiết (mã :)) về mẫu này?
Dherik

3
Mẫu đó không cho phép mở rộng các giá trị của enum. Đó là điểm trong câu hỏi.
Eria

55

Dưới vỏ bọc, ENUM của bạn chỉ là một lớp thông thường được tạo bởi trình biên dịch. Lớp tạo ra đó mở rộng java.lang.Enum. Lý do kỹ thuật bạn không thể mở rộng lớp được tạo là lớp được tạo final. Các lý do khái niệm cho nó là cuối cùng được thảo luận trong chủ đề này. Nhưng tôi sẽ thêm các cơ chế vào cuộc thảo luận.

Dưới đây là một thử nghiệm enum:

public enum TEST {  
    ONE, TWO, THREE;
}

Mã kết quả từ javap:

public final class TEST extends java.lang.Enum<TEST> {
  public static final TEST ONE;
  public static final TEST TWO;
  public static final TEST THREE;
  static {};
  public static TEST[] values();
  public static TEST valueOf(java.lang.String);
}

Có thể hiểu rằng bạn có thể tự nhập lớp này và bỏ "cuối cùng". Nhưng trình biên dịch ngăn bạn mở rộng "java.lang.Enum" trực tiếp. Bạn có thể quyết định KHÔNG mở rộng java.lang.Enum, nhưng sau đó lớp của bạn và các lớp dẫn xuất của nó sẽ không phải là một thể hiện của java.lang.Enum ... có thể không thực sự quan trọng với bạn theo bất kỳ cách nào!


1
Khối tĩnh trống đang làm gì? 'tĩnh {};'
soote

1
Nó không có mã trong đó. Chương trình "javap" hiển thị khối trống.
ChrisCantrell 22/03/2016

Thật kỳ lạ khi có nó ở đó nếu nó không làm gì cả phải không?
soote 22/03/2016

4
Bạn đúng rồi! Lỗi của tôi. Nó KHÔNG phải là một khối mã rỗng. Nếu bạn chạy "javap -c", bạn sẽ thấy mã thực tế bên trong khối tĩnh. Khối tĩnh tạo tất cả các phiên bản ENUM (MỘT, HAI và BA ở đây). Xin lỗi vì điều đó.
ChrisCantrell 22/03/2016

1
Cảm ơn đã nêu rõ sự thật: vì java.lang.Enum được tuyên bố là cuối cùng.
Benjamin

26
enum A {a,b,c}
enum B extends A {d}
/*B is {a,b,c,d}*/

có thể được viết như:

public enum All {
    a       (ClassGroup.A,ClassGroup.B),
    b       (ClassGroup.A,ClassGroup.B),
    c       (ClassGroup.A,ClassGroup.B),
    d       (ClassGroup.B) 
...
  • Classgroup.B.getMembers () chứa {a, b, c, d}

Làm thế nào nó có thể hữu ích: Giả sử chúng ta muốn một cái gì đó như: Chúng ta có các sự kiện và chúng ta đang sử dụng enums. Những enum có thể được nhóm lại bằng cách xử lý tương tự. Nếu chúng ta có hoạt động với nhiều phần tử, thì một số sự kiện bắt đầu hoạt động, một số chỉ là bước và phần khác kết thúc hoạt động. Để thu thập hoạt động như vậy và tránh trường hợp chuyển đổi dài, chúng ta có thể nhóm chúng như ví dụ và sử dụng:

if(myEvent.is(State_StatusGroup.START)) makeNewOperationObject()..
if(myEnum.is(State_StatusGroup.STEP)) makeSomeSeriousChanges()..
if(myEnum.is(State_StatusGroup.FINISH)) closeTransactionOrSomething()..

Thí dụ:

public enum AtmOperationStatus {
STARTED_BY_SERVER       (State_StatusGroup.START),
SUCCESS             (State_StatusGroup.FINISH),
FAIL_TOKEN_TIMEOUT      (State_StatusGroup.FAIL, 
                    State_StatusGroup.FINISH),
FAIL_NOT_COMPLETE       (State_StatusGroup.FAIL,
                    State_StatusGroup.STEP),
FAIL_UNKNOWN            (State_StatusGroup.FAIL,
                    State_StatusGroup.FINISH),
(...)

private AtmOperationStatus(StatusGroupInterface ... pList){
    for (StatusGroupInterface group : pList){
        group.addMember(this);
    }
}
public boolean is(StatusGroupInterface with){
    for (AtmOperationStatus eT : with.getMembers()){
        if( eT .equals(this))   return true;
    }
    return false;
}
// Each group must implement this interface
private interface StatusGroupInterface{
    EnumSet<AtmOperationStatus> getMembers();
    void addMember(AtmOperationStatus pE);
}
// DEFINING GROUPS
public enum State_StatusGroup implements StatusGroupInterface{
    START, STEP, FAIL, FINISH;

    private List<AtmOperationStatus> members = new LinkedList<AtmOperationStatus>();

    @Override
    public EnumSet<AtmOperationStatus> getMembers() {
        return EnumSet.copyOf(members);
    }

    @Override
    public void addMember(AtmOperationStatus pE) {
        members.add(pE);
    }
    static { // forcing initiation of dependent enum
        try {
            Class.forName(AtmOperationStatus.class.getName()); 
        } catch (ClassNotFoundException ex) { 
            throw new RuntimeException("Class AtmEventType not found", ex); 
        }
    }
}
}
//Some use of upper code:
if (p.getStatus().is(AtmOperationStatus.State_StatusGroup.FINISH)) {
    //do something
}else if (p.getStatus().is(AtmOperationStatus.State_StatusGroup.START)) {
    //do something      
}  

Thêm một số nâng cao:

public enum AtmEventType {

USER_DEPOSIT        (Status_EventsGroup.WITH_STATUS,
              Authorization_EventsGroup.USER_AUTHORIZED,
              ChangedMoneyAccountState_EventsGroup.CHANGED,
              OperationType_EventsGroup.DEPOSIT,
              ApplyTo_EventsGroup.CHANNEL),
SERVICE_DEPOSIT     (Status_EventsGroup.WITH_STATUS,
              Authorization_EventsGroup.TERMINAL_AUTHORIZATION,
              ChangedMoneyAccountState_EventsGroup.CHANGED,
              OperationType_EventsGroup.DEPOSIT,
              ApplyTo_EventsGroup.CHANNEL),
DEVICE_MALFUNCTION  (Status_EventsGroup.WITHOUT_STATUS,
              Authorization_EventsGroup.TERMINAL_AUTHORIZATION,
              ChangedMoneyAccountState_EventsGroup.DID_NOT_CHANGED,
              ApplyTo_EventsGroup.DEVICE),
CONFIGURATION_4_C_CHANGED(Status_EventsGroup.WITHOUT_STATUS,
              ApplyTo_EventsGroup.TERMINAL,
              ChangedMoneyAccountState_EventsGroup.DID_NOT_CHANGED),
(...)

Ở trên nếu chúng tôi gặp một số lỗi (myEvent.is (State_Statusgroup.FAIL)) thì lặp đi lặp lại bởi các sự kiện trước đó, chúng tôi có thể dễ dàng kiểm tra xem chúng tôi có phải hoàn nguyên chuyển tiền bằng cách:

if(myEvent2.is(ChangedMoneyAccountState_EventsGroup.CHANGED)) rollBack()..

Nó có thể hữu ích cho:

  1. bao gồm siêu dữ liệu khám phá về xử lý logic, ít phải nhớ
  2. thực hiện một số đa kế thừa
  3. chúng tôi không muốn sử dụng các cấu trúc lớp, ví dụ. để gửi tin nhắn trạng thái ngắn

13

Đây là một cách tôi tìm thấy làm thế nào để mở rộng một enum vào enum khác, là một cách tiếp cận rất căng thẳng:

Suposse bạn có một enum với hằng số chung:

public interface ICommonInterface {

    String getName();

}


public enum CommonEnum implements ICommonInterface {
    P_EDITABLE("editable"),
    P_ACTIVE("active"),
    P_ID("id");

    private final String name;

    EnumCriteriaComun(String name) {
        name= name;
    }

    @Override
    public String getName() {
        return this.name;
    }
}

sau đó bạn có thể thử làm một hướng dẫn kéo dài theo cách này:

public enum SubEnum implements ICommonInterface {
    P_EDITABLE(CommonEnum.P_EDITABLE ),
    P_ACTIVE(CommonEnum.P_ACTIVE),
    P_ID(CommonEnum.P_ID),
    P_NEW_CONSTANT("new_constant");

    private final String name;

    EnumCriteriaComun(CommonEnum commonEnum) {
        name= commonEnum.name;
    }

    EnumCriteriaComun(String name) {
        name= name;
    }

    @Override
    public String getName() {
        return this.name;
    }
}

tất nhiên mỗi khi bạn cần gia hạn một hằng số, bạn phải sửa đổi các tệp SubEnum của mình.


thật thú vị, chúng ta cũng có thể sử dụng chính enum toString () và ở cuối chuỗi so sánh; và để sử dụng chuyển đổi, chúng ta sẽ phải bỏ đối tượng vào một enum đã biết; vấn đề duy nhất là 2 nhà phát triển mở rộng và tạo một id enum giống hệt nhau và sau đó cố gắng hợp nhất cả hai mã :), bây giờ tôi nghĩ rằng tôi hiểu tại sao enum nên không thể mở rộng.
Sức mạnh Bảo Bình

11

Trong trường hợp bạn bỏ lỡ nó, có một chương trong cuốn sách xuất sắc của Joshua Bloch " Java hiệu quả, phiên bản 2 ".

  • Chương 6 - Enums và chú thích
    • Mục 34: Thi đua enum mở rộng với giao diện

Trích xuất tại đây .

Chỉ cần kết luận:

Một nhược điểm nhỏ của việc sử dụng các giao diện để mô phỏng các enum có thể mở rộng là việc triển khai không thể được kế thừa từ loại enum này sang loại khác. Trong trường hợp ví dụ Hoạt động của chúng tôi, logic lưu trữ và truy xuất ký hiệu được liên kết với một thao tác được sao chép trong BasicOperation và ExtendedOperation. Trong trường hợp này không thành vấn đề vì rất ít mã được sao chép. Nếu có số lượng chức năng chia sẻ lớn hơn, bạn có thể gói nó trong lớp trình trợ giúp hoặc phương thức trợ giúp tĩnh để loại bỏ sao chép mã.

Tóm lại, trong khi bạn không thể viết một loại enum có thể mở rộng, bạn có thể mô phỏng nó bằng cách viết một giao diện đi với một loại enum cơ bản thực hiện giao diện. Điều này cho phép khách hàng tự viết enum thực hiện giao diện. Những enum này sau đó có thể được sử dụng ở bất cứ nơi nào có thể sử dụng loại enum cơ bản, giả sử API được viết theo giao diện.


6

Tôi có xu hướng tránh enums, bởi vì chúng không thể mở rộng. Để giữ nguyên ví dụ về OP, nếu A ở trong thư viện và B trong mã của riêng bạn, bạn không thể gia hạn A nếu đó là enum. Đây là cách tôi đôi khi thay thế enums:

// access like enum: A.a
public class A {
    public static final A a = new A();
    public static final A b = new A();
    public static final A c = new A();
/*
 * In case you need to identify your constant
 * in different JVMs, you need an id. This is the case if
 * your object is transfered between
 * different JVM instances (eg. save/load, or network).
 * Also, switch statements don't work with
 * Objects, but work with int.
 */
    public static int maxId=0;
    public int id = maxId++;
    public int getId() { return id; }
}

public class B extends A {
/*
 * good: you can do like
 * A x = getYourEnumFromSomeWhere();
 * if(x instanceof B) ...;
 * to identify which enum x
 * is of.
 */
    public static final A d = new A();
}

public class C extends A {
/* Good: e.getId() != d.getId()
 * Bad: in different JVMs, C and B
 * might be initialized in different order,
 * resulting in different IDs.
 * Workaround: use a fixed int, or hash code.
 */
    public static final A e = new A();
    public int getId() { return -32489132; };
}

Có một số hố cần tránh, xem các bình luận trong mã. Tùy thuộc vào nhu cầu của bạn, đây là một sự thay thế vững chắc, có thể mở rộng cho enums.


1
nó có thể ổn nếu bạn chỉ cần một số thứ tự cho các trường hợp. Nhưng enums cũng có một tài sản tên là khá hữu ích.
vào

6

Đây là cách tôi tăng cường mẫu kế thừa enum với kiểm tra thời gian chạy trong trình khởi tạo tĩnh. Các BaseKind#checkEnumExtenderkiểm tra "mở rộng" enum khai báo tất cả các giá trị của enum cơ sở theo cùng một cách chính xác #name()#ordinal() vẫn hoàn toàn tương thích.

Vẫn còn sao chép liên quan đến việc khai báo các giá trị nhưng chương trình không thành công nhanh nếu ai đó thêm hoặc sửa đổi một giá trị trong lớp cơ sở mà không cập nhật mở rộng giá trị.

Hành vi phổ biến cho các enum khác nhau kéo dài lẫn nhau:

public interface Kind {
  /**
   * Let's say we want some additional member.
   */
  String description() ;

  /**
   * Standard {@code Enum} method.
   */
  String name() ;

  /**
   * Standard {@code Enum} method.
   */
  int ordinal() ;
}

Cơ sở enum, với phương thức xác minh:

public enum BaseKind implements Kind {

  FIRST( "First" ),
  SECOND( "Second" ),

  ;

  private final String description ;

  public String description() {
    return description ;
  }

  private BaseKind( final String description ) {
    this.description = description ;
  }

  public static void checkEnumExtender(
      final Kind[] baseValues,
      final Kind[] extendingValues
  ) {
    if( extendingValues.length < baseValues.length ) {
      throw new IncorrectExtensionError( "Only " + extendingValues.length + " values against "
          + baseValues.length + " base values" ) ;
    }
    for( int i = 0 ; i < baseValues.length ; i ++ ) {
      final Kind baseValue = baseValues[ i ] ;
      final Kind extendingValue = extendingValues[ i ] ;
      if( baseValue.ordinal() != extendingValue.ordinal() ) {
        throw new IncorrectExtensionError( "Base ordinal " + baseValue.ordinal()
            + " doesn't match with " + extendingValue.ordinal() ) ;
      }
      if( ! baseValue.name().equals( extendingValue.name() ) ) {
        throw new IncorrectExtensionError( "Base name[ " + i + "] " + baseValue.name()
            + " doesn't match with " + extendingValue.name() ) ;
      }
      if( ! baseValue.description().equals( extendingValue.description() ) ) {
        throw new IncorrectExtensionError( "Description[ " + i + "] " + baseValue.description()
            + " doesn't match with " + extendingValue.description() ) ;
      }
    }
  }


  public static class IncorrectExtensionError extends Error {
    public IncorrectExtensionError( final String s ) {
      super( s ) ;
    }
  }

}

Mẫu mở rộng:

public enum ExtendingKind implements Kind {
  FIRST( BaseKind.FIRST ),
  SECOND( BaseKind.SECOND ),
  THIRD( "Third" ),
  ;

  private final String description ;

  public String description() {
    return description ;
  }

  ExtendingKind( final BaseKind baseKind ) {
    this.description = baseKind.description() ;
  }

  ExtendingKind( final String description ) {
    this.description = description ;
  }

}

4

Dựa trên @Tom Hawtin - câu trả lời tackline, chúng tôi thêm hỗ trợ chuyển đổi,

interface Day<T> {
    ...
  T valueOf();
}

public enum Weekday implements Day<Weekday> {
    MON, TUE, WED, THU, FRI;
   Weekday valueOf(){
     return valueOf(name());
   }
}

public enum WeekendDay implements Day<WeekendDay> {
    SAT, SUN;
   WeekendDay valueOf(){
     return valueOf(name());
   }
}

Day<Weekday> wds = Weekday.MON;
Day<WeekendDay> wends = WeekendDay.SUN;

switch(wds.valueOf()){
    case MON:
    case TUE:
    case WED:
    case THU:
    case FRI:
}

switch(wends.valueOf()){
    case SAT:
    case SUN:
}

Việc sử dụng valueOf()phương pháp là gì?
Axel Advento

@AxelAdvento Ý tưởng ở đây là chúng tôi phụ thuộc vào giao diện Daycó phương thức valueOf()sau đó switch(Day.valueOf()), nó được thực hiện bởi WeekDay, WeekEndDayenums.
Khaled Lela

3

Tôi đề nghị bạn thực hiện theo cách khác xung quanh cách tiếp cận.

Thay vì mở rộng bảng liệt kê hiện có, hãy tạo một bảng liệt kê lớn hơn và tạo một tập hợp con của nó. Ví dụ nếu bạn có một bảng liệt kê được gọi là PET và bạn muốn mở rộng nó thành ANIMAL, bạn nên làm điều này thay vào đó:

public enum ANIMAL {
    WOLF,CAT, DOG
} 
EnumSet<ANIMAL> pets = EnumSet.of(ANIMAL.CAT, ANIMAL.DOG);

Hãy cẩn thận, thú cưng không phải là một bộ sưu tập bất biến, bạn có thể muốn sử dụng Guava hoặc Java9 để an toàn hơn.


2

Có vấn đề tương tự bản thân tôi muốn đăng quan điểm của tôi. Tôi nghĩ rằng có một vài yếu tố thúc đẩy để làm một cái gì đó như thế này:

  • Bạn muốn có một số mã enum liên quan, nhưng trong các lớp khác nhau. Trong trường hợp của tôi, tôi đã có một lớp cơ sở với một số mã được định nghĩa trong một enum liên quan. Vào một ngày nào đó sau (hôm nay!) Tôi muốn cung cấp một số chức năng mới cho lớp cơ sở, cũng có nghĩa là các mã mới cho enum.
  • Lớp dẫn xuất sẽ hỗ trợ cả enum của lớp cơ sở cũng như lớp của nó. Không có giá trị enum trùng lặp! Vậy: làm thế nào để có một enum cho lớp con bao gồm enum của cha mẹ của nó cùng với các giá trị mới của nó.

Sử dụng một giao diện không thực sự cắt nó: bạn có thể vô tình nhận được các giá trị enum trùng lặp. Không mong muốn.

Cuối cùng tôi chỉ kết hợp các enum: điều này đảm bảo rằng không thể có bất kỳ giá trị trùng lặp nào, với chi phí ít bị ràng buộc hơn với lớp liên kết của nó. Nhưng, tôi nghĩ rằng vấn đề trùng lặp là mối quan tâm chính của tôi ...


2

Như một sự trợ giúp để hiểu tại sao việc mở rộng Enum không hợp lý ở cấp độ thực thi ngôn ngữ để xem xét điều gì sẽ xảy ra nếu bạn chuyển một thể hiện của Enum mở rộng sang một thói quen chỉ hiểu Enum cơ sở. Một công tắc mà trình biên dịch đã hứa có tất cả các trường hợp được bảo hiểm trên thực tế sẽ không bao gồm các giá trị Enum mở rộng đó.

Điều này nhấn mạnh thêm rằng các giá trị Java Enum không phải là số nguyên như C, chẳng hạn: để sử dụng Java Enum làm chỉ mục mảng, bạn phải yêu cầu rõ ràng thành viên thứ tự () của nó, để cung cấp cho java Enum một giá trị số nguyên tùy ý mà bạn phải thêm một trường rõ ràng cho điều đó và tham chiếu có tên thành viên.

Đây không phải là một nhận xét về mong muốn của OP, chỉ là tại sao Java sẽ không bao giờ thực hiện nó.


1

Với hy vọng giải pháp tao nhã này của một đồng nghiệp của tôi thậm chí còn được thấy trong bài viết dài này, tôi muốn chia sẻ cách tiếp cận này để phân lớp theo cách tiếp cận giao diện và hơn thế nữa.

Xin lưu ý rằng chúng tôi sử dụng các ngoại lệ tùy chỉnh ở đây và mã này sẽ không được biên dịch trừ khi bạn thay thế nó bằng các ngoại lệ của bạn.

Các tài liệu rất phong phú và tôi hy vọng nó có thể hiểu được cho hầu hết các bạn.

Giao diện mà mọi enum phân lớp cần thực hiện.

public interface Parameter {
  /**
   * Retrieve the parameters name.
   *
   * @return the name of the parameter
   */
  String getName();

  /**
   * Retrieve the parameters type.
   *
   * @return the {@link Class} according to the type of the parameter
   */
  Class<?> getType();

  /**
   * Matches the given string with this parameters value pattern (if applicable). This helps to find
   * out if the given string is a syntactically valid candidate for this parameters value.
   *
   * @param valueStr <i>optional</i> - the string to check for
   * @return <code>true</code> in case this parameter has no pattern defined or the given string
   *         matches the defined one, <code>false</code> in case <code>valueStr</code> is
   *         <code>null</code> or an existing pattern is not matched
   */
  boolean match(final String valueStr);

  /**
   * This method works as {@link #match(String)} but throws an exception if not matched.
   *
   * @param valueStr <i>optional</i> - the string to check for
   * @throws ArgumentException with code
   *           <dl>
   *           <dt>PARAM_MISSED</dt>
   *           <dd>if <code>valueStr</code> is <code>null</code></dd>
   *           <dt>PARAM_BAD</dt>
   *           <dd>if pattern is not matched</dd>
   *           </dl>
   */
  void matchEx(final String valueStr) throws ArgumentException;

  /**
   * Parses a value for this parameter from the given string. This method honors the parameters data
   * type and potentially other criteria defining a valid value (e.g. a pattern).
   *
   * @param valueStr <i>optional</i> - the string to parse the parameter value from
   * @return the parameter value according to the parameters type (see {@link #getType()}) or
   *         <code>null</code> in case <code>valueStr</code> was <code>null</code>.
   * @throws ArgumentException in case <code>valueStr</code> is not parsable as a value for this
   *           parameter.
   */
  Object parse(final String valueStr) throws ArgumentException;

  /**
   * Converts the given value to its external form as it is accepted by {@link #parse(String)}. For
   * most (ordinary) parameters this is simply a call to {@link String#valueOf(Object)}. In case the
   * parameter types {@link Object#toString()} method does not return the external form (e.g. for
   * enumerations), this method has to be implemented accordingly.
   *
   * @param value <i>mandatory</i> - the parameters value
   * @return the external form of the parameters value, never <code>null</code>
   * @throws InternalServiceException in case the given <code>value</code> does not match
   *           {@link #getType()}
   */
  String toString(final Object value) throws InternalServiceException;
}

Lớp cơ sở ENUM thực hiện.

public enum Parameters implements Parameter {
  /**
   * ANY ENUM VALUE
   */
  VALUE(new ParameterImpl<String>("VALUE", String.class, "[A-Za-z]{3,10}"));

  /**
   * The parameter wrapped by this enum constant.
   */
  private Parameter param;

  /**
   * Constructor.
   *
   * @param param <i>mandatory</i> - the value for {@link #param}
   */
  private Parameters(final Parameter param) {
    this.param = param;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String getName() {
    return this.param.getName();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Class<?> getType() {
    return this.param.getType();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean match(final String valueStr) {
    return this.param.match(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void matchEx(final String valueStr) {
    this.param.matchEx(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Object parse(final String valueStr) throws ArgumentException {
    return this.param.parse(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toString(final Object value) throws InternalServiceException {
    return this.param.toString(value);
  }
}

ENUM được phân lớp mà "kế thừa" từ lớp cơ sở.

public enum ExtendedParameters implements Parameter {
  /**
   * ANY ENUM VALUE
   */
  VALUE(my.package.name.VALUE);

  /**
   * EXTENDED ENUM VALUE
   */
  EXTENDED_VALUE(new ParameterImpl<String>("EXTENDED_VALUE", String.class, "[0-9A-Za-z_.-]{1,20}"));

  /**
   * The parameter wrapped by this enum constant.
   */
  private Parameter param;

  /**
   * Constructor.
   *
   * @param param <i>mandatory</i> - the value for {@link #param}
   */
  private Parameters(final Parameter param) {
    this.param = param;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String getName() {
    return this.param.getName();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Class<?> getType() {
    return this.param.getType();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean match(final String valueStr) {
    return this.param.match(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void matchEx(final String valueStr) {
    this.param.matchEx(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Object parse(final String valueStr) throws ArgumentException {
    return this.param.parse(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toString(final Object value) throws InternalServiceException {
    return this.param.toString(value);
  }
}

Cuối cùng là ParameterImpl chung để thêm một số tiện ích.

public class ParameterImpl<T> implements Parameter {
  /**
   * The default pattern for numeric (integer, long) parameters.
   */
  private static final Pattern NUMBER_PATTERN = Pattern.compile("[0-9]+");

  /**
   * The default pattern for parameters of type boolean.
   */
  private static final Pattern BOOLEAN_PATTERN = Pattern.compile("0|1|true|false");

  /**
   * The name of the parameter, never <code>null</code>.
   */
  private final String name;

  /**
   * The data type of the parameter.
   */
  private final Class<T> type;

  /**
   * The validation pattern for the parameters values. This may be <code>null</code>.
   */
  private final Pattern validator;

  /**
   * Shortcut constructor without <code>validatorPattern</code>.
   *
   * @param name <i>mandatory</i> - the value for {@link #name}
   * @param type <i>mandatory</i> - the value for {@link #type}
   */
  public ParameterImpl(final String name, final Class<T> type) {
    this(name, type, null);
  }

  /**
   * Constructor.
   *
   * @param name <i>mandatory</i> - the value for {@link #name}
   * @param type <i>mandatory</i> - the value for {@link #type}
   * @param validatorPattern - <i>optional</i> - the pattern for {@link #validator}
   *          <dl>
   *          <dt style="margin-top:0.25cm;"><i>Note:</i>
   *          <dd>The default validation patterns {@link #NUMBER_PATTERN} or
   *          {@link #BOOLEAN_PATTERN} are applied accordingly.
   *          </dl>
   */
  public ParameterImpl(final String name, final Class<T> type, final String validatorPattern) {
    this.name = name;
    this.type = type;
    if (null != validatorPattern) {
      this.validator = Pattern.compile(validatorPattern);

    } else if (Integer.class == this.type || Long.class == this.type) {
      this.validator = NUMBER_PATTERN;
    } else if (Boolean.class == this.type) {
      this.validator = BOOLEAN_PATTERN;
    } else {
      this.validator = null;
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean match(final String valueStr) {
    if (null == valueStr) {
      return false;
    }
    if (null != this.validator) {
      final Matcher matcher = this.validator.matcher(valueStr);
      return matcher.matches();
    }
    return true;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void matchEx(final String valueStr) throws ArgumentException {
    if (false == this.match(valueStr)) {
      if (null == valueStr) {
        throw ArgumentException.createEx(ErrorCode.PARAM_MISSED, "The value must not be null",
            this.name);
      }
      throw ArgumentException.createEx(ErrorCode.PARAM_BAD, "The value must match the pattern: "
          + this.validator.pattern(), this.name);
    }
  }

  /**
   * Parse the parameters value from the given string value according to {@link #type}. Additional
   * the value is checked by {@link #matchEx(String)}.
   *
   * @param valueStr <i>optional</i> - the string value to parse the value from
   * @return the parsed value, may be <code>null</code>
   * @throws ArgumentException in case the parameter:
   *           <ul>
   *           <li>does not {@link #matchEx(String)} the {@link #validator}</li>
   *           <li>cannot be parsed according to {@link #type}</li>
   *           </ul>
   * @throws InternalServiceException in case the type {@link #type} cannot be handled. This is a
   *           programming error.
   */
  @Override
  public T parse(final String valueStr) throws ArgumentException, InternalServiceException {
    if (null == valueStr) {
      return null;
    }
    this.matchEx(valueStr);

    if (String.class == this.type) {
      return this.type.cast(valueStr);
    }
    if (Boolean.class == this.type) {
      return this.type.cast(Boolean.valueOf(("1".equals(valueStr)) || Boolean.valueOf(valueStr)));
    }
    try {
      if (Integer.class == this.type) {
        return this.type.cast(Integer.valueOf(valueStr));
      }
      if (Long.class == this.type) {
        return this.type.cast(Long.valueOf(valueStr));
      }
    } catch (final NumberFormatException e) {
      throw ArgumentException.createEx(ErrorCode.PARAM_BAD, "The value cannot be parsed as "
          + this.type.getSimpleName().toLowerCase() + ".", this.name);
    }

    return this.parseOther(valueStr);
  }

  /**
   * Field access for {@link #name}.
   *
   * @return the value of {@link #name}.
   */
  @Override
  public String getName() {
    return this.name;
  }

  /**
   * Field access for {@link #type}.
   *
   * @return the value of {@link #type}.
   */
  @Override
  public Class<T> getType() {
    return this.type;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final String toString(final Object value) throws InternalServiceException {
    if (false == this.type.isAssignableFrom(value.getClass())) {
      throw new InternalServiceException(ErrorCode.PANIC,
          "Parameter.toString(): Bad type of value. Expected {0} but is {1}.", this.type.getName(),
          value.getClass().getName());
    }
    if (String.class == this.type || Integer.class == this.type || Long.class == this.type) {
      return String.valueOf(value);
    }
    if (Boolean.class == this.type) {
      return Boolean.TRUE.equals(value) ? "1" : "0";
    }

    return this.toStringOther(value);
  }

  /**
   * Parse parameter values of other (non standard types). This method is called by
   * {@link #parse(String)} in case {@link #type} is none of the supported standard types (currently
   * String, Boolean, Integer and Long). It is intended for extensions.
   * <dl>
   * <dt style="margin-top:0.25cm;"><i>Note:</i>
   * <dd>This default implementation always throws an InternalServiceException.
   * </dl>
   *
   * @param valueStr <i>mandatory</i> - the string value to parse the value from
   * @return the parsed value, may be <code>null</code>
   * @throws ArgumentException in case the parameter cannot be parsed according to {@link #type}
   * @throws InternalServiceException in case the type {@link #type} cannot be handled. This is a
   *           programming error.
   */
  protected T parseOther(final String valueStr) throws ArgumentException, InternalServiceException {
    throw new InternalServiceException(ErrorCode.PANIC,
        "ParameterImpl.parseOther(): Unsupported parameter type: " + this.type.getName());
  }

  /**
   * Convert the values of other (non standard types) to their external form. This method is called
   * by {@link #toString(Object)} in case {@link #type} is none of the supported standard types
   * (currently String, Boolean, Integer and Long). It is intended for extensions.
   * <dl>
   * <dt style="margin-top:0.25cm;"><i>Note:</i>
   * <dd>This default implementation always throws an InternalServiceException.
   * </dl>
   *
   * @param value <i>mandatory</i> - the parameters value
   * @return the external form of the parameters value, never <code>null</code>
   * @throws InternalServiceException in case the given <code>value</code> does not match
   *           {@link #getClass()}
   */
  protected String toStringOther(final Object value) throws InternalServiceException {
    throw new InternalServiceException(ErrorCode.PANIC,
        "ParameterImpl.toStringOther(): Unsupported parameter type: " + this.type.getName());
  }
}

0

Cách của tôi để mã đó sẽ như sau:

// enum A { a, b, c }
static final Set<Short> enumA = new LinkedHashSet<>(Arrays.asList(new Short[]{'a','b','c'}));

// enum B extends A { d }
static final Set<Short> enumB = new LinkedHashSet<>(enumA);
static {
    enumB.add((short) 'd');
    // If you have to add more elements:
    // enumB.addAll(Arrays.asList(new Short[]{ 'e', 'f', 'g', '♯', '♭' }));
}

LinkedHashSetcung cấp cả hai mục nhập đó chỉ tồn tại một lần và thứ tự của chúng được giữ nguyên. Nếu thứ tự không quan trọng, bạn có thể sử dụng HashSetthay thế. Đoạn mã sau không thể có trong Java:

for (A a : B.values()) { // enum B extends A { d }
    switch (a) {
        case a:
        case b:
        case c:
            System.out.println("Value is: " + a.toString());
        break;
        default:
            throw new IllegalStateException("This should never happen.");
    }
}

Mã này có thể được viết như sau:

for (Short a : enumB) {
    switch (a) {
        case 'a':
        case 'b':
        case 'c':
            System.out.println("Value is: " + new String(Character.toChars(a)));
        break;
        default:
            throw new IllegalStateException("This should never happen.");
    }
}

Từ Java 7 trở đi, bạn thậm chí có thể làm tương tự với String:

// enum A { BACKWARDS, FOREWARDS, STANDING }
static final Set<String> enumA = new LinkedHashSet<>(Arrays.asList(new String[] {
        "BACKWARDS", "FOREWARDS", "STANDING" }));

// enum B extends A { JUMP }
static final Set<String> enumB = new LinkedHashSet<>(enumA);
static {
    enumB.add("JUMP");
}

Sử dụng thay thế enum:

for (String a : enumB) {
    switch (a) {
        case "BACKWARDS":
        case "FOREWARDS":
        case "STANDING":
            System.out.println("Value is: " + a);
        break;
        default:
            throw new IllegalStateException("This should never happen.");
    }
}
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.