Iterieren Sie Enum-Werte mit Java-Generika

81

Ich versuche einen Weg zu finden, um die Werte einer Aufzählung zu durchlaufen, während Generika verwendet werden. Ich bin mir nicht sicher, wie ich das machen soll oder ob es möglich ist.

Der folgende Code zeigt, was ich tun möchte. Beachten Sie, dass der Code T.values ​​() im folgenden Code nicht gültig ist.

public class Filter<T> {
    private List<T> availableOptions = new ArrayList<T>();
    private T selectedOption;

    public Filter(T selectedOption) {
        this.selectedOption = selectedOption;
        for (T option : T.values()) {  // INVALID CODE
            availableOptions.add(option);
        }
    }
}

So würde ich ein Filterobjekt instanziieren:

Filter<TimePeriod> filter = new Filter<TimePeriod>(TimePeriod.ALL);

Die Aufzählung ist wie folgt definiert:

public enum TimePeriod {
    ALL("All"), 
    FUTURE("Future"), 
    NEXT7DAYS("Next 7 Days"), 
    NEXT14DAYS("Next 14 Days"), 
    NEXT30DAYS("Next 30 Days"), 
    PAST("Past"),
    LAST7DAYS("Last 7 Days"), 
    LAST14DAYS("Last 14 Days"),
    LAST30DAYS("Last 30 Days"); 

    private final String name;

    private TimePeriod(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name;
    }
}

Mir ist klar, dass es möglicherweise nicht sinnvoll ist, die Werte einer Aufzählung in eine Liste zu kopieren, aber ich verwende eine Bibliothek, die eine Liste von Werten als Eingabe benötigt und nicht mit Aufzählungen funktioniert.


BEARBEITEN 05.02.2010:

Die meisten der vorgeschlagenen Antworten sind sehr ähnlich und schlagen Folgendes vor:

class Filter<T extends Enum<T>> {
    private List<T> availableOptions = new ArrayList<T>();
    private T selectedOption;

    public Filter(T selectedOption) {
        Class<T> clazz = (Class<T>) selectedOption.getClass();
        for (T option : clazz.getEnumConstants()) {
            availableOptions.add(option);
        }
    }
}

Dies würde gut funktionieren, wenn ich sicher sein kann, dass selectedOption einen Wert ungleich Null hat. Leider ist dieser Wert in meinem Anwendungsfall oft null, da es auch einen öffentlichen Filter () no-arg-Konstruktor gibt. Dies bedeutet, dass ich eine selectedOption.getClass () nicht ausführen kann, ohne eine NPE zu erhalten. Diese Filterklasse verwaltet eine Liste der verfügbaren Optionen, welche der Optionen ausgewählt ist. Wenn nichts ausgewählt ist, ist selectedOption null.

Das einzige, was ich denken kann, um dies zu lösen, ist, tatsächlich eine Klasse im Konstruktor zu übergeben. Also so etwas wie das:

class Filter<T extends Enum<T>> {
    private List<T> availableOptions = new ArrayList<T>();
    private T selectedOption;

    public Filter(Class<T> clazz) {
        this(clazz,null);
    }

    public Filter(Class<T> clazz, T selectedOption) {
        this.selectedOption = selectedOption;
        for (T option : clazz.getEnumConstants()) {
            availableOptions.add(option);
        }
    }
}

Haben Sie Ideen, wie Sie dies tun können, ohne einen zusätzlichen Klassenparameter in den Konstruktoren zu benötigen?

Tauren
quelle
1
Der getClassCode schlägt auch fehl, wenn die Enum-Konstante eine anonyme Unterklasse ist ( Beispiel ). getDeclaringClasssollte stattdessen verwendet werden.
Radiodef

Antworten:

125

Dies ist in der Tat ein schwieriges Problem. Eines der Dinge, die Sie tun müssen, ist Java mitzuteilen, dass Sie eine Aufzählung verwenden. Dies geschieht, indem Sie angeben, dass Sie die Enum-Klasse für Ihre Generika erweitern. Diese Klasse hat jedoch nicht die Funktion values ​​(). Sie müssen also die Klasse nehmen, für die Sie die Werte erhalten können.

Das folgende Beispiel soll Ihnen helfen, Ihr Problem zu beheben:

public <T extends Enum<T>> void enumValues(Class<T> enumType) {
        for (T c : enumType.getEnumConstants()) {
             System.out.println(c.name());
        }
}
Thirler
quelle
Gibt c.name () nicht den Namen des Aufzählungselements zurück? Wie wäre es mit der Rückgabe seines Wertes? Angenommen, für ein QUIZ-Element ("Quizzie") wird das "Quizzie" und nicht das QUIZ zurückgegeben.
Stephane
Dazu benötigen Sie eine Methode in der Aufzählung, die den Wert zurückgibt. Sagen Sie : getFriendlyName(). Dann müssten Sie Ihrer Aufzählung eine Schnittstelle hinzufügen und dann die oben genannten Generika anpassen, um sowohl eine Aufzählung als auch die Schnittstelle für den Typ zu benötigen T. Die Funktion wird dann so etwas wie:public <E extends Enum<?> & EnumWithFriendlyName> void friendlyEnumValues(Class<T> enumType)
Thirler
16

Eine andere Option ist die Verwendung von EnumSet:

class PrintEnumConsants {

    static <E extends Enum <E>> void foo(Class<E> elemType) {
        for (E e : java.util.EnumSet.allOf(elemType)) {
            System.out.println(e);
        }
    }

    enum Color{RED,YELLOW,BLUE};
    public static void main(String[] args) {
        foo(Color.class);
    } 

}
Elende Variable
quelle
6

Der Vollständigkeit halber bietet JDK8 eine relativ saubere und präzisere Möglichkeit, dies zu erreichen, ohne die Synthethik verwenden zu müssen values() in der Enum-Klasse verwenden zu müssen:

Bei einer einfachen Aufzählung:

private enum TestEnum {
    A,
    B,
    C
}

Und ein Testclient:

@Test
public void testAllValues() {
    System.out.println(collectAllEnumValues(TestEnum.class));
}

Dies wird gedruckt {A, B, C}:

public static <T extends Enum<T>> String collectAllEnumValues(Class<T> clazz) {
    return EnumSet.allOf(clazz).stream()
            .map(Enum::name)
            .collect(Collectors.joining(", " , "\"{", "}\""));
}

Code kann trivial angepasst werden, um verschiedene Elemente abzurufen oder auf andere Weise zu sammeln.

Quantum
quelle
5

Verwenden einer unsicheren Besetzung:

class Filter<T extends Enum<T>> {
    private List<T> availableOptions = new ArrayList<T>();
    private T selectedOption;

    public Filter(T selectedOption) {
        Class<T> clazz = (Class<T>) selectedOption.getClass();
        for (T option : clazz.getEnumConstants()) {
            availableOptions.add(option);
        }
    }
}
Bozho
quelle
getClassschlägt fehl, wenn die Enum-Konstante eine anonyme Unterklasse ist ( Beispiel ). getDeclaringClasssollte stattdessen verwendet werden.
Radiodef
1

Wenn Sie sicher sind, dass selectedOptionder Konstruktor Filter(T selectedOption)nicht null ist. Sie können Reflexion verwenden. So was.

public class Filter<T> {
    private List<T> availableOptions = new ArrayList<T>();
    private T selectedOption;

    public Filter(T selectedOption) {
        this.selectedOption = selectedOption;
        for (T option : this.selectedOption.getClass().getEnumConstants()) {  // INVALID CODE
            availableOptions.add(option);
        }
    }
}

Hoffe das hilft.

NawaMan
quelle
1

So ermitteln Sie den Wert der generischen Aufzählung:

  protected Set<String> enum2set(Class<? extends Enum<?>> e) {
    Enum<?>[] enums = e.getEnumConstants();
    String[] names = new String[enums.length];
    for (int i = 0; i < enums.length; i++) {
      names[i] = enums[i].toString();
    }
    return new HashSet<String>(Arrays.asList(names));
  }

Beachten Sie in der obigen Methode den Aufruf der toString () -Methode.

Und dann definieren Sie die Aufzählung mit einer solchen toString () -Methode.

public enum MyNameEnum {

  MR("John"), MRS("Anna");

  private String name;

  private MyNameEnum(String name) {
    this.name = name;
  }

  public String toString() {
    return this.name;
  }

}
Stephane
quelle
0

Das Hauptproblem ist, dass Sie ein Array in eine Liste konvertieren müssen, oder? Sie können dies tun, indem Sie einen bestimmten Typ (TimePeriod anstelle von T) und den folgenden Code verwenden.

Verwenden Sie also so etwas:

List<TimePeriod> list = new ArrayList<TimePeriod>();
list.addAll(Arrays.asList(sizes));

Jetzt können Sie die Liste an jede Methode übergeben, die eine Liste haben möchte.

Steve McLeod
quelle
0

Wenn Sie Filter als deklarieren

public class Filter<T extends Iterable>

dann

import java.util.Iterator;

public enum TimePeriod implements Iterable {
    ALL("All"),
    FUTURE("Future"),
    NEXT7DAYS("Next 7 Days"),
    NEXT14DAYS("Next 14 Days"),
    NEXT30DAYS("Next 30 Days"),
    PAST("Past"),
    LAST7DAYS("Last 7 Days"),
    LAST14DAYS("Last 14 Days"),
    LAST30DAYS("Last 30 Days");

    private final String name;

    private TimePeriod(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name;
    }

    public Iterator<TimePeriod> iterator() {
        return new Iterator<TimePeriod>() {

            private int index;

            @Override
            public boolean hasNext() {
                return index < LAST30DAYS.ordinal();
            }

            @Override
            public TimePeriod next() {
                switch(index++) {
                    case    0   : return        ALL;
                    case    1   : return        FUTURE;
                    case    2   : return        NEXT7DAYS;
                    case    3   : return        NEXT14DAYS;
                    case    4   : return        NEXT30DAYS;
                    case    5   : return        PAST;
                    case    6   : return        LAST7DAYS;
                    case    7   : return        LAST14DAYS;
                    case    8   : return        LAST30DAYS;
                    default: throw new IllegalStateException();
                }
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }
}

Und die Verwendung ist ganz einfach:

public class Filter<T> {
    private List<T> availableOptions = new ArrayList<T>();
    private T selectedOption;

    public Filter(T selectedOption) {
        this.selectedOption = selectedOption;
        Iterator<TimePeriod> it = selectedOption.iterator();
        while(it.hasNext()) {
            availableOptions.add(it.next());
        }
    }
}
Boris Pavlović
quelle
Interessanter Ansatz. Ich denke, dies würde funktionieren, ohne dass die Klasse <T> deklariert werden muss. Ich würde es jedoch vorziehen, nicht alle meine Aufzählungen ändern zu müssen, also neige ich zu einer anderen Methode.
Tauren
0

Hier unten ein Beispiel einer Wrapper-Klasse um eine Aufzählung. Ist ein bisschen komisch bu ist was ich brauche:

public class W2UIEnum<T extends Enum<T> & Resumable> {

public String id;
public String caption;

public W2UIEnum(ApplicationContext appContext, T t) {
    this.id = t.getResume();
    this.caption = I18N.singleInstance.getI18nString(t.name(), "enum_"
            + t.getClass().getSimpleName().substring(0, 1).toLowerCase()
            + t.getClass().getSimpleName().substring(1,
                    t.getClass().getSimpleName().length()), appContext
            .getLocale());
}

public static <T extends Enum<T> & Resumable> List<W2UIEnum<T>> values(
        ApplicationContext appContext, Class<T> enumType) {
    List<W2UIEnum<T>> statusList = new ArrayList<W2UIEnum<T>>();
    for (T status : enumType.getEnumConstants()) {
        statusList.add(new W2UIEnum(appContext, status));
    }
    return statusList;
}

}}

Benjamin Fuentes
quelle
0

Ich habe es so gemacht

    protected List<String> enumToList(Class<? extends Enum<?>> e) {
            Enum<?>[] enums = e.getEnumConstants();
            return Arrays.asList(enums).stream()
                   .map(name -> name.toString())
                   .collect(Collectors.toList());
        }
Pradeep Sreeram
quelle