2016-11-10 5 views
2

Примечание: этот вопрос больше о дженериках, чем о перечислениях.Как получить константу перечисления из первого подходящего типа перечисления

У меня есть несколько типов перечислений, все реализующие общий интерфейс IEffect.

Например

enum ElementalEffect implements IEffect { 
    FIRE, WATER; 
} 
enum CombatEffect implements IEffect { 
    PARALYSIS, SLEEP; 
} 

Я хотел бы разобрать конфигурационный файл, который должен добавлять эффекты к оружию. Для этого я должен разрешить данное имя для одного из эффектов. Для того, чтобы сохранить его простым в обслуживании, я думал о написании метода, как это (. В основном псевдо-код, это не компилируется На самом деле точка вопрос в том, как сделать эту компиляцию):

IEffect resolveEffectName(String name, Class... clazzes) { 
    for(Class clazz : clazzes) { 
     try { 
      return Enum.valueOf(clazz, name); 
     } catch(IllegalArgumentException) { /* ignore, try next class */} 
    } 
    throw new IllegalArgumentException("No matching effect found for " + name); 
} 
// resolveEffectName(readNameFromFile, ElementalEffect.class, CombatEffect.class); 

Теперь у меня проблема в том, что я не могу понять, как написать этот метод без компилятора говорит мне

метод valueOf(Class<T>, String) в типе Enum не применим для аргументов ...


Люди говорят, что должно быть

private static ICombatEffectType getFirstResolved(String name, Class<? extends Enum<?>>... classes) { 
    for (Class<? extends Enum<?>> clazz : classes) { 
     try { 
      return Enum.valueOf(clazz, name); 
     } catch (IllegalArgumentException e) { 
     } 
    } 
    return null; 
} 

Это не работает. Не стесняйтесь попробовать (если вы мне не верите).

Метод valueOf(Class<T>, String) в типе Enum не применяется для аргументов (Class<capture#6-of ? extends Enum<?>>, String)

+1

'Класс > ... clazzes' –

+0

Это не работает, потому что захваты для '?' в классе и один для вложенного Enum отличаются друг от друга, но должны быть одинаковыми, чтобы иметь возможность вызвать Enum.valueOf – WorldSEnder

+0

Hm , правда. Но учтите, что если у вас возникли проблемы с написанием этого типа безопасным способом, вам просто нужно будет найти совершенно другой подход. –

ответ

2

Вы можете написать это свободно, если это ваш стиль:

class FluentGetter { 
    private final String name; 
    private IEffect found; 

    FluentGetter(String name) { this.name = name; } 

    <T extends Enum<T> & IEffect> FluentGetter search(Class<T> clazz) { 
    if (found == null) { // If you've already found something, don't overwrite that. 
     try { 
     found = Enum.valueOf(clazz, name); 
     } catch (IllegalArgumentException e) {} 
    } 
    return this; 
    } 

    IEffect get() { 
    return found; // + check if it's null, if you want. 
    } 
} 

Тогда:

IEffect effect = 
    new FluentGetter(name) 
     .search(ElementalEffect.class) 
     .search(CombatEffect.class) 
     .get(); 

Это позволяет избежать проблемы общих границ в массиве классов, имея отдельные вызовы методов для каждого из них.

Довольно уверен, что я не буду использовать это сам; просто бросая его в качестве опции.

+0

Мне это нравится. Поскольку только определенные классы эффектов всех возможных будут включены для определенного типа * *, это делает поиск только некоторых из них очень легким – WorldSEnder

0

Это должно составить (с предупреждениями), используя первый метод:

return (IEffect) Enum.valueOf((Class<Enum>) clazz, name); 
1

Что вы действительно хотите представляет собой карту имен для констант перечисления, которую вы можете легко создать с использованием потоков:

private static final Map<String, IEffect> constants 
    = Stream.of(ElementalEffect.values(), CombatEffect.values()) 
     .flatMap(Arrays::stream) 
     .collect(Collectors.toMap(Enum::name, Function.identity())); 

IEffect resolveEffectName(String name) { 
    if(!constants.containsKey(name)) 
     throw new IllegalArgumentException("No matching effect found for " + name); 

    return constants.get(name); 
} 
+0

Несомненно, вы можете передать только одно значение в 'clazzes'? –

+0

@ AndyTurner Вы правы, я думал, что ковариации магов будет достаточно. хм, теперь я просто сбиваю с толку. –

+0

Первый способ не работает, так как он требует того же типа привязки для каждого класса enum. Но вы правы в своей второй точке, я должен прекомпретировать сопоставление и нормализовать имена – WorldSEnder

1

Я нашел способ сделать все работы без предупреждений с помощью класса-оболочки:

private static class Wrapper<T extends Enum<T> & IEffect> { 
    private Class<T> clazz; 

    public Wrapper(Class<T> clazz) { 
     this.clazz = clazz; 
    } 

    public IEffect resolveName(String name) { 
     return Enum.valueOf(clazz, name); 
    } 
} 
private static IEffect getFirstResolved(String name, Wrapper<?>... clazzes) { 
    for (Wrapper<?> clazz : clazzes) { 
     try { 
      return clazz.resolveName(name); 
     } catch (IllegalArgumentException e) {} 
    } 
    throw new IllegalArgumentException("No enum has a member " + name); 
} 

// Example call 
ICombatEffectType elemental = getFirstResolved(
      type, 
      new Wrapper<>(ElementalType.class), 
      new Wrapper<>(StatusEffect.class)); 
+0

Хороший способ, но я думаю, что обертка не помогает. У вас не должно быть проблем с предупреждением, если вы правильно набираете текст, и даже если у вас это есть, очень жаль, что вы должны усовершенствовать свой дизайн для обработки этого – davidxxx

+0

. Что делает обертка, это обеспечить компилятор, что в классе > 'обе анонимные записи одинаковы (необходимо, чтобы иметь возможность вызвать Enum # valueOf с этим классом) – WorldSEnder

Смежные вопросы