Bisakah enum disubklasifikasikan untuk menambahkan elemen baru?

535

Saya ingin mengambil enum yang ada dan menambahkan lebih banyak elemen ke dalamnya sebagai berikut:

enum A {a,b,c}

enum B extends A {d}

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

Apakah ini mungkin di Jawa?

Mike
sumber
12
Alasan untuk melakukan ini adalah untuk menguji situasi di mana ada nilai enum yang tidak valid tanpa memperkenalkan nilai enum yang tidak valid di sumber inti.
Archimedes Trajano
Ya contoh kemurnian "linguistik". Saya pikir apa yang diinginkan adalah untuk ide penghematan tenaga kerja "pembukuan" dari satu set bilangan bulat yang bertambah secara otomatis seperti yang ada di C ++ sehingga Anda dapat memulai set baru sebagai perpanjangan dari set lama mulai dari 1+ nilai terakhir dari set sebelumnya, dan jika entri diberi nama mewarisi nama-nama dari "subset umum". Meskipun java enum memiliki beberapa hal bagus tentang itu, ia tidak memiliki bilangan bulat otomatis otomatis tambahan sederhana yang menyatakan bantuan yang disediakan oleh C ++ enum.
peterk
4
Sebenarnya, ketika Anda memperluas enum Anda dengan nilai-nilai baru, Anda membuat bukan subkelas, tetapi superkelas. Anda dapat menggunakan nilai-nilai enum dasar di mana-mana alih-alih enum "extended", tetapi tidak sebaliknya, jadi menurut Prinsip Substitusi Liskov, extended enum adalah superclass dari enum dasar.
Ilya
@Ilya ... ya itu benar. Saya menunjukkan bahwa pertanyaannya memiliki kasus penggunaan dunia nyata. Demi argumen, mempertimbangkan dasar Enum dari: PrimaryColours; masuk akal untuk ingin super- kelas ini ke Enum PrimaryAndPastelColoursdengan menambahkan nama warna baru. Liskov masih merupakan gajah di dalam ruangan. Jadi mengapa tidak mulai dengan basis Enum dari: AllMyColours- Dan kemudian orang mungkin sub- kelas semua warna untuk: PrimaryAndPastelColoursdan kemudian sub- kelas ini ke: PrimaryColours(menjaga hierarki dalam pikiran). Java tidak akan mengizinkan itu juga.
Akan

Jawaban:

451

Tidak, Anda tidak dapat melakukan ini di Jawa. Selain dari hal lain, dmungkin akan menjadi contoh A(mengingat ide normal "meluas"), tetapi pengguna yang hanya tahu tentang Atidak akan mengetahuinya - yang mengalahkan titik enum menjadi set terkenal nilai-nilai.

Jika Anda dapat memberi tahu kami lebih banyak tentang bagaimana Anda ingin menggunakan ini, kami berpotensi menyarankan solusi alternatif.

Jon Skeet
sumber
516
Semua enums secara implisit memperluas java.lang.Enum. Karena Java tidak mendukung multiple inheritance, enum tidak dapat memperpanjang yang lainnya.
givanse
9
Alasan saya ingin memperpanjang adalah karena saya ingin memiliki kelas dasar yang disebut misalnya IntEnum, yang terlihat seperti ini: stackoverflow.com/questions/1681976/enum-with-int-value-in-java/… . Maka semua enum saya dapat memperpanjangnya ... dalam hal ini hanya mendapat manfaat dari warisan dan dengan demikian saya tidak perlu menduplikasi kode "int-based enum" ini sering. Saya baru ke Jawa dan berasal dari C #, dan saya berharap saya kehilangan sesuatu. Pendapat saya saat ini adalah bahwa enum Jawa adalah sakit dibandingkan dengan C #.
Tyler Collier
30
@Tyler: C # enums hanyalah nama yang dikaitkan dengan angka, tanpa validasi otomatis atau apa pun . IMO enums adalah satu bit dari Java yang sebenarnya lebih baik daripada C #.
Jon Skeet
21
Tidak setuju dengan @JonSkeet di sini. Dalam use case saya, saya ingin memisahkan semua logika jahat dalam enum besar saya, dan menyembunyikan logika itu, dan mendefinisikan enum bersih yang memanjang yang lain yang tersembunyi. Enum dengan banyak logika mengalahkan gagasan memiliki variabel bersih yang dideklarasikan sehingga Anda tidak perlu mendeklarasikan ratusan variabel string statis sehingga kelas dengan 5 enum tidak mendapatkan garis yang tidak terbaca dan terlalu besar. Saya tidak ingin pengembang lain peduli dengan menyalin dan menempelkan kedamaian kode untuk proyek berikutnya dan juga memperpanjang base_enum ... masuk akal bagi saya ...
mmm
43
@ givanse ... tidak setuju dengan Anda tentang ekstensi implisit java.lang.Enum menjadi penyebab non-pewarisan karena setiap kelas di java juga secara implisit mewarisi kelas Obyek namun dapat mewarisi beberapa kelas lain karena kemudian akan datang ke dalam hierarki sebagai Object->A->BgantiObject->A->B extends Object
mickeymoon
317

Enum mewakili enumerasi lengkap dari nilai yang mungkin. Jadi jawaban (tidak membantu) adalah tidak.

Sebagai contoh masalah nyata, ambil hari kerja, hari akhir pekan dan, serikat pekerja, hari dalam seminggu. Kami dapat menetapkan semua hari dalam hari-hari-minggu tetapi kami tidak akan dapat mewakili properti khusus untuk hari kerja dan akhir pekan.

Apa yang bisa kami lakukan adalah memiliki tiga tipe enum dengan pemetaan antara hari kerja / akhir pekan dan hari dalam seminggu.

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

Atau, kita bisa memiliki antarmuka terbuka untuk hari-minggu:

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

Atau kita bisa menggabungkan dua pendekatan:

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() { ... }
}
Tom Hawtin - tackline
sumber
20
Apakah tidak ada masalah dengan ini? Pernyataan pergantian tidak akan berfungsi pada antarmuka, tetapi berfungsi pada enum biasa. Tidak bekerja dengan mengganti jenis membunuh salah satu hal yang lebih baik tentang enum.
Manius
9
Saya pikir mungkin ada masalah lain dengan ini. Tidak ada persamaan antara Weekday.MON dan DayOfWeek.MON. Bukankah itu manfaat besar lainnya dari enum? Saya tidak punya solusi yang lebih baik, hanya menyadari ini karena saya sedang berusaha menemukan jawaban terbaik. Kurangnya bisa menggunakan == memaksa tangan sedikit.
Snekse
2
@Crusader ya, itulah tepatnya trade-off. Jika Anda menginginkan sesuatu yang dapat diperluas, Anda tidak dapat memiliki pernyataan sakelar yang diperbaiki, jika Anda menginginkan sekumpulan nilai yang diketahui tetap, Anda secara tautologis tidak dapat memiliki sesuatu yang dapat diperluas.
djechlin
3
Beralih dari enum ke antarmuka, Anda juga kehilangan panggilan statis ke nilai (). Ini membuat refactoring sulit, terutama jika Anda memutuskan untuk memperpanjang enum Anda dan menambahkan antarmuka sebagai penghalang abstraksi ke enum yang sudah mapan.
Joshua Goldberg
4
Pendekatan untuk memperoleh enum dari antarmuka ini digunakan oleh Java 1.7 API, misal java.nio.file.Files.write () menggunakan array OpenOption sebagai argumen terakhir. OpenOption adalah antarmuka, tetapi ketika kita memanggil fungsi ini, kita biasanya melewati konstanta enum StandardOpenOption, yang diturunkan dari OpenOption. Ini memiliki keuntungan karena dapat diperluas, tetapi juga memiliki kelemahan. Implementasinya menderita karena OpenOption adalah sebuah antarmuka. Itu menciptakan HashSet <OpenOption> dari array yang diteruskan, ketika itu bisa membuat EnumSet yang lebih hemat ruang dan waktu. Dan itu tidak bisa menggunakan saklar.
Klitos Kyriacou 6-15
71

Solusi yang disarankan untuk ini adalah pola enum yang dapat diperluas .

Ini melibatkan pembuatan antarmuka dan menggunakannya di mana Anda saat ini menggunakan enum. Kemudian buat enum mengimplementasikan antarmuka. Anda bisa menambahkan lebih banyak konstanta dengan membuat enum baru itu juga memperluas antarmuka.

JodaStephen
sumber
Sebaiknya sebutkan penggunaan metode pabrik di antarmuka. Cara yang bagus untuk berbagi fungsionalitas umum antara Enum terkait mengingat perluasan bukan merupakan solusi yang layak.
Tim Clemons
8
Bisakah Anda memberikan rincian lebih lanjut (kode :)) tentang pola ini?
Dherik
3
Pola itu tidak memungkinkan untuk memperpanjang nilai-nilai enum. Yang merupakan titik dalam pertanyaan yang diajukan.
Eria
55

Di bawah selimut ENUM Anda hanyalah kelas reguler yang dihasilkan oleh kompiler. Kelas yang dihasilkan meluas java.lang.Enum. Alasan teknis Anda tidak dapat memperluas kelas yang dihasilkan adalah karena kelas yang dihasilkan adalah final. Alasan konseptual untuk menjadi final dibahas dalam topik ini. Tapi saya akan menambahkan mekanisme untuk diskusi.

Berikut ini adalah tes enum:

public enum TEST {  
    ONE, TWO, THREE;
}

Kode yang dihasilkan dari 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);
}

Dapat dibayangkan Anda bisa mengetikkan kelas ini sendiri dan menjatuhkan "final". Tetapi kompiler mencegah Anda untuk memperluas "java.lang.Enum" secara langsung. Anda dapat memutuskan untuk TIDAK memperpanjang java.lang.Enum, tetapi kemudian kelas Anda dan kelas turunannya tidak akan menjadi instance dari java.lang.Enum ... yang mungkin tidak terlalu berarti bagi Anda!

ChrisCantrell
sumber
1
Apa yang dilakukan dengan blok statis kosong? 'statis {};'
soote
1
Tidak ada kode di dalamnya. Program "javap" menunjukkan blok kosong.
ChrisCantrell
Aneh untuk memilikinya di sana jika tidak melakukan apa-apa bukan
soote
4
Kamu benar! Kesalahanku. Ini BUKAN blok kode yang kosong. Jika Anda menjalankan "javap -c" Anda akan melihat kode aktual di dalam blok statis. Blok statis membuat semua instance ENUM (SATU, DUA, dan TIGA di sini). Maaf soal itu.
ChrisCantrell
1
Terima kasih telah menyatakan fakta yang sebenarnya: karena java.lang.Enum dinyatakan final.
Benjamin
26
enum A {a,b,c}
enum B extends A {d}
/*B is {a,b,c,d}*/

dapat ditulis sebagai:

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

Bagaimana ini bisa berguna: Katakanlah kita menginginkan sesuatu seperti: Kami memiliki acara dan kami menggunakan enum. Enum tersebut dapat dikelompokkan berdasarkan pemrosesan serupa. Jika kita memiliki operasi dengan banyak elemen, maka beberapa peristiwa mulai beroperasi, beberapa hanya langkah dan yang lain mengakhiri operasi. Untuk mengumpulkan operasi seperti itu dan menghindari kasus sakelar panjang, kami dapat mengelompokkannya seperti dalam contoh dan menggunakan:

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

Contoh:

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      
}  

Tambahkan beberapa yang lebih canggih:

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

Di atas jika kita mengalami beberapa kegagalan (myEvent.is (State_StatusGroup.FAIL)) maka iterasi dengan peristiwa sebelumnya kita dapat dengan mudah memeriksa apakah kita harus mengembalikan transfer uang dengan:

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

Ini dapat bermanfaat untuk:

  1. termasuk meta-data explicite tentang pemrosesan logika, kurang untuk diingat
  2. mengimplementasikan beberapa multi-inheritance
  3. kami tidak ingin menggunakan struktur kelas, mis. untuk mengirim pesan status pendek
Waldemar Wosiński
sumber
13

Berikut adalah cara bagaimana saya menemukan cara memperluas enum ke enum lain, adalah pendekatan yang sangat ketat:

Misalkan Anda memiliki enum dengan konstanta yang sama:

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

maka Anda dapat mencoba melakukan manual meluas dengan cara ini:

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

tentu saja setiap kali Anda perlu memperluas konstanta Anda harus memodifikasi file SubEnum Anda.

Juan Pablo G
sumber
menarik, kita bisa menggunakan juga toumtring () yang sangat enum, dan pada akhirnya membandingkan string; dan untuk menggunakan switch, kita hanya perlu melemparkan objek ke enum yang diketahui; satu-satunya masalah adalah 2 pengembang memperluas dan membuat id enum identik, dan kemudian mencoba untuk menggabungkan kedua kode :), sekarang saya pikir saya mengerti mengapa enum harus tetap tidak dapat diperpanjang.
Aquarius Power
11

Jika Anda melewatkannya, ada bab dalam buku Joshua Bloch yang sangat bagus " Java Effective, 2nd edition ".

  • Bab 6 - Enum dan Anotasi
    • Butir 34: Meniru enum yang dapat diperluas dengan antarmuka

Ekstrak di sini .

Hanya kesimpulannya:

Kelemahan kecil dari penggunaan antarmuka untuk meniru enum yang dapat dikembangkan adalah implementasi tidak dapat diwarisi dari satu jenis enum ke yang lain. Dalam kasus contoh Operasi kami, logika untuk menyimpan dan mengambil simbol yang terkait dengan operasi diduplikasi dalam BasicOperation dan ExtendedOperation. Dalam hal ini tidak masalah karena sangat sedikit kode yang diduplikasi. Jika ada sejumlah besar fungsi bersama, Anda bisa merangkumnya dalam kelas pembantu atau metode pembantu statis untuk menghilangkan duplikasi kode.

Singkatnya, sementara Anda tidak bisa menulis tipe enum yang bisa diperluas, Anda bisa meniru itu dengan menulis antarmuka untuk pergi dengan tipe enum dasar yang mengimplementasikan antarmuka. Ini memungkinkan klien untuk menulis enum mereka sendiri yang mengimplementasikan antarmuka. Enum ini kemudian dapat digunakan di mana pun tipe enum dasar dapat digunakan, dengan asumsi API ditulis dalam bentuk antarmuka.

Guillaume Husta
sumber
6

Saya cenderung menghindari enum, karena tidak dapat diperpanjang. Untuk tetap menggunakan contoh OP, jika A ada di perpustakaan dan B di kode Anda sendiri, Anda tidak bisa memperpanjang A jika itu adalah enum. Inilah mengapa saya terkadang mengganti enum:

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

Ada beberapa lubang yang harus dihindari, lihat komentar dalam kode. Tergantung pada kebutuhan Anda, ini adalah alternatif yang solid dan dapat diperluas untuk enum.

sulai
sumber
1
mungkin baik-baik saja jika Anda hanya perlu beberapa peraturan untuk contoh. Tetapi enum juga memiliki nama properti yang cukup berguna.
inor
6

Ini adalah bagaimana saya meningkatkan pola pewarisan enum dengan pemeriksaan runtime di penginisialisasi statis. The BaseKind#checkEnumExtendercek yang "memperpanjang" enum menyatakan semua nilai-nilai dasar enum dengan cara yang persis sama sehingga #name()dan #ordinal()tetap sepenuhnya kompatibel.

Masih ada copy-paste yang terlibat untuk mendeklarasikan nilai tetapi program gagal dengan cepat jika seseorang menambahkan atau memodifikasi nilai di kelas dasar tanpa memperbarui yang diperluas.

Perilaku umum untuk enum yang berbeda saling memperpanjang:

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

Base enum, dengan metode verifikasi:

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

}

Sampel ekstensi:

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

}
Laurent Caillette
sumber
4

Berdasarkan @Tom Hawtin - jawaban tackline kami tambahkan dukungan sakelar,

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:
}
Khaled Lela
sumber
Apa gunanya valueOf()metode ini?
Axel Advento
@AxelAdvento Idenya di sini adalah kita bergantung pada antarmuka Dayyang memiliki metode valueOf()lalu switch(Day.valueOf()), ini diterapkan oleh WeekDay, WeekEndDayenums.
Khaled Lela
3

Saya sarankan Anda mengambil pendekatan sebaliknya.

Alih-alih memperluas enumerasi yang ada, buat yang lebih besar dan buat subset dari itu. Sebagai contoh jika Anda memiliki enumerasi yang disebut PET dan Anda ingin memperpanjangnya ke HEWAN Anda harus melakukan ini sebagai gantinya:

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

Hati-hati, hewan peliharaan bukan koleksi abadi, Anda mungkin ingin menggunakan Guava atau Java9 untuk keamanan lebih.

Guillaume Robbe
sumber
2

Setelah memiliki masalah yang sama, saya ingin memposting perspektif saya. Saya pikir ada beberapa faktor pendorong untuk melakukan sesuatu seperti ini:

  • Anda ingin memiliki beberapa kode enum terkait, tetapi di kelas yang berbeda. Dalam kasus saya, saya memiliki kelas dasar dengan beberapa kode yang didefinisikan dalam enum terkait. Pada beberapa tanggal kemudian (hari ini!) Saya ingin memberikan beberapa fungsionalitas baru ke kelas dasar, yang juga berarti kode baru untuk enum.
  • Kelas turunan akan mendukung enum kelas dasar maupun miliknya sendiri. Tidak ada nilai duplikat enum! Jadi: bagaimana cara membuat enum untuk subclass yang menyertakan enum dari induknya beserta nilai-nilai barunya.

Menggunakan antarmuka tidak benar-benar memotongnya: Anda dapat secara tidak sengaja mendapatkan nilai duplikat enum. Tidak diinginkan.

Saya akhirnya hanya menggabungkan enum: ini memastikan bahwa tidak ada nilai duplikat, dengan mengorbankan ikatan yang kurang erat dengan kelas terkait. Tapi, saya pikir masalah duplikat adalah perhatian utama saya ...

dsummersl
sumber
2

Sebagai bantuan untuk memahami mengapa memperpanjang Enum tidak masuk akal pada tingkat implementasi bahasa untuk mempertimbangkan apa yang akan terjadi jika Anda melewatkan instance dari Enum yang diperluas ke rutin yang hanya memahami basis Enum. Suatu saklar yang dijanjikan oleh kompiler dengan semua case yang dicakup sebenarnya tidak akan mencakup nilai-nilai Enum yang diperluas.

Ini lebih jauh menekankan bahwa nilai-nilai Java Enum bukan bilangan bulat seperti C, misalnya: untuk menggunakan Java Enum sebagai indeks array, Anda harus secara eksplisit meminta anggota ordinal (), untuk memberikan java Enum nilai integer acak yang harus Anda tambahkan bidang eksplisit untuk itu dan referensi yang bernama anggota.

Ini bukan komentar tentang keinginan OP, hanya tentang mengapa Java tidak akan pernah melakukannya.

pengguna2543191
sumber
1

Dengan harapan solusi elegan dari kolega saya ini bahkan terlihat di posting panjang ini saya ingin membagikan pendekatan ini untuk subkelas yang mengikuti pendekatan antarmuka dan seterusnya.

Perlu diketahui bahwa kami menggunakan pengecualian khusus di sini dan kode ini tidak dapat dikompilasi kecuali Anda menggantinya dengan pengecualian Anda.

Dokumentasinya luas dan saya harap ini bisa dimengerti oleh sebagian besar dari Anda.

Antarmuka yang harus diimplementasikan setiap enclass subclass.

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

Kelas dasar penerapan 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 subkelas yang "mewarisi" dari kelas dasar.

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

Akhirnya ParameterImpl generik untuk menambahkan beberapa utilitas.

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

Cara saya mengode itu adalah sebagai berikut:

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

LinkedHashSetmenyatakan bahwa setiap entri hanya ada satu kali, dan urutannya dipertahankan. Jika pesanan tidak masalah, Anda bisa menggunakannya HashSet. Kode berikut tidak dimungkinkan di 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.");
    }
}

Kode dapat ditulis sebagai berikut:

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

Dari Java 7 dan seterusnya Anda bahkan dapat melakukan hal yang sama dengan 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");
}

Menggunakan penggantian 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.");
    }
}
Matthias Ronge
sumber