Ітерація значень переліку, використовуючи дженерики Java


81

Я намагаюся знайти спосіб перебору значень переліку, використовуючи загальні засоби. Не знаю, як це зробити або якщо це можливо.

Наступний код ілюструє, що я хочу зробити. Зверніть увагу, що код T.values ​​() не є дійсним у наступному коді.

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

Я усвідомлюю, що, можливо, не має сенсу копіювати значення перерахування в список, але я використовую бібліотеку, яка потребує списку значень як вхідні дані і не працюватиме з переліченнями.


РЕДАКТУВАТИ 02.02.2010:

Більшість запропонованих відповідей дуже схожі і пропонують зробити щось подібне:

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 має ненульове значення. На жаль, у моєму випадку використання це значення часто є нульовим, оскільки існує також публічний конструктор Filter () no-arg. Це означає, що я не можу зробити selectedOption.getClass (), не отримавши NPE. Цей клас фільтра керує списком доступних параметрів, який із них вибрано. Якщо нічого не вибрано, selectedOption має значення null.

Єдине, що я можу подумати, щоб вирішити це, - це насправді передати клас у конструкторі. Отож приблизно так:

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 у конструкторах?


1
getClassКод також не , якщо константа перерахування є анонімним підклас ( приклад ). getDeclaringClassслід використовувати замість цього.
Radiodef

Відповіді:


125

Це справді важка проблема. Однією з речей, яку вам потрібно зробити, є сказати java, що ви використовуєте перерахування. Це заявляючи, що ви розширюєте клас Enum для своїх загальних препаратів. Однак цей клас не має функції values ​​(). Отже, ви повинні взяти клас, для якого ви можете отримати значення.

Наступний приклад повинен допомогти вам вирішити проблему:

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

Чи не повертає c.name () ім'я елемента перерахування? Як щодо повернення його вартості? Скажімо, для пункту QUIZ ("quizzie") повертається "quizzie", а не QUIZ.
Стефан

Для цього вам необхідно буде мати метод на перерахування , який повертає значення слово: getFriendlyName(). Тоді вам доведеться додати інтерфейс до вашого переліку, а потім адаптувати описані вище загальні вимоги, щоб вимагати як перелік, так і інтерфейс для типу T. Потім функція стає приблизно public <E extends Enum<?> & EnumWithFriendlyName> void friendlyEnumValues(Class<T> enumType)
такою

17

Інший варіант - використовувати 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);
    } 

}

6

Для повноти, JDK8 дає нам відносно чистий і лаконічний спосіб досягти цього без необхідності використовувати синтетичний values()у класі Enum:

Враховуючи простий перелік:

private enum TestEnum {
    A,
    B,
    C
}

І тестовий клієнт:

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

Буде надруковано {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(", " , "\"{", "}\""));
}

Код може бути тривіально адаптований для отримання різних елементів або для збору по-різному.


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

getClassне вдасться, якщо константа перерахування є анонімним підкласом ( приклад ). getDeclaringClassслід використовувати замість цього.
Radiodef

1

Якщо ви впевнені, що selectedOptionконструктор Filter(T selectedOption)не має значення null. Можна використовувати рефлексію. Подобається це.

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

Сподіваюся, це допомагає.


1

Щоб отримати значення загального переліку:

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

Зверніть увагу на наведений вище метод виклику методу toString ().

А потім визначте перелік таким методом toString ().

public enum MyNameEnum {

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

  private String name;

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

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

}

0

Основна проблема полягає в тому, що вам потрібно перетворити масив у список, так? Ви можете зробити це, використовуючи певний тип (TimePeriod замість T) і наступний код.

Тож використовуйте щось подібне:

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

Тепер ви можете передати список у будь-який метод, який хоче створити список.


0

Якщо ви оголосите фільтр як

public class Filter<T extends Iterable>

тоді

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

І використання досить просто:

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

Цікавий підхід. Я думаю, це спрацювало б без необхідності оголошувати клас <T>. Однак я вважаю за краще не змінювати всі свої переліки, тому я схиляюся до іншого методу.
Таурен

0

Тут нижче приклад класу обгортки навколо Enum. Це трохи дивно, але це те, що мені потрібно:

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

}


0

Я зробив це так

    protected List<String> enumToList(Class<? extends Enum<?>> e) {
            Enum<?>[] enums = e.getEnumConstants();
            return Arrays.asList(enums).stream()
                   .map(name -> name.toString())
                   .collect(Collectors.toList());
        }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.