2009-08-11 14 views
17

Mam kilka metod pomocniczych, które konwertują wartości wyliczeniowe na listę łańcuchów odpowiednich do wyświetlenia przez element HTML <select>. Zastanawiałem się, czy można je przekształcić w jedną metodę polimorficzną.Polimorficznie konwertuj wartości wyliczenia Java na listę łańcuchów znaków

Jest to przykład jednego z moich dotychczasowych metod:

/** 
* Gets the list of available colours. 
* 
* @return the list of available colours 
*/ 
public static List<String> getColours() { 
    List<String> colours = new ArrayList<String>(); 

    for (Colour colour : Colour.values()) { 
    colours.add(colour.getDisplayValue()); 
    } 

    return colours; 
} 

jestem jeszcze całkiem nowy rodzajowych Java, więc nie jestem pewien, jak przekazać rodzajowe enum do metody i że mają również używane w pętli for.

Zauważ, że wiem, że wszystkie wyliczenia będą miały tę metodę getDisplayValue, ale niestety nie mają wspólnego typu, który ją definiuje (i nie mogę go wprowadzić), więc myślę, że będzie to konieczne uzyskać dostęp do refleksji ...?

Z góry dziękuję za pomoc.

Odpowiedz

17

użyciu Class#getEnumConstants() jest prosta:

static <T extends Enum<T>> List<String> toStringList(Class<T> clz) { 
    try { 
     List<String> res = new LinkedList<String>(); 
     Method getDisplayValue = clz.getMethod("getDisplayValue"); 

     for (Object e : clz.getEnumConstants()) { 
      res.add((String) getDisplayValue.invoke(e)); 

     } 

     return res; 
    } catch (Exception ex) { 
     throw new RuntimeException(ex); 
    } 
} 

to nie jest całkowicie bezpieczne, ponieważ można mieć Enum bez metody getDisplayValue.

+0

Nie używa metody getDisplayValue. –

+0

zmodyfikowany, zapoznaj się z obsługą wyjątków – dfa

+0

Działa ładnie, dzięki! –

-1

(przepraszam, to jest C#. Nie widziałem, że pytanie było dla Javy.)

public string[] GetValues<T>() 
{ 
    return Enum.GetNames(typeof(T)); 
} 

Dla Java, oczywiście, wszystkie typy enum nadal dziedziczyć java.util.Enum, więc można napisać:

public string[] getValues<T extends Enum<T>>() 
{ 
    // use reflection on T.class 
} 

Od java.util.Enum nie faktycznie realizować wartości(), myślę, że odbicie jest jedynym sposobem, aby przejść.

+0

Twój kod Java nawet się nie kompiluje; FYI użyj aClass.getEnumValues ​​() w Javie zamiast Enum.GetNames (typeof (T)) – dfa

3

Są dwie rzeczy, które możesz zrobić tutaj. Pierwszy (prostszy, a zatem lepiej) droga będzie tylko mieć swoje getStrings() Metoda wziąć lista niektórych interfejsu, i twoje teksty stałe wdrożenia tego interfejsu:

public interface DisplayableSelection { 
    public String getDisplayValue(); 
} 

private static List<String> getStrings (Collection<DisplayableSelection> things) { 
    List<String> retval = new ArrayList<String>(); 
    for (DisplayableSelection thing : things) { 
    retval.add(thing.getDisplayValue()); 
    } 
} 

private static List<String> getColours() { 
    return getStrings(Colour.values()); 
} 

Jeśli naprawdę zależy wewnętrznie, że typ to Enum, możesz także użyć faktu, że wszystkie wyliczone typy automatycznie podklasują wbudowany typ Enum. Tak więc, dla przykładu (disclaimer: I myśleć to kompiluje, ale nie faktycznie próbowałem):

public interface DisplayableEnum { 
    public String getDisplayValue(); 
} 

private static <T extends Enum<T> & DisplayableEnum > List<String> getDisplayValues(Class<T> pClass) { 
    List<String> retval = new ArrayList<String>(); 
    for (DisplayableSelection thing : pClass.getEnumConstants()) { 
    retval.add(thing.getDisplayValue()); 
    } 
} 

private static List<String> getColours() { 
    return getStrings(Colour.class); 
} 

Ta druga forma może być przydatna, jeśli chcesz zrobić coś, co konkretnie wymaga wyliczenia (np użyj EnumMap lub EnumSet z jakiegoś powodu); w przeciwnym razie skorzystałbym z pierwszego (ponieważ z tą metodą można również używać typów niereklamowanych lub tylko podzbioru wyliczenia).

+0

Dzięki, ale już wyjaśniłem, że pierwsza opcja nie jest dostępna dla mnie. –

+0

+1 dla drugiej opcji, ponieważ sprawia, że ​​zaakceptowany typ odpowiedzi jest bezpieczny, upewniając się, że implementuje metodę getDisplayValue(). i usuwa odbicie. –

+0

+1 także dla drugiej opcji, bez refleksji, @Sbodd rockujesz ;-) Elegancko rozwiązałem problem, który próbowałem obejść w Javie z powodu Scala enum, implementacja errr. – virtualeyes

0

Zauważ, że wiem, że teksty stałe w pytania będą miały tę metodę getDisplayValue, ale niestety nie dzielić wspólny typ, który określa ją (i nie może wprowadzić jeden), więc domyślam się, że będzie musiało być dostępne w odbiciu ... ...

Zgadujesz poprawnie. Alternatywą byłoby, aby wszystkie enum implementowały toString(), zwracając wartość wyświetlaną - ale jeśli nie możesz ich zaimplementować, to przypuszczam, że nie jest to możliwe.

+0

Niestety, nie mogę tego zrobić, ponieważ wynik polecenia toString musi być zgodny z wartościami w bazie danych, których nie mogę zmienić! –

10

można trzymać ten sposób w pewnym klasie użytkowej:

public static <T extends Enum<T>> List<String> getDisplayValues(Class<T> enumClass) { 
    try { 
     T[] items = enumClass.getEnumConstants(); 
     Method accessor = enumClass.getMethod("getDisplayValue"); 

     ArrayList<String> names = new ArrayList<String>(items.length); 
     for (T item : items) 
      names.add(accessor.invoke(item).toString())); 

     return names; 
    } catch (NoSuchMethodException ex) { 
     // Didn't actually implement getDisplayValue(). 
    } catch (InvocationTargetException ex) { 
     // getDisplayValue() threw an exception. 
    } 
} 

Źródło: Examining Enums

+0

Nie używa metody getDisplayValue. –

+0

Tak, uświadomiłem sobie, że po tym, jak napisałem. Teraz to robi. –

+0

Skąd pochodzi funkcja Displayable? –

2

Chciałbym wykorzystać do java.util.ResourceBundle z plikiem wiązki przypisanych do (i być może nazwa klasy) wartości toString swoich teksty stałe dzięki czemu twój kod staje się czymś w rodzaju:

bundle.getString(enum.getClass().getName() + enum.toString()); 
1

Oto jak Proponuję udać się o nim:

pierwszej metodzie pomocnika i statycznej klasie wewnętrznej w klasie użytkowej gdzieś:

@SuppressWarnings("unchecked") 
    public static <T> T generateProxy(Object realObject, Class<?>... interfaces) { 
     return (T) Proxy.newProxyInstance(realObject.getClass().getClassLoader(), interfaces, new SimpleInvocationHandler(realObject)); 
    } 


    private static class SimpleInvocationHandler implements InvocationHandler { 
     private Object invokee; 

     public SimpleInvocationHandler(Object invokee) { 
      this.invokee = invokee; 
     } 

     public Object invoke(Object proxy, Method method, Object[] args) 
       throws Throwable { 
      method = invokee.getClass().getMethod(method.getName(), method.getParameterTypes()); 
      if (!method.isAccessible()) { 
       method.setAccessible(true); 
      } 
      try { 
       return method.invoke(invokee, args); 
      } catch (InvocationTargetException e) { 
       throw e.getTargetException(); 
      } 
     } 
    } 

a następnie umieścić, że wraz z wyliczenia:

interface DisplayableEnum { 
     String getDisplayValue(); 
    } 

private static List<String> getFromEnum(Class<? extends Enum<?>> displayableEnum) { 
    List<String> ret = new ArrayList<String>(); 
    for (Enum e : displayableEnum.getEnumConstants()) { 
     DisplayableEnum de = generateProxy(e, DisplayableEnum.class); 
     ret.add(de.getDisplayValue()); 
    } 
    return ret; 
} 

Jeśli wydajność jest problemem związanym z generowaniem tylu obiektów proxy, wówczas podążałbym ścieżką tworzenia zmiennej klasy, która implementuje DisplayableEnum, która może hange z każdą stałą enum (rodzaj wzorca masowego) i mają program obsługi wywołania, który jest bardziej elastyczny o swoim rzeczywistym obiekcie i wywołuje właściwy przy każdym przejściu przez pętlę.

3

Takie podejście pozwala uniknąć refleksji:

public static interface Displayer<T> { 
    String displayName(T t); 
    } 

    public static <T> List<String> getDisplayNames(Iterable<? extends T> stuff, 
     Displayer<T> displayer) { 
    List<String> list = new ArrayList<String>(); 
    for (T t : stuff) { 
     list.add(displayer.displayName(t)); 
    } 
    return list; 
    } 

... ale wymaga osobnego typu na wszystko chcesz wyświetlić:

enum Foo { 
    BAR("BAR"), BAZ("BAZ"); 
    private final String displayName; 

    private Foo(String displayName) { 
     this.displayName = displayName; 
    } 

    public String getDisplayName() { 
     return displayName; 
    } 
    } 

    public static void main(String[] args) { 
    Displayer<Foo> fooDisplayer = new Displayer<Foo>() { 
     public String displayName(Foo foo) { 
     return foo.getDisplayName(); 
     } 
    }; 

    System.out.println(getDisplayNames(Arrays.asList(Foo.BAR, Foo.BAZ), 
     fooDisplayer)); 
    } 

W tym przypadku stosowany jest typ anonimowy, ale może to być bezpaństwowy singleton lub somesuch.

0

edytowany w ten sposób metody na pierwszej odpowiedzi i to działa bez problemu i bez wykonania jakiegokolwiek interfejsu

public static <T extends Enum<T>> List<String> getDisplayValues(
     Class<T> enumClass) { 
    try { 
     T[] items = enumClass.getEnumConstants(); 
     Method accessor = enumClass.getMethod("toString"); 

     ArrayList<String> names = new ArrayList<String>(items.length); 
     for (T item : items) 
      names.add(accessor.invoke(item).toString()); 

     return names; 
    } catch (NoSuchMethodException ex) { 
     // Didn't actually implement getDisplayValue(). 
     Log.e(TAG, "getDisplayValues [" + ex+"]"); 
    } catch (InvocationTargetException ex) { 
     // getDisplayValue() threw an exception. 
     Log.e(TAG, "getDisplayValues [" + ex+"]"); 
    } catch (IllegalAccessException ex) { 
     // getDisplayValue() threw an exception. 
     Log.e(TAG, "getDisplayValues [" + ex+"]"); 
    } 
    return null; 
} 
-4

Przyjdź na facetów .. jej nie takie trudne. Im przy użyciu porównania ciąg .. ale możesz po prostu porównać typ obiektu, jeśli chcesz.

public static <T extends Enum<T>> Map<T, String> Initialize_Map(Class<T> enumClass) { 
    Map<T, String> map = new HashMap<T, String>(); 
    for (T val : enumClass.getEnumConstants()) { 
    map.put(val, val.toString() + (val.toString().equals("ENUM_ELMT") ? " (appended)" : "")); 
    } 
    return map;  
} 
Powiązane problemy