Numaralandırmalar yeni öğeler eklemek için alt sınıflara ayrılabilir mi?


534

Mevcut bir numaralandırma almak ve ona aşağıdaki gibi daha fazla öğe eklemek istiyorum:

enum A {a,b,c}

enum B extends A {d}

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

Java ile bu mümkün mü?


12
Bunu yapmanın bir nedeni, çekirdek kaynağa geçersiz bir enum değeri girmeden geçersiz bir enum değerinin olduğu durumu test etmektir.
Arşimet Trajano

Evet "dilsel" saflığa bir örnek. Ben istenen bir C ++ gibi bir tamsayılar otomatik artan bir dizi "defter tutma" emek tasarrufu fikir olduğunu düşünüyorum böylece yeni küme 1 + son değerinden başlayan eski kümesinin bir uzantısı olarak başlayabilirsiniz önceden ayarlanmışsa ve girdiler adlandırılmışsa adları "ortak alt kümeden" devralır. Java enum hakkında bazı güzel şeyler olsa da, C ++ enum sağladığı yardım bildiren basit otomatik otomatik artan tamsayı eksik.
peterk

4
Aslında, enumunuzu yeni değerlerle genişlettiğinizde, alt sınıf değil, üst sınıf oluşturursunuz. Taban enum değerlerini "genişletilmiş" enum yerine her yerde kullanabilirsiniz, ancak tam tersi değil, bu nedenle Liskov İkame İlkesine göre, genişletilmiş enum, taban enumunun süper sınıfıdır.
Ilya

@Ilya ... evet bu doğru. Sorunun kesin gerçek kullanım örnekleri olduğunu belirtiyorum. Tartışma uğruna, bir temel Enum düşünün PrimaryColours:; yeni renk adları ekleyerek bunu Enum'da süper sınıflamak istemek mantıklıdır PrimaryAndPastelColours. Liskov hala odadaki fil. : Öyleyse neden bir baz Enum ile başlamaz AllMyColoursSonra bir - kudreti alt : Tüm renk -sınıfı için PrimaryAndPastelColoursve sonradan alt bu -sınıfı: PrimaryColours(akılda hiyerarşiyi tutarak). Java da buna izin vermez.
will

Yanıtlar:


450

Hayır, bunu Java ile yapamazsınız. Başka bir şey dışında, dmuhtemelen A("genişletme" normal fikri göz önüne alındığında) bir örnek olacaktır , ancak sadece bilen kullanıcılar Abunu bilmezler - ki bu bir enumun iyi bilinen bir dizi olması değerler.

Bunu nasıl kullanmak istediğiniz hakkında bize daha fazla bilgi verebilirseniz, potansiyel olarak alternatif çözümler önerebiliriz.


516
Tüm numaralandırmalar dolaylı olarak java.lang.Enum'u genişletir. Java çoklu mirası desteklemediğinden, bir numaralandırma başka hiçbir şeyi genişletemez.
givanse

9
Genişletmek istediğim nedeni, örneğin IntEnum adında bir temel sınıf olmasını istememdir, şuna benzer: stackoverflow.com/questions/1681976/enum-with-int-value-in-java/… . Sonra tüm benim enums genişletmek olabilir ... bu durumda sadece miras yararlanan ve bu nedenle bu "int tabanlı enum" kodunu sık sık çoğaltmak zorunda kalmazsınız. Java için yeniyim ve C # 'dan geliyorum ve umarım bir şey eksik. Şu anki düşüncem Java numaralarının C # ile karşılaştırıldığında bir ağrı olduğudur.
Tyler Collier

30
@Tyler: C # numaralandırmaları, otomatik doğrulama veya herhangi bir şey olmadan yalnızca sayılarla ilişkili adlardır . IMO sıralamaları Java'nın aslında C # 'dan daha iyi olan bir bitidir.
Jon Skeet

21
@JonSkeet ile anlaşamadık. Kullanım durumumda, büyük numaramdaki tüm kötü mantığı ayırmak ve mantığın gizlenmesini ve gizli olanı genişleten temiz bir numaralandırma tanımlamak istiyorum. Çok sayıda mantık içeren numaralandırmalar, temiz değişkenleri bildirme fikrini yener, böylece yüzlerce statik dize değişkeni bildirmek zorunda kalmazsınız, böylece 5 sıralı bir sınıf okunamaz ve satırlarda çok büyük olmaz. Diğer geliştiricilerin de bir sonraki proje için bu kod barışını kopyalayıp yapıştırmakla ilgilenmesini istemiyorum ve bunun yerine base_enum'u genişletiyorum ... bana mantıklı geliyor ...
mmm

43
@givanse ... java.lang.Enum'un miras olmamasının nedeni olarak örtülü olarak uzlaşma noktasında sizinle aynı fikirde değil, çünkü java'daki her sınıf da Object sınıfını örtülü olarak devralır, ancak daha sonra geleceği gibi başka bir sınıfı devralabilir Object->A->Byerine hiyerarşiye doğruObject->A->B extends Object
mickeymoon

317

Numaralandırmalar, olası değerlerin tam bir numaralandırmasını temsil eder. Yani (yararsızdır) cevap hayır.

Gerçek bir soruna örnek olarak hafta içi, hafta sonu günü ve birlik, haftanın günleri alın. Tüm günleri haftanın günleri içinde tanımlayabiliriz, ancak hafta içi ve hafta sonu günlerine özel mülkleri temsil edemeyiz.

Yapabileceğimiz, hafta içi / hafta sonu günleri ile haftanın günleri arasında haritalama içeren üç numara türü vardır.

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

Alternatif olarak, haftanın günü için açık uçlu bir arayüze sahip olabiliriz:

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

Veya iki yaklaşımı birleştirebiliriz:

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
Bununla ilgili bir sorun yok mu? Bir switch deyimi bir arabirimde çalışmaz, ancak düzenli bir numaralandırma üzerinde çalışır. W / switch tür çalışmayan enums hakkında güzel şeylerden birini öldürür.
Manius

9
Bununla ilgili başka bir sorun olabileceğini düşünüyorum. Weekday.MON ve DayOfWeek.MON arasında eşitlik yoktur. Enumların diğer büyük yararı bu değil mi? Daha iyi bir çözümüm yok, sadece en iyi cevabı bulmaya çalışırken bunu fark ediyorum. == kullanamama, eli biraz zorlar.
Snekse

2
@ Haçlı evet, bu kesinlikle değiş tokuş. Genişletilebilir bir şey istiyorsanız, sabit anahtar deyimleriniz olamaz, bilinen bir dizi sabit değer istiyorsanız, totolojik olarak genişletilebilir bir şey olamaz.
djechlin

3
Numaralandırmadan arayüze geçtiğinizde, değerlerin statik çağrısını da kaybedersiniz (). Bu, özellikle numaralandırmanızı genişletmeye ve arayüzü yerleşik bir numaralandırmaya bir soyutlama bariyeri olarak eklemeye karar verirseniz yeniden düzenlemeyi zorlaştırır.
Joshua Goldberg

4
Bir enum'u bir arabirimden türetmenin bu yaklaşımı, Java 1.7 API tarafından kullanılır, örneğin java.nio.file.Files.write (), son argüman olarak bir OpenOption dizisi alır. OpenOption bir arabirimdir, ancak bu işlevi çağırdığımızda genellikle OpenOption'dan türetilen bir StandardOpenOption enum sabitini geçiririz. Bu, genişletilebilir olma avantajına sahiptir, ancak dezavantajları da vardır. Uygulama, OpenOption'un bir arayüz olduğu gerçeğinden muzdariptir. Daha fazla alan ve zaman açısından verimli bir EnumSet oluşturabildiği zaman, geçirilen diziden bir HashSet <AçıkOption> oluşturur. Ve anahtar kullanamaz.
Klitos Kyriacou

71

Bunun için önerilen çözüm, genişletilebilir numaralandırma modelidir .

Bu, bir arayüz oluşturmayı ve şu anda numaralandırmayı kullandığınız arayüzü kullanmayı içerir. Sonra numaralandırma arabirimi uygulamak olun. Yeni numaralandırmanın arabirimi de genişletmesini sağlayarak daha fazla sabit ekleyebilirsiniz.


Arayüzde bir fabrika yöntemini kullandıklarını belirtmeye değer. Genişletmenin uygulanabilir bir çözüm olmadığı göz önüne alındığında, ortak İşlevleri ilgili Numaralandırmalar arasında paylaşmanın harika bir yolu.
Tim Clemons

8
Bu model hakkında daha fazla ayrıntı (kod :)) sağlayabilir misiniz?
Dherik

3
Bu model bir enum'un değerlerini genişletmeye izin vermez. Sorunun sorulması gereken nokta budur.
Eria

55

Kapakların altında ENUM, derleyici tarafından oluşturulan normal bir sınıftır. Bu üretilen sınıf uzar java.lang.Enum. Oluşturulan sınıfı genişletememenizin teknik nedeni, oluşturulan sınıfın olmasıdır final. Nihai olmasının kavramsal nedenleri bu konuda tartışılmaktadır. Ama tartışmaya mekaniği ekleyeceğim.

İşte bir test sıralaması:

public enum TEST {  
    ONE, TWO, THREE;
}

Javap sonuç kodu:

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

Muhtemelen bu sınıfı kendi başınıza yazabilir ve "final" i bırakabilirsiniz. Ancak derleyici doğrudan "java.lang.Enum" u uzatmanızı engeller. Java.lang.Enum'u genişletmemeye karar verebilirsiniz, ancak daha sonra sınıfınız ve türetilmiş sınıfları, java.lang.Enum örneğini oluşturmazdı ... bu sizin için hiçbir şekilde önemli olmayabilir!


1
Boş statik blok ne yapıyor? 'statik {};'
soote

1
İçinde kod yok. "Javap" programı boş bloğu gösterir.
ChrisCantrell

Bir şey yapmıyorsa orada olması garip değil mi?
16'da

4
Haklısın! Benim hatam. Boş bir kod bloğu DEĞİLDİR. "Javap -c" komutunu çalıştırırsanız, statik bloğun içinde gerçek kodu görürsünüz. Statik blok tüm ENUM örneklerini oluşturur (burada ONE, TWO ve THREE). Bunun için üzgünüm.
ChrisCantrell

1
Düz gerçeği belirttiğiniz için teşekkürler: çünkü java.lang.Enum nihai olarak ilan edildi.
Benjamin

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

şu şekilde yazılabilir:

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} içerir

Nasıl faydalı olabilir: Diyelim ki şöyle bir şey istiyoruz: Etkinliklerimiz var ve numaralandırmalar kullanıyoruz. Bu numaralandırmalar benzer işlemlerle gruplandırılabilir. Birçok elemanla çalışmamız varsa, bazı olaylar çalışmaya başlar, bazıları sadece adımdır, diğerleri ise işlemi sonlandırır. Böyle bir işlemi toplamak ve uzun anahtar durumundan kaçınmak için bunları örnek olarak gruplayabilir ve kullanabiliriz:

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

Misal:

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      
}  

Biraz daha gelişmiş ekleyin:

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

Yukarıda bazı hatalarımız varsa (myEvent.is (State_StatusGroup.FAIL)), önceki olaylara göre yineleyerek, para transferini geri döndürmek zorunda olup olmadığımızı kolayca kontrol edebiliriz:

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

Aşağıdakiler için yararlı olabilir:

  1. işleme mantığı hakkında açıklayıcı meta veriler dahil, hatırlanması daha az
  2. çok sayıda mirasın uygulanması
  3. sınıf yapılarını kullanmak istemiyoruz, ör. kısa durum mesajları göndermek için

13

İşte bir enumun diğer enum'a nasıl genişletileceğini bulduğum bir yol, çok düz bir yaklaşımdır:

Suposse, ortak sabitleri olan bir numaralandırmanız olduğunu varsayalım:

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

bu şekilde bir manuel genişletme yapmaya çalışabilirsiniz:

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

Tabii ki bir sabiti uzatmanız gerektiğinde SubEnum dosyalarınızı değiştirmeniz gerekir.


ilginç, biz de toString () enum kullanabilirsiniz ve sonunda dizeleri karşılaştırın; ve anahtarı kullanmak için, nesneyi bilinen bir numaraya dökmemiz gerekir; tek sorun 2 geliştiricileri aynı numaralandırma kimliği genişletme ve oluşturma ve daha sonra her iki kodu birleştirmek için olurdu :), şimdi ben enum neden uzatılamaz kalması gerektiğini anlıyorum düşünüyorum.
Kova Gücü

11

Kaçırmanız durumunda, mükemmel Joshua Bloch'un " Java Effective, 2nd edition " kitabında bir bölüm var .

  • Bölüm 6 - Numaralandırmalar ve Ek Açıklamalar
    • Madde 34: Arabirimli genişletilebilir numaralandırmaları taklit etme

Özü burada .

Sadece sonuç:

Genişletilebilir numaralandırmaları taklit etmek için arabirimlerin kullanımının küçük bir dezavantajı, uygulamaların bir numaralandırma türünden diğerine miras alınamamasıdır. Operasyon örneğimizde, bir işlemle ilişkili sembolü saklamak ve almak için mantık BasicOperation ve ExtendedOperation'da çoğaltılır. Bu durumda çok az kod kopyalandığı için önemli değil. Daha fazla miktarda paylaşılan işlevsellik varsa, kod çoğaltmasını ortadan kaldırmak için bir yardımcı sınıfta veya statik bir yardımcı yöntemde kapsülleyebilirsiniz.

Özetle, genişletilebilir bir enum türü yazamasanız da, arabirimi uygulayan temel bir enum türüyle gitmek için bir arabirim yazarak bunu taklit edebilirsiniz. Bu, istemcilerin arabirimi uygulayan kendi numaralarını yazmalarını sağlar. Bu numaralandırmalar daha sonra, API'lerin arayüz açısından yazıldığı varsayılarak, temel numaralandırma türünün kullanılabildiği her yerde kullanılabilir.


6

Numaralandırmalardan kaçınma eğilimindeyim çünkü genişletilemezler. OP örneğiyle kalmak için, A bir kitaplıkta ve B kendi kodunuzda ise, bir enum ise A'yı genişletemezsiniz. Bazen enums yerine böyle:

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

Kaçınılması gereken bazı çukurlar var, koddaki yorumlara bakın. İhtiyaçlarınıza bağlı olarak, bu numaralandırmalara sağlam, genişletilebilir bir alternatiftir.


1
örnekler için biraz sıraya ihtiyacınız varsa iyi olabilir. Ama numaralandırmalar da oldukça kullanışlı bir isim özelliği.
inor

6

Statik başlatıcıda çalışma zamanı denetimi ile numaralandırma kalıtım modelini bu şekilde geliştiriyorum. BaseKind#checkEnumExtenderEnum "uzantı" tamamen aynı şekilde taban enum bütün değerlerini beyan olmadığını kontrol eder, böylece #name()ve #ordinal()tam uyumlu kalır.

Değerleri bildirmek için hala kopyala-yapıştır var, ancak biri taban sınıfta bir değeri genişletip güncellemeden değiştirirse program hızlı bir şekilde başarısız olur.

Birbirine uzanan farklı numaralandırmalar için ortak davranış:

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

Baz enum, doğrulama yöntemi ile:

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

}

Uzantı örneği:

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 cevabına dayanarak anahtar desteği ekliyoruz,

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()Yöntemin kullanımı nedir ?
Axel Advento

Buradaki fikir arayüzde bağlıdır olduğu @AxelAdvento Dayyöntemi vardır valueOf()o switch(Day.valueOf()), o uyguladığı oluyor WeekDay, WeekEndDayçeteleler.
Khaled Lela

3

Yaklaşımdan başka bir yol almanızı öneririm.

Mevcut numaralandırmayı genişletmek yerine, daha büyük bir numara oluşturun ve alt kümesini oluşturun. Örnek olarak PET adında bir numaralandırma yaptıysanız ve bunu HAYVAN'a genişletmek istiyorsanız, bunun yerine şunları yapmalısınız:

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

Dikkatli olun, evcil hayvanlar değişmez bir koleksiyon değildir, daha fazla güvenlik için Guava veya Java9'u kullanmak isteyebilirsiniz.


2

Aynı sorunu kendim yaşadıktan sonra bakış açımı göndermek istiyorum. Böyle bir şey yapmak için birkaç motive edici faktör olduğunu düşünüyorum:

  • Bazı ilgili numaralandırma kodlarına sahip olmak istiyorsunuz, ancak farklı sınıflarda. Benim durumumda, ilişkili bir numarada tanımlanan birkaç kod içeren bir temel sınıf vardı. Daha sonraki bir tarihte (bugün!) Temel sınıfa yeni bir işlevsellik sağlamak istedim, bu da numaralandırma için yeni kodlar anlamına geliyordu.
  • Türetilmiş sınıf hem temel sınıfların numaralarını hem de kendi sınıflarını destekleyecektir. Yinelenen numaralandırma değeri yok! Öyleyse: yeni değerleri ile birlikte üst öğesinin enum'larını içeren alt sınıf için bir enum nasıl elde edilir.

Bir arayüz kullanmak gerçekten kesmez: yanlışlıkla yinelenen numaralandırma değerlerini alabilirsiniz. Arzulanan değil.

Ben sadece enums birleştirmek sona erdi: Bu ilişkili sınıfa daha az sıkı bağlı pahasına herhangi bir yinelenen değerleri olamaz sağlar. Ancak, yinelenen sorunun ana endişem olduğunu düşündüm ...


2

Bir Enum'un genişletilmesinin neden dil uygulama düzeyinde makul olmadığını anlamaya yardımcı olarak, genişletilmiş Enum'un bir örneğini yalnızca temel Enum'u anlayan bir rutine aktarırsanız ne olacağını düşünmek için makul değildir. Derleyicinin tüm vakaları kapsadığı vaat edilen bir anahtar aslında bu genişletilmiş Enum değerlerini kapsamaz.

Bu ayrıca, Java Enum değerlerinin C gibi tamsayı olmadığını vurgular, örneğin: bir Java Enum'u dizi dizini olarak kullanmak için, bir java Enum'a eklemeniz gereken rasgele bir tamsayı değeri vermek için ordinal () üyesini açıkça istemeniz gerekir. bunun için açık bir alan ve bu üyeye gönderme.

Bu OP'nin arzusu üzerine bir yorum değil, sadece Java'nın neden bunu yapmayacağı konusunda.


1

Bir meslektaşımın bu zarif çözümünün bu uzun yazıda bile görüleceği umuduyla, arayüz yaklaşımını ve ötesini takip eden alt sınıflandırma için bu yaklaşımı paylaşmak istiyorum.

Burada özel istisnalar kullandığımızı ve istisnalarla değiştirmediğiniz sürece bu kodun derlenmeyeceğini lütfen unutmayın.

Dokümantasyon kapsamlı ve umarım çoğunuz için anlaşılabilir.

Her alt sınıf numaralandırmasının uygulaması gereken arabirim.

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

Uygulayıcı ENUM temel sınıfı.

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

Temel sınıftan "miras alınan" alt sınıflı 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);
  }
}

Son olarak, bazı yardımcı programları eklemek için genel 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

Aşağıdaki gibi kodlamak için yolu:

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

LinkedHashSether girişin yalnızca bir kez var olmasını ve sıralarının korunmasını sağlar. Sipariş önemli değilse kullanabilirsiniz HashSet. Java'da şu kod mümkün değildir :

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

Kod aşağıdaki gibi yazılabilir:

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'den itibaren aşağıdakileri bile yapabilirsiniz 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");
}

Numaralandırma değişimini kullanma:

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.");
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.