Поиск значения enum с помощью Java 8 Stream API
Предположим, что существует простое перечисление, называемое Type, определенное следующим образом:
enum Type{
X("S1"),
Y("S2");
private String s;
private Type(String s) {
this.s = s;
}
}
Поиск правильного перечисления для данного s
выполняется тривиально со статическим методом с помощью for-loop (предположим, что метод определен внутри enum), например:
private static Type find(String val) {
for (Type e : Type.values()) {
if (e.s.equals(val))
return e;
}
throw new IllegalStateException(String.format("Unsupported type %s.", val));
}
Я думаю, что функциональный эквивалент, выраженный с помощью Stream API, будет примерно таким:
private static Type find(String val) {
return Arrays.stream(Type.values())
.filter(e -> e.s.equals(val))
.reduce((t1, t2) -> t1)
.orElseThrow(() -> {throw new IllegalStateException(String.format("Unsupported type %s.", val));});
}
Как мы можем написать это лучше и проще? Этот код чувствует себя принужденным и не очень ясным. reduce()
особенно кажется неуклюжим и жестоким, поскольку он ничего не накапливает, не выполняет вычисления и всегда просто возвращает t1
(если фильтр возвращает одно значение - если это не так явно катастрофа), не говоря уже о t2
есть лишнее и запутанное. Тем не менее я не смог найти ничего в Stream API, который просто как-то возвращает непосредственно T
из Stream<T>
.
Есть ли лучший способ?
Ответы
Ответ 1
Вместо этого я использовал бы findFirst
:
return Arrays.stream(Type.values())
.filter(e -> e.s.equals(val))
.findFirst()
.orElseThrow(() -> new IllegalStateException(String.format("Unsupported type %s.", val)));
<ч/" > Хотя Map
может быть лучше в этом случае:
enum Type{
X("S1"),
Y("S2");
private static class Holder {
static Map<String, Type> MAP = new HashMap<>();
}
private Type(String s) {
Holder.MAP.put(s, this);
}
public static Type find(String val) {
Type t = Holder.MAP.get(val);
if(t == null) {
throw new IllegalStateException(String.format("Unsupported type %s.", val));
}
return t;
}
}
Я узнал этот трюк из этого . В основном загрузчик классов инициализирует статические классы перед классом enum, что позволяет вам заполнить Map
в самом конструкторе перечисления. Очень удобно!
Надеюсь, это поможет!:)
Ответ 2
Принятый ответ работает хорошо, но если вы хотите избежать создания нового потока с временным массивом, вы можете использовать EnumSet.allOf()
.
EnumSet.allOf(Type.class)
.stream()
.filter(e -> e.s.equals(val))
.findFirst()
.orElseThrow(String.format("Unsupported type %s.", val));
Ответ 3
Arrays.stream(Type.values()).filter(v -> v.s.equals(val)).findAny().orElseThrow(...);
Ответ 4
Как использовать findAny()
вместо reduce
?
private static Type find(String val) {
return Arrays.stream(Type.values())
.filter(e -> e.s.equals(val))
.findAny()
.orElseThrow(() -> new IllegalStateException(String.format("Unsupported type %s.", val)));
}
Ответ 5
Я знаю, что этот вопрос старый, но я пришел сюда из дубликата. Мой ответ строго не отвечает на вопрос OP о том, как решить проблему с использованием потоков Java. Вместо этого этот ответ расширяет решение Map
-based, предложенное в принятом ответе, чтобы стать более доступным (IMHO).
Итак, вот что: я предлагаю ввести специальный вспомогательный класс, который я назвал EnumLookup
.
Предполагая, что перечисление Type
немного лучше написано (значащее имя поля + getter), я EnumLookup
константу EnumLookup
как EnumLookup
ниже:
enum Type {
X("S1"),
Y("S2");
private static final EnumLookup<Type, String> BY_CODE = EnumLookup.of(Type.class, Type::getCode, "code");
private final String code;
Type(String code) {
this.code = code;
}
public String getCode() {
return code;
}
public static EnumLookup<Type, String> byCode() {
return BY_CODE;
}
}
Затем использование становится (действительно, IMO) действительно читаемым:
Type type = Type.byCode().get("S1"); // returns Type.X
Optional<Type> optionalType = Type.byCode().find("S2"); // returns Optional(Type.Y)
if (Type.byCode().contains("S3")) { // returns false
// logic
}
Наконец, здесь код вспомогательного класса EnumLookup
:
public final class EnumLookup<E extends Enum<E>, ID> {
private final Class<E> enumClass;
private final ImmutableMap<ID, E> valueByIdMap;
private final String idTypeName;
private EnumLookup(Class<E> enumClass, ImmutableMap<ID, E> valueByIdMap, String idTypeName) {
this.enumClass = enumClass;
this.valueByIdMap = valueByIdMap;
this.idTypeName = idTypeName;
}
public boolean contains(ID id) {
return valueByIdMap.containsKey(id);
}
public E get(ID id) {
E value = valueByIdMap.get(id);
if (value == null) {
throw new IllegalArgumentException(String.format(
"No such %s with %s: %s", enumClass.getSimpleName(), idTypeName, id
));
}
return value;
}
public Optional<E> find(ID id) {
return Optional.ofNullable(valueByIdMap.get(id));
}
//region CONSTRUCTION
public static <E extends Enum<E>, ID> EnumLookup<E, ID> of(
Class<E> enumClass, Function<E, ID> idExtractor, String idTypeName) {
ImmutableMap<ID, E> valueByIdMap = Arrays.stream(enumClass.getEnumConstants())
.collect(ImmutableMap.toImmutableMap(idExtractor, Function.identity()));
return new EnumLookup<>(enumClass, valueByIdMap, idTypeName);
}
public static <E extends Enum<E>> EnumLookup<E, String> byName(Class<E> enumClass) {
return of(enumClass, Enum::name, "enum name");
}
//endregion
}
Обратите внимание, что:
-
Я использовал здесь Guava ImmutableMap
, но вместо этого можно использовать обычный HashMap
или LinkedHashMap
.
-
Если вы не согласны с отсутствием ленивой инициализации в вышеуказанном подходе, вы можете отложить создание EnumLookup
до byCode
пор, пока не будет byCode
метод метода " byCode
(например, использование идиомы ленивого держателя, как в принятом ответе)
Ответ 6
Я думаю, что второй ответ Алексис С. (ответ Алексис С.) является хорошим с точки зрения сложности. Вместо того, чтобы искать в O (n) каждый раз, когда вы ищите код, используя
return Arrays.stream(Type.values())
.filter(e -> e.s.equals(val))
.findFirst()
.orElseThrow(() -> new IllegalStateException(String.format("Unsupported type %s.", val)));
Вы можете использовать время O (n) при загрузке класса, поместив все элементы на карту, а затем получить доступ к коду типа в постоянное время O (1), используя карту.
enum Type{
X("S1"),
Y("S2");
private final String code;
private static Map<String, Type> mapping = new HashMap<>();
static {
Arrays.stream(Type.values()).forEach(type-> mapping.put(type.getCode(), type));
}
Type(String code) {
this.code = code;
}
public String getCode() {
return code;
}
public static Type forCode(final String code) {
return mapping.get(code);
}
}
Ответ 7
Я еще не могу добавить комментарий, поэтому я отправляю ответ, чтобы дополнить вышеприведенный ответ, просто следуя той же идее, но используя подход java 8:
public static Type find(String val) {
return Optional
.ofNullable(Holder.MAP.get(val))
.orElseThrow(() -> new IllegalStateException(String.format("Unsupported type %s.", val)));
}
Ответ 8
Вам нужен getter для String s.
В приведенном ниже примере этот метод getDesc()
:
public static StatusManifestoType getFromValue(String value) {
return Arrays.asList(values()).stream().filter(t -> t.getDesc().equals(value)).findAny().orElse(null);
}
Ответ 9
Вам нужен геттер для String, но я использую этот шаблон:
private static final Map<String, Type> TYPE_MAP =
Collections.unmodifiableMap(
EnumSet.allOf(Type.class)
.stream()
.collect(Collectors.toMap(Type::getS, e -> e)));
public static Type find(String s) {
return TYPE_MAP.get(s);
}
Нет для петель, только потоки. Быстрый поиск в отличие от создания потока при каждом вызове метода.