enums สามารถ subclassed เพื่อเพิ่มองค์ประกอบใหม่ได้หรือไม่


534

ฉันต้องการที่จะใช้ enum ที่มีอยู่และเพิ่มองค์ประกอบมากขึ้นดังต่อไปนี้:

enum A {a,b,c}

enum B extends A {d}

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

เป็นไปได้ใน Java?


12
เหตุผลในการทำเช่นนี้คือการทดสอบสถานการณ์ที่มีค่า enum ที่ไม่ถูกต้องโดยไม่แนะนำค่า enum ที่ไม่ถูกต้องในแหล่งที่มาหลัก
Archimedes Trajano

ใช่ตัวอย่างของความบริสุทธิ์ "ภาษาศาสตร์" ฉันคิดว่าสิ่งที่ต้องการคือความคิดประหยัดแรงงาน "การทำบัญชี" ของชุดจำนวนเต็มอัตโนมัติอย่างที่มีใน C ++ เพื่อให้คุณสามารถเริ่มชุดใหม่เป็นส่วนขยายของชุดเก่าเริ่มต้นที่ 1+ ค่าสุดท้ายของ ชุดก่อนหน้าและหากชื่อมีการสืบทอดชื่อจาก "ชุดย่อยทั่วไป" แม้ว่า java enum มีบางสิ่งที่ดีเกี่ยวกับมัน แต่มันก็ไม่มีจำนวนเต็มอัตโนมัติที่เพิ่มขึ้นโดยอัตโนมัติประกาศความช่วยเหลือที่ C ++ enum ให้
peterk

4
ที่จริงแล้วเมื่อคุณขยาย enum ของคุณด้วยค่าใหม่คุณกำลังสร้างไม่ใช่คลาสย่อย แต่เป็นซูเปอร์คลาส คุณสามารถใช้ค่า enum พื้นฐานได้ทุกที่แทน enum ที่ "ขยาย" แต่ไม่ใช่ในทางกลับกันดังนั้นตามหลักการแทนที่ Liskov หลักการที่ขยาย Enum คือ enclass ของ base enum
Ilya

@Ilya ... ใช่ว่าเป็นเรื่องจริง ฉันชี้ให้เห็นว่าคำถามนี้มีกรณีการใช้งานจริงที่แน่นอน เพื่อประโยชน์ในการโต้แย้งพิจารณาEnum ฐานของ: PrimaryColours; มีเหตุผลที่ต้องการซูเปอร์คลาสนี้ให้กับ Enum PrimaryAndPastelColoursโดยการเพิ่มชื่อสีใหม่ Liskov ยังคงเป็นช้างอยู่ในห้อง ดังนั้นทำไมไม่เริ่มต้นด้วยฐานของ Enum: AllMyColours- และแล้วหนึ่งอาจ ย่อย -class ทุกสีที่: PrimaryAndPastelColoursและต่อมาย่อย -class นี้ไปที่: PrimaryColours(การรักษาลำดับชั้นในใจ) Java ไม่อนุญาตเช่นนั้น
จะ

คำตอบ:


450

ไม่คุณไม่สามารถทำได้ใน Java นอกเหนือจากสิ่งอื่นdแล้วก็น่าจะเป็นตัวอย่างของA(ให้ความคิดปกติของ "ขยาย") แต่ผู้ใช้ที่รู้เพียงเกี่ยวกับAจะไม่ทราบเกี่ยวกับมัน - ซึ่งเอาชนะจุด enum เป็นชุดที่รู้จักกันดีของ ค่า

หากคุณสามารถบอกเราเพิ่มเติมเกี่ยวกับวิธีที่คุณต้องการใช้สิ่งนี้เราสามารถแนะนำทางเลือกอื่น ๆ


516
enums ทั้งหมดขยาย java.lang.Enum โดยปริยาย เนื่องจาก Java ไม่สนับสนุนการสืบทอดหลายค่า enum จึงไม่สามารถขยายสิ่งอื่นใดได้
givanse

9
เหตุผลที่ฉันต้องการที่จะขยายเป็นเพราะฉันต้องการที่จะมีชั้นฐานที่เรียกว่าเช่น IntEnum ที่มีลักษณะเช่นนี้stackoverflow.com/questions/1681976/enum-with-int-value-in-java/... จากนั้นจำนวนของฉันทั้งหมดสามารถขยายได้ ... ในกรณีนี้เพียงแค่รับผลประโยชน์จากการสืบทอดและดังนั้นฉันจึงไม่ต้องทำซ้ำรหัส "int-based enum" นี้บ่อยๆ ฉันยังใหม่กับ Java และมาจาก C # และฉันหวังว่าฉันจะพลาดบางสิ่งบางอย่าง ความเห็นในปัจจุบันของฉันคือ Java enums เป็นความเจ็บปวดเมื่อเทียบกับ C #
Tyler Collier

30
@Tyler: C # enums เป็นเพียงชื่อที่เกี่ยวข้องกับตัวเลขโดยไม่มีการตรวจสอบอัตโนมัติหรืออะไรเลย IMO enums เป็น Java หนึ่งบิตซึ่งดีกว่า C #
Jon Skeet

21
ไม่สร้างความรำคาญด้วย @JonSkeet ที่นี่ ในกรณีที่ใช้งานของฉันฉันต้องการแยกตรรกะที่น่ารังเกียจทั้งหมดใน enum ขนาดใหญ่ของฉันและซ่อนตรรกะไว้และกำหนด enum ที่สะอาดที่ขยายอีกอันหนึ่งที่ซ่อนอยู่ Enums ที่มีลอจิกจำนวนมากจะเต้นตามความคิดของการประกาศตัวแปรแบบสะอาดดังนั้นคุณไม่จำเป็นต้องประกาศตัวแปรสแตติกหลายร้อยตัวดังนั้นคลาสที่มี 5 enums จะไม่สามารถอ่านได้และใหญ่เกินไปในบรรทัด ฉันไม่ต้องการให้ผู้พัฒนารายอื่นกังวลเกี่ยวกับการคัดลอกและวางความสงบสุขของรหัสสำหรับโครงการต่อไปเช่นกันและแทนที่จะขยาย base_enum ... มันสมเหตุสมผลสำหรับฉัน ...
mmm

43
@ givanse ... ไม่เห็นด้วยกับคุณในจุดของการขยายโดยนัยของ java.lang.Enum เป็นสาเหตุของการไม่สืบทอดเนื่องจากทุกคลาสใน java ยังสืบทอดคลาส Object โดยปริยาย แต่มันสามารถสืบทอดคลาสอื่นได้ เข้าสู่ลำดับชั้นObject->A->BแทนObject->A->B extends Object
mickeymoon

317

Enums แสดงถึงการแจงนับที่สมบูรณ์ของค่าที่เป็นไปได้ ดังนั้นคำตอบ (ไม่ช่วยเหลือ) จึงไม่ใช่

เป็นตัวอย่างของปัญหาที่แท้จริงใช้วันธรรมดาวันหยุดสุดสัปดาห์และสหภาพวันของสัปดาห์ เราสามารถกำหนดวันทั้งหมดภายในไม่กี่วันของสัปดาห์ แต่จากนั้นเราจะไม่สามารถแสดงคุณสมบัติพิเศษสำหรับทั้งวันธรรมดาและวันหยุดสุดสัปดาห์

สิ่งที่เราทำได้คือมี enum สามประเภทพร้อมการทำแผนที่ระหว่างวันธรรมดา / วันหยุดสุดสัปดาห์และวันแห่งสัปดาห์

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

อีกทางเลือกหนึ่งเราอาจมีส่วนต่อประสานปลายเปิดสำหรับวันของสัปดาห์:

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

หรือเราสามารถรวมสองวิธีเข้าด้วยกัน:

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
มีปัญหากับสิ่งนี้หรือไม่? คำสั่ง switch จะไม่ทำงานบนอินเทอร์เฟซ แต่ใช้งานได้กับ enum ปกติ ไม่ทำงานโดยที่ไม่มีการสลับชนิดของการฆ่าหนึ่งในสิ่งที่ดีกว่าเกี่ยวกับ enums
Manius

9
ฉันคิดว่าอาจมีปัญหาอีกอย่างสำหรับเรื่องนี้ ไม่มีความเท่าเทียมกันระหว่าง Weekday.MON และ DayOfWeek.MON นั่นคือผลประโยชน์ที่ยิ่งใหญ่อื่น ๆ ของ enums หรือไม่ ฉันไม่มีวิธีแก้ปัญหาที่ดีกว่าเพียงแค่ตระหนักว่าฉันกำลังพยายามหาคำตอบที่ดีที่สุด การขาดความสามารถในการใช้ == บังคับให้มือเล็กน้อย
Snekse

2
@ ผู้อ่านใช่นั่นคือการแลกเปลี่ยนที่แม่นยำ หากคุณต้องการบางสิ่งที่สามารถขยายได้คุณไม่สามารถแก้ไขงบสวิตช์ได้หากคุณต้องการชุดของค่าที่ทราบแน่นอนคุณจะไม่สามารถขยายบางสิ่งได้
djechlin

3
จาก enum ไปยังส่วนต่อประสานคุณจะสูญเสียการโทรไปยังค่า () สิ่งนี้ทำให้การปรับโครงสร้างใหม่ยากโดยเฉพาะอย่างยิ่งถ้าคุณตัดสินใจที่จะขยาย enum ของคุณและเพิ่มส่วนต่อประสานเป็นอุปสรรคนามธรรมให้กับ enum ที่กำหนดไว้
Joshua Goldberg

4
วิธีการรับ enum จากอินเตอร์เฟสนี้ใช้โดย Java 1.7 API เช่น java.nio.file.Files.write () รับอาร์เรย์ OpenOption เป็นอาร์กิวเมนต์สุดท้าย OpenOption เป็นอินเตอร์เฟส แต่เมื่อเราเรียกใช้ฟังก์ชันนี้เรามักจะผ่านค่าคงที่ StandardOpenOption enum ซึ่งมาจาก OpenOption นี่เป็นข้อดีของการขยายได้ แต่ก็มีข้อเสีย การใช้งานได้รับความทุกข์ทรมานจากข้อเท็จจริงที่ว่า OpenOption เป็นอินเตอร์เฟส มันสร้าง HashSet <OpenOption> จากอาเรย์ที่ส่งผ่านเมื่อมันสามารถสร้าง EnumSet ที่ประหยัดพื้นที่และเวลามากขึ้น และไม่สามารถใช้สวิตช์ได้
Klitos Kyriacou

71

วิธีแก้ปัญหาที่แนะนำสำหรับสิ่งนี้คือรูปแบบ enum ที่ขยายได้

สิ่งนี้เกี่ยวข้องกับการสร้างอินเทอร์เฟซและใช้สิ่งที่คุณใช้ enum ในปัจจุบัน จากนั้นให้ enum ใช้อินเตอร์เฟส คุณสามารถเพิ่มค่าคงที่ได้มากขึ้นโดยทำให้ enum ใหม่นั้นขยายส่วนต่อประสาน


มันคุ้มค่าที่จะเรียกใช้วิธีการของโรงงานในอินเตอร์เฟส วิธีที่ยอดเยี่ยมในการแบ่งปันการทำงานทั่วไประหว่าง Enums ที่เกี่ยวข้องเนื่องจากการขยายไม่ใช่โซลูชันที่ทำงานได้
Tim Clemons

8
คุณสามารถให้รายละเอียดเพิ่มเติม (รหัส :)) เกี่ยวกับรูปแบบนี้ได้หรือไม่
Dherik

3
รูปแบบนั้นไม่อนุญาตให้ขยายค่าของ enum จุดไหนในคำถามที่ถาม
Eria

55

ภายใต้หน้าปกของคุณ ENUM เป็นเพียงคลาสปกติที่สร้างโดยคอมไพเลอร์ java.lang.Enumว่าชั้นที่สร้างยื่นออกมา finalเหตุผลทางเทคนิคที่คุณไม่สามารถขยายชั้นเรียนที่สร้างขึ้นเป็นว่าชั้นที่สร้างขึ้นเป็น เหตุผลทางความคิดของการเป็นขั้นสุดท้ายจะกล่าวถึงในหัวข้อนี้ แต่ฉันจะเพิ่มกลไกในการสนทนา

นี่คือการทดสอบ enum:

public enum TEST {  
    ONE, TWO, THREE;
}

รหัสผลลัพธ์จาก 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);
}

เป็นไปได้ว่าคุณสามารถพิมพ์คลาสนี้ด้วยตัวคุณเองและวาง "ขั้นสุดท้าย" แต่คอมไพเลอร์ป้องกันไม่ให้คุณขยาย "java.lang.Enum" โดยตรง คุณสามารถตัดสินใจว่าจะไม่ขยาย java.lang.Enum แต่จากนั้นคลาสและคลาสที่ได้รับของคุณจะไม่เป็นอินสแตนซ์ของ java.lang.Enum ... ซึ่งอาจไม่สำคัญสำหรับคุณ แต่อย่างใด!


1
บล็อกคงที่ว่างอยู่กำลังทำอะไร 'คงที่ {};'
soote

1
มันไม่มีรหัสในนั้น โปรแกรม "javap" แสดงบล็อคว่างเปล่า
ChrisCantrell

แปลกไหมที่มีมันถ้ามันไม่ทำอะไรใช่ไหม?
soote

4
คุณพูดถูก! ความผิดพลาดของฉัน. มันไม่ใช่บล็อกว่างเปล่าของรหัส หากคุณเรียกใช้ "javap -c" คุณจะเห็นรหัสจริงภายในบล็อกคงที่ บล็อกแบบสแตติกสร้างอินสแตนซ์ ENUM ทั้งหมด (หนึ่ง, สองและสามที่นี่) ขอโทษด้วยกับเรื่องนั้น.
ChrisCantrell

1
ขอขอบคุณที่ระบุความจริงที่ตรง: เนื่องจาก java.lang.Enum ได้รับการประกาศขั้นสุดท้าย
Benjamin

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

สามารถเขียนเป็น:

public enum All {
    a       (ClassGroup.A,ClassGroup.B),
    b       (ClassGroup.A,ClassGroup.B),
    c       (ClassGroup.A,ClassGroup.B),
    d       (ClassGroup.B) 
...
  • ClassGroup.B.getMembers ()มี {a, b, c, d}

มันมีประโยชน์อย่างไร:สมมติว่าเราต้องการอะไร: เรามีกิจกรรมและเรากำลังใช้ enums enums เหล่านั้นสามารถจัดกลุ่มได้โดยการประมวลผลที่คล้ายกัน หากเรามีการดำเนินการที่มีองค์ประกอบหลายอย่างเหตุการณ์บางอย่างก็เริ่มดำเนินการบางอย่างเป็นเพียงขั้นตอนและสิ้นสุดการดำเนินการ เพื่อรวบรวมการดำเนินการดังกล่าวและหลีกเลี่ยงกรณีสวิตช์แบบยาวเราสามารถจัดกลุ่มให้เป็นตัวอย่างและใช้:

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

ตัวอย่าง:

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      
}  

เพิ่มขั้นสูง:

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),
(...)

ที่ด้านบนหากเรามีข้อผิดพลาด (myEvent.is (State_StatusGroup.FAIL)) จากนั้นทำซ้ำโดยเหตุการณ์ก่อนหน้านี้เราสามารถตรวจสอบได้อย่างง่ายดายว่าเราจะต้องยกเลิกการโอนเงินโดย:

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

มันจะมีประโยชน์สำหรับ:

  1. รวมถึง meta-data ที่ชัดเจนเกี่ยวกับตรรกะการประมวลผลซึ่งน้อยกว่าที่จะจำ
  2. การนำบางมรดกมาใช้
  3. เราไม่ต้องการใช้โครงสร้างคลาสเช่น สำหรับการส่งข้อความสั้น ๆ

13

นี่คือวิธีที่ฉันค้นพบวิธีการขยาย enum ไปยัง enum อื่น ๆ เป็นวิธีการ straighfoward มาก:

Suposse คุณมี enum ที่มีค่าคงที่ทั่วไป:

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

จากนั้นคุณสามารถลองขยายคู่มือด้วยวิธีนี้:

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

แน่นอนทุกครั้งที่คุณต้องการขยายค่าคงที่คุณต้องแก้ไขไฟล์ SubEnum ของคุณ


ที่น่าสนใจเราสามารถใช้ enum toString () และท้ายที่สุดการเปรียบเทียบสตริง; และการใช้สวิตช์เราก็แค่ต้องโยนวัตถุนั้นไปยังค่า enum ที่รู้จัก ปัญหาเพียงอย่างเดียวคือผู้พัฒนา 2 คนกำลังขยายและสร้างรหัส enum ที่เหมือนกันและต่อมาพยายามรวมรหัสทั้งสอง :) ตอนนี้ฉันคิดว่าฉันเข้าใจว่าทำไม enum จึงไม่สามารถขยายได้
กุมภ์กุมภ์

11

ในกรณีที่คุณพลาดมันมีบทหนึ่งในหนังสือยอดเยี่ยมของ Joshua Bloch " Java Effective, 2nd edition "

  • บทที่ 6 - Enums และคำอธิบายประกอบ
    • รายการที่ 34: จำลอง enums ที่ขยายได้ด้วยอินเตอร์เฟส

แยกที่นี่ที่นี่

เพียงข้อสรุป:

ข้อเสียเล็กน้อยของการใช้อินเทอร์เฟซเพื่อจำลอง enums ที่ขยายได้คือการใช้งานไม่สามารถสืบทอดจาก enum ประเภทหนึ่งไปยังอีกประเภทหนึ่ง ในกรณีของตัวอย่างการดำเนินงานของเราตรรกะในการจัดเก็บและเรียกสัญลักษณ์ที่เกี่ยวข้องกับการดำเนินการซ้ำกันใน BasicOperation และ ExtendedOperation ในกรณีนี้ไม่สำคัญเนื่องจากมีการทำสำเนารหัสน้อยมาก หากมีจำนวนมากของฟังก์ชั่นที่ใช้ร่วมกันคุณสามารถแค็ปซูลในคลาสผู้ช่วยหรือวิธีการช่วยเหลือแบบคงที่เพื่อกำจัดการทำสำเนารหัส

โดยสรุปในขณะที่คุณไม่สามารถเขียนประเภท enum ที่ขยายได้คุณสามารถเลียนแบบได้โดยการเขียนอินเตอร์เฟสเพื่อไปกับชนิด enum พื้นฐานที่ใช้อินเทอร์เฟซ สิ่งนี้อนุญาตให้ไคลเอนต์เขียน enums ของตนเองที่ใช้อินเทอร์เฟซ สามารถใช้ enums เหล่านี้ได้ทุกที่ที่สามารถใช้ประเภท enum ขั้นพื้นฐานได้โดยสมมติว่า API นั้นเขียนในรูปของอินเตอร์เฟส


6

ฉันมักจะหลีกเลี่ยง enums เพราะพวกเขาไม่สามารถขยายได้ หากต้องการอยู่กับตัวอย่างของ OP หาก A อยู่ในไลบรารีและ B ในรหัสของคุณเองคุณจะไม่สามารถขยาย A ได้ถ้ามันเป็น enum นี่คือวิธีที่ฉันแทนที่ 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; };
}

มีบางหลุมที่ต้องหลีกเลี่ยงดูความคิดเห็นในรหัส ขึ้นอยู่กับความต้องการของคุณนี่เป็นทางเลือกที่มั่นคงและขยายได้สำหรับ enums


1
มันอาจจะไม่เป็นไรถ้าคุณแค่ต้องการลำดับ แต่ enums ยัง hava คุณสมบัติชื่อที่มีประโยชน์สวย
inor

6

นี่คือวิธีที่ฉันปรับปรุงรูปแบบการสืบทอด enum ด้วยการตรวจสอบรันไทม์ใน static initializer การBaseKind#checkEnumExtenderตรวจสอบว่า "การขยาย" enum ประกาศค่าทั้งหมดของ enum ฐานในลักษณะเดียวกันดังนั้น#name()และ#ordinal()ยังคงเข้ากันได้อย่างสมบูรณ์

ยังคงมีการคัดลอกวางที่เกี่ยวข้องกับการประกาศค่า แต่โปรแกรมล้มเหลวอย่างรวดเร็วหากใครบางคนเพิ่มหรือแก้ไขค่าในชั้นฐานโดยไม่ต้องปรับปรุงการขยาย

พฤติกรรมทั่วไปสำหรับความแตกต่าง enums ขยายซึ่งกันและกัน:

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() ;
}

Enum ฐานพร้อมวิธีการตรวจสอบ:

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

}

ตัวอย่างการขยาย:

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

จาก @Tom Hawtin -คำตอบtacklineเราเพิ่มการสนับสนุนสวิตช์

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

การใช้valueOf()วิธีการคืออะไร?
Axel Advento

@AxelAdvento ความคิดที่นี่คือเราขึ้นอยู่กับส่วนต่อประสานDayที่มีวิธีการvalueOf()แล้วswitch(Day.valueOf())มันดำเนินการโดยWeekDay, WeekEndDayenums
เลดเลด

3

ฉันขอแนะนำให้คุณใช้วิธีอื่นในการเข้าใกล้

แทนที่จะขยายการแจงนับที่มีอยู่สร้างที่มีขนาดใหญ่กว่าและสร้างส่วนย่อยของมัน เพื่อเป็นตัวอย่างถ้าคุณมีการแจงนับเรียกว่า PET และคุณต้องการขยายเป็นสัตว์คุณควรทำสิ่งนี้แทน:

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

ระวังสัตว์เลี้ยงไม่ใช่คอลเล็กชันที่ไม่เปลี่ยนรูปคุณอาจต้องการใช้ Guava หรือ Java9 เพื่อความปลอดภัยมากขึ้น


2

มีปัญหาเดียวกันนี้เองฉันต้องการโพสต์มุมมองของฉัน ฉันคิดว่ามีปัจจัยกระตุ้นสองอย่างสำหรับการทำสิ่งนี้:

  • คุณต้องการที่จะมีรหัส enum ที่เกี่ยวข้อง แต่ในชั้นเรียนที่แตกต่างกัน ในกรณีของฉันฉันมีคลาสฐานที่มีรหัสหลายรหัสที่กำหนดไว้ใน enum ที่เกี่ยวข้อง ในวันต่อมา (วันนี้!) ฉันต้องการให้ฟังก์ชั่นใหม่บางอย่างกับคลาสฐานซึ่งก็หมายถึงรหัสใหม่สำหรับ enum
  • คลาสที่ได้รับจะสนับสนุนทั้งคลาสพื้นฐานของ enum รวมถึงของตัวเอง ไม่มีค่า enum ซ้ำ! ดังนั้น: วิธีการมี enum สำหรับคลาสย่อยที่มีค่า enum ของพาเรนต์พร้อมกับค่าใหม่

การใช้อินเทอร์เฟซไม่ได้เป็นการตัดจริง ๆ : คุณสามารถรับค่า enum ซ้ำได้โดยไม่ตั้งใจ ไม่ถูกใจ

ฉันลงเอยด้วยการรวม enums: สิ่งนี้ทำให้มั่นใจได้ว่าจะไม่สามารถมีค่าซ้ำซ้อนได้โดยเสียค่าใช้จ่ายในการผูกติดแน่นกับชั้นเรียนที่เกี่ยวข้องน้อยลง แต่ฉันคิดว่าปัญหาที่ซ้ำกันคือความกังวลหลักของฉัน ...


2

ในฐานะที่เป็นเครื่องช่วยในการทำความเข้าใจว่าทำไมการขยาย Enum จึงไม่สมเหตุสมผลในระดับการใช้ภาษาเพื่อพิจารณาว่าจะเกิดอะไรขึ้นหากคุณส่งตัวอย่างของ Enum ที่ขยายไปสู่รูทีนที่เข้าใจ Enum พื้นฐานเท่านั้น สวิตช์ที่คอมไพเลอร์สัญญาว่ามีทุกกรณีที่ครอบคลุมในความเป็นจริงจะไม่ครอบคลุมค่า Enum ที่ขยาย

นี่เป็นการเน้นย้ำว่าค่า Java Enum ไม่ใช่จำนวนเต็มเช่น C's ตัวอย่าง: ในการใช้ Java Enum เป็นดัชนีอาร์เรย์คุณต้องขอสมาชิก ordinal () อย่างชัดเจนเพื่อให้ java Enum เป็นค่าจำนวนเต็มแบบสุ่มคุณต้องเพิ่ม เขตข้อมูลที่ชัดเจนสำหรับการนั้นและการอ้างอิงที่มีชื่อสมาชิก

นี่ไม่ใช่ความคิดเห็นเกี่ยวกับความต้องการของ OP เพียงเพราะเหตุใด Java จึงไม่เคยทำ


1

ด้วยความหวังว่าคำตอบที่สง่างามของเพื่อนร่วมงานของฉันจะเห็นได้ในโพสต์ที่ยาวนานนี้ฉันต้องการแบ่งปันวิธีการนี้สำหรับการทำคลาสย่อยซึ่งเป็นไปตามวิธีการอินเทอร์เฟซและอื่น ๆ

โปรดทราบว่าเราใช้ข้อยกเว้นที่กำหนดเองที่นี่และรหัสนี้จะไม่รวบรวมเว้นแต่คุณจะแทนที่ด้วยข้อยกเว้นของคุณ

เอกสารครอบคลุมและฉันหวังว่าจะเป็นที่เข้าใจสำหรับคุณส่วนใหญ่

อินเตอร์เฟสที่ทุกคลาสย่อยต้องใช้

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

การใช้คลาสฐาน ENUM

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 ที่ "สืบทอด" จากคลาสฐาน

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

ในที่สุด ParameterImpl ทั่วไปเพื่อเพิ่มอรรถประโยชน์บางอย่าง

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

วิธีรหัสของฉันที่จะเป็นดังนี้:

// 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', '♯', '♭' }));
}

LinkedHashSetให้ทั้งสองที่แต่ละรายการมีอยู่เพียงครั้งเดียวและสั่งซื้อของพวกเขาจะถูกเก็บไว้ หากคำสั่งซื้อไม่สำคัญคุณสามารถใช้HashSetแทนได้ รหัสต่อไปนี้เป็นไปไม่ได้ใน 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.");
    }
}

รหัสสามารถเขียนได้ดังนี้:

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.");
    }
}

จาก Java 7 เป็นต้นไปคุณสามารถทำเช่นเดียวกันกับ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");
}

ใช้การเปลี่ยน 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.");
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.