使用 java 泛型迭代枚举值

我试图找到一种在使用泛型时迭代枚举值的方法。不知道如何做到这一点,或者是否有可能。

下面的代码说明了我想要做什么。请注意,代码 值()在下面的代码中无效。

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

下面是我如何实例化 Filter 对象:

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

枚举的定义如下:

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

我意识到将枚举的值复制到列表中可能没有意义,但是我使用的库需要一个值列表作为输入,并且不能使用枚举。


2010年2月5日编辑:

提出的大多数答案都非常相似,建议采取以下措施:

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

如果我可以确定 selectedOption 具有非空值,那么这将非常有用。不幸的是,在我的用例中,这个值通常为 null,因为还有一个 Public Filter () no-arg 构造函数。这意味着我不能在不获得 NPE 的情况下执行 selectedOption.getClass ()。此筛选器类管理可用选项的列表,这些选项中有哪些被选中。当没有选择任何内容时,selectedOption 为 null。

我唯一能想到的解决这个问题的方法就是在构造函数中实际传入一个 Class。比如说:

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

有什么办法可以在不需要构造函数中额外的 Class 参数的情况下做到这一点吗?

52274 次浏览

This is a hard problem indeed. One of the things you need to do is tell java that you are using an enum. This is by stating that you extend the Enum class for your generics. However this class doesn't have the values() function. So you have to take the class for which you can get the values.

The following example should help you fix your problem:

public <T extends Enum<T>> void enumValues(Class<T> enumType) {
for (T c : enumType.getEnumConstants()) {
System.out.println(c.name());
}
}

The root problem is that you need to convert an array to a list, right? You can do this, by using a specific type (TimePeriod instead of T), and the following code.

So use something like this:

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

Now you can pass list into any method that wants a list.

If you are sure that selectedOption of the constructor Filter(T selectedOption) is not null. You can use reflection. Like this.

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

Hope this helps.

Using an unsafe cast:

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

Another option is to use 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);
}


}

If you declare Filter as

public class Filter<T extends Iterable>

then

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

And usage is quite easy:

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

Here below an example of a wrapper class around an Enum. Is a little bit weird bu is what i need :

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

}

For completeness, JDK8 gives us a relatively clean and more concise way of achieving this without the need to use the synthethic values() in Enum class:

Given a simple enum:

private enum TestEnum {
A,
B,
C
}

And a test client:

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

This will print {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 can be trivially adapted to retrieve different elements or to collect in a different way.

To get the value of the generic enumeration:

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

Note in the above method the call to the toString() method.

And then define the enumeration with such a toString() method.

public enum MyNameEnum {


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


private String name;


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


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


}

I did it like this

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