2012-07-15 29 views
42

Mam rodzajowego interfejsu takiego:Jak zaimplementować wyliczanie za pomocą generycznych?

interface A<T> { 
    T getValue(); 
} 

Interfejs ten ma ograniczone instancje, dlatego najlepiej byłoby wdrożyć je jako wartości enum. Problemem jest to, te przypadki mają inny typ wartości, więc próbowałem następujące podejście, ale nie kompilacji:

public enum B implements A { 
    A1<String> { 
     @Override 
     public String getValue() { 
      return "value"; 
     } 
    }, 
    A2<Integer> { 
     @Override 
     public Integer getValue() { 
      return 0; 
     } 
    }; 
} 

pojęcia o tym?

Odpowiedz

42

Nie możesz. Java nie zezwala na typy ogólne dla stałych enum. Są one akceptowane na typów enum, choć:

public enum B implements A<String> { 
    A1, A2; 
} 

Co można zrobić w tym przypadku jest albo mieć typ enum dla każdego typu rodzajowego lub „fałszywe” posiadające enum po prostu czyni go Klasa:

public class B<T> implements A<T> { 
    public static final B<String> A1 = new B<String>(); 
    public static final B<Integer> A2 = new B<Integer>(); 
    private B() {}; 
} 

Niestety, obie mają wady.

+1

To podejście nie rozwiązuje mojego problemu, chyba że używam 'enum B implemts A ', co z kolei sprawia, że ​​ogólny interfejs jest bez znaczenia. –

+6

Właśnie o to chodzi - twój problem nie może zostać rozwiązany za pomocą pojedynczego 'Enum'. – Jorn

+0

Teraz przynajmniej rozumiem, że słowo "enum" nie jest odpowiednie dla moich wymagań. Dzięki! –

24

Jako programiści Javy projektujący niektóre interfejsy API często napotykamy ten problem. Byłem potwierdzając moje własne wątpliwości, gdy natknąłem się na ten post, ale mam opisowy obejście do niego:

// class name is awful for this example, but it will make more sense if you 
// read further 
public interface MetaDataKey<T extends Serializable> extends Serializable 
{ 
    T getValue(); 
} 

public final class TypeSafeKeys 
{ 
    static enum StringKeys implements MetaDataKey<String> 
    { 
     A1("key1"); 

     private final String value; 

     StringKeys(String value) { this.value = value; } 

     @Override 
     public String getValue() { return value; } 
    } 

    static enum IntegerKeys implements MetaDataKey<Integer> 
    { 
     A2(0); 

     private final Integer value; 

     IntegerKeys (Integer value) { this.value = value; } 

     @Override 
     public Integer getValue() { return value; } 
    } 

    public static final MetaDataKey<String> A1 = StringKeys.A1; 
    public static final MetaDataKey<Integer> A2 = IntegerKeys.A2; 
} 

w tym momencie, można uzyskać korzyści z bycia prawdziwie stały enum wartość chłodnika (i cały korzyści, które pasują do tego), a także jest unikalną implementacją interface, ale masz globalną dostępność pożądaną przez enum.

Dodaje to wyraźnie gadatliwości, co stwarza możliwość popełnienia błędów podczas kopiowania/wklejania. Możesz zrobić enum s public i po prostu dodać dodatkową warstwę do ich dostępu.

Projekty, które mają tendencję do korzystania z tych funkcji, zwykle cierpią z powodu kruchej implementacji equals, ponieważ zwykle są połączone z inną unikatową wartością, na przykład nazwą, która może zostać nieświadomie zduplikowana w całym kodzie w podobny, ale inny sposób. Używając całych kart, równość to freebie, która jest odporna na takie kruche zachowanie.

Główną wadą takiego systemu, poza szczegółowością, jest zamiana między kluczami unikatowymi na całym świecie (np. Przesyłanie do JSON). Jeśli są tylko kluczami, to można je bezpiecznie przywrócić (zduplikować) kosztem marnowania pamięci, ale używając słabości - equals - to zaleta.

Istnieje obejście tego, który zapewnia globalną niepowtarzalność realizacji przez zaśmiecania go z anonimowego typu na światowym przykład:

public abstract class BasicMetaDataKey<T extends Serializable> 
    implements MetaDataKey<T> 
{ 
    private final T value; 

    public BasicMetaDataKey(T value) 
    { 
     this.value = value; 
    } 

    @Override 
    public T getValue() 
    { 
     return value; 
    } 

    // @Override equals 
    // @Override hashCode 
} 

public final class TypeSafeKeys 
{ 
    public static final MetaDataKey<String> A1 = 
     new BasicMetaDataKey<String>("value") {}; 
    public static final MetaDataKey<Integer> A2 = 
     new BasicMetaDataKey<Integer>(0) {}; 
} 

pamiętać, że każda instancja korzysta anonimową realizację, ale potrzebna jest nic innego, aby je realizować , więc {} są puste. Jest to zarówno mylące, jak i denerwujące, ale działa, jeśli odwołania do instancji są lepsze, a bałagan jest ograniczony do minimum, chociaż może być nieco zagadkowy dla mniej doświadczonych programistów Java, przez co trudniej jest je utrzymać.

Wreszcie, jedynym sposobem na zapewnienie globalnej wyjątkowości i przeniesienie jest nieco bardziej twórcze z tym, co się dzieje.Najczęstszym zastosowaniem dla globalnie udostępnianych interfejsów, które widziałem są dla metadanych wiader, które mają tendencję do mieszania wielu różnych wartościach, z różnych typów (w T, w przeliczeniu na podstawie klucza):

public interface MetaDataKey<T extends Serializable> extends Serializable 
{ 
    Class<T> getType(); 
    String getName(); 
} 

public final class TypeSafeKeys 
{ 
    public static enum StringKeys implements MetaDataKey<String> 
    { 
     A1; 

     @Override 
     public Class<String> getType() { return String.class; } 

     @Override 
     public String getName() 
     { 
      return getDeclaringClass().getName() + "." + name(); 
     } 
    } 

    public static enum IntegerKeys implements MetaDataKey<Integer> 
    { 
     A2; 

     @Override 
     public Class<Integer> getType() { return Integer.class; } 

     @Override 
     public String getName() 
     { 
      return getDeclaringClass().getName() + "." + name(); 
     } 
    } 

    public static final MetaDataKey<String> A1 = StringKeys.A1; 
    public static final MetaDataKey<Integer> A2 = IntegerKeys.A2; 
} 

Zapewnia to taka sama elastyczność, jak w przypadku pierwszej opcji, i zapewnia mechanizm do uzyskania odniesienia za pośrednictwem refleksji, jeśli okaże się to konieczne później, w związku z tym unikając potrzeby późniejszego określenia. Pozwala to również uniknąć wielu błędów związanych z kopiowaniem/wklejaniem, które zapewnia pierwsza opcja, ponieważ nie zostaną skompilowane, jeśli pierwsza metoda jest błędna, a druga metoda nie musi być zmieniana. Jedyna uwaga to to, że powinieneś upewnić się, że enum, które mają być używane w ten sposób, to: public, aby uniknąć błędów dostępu, ponieważ nie mają one dostępu do wewnętrznej domeny. jeśli nie chcesz, aby te przewody przechodziły przez przewód połączony z marshallem, możesz je ukryć przed zewnętrznymi paczkami, aby automatycznie je odrzucić (podczas marshallingu sprawdź odblaskowo, czy dostępny jest enum, a jeśli nie, następnie zignoruj ​​klucz/wartość). Nie ma nic zyskanego ani zagubionego przez uczynienie go public, z wyjątkiem zapewnienia dwóch sposobów dostępu do instancji, jeśli bardziej oczywiste są odniesienia static (ponieważ instancje enum są po prostu takowe).

Po prostu żałuję, że nie zrobili tego, aby enum s mógł rozszerzać obiekty w Javie. Może w Javie 9?

Ostateczna opcja tak naprawdę nie rozwiązuje twoich potrzeb, ponieważ prosiłeś o wartości, ale podejrzewam, że zbliża się to do rzeczywistego celu.

+0

Wielkie dzięki za udostępnienie tego obejścia! Właściwie nie musisz używać 'wyliczenia', po prostu korzystaj z anonimowych klas wewnętrznych, co właśnie teraz robię. –

+1

Bez problemu. Anonimowa klasa wewnętrzna jest środkowym przykładem. Staram się unikać anonimowych klas dla łatwości konserwacji (oba skompilowane kody stają się mylące z anonimowymi typami "$ 1", "$ 2", a wielu programistów nie dostrzeże powodu bycia typem anonimowym), ale z pewnością jest to poprawne i mniejsze podejście. – pickypg

+0

Naprawdę doceniam tę odpowiedź. Dziękuję za Twój wysiłek. –

Powiązane problemy