Iterar valores de enumeración usando genéricos java

81

Estoy tratando de encontrar una manera de iterar a través de los valores de una enumeración mientras uso genéricos. No estoy seguro de cómo hacer esto o si es posible.

El siguiente código ilustra lo que quiero hacer. Tenga en cuenta que el código T.values ​​() no es válido en el siguiente código.

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

Así es como crearía una instancia de un objeto Filter:

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

La enumeración se define de la siguiente manera:

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

Me doy cuenta de que puede que no tenga sentido copiar los valores de una enumeración a una lista, pero estoy usando una biblioteca que necesita una lista de valores como entrada y no funcionará con enumeraciones.


EDITAR 5/2/2010:

La mayoría de las respuestas propuestas son muy similares y sugieren hacer algo como esto:

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

Esto funcionaría muy bien si puedo estar seguro de que selectedOption tiene un valor no nulo. Desafortunadamente, en mi caso de uso, este valor a menudo es nulo, ya que también hay un constructor público Filter () sin argumentos. Esto significa que no puedo hacer un selectedOption.getClass () sin obtener un NPE. Esta clase de filtro gestiona una lista de opciones disponibles cuál de las opciones está seleccionada. Cuando no se selecciona nada, selectedOption es nulo.

Lo único que puedo pensar para resolver esto es pasar una clase en el constructor. Entonces algo como esto:

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

¿Alguna idea de cómo hacer esto sin necesidad de un parámetro Class adicional en los constructores?

Tauren
fuente
1
El getClasscódigo también fallará si la constante de enumeración es una subclase anónima ( ejemplo ). getDeclaringClassdebe usarse en su lugar.
Radiodef

Respuestas:

125

Este es un problema realmente difícil. Una de las cosas que debe hacer es decirle a java que está usando una enumeración. Esto es indicando que amplía la clase Enum para sus genéricos. Sin embargo, esta clase no tiene la función values ​​(). Entonces tienes que tomar la clase para la que puedas obtener los valores.

El siguiente ejemplo debería ayudarlo a solucionar su problema:

public <T extends Enum<T>> void enumValues(Class<T> enumType) {
        for (T c : enumType.getEnumConstants()) {
             System.out.println(c.name());
        }
}
Thirler
fuente
¿No devuelve c.name () el nombre del elemento de enumeración? ¿Qué tal devolver su valor? Diga, para un elemento QUIZ ("quizzie"), devolviendo el "quizzie" y no el QUIZ.
Stephane
Para eso tendría que tener un método en la enumeración que devuelve el valor de decir: getFriendlyName(). Luego, tendría que agregar una interfaz a su enumeración y luego adaptar los genéricos anteriores para requerir tanto una enumeración como la interfaz para el tipo T. La función se convierte en algo como:public <E extends Enum<?> & EnumWithFriendlyName> void friendlyEnumValues(Class<T> enumType)
Thirler
16

Otra opción es usar 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);
    } 

}
Variable miserable
fuente
6

Para completar, JDK8 nos brinda una forma relativamente limpia y más concisa de lograr esto sin la necesidad de usar el sintético values()en la clase Enum:

Dada una enumeración simple:

private enum TestEnum {
    A,
    B,
    C
}

Y un cliente de prueba:

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

Esto imprimirá {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(", " , "\"{", "}\""));
}

El código se puede adaptar trivialmente para recuperar diferentes elementos o para recopilar de una manera diferente.

cuántico
fuente
5

Usando un yeso inseguro:

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
fuente
getClassfallará si la constante de enumeración es una subclase anónima ( ejemplo ). getDeclaringClassdebe usarse en su lugar.
Radiodef
1

Si está seguro de que selectedOptionel constructor Filter(T selectedOption)no es nulo. Puedes usar la reflexión. Me gusta esto.

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

Espero que esto ayude.

NawaMan
fuente
1

Para obtener el valor de la enumeración genérica:

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

Tenga en cuenta en el método anterior la llamada al método toString ().

Y luego defina la enumeración con dicho método toString ().

public enum MyNameEnum {

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

  private String name;

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

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

}
Stephane
fuente
0

El problema principal es que necesitas convertir una matriz en una lista, ¿verdad? Puede hacer esto, usando un tipo específico (TimePeriod en lugar de T) y el siguiente código.

Entonces usa algo como esto:

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

Ahora puede pasar la lista a cualquier método que desee una lista.

Steve McLeod
fuente
0

Si declara Filtro como

public class Filter<T extends Iterable>

entonces

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

Y el uso es bastante sencillo:

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ć
fuente
Enfoque interesante. Creo que esto funcionaría sin necesidad de declarar Class <T>. Sin embargo, prefiero no tener que cambiar todas mis enumeraciones, así que me inclino por un método diferente.
Tauren
0

A continuación, se muestra un ejemplo de una clase contenedora en torno a un Enum. Es un poco extraño pero es lo que necesito:

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

}

Benjamín Fuentes
fuente
0

Lo hice asi

    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
fuente