使用 Java 8 Stream API 查找枚举值
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/27807232/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Finding enum value with Java 8 Stream API
提问by quantum
Suppose there is a simple enum called Type defined like this:
假设有一个名为 Type 的简单枚举定义如下:
enum Type{
X("S1"),
Y("S2");
private String s;
private Type(String s) {
this.s = s;
}
}
Finding the correct enum for given s
is trivially done with static method with for-loop (assume the method is defined inside enum), e.g.:
s
使用带有 for 循环的静态方法可以轻松找到给定的正确枚举(假设该方法是在枚举中定义的),例如:
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));
}
I think the functional equivalent of this expressed with Stream API would be something like this:
我认为用 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));});
}
How could we write this better and simpler? This code feels coerced and not very clear. The reduce()
especially seems clunky and abused as it doesn't accumulate anything, performs no calculation and always simply returns t1
(provided the filter returns one value - if it doesn't that's clearly a disaster), not to mention t2
is there superfluous and confusing. Yet I couldn't find anything in Stream API that simply somehow returns directly a T
from a Stream<T>
.
我们怎样才能写得更好更简单?这段代码感觉很强制,不是很清楚。该reduce()
特别是似乎笨重和滥用,因为它不累积什么,不进行计算,并始终直接返回t1
(提供过滤器返回一个值-如果不说的显然是个灾难),更何况t2
是有多余的混淆。然而,我在 Stream API 中找不到任何以某种方式直接T
从 a返回 a的内容Stream<T>
。
Is there a better way?
有没有更好的办法?
采纳答案by Alexis C.
I would use findFirst
instead:
我会用findFirst
:
return Arrays.stream(Type.values())
.filter(e -> e.s.equals(val))
.findFirst()
.orElseThrow(() -> new IllegalStateException(String.format("Unsupported type %s.", val)));
虽然
Map
Map
在这种情况下a可能会更好: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;
}
}
I learnt this trick from this answer. Basically the class loader initializes the static classes before the enum class, which allows you to fill the Map
in the enum constructor itself. Very handy !
我从这个答案中学到了这个技巧。基本上类加载器在枚举类之前初始化静态类,这允许您填充Map
枚举构造函数本身。非常便利 !
Hope it helps ! :)
希望能帮助到你 !:)
回答by sprinter
Arrays.stream(Type.values()).filter(v -> v.s.equals(val)).findAny().orElseThrow(...);
回答by Todd
How about using findAny()
instead of reduce
?
使用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)));
}
回答by Thiago
I can't add a comment yet, so I am posting an answer to complement the above answer, just following the same idea but using java 8 approach:
我还不能添加评论,所以我发布了一个答案来补充上述答案,只是遵循相同的想法,但使用 java 8 方法:
public static Type find(String val) {
return Optional
.ofNullable(Holder.MAP.get(val))
.orElseThrow(() -> new IllegalStateException(String.format("Unsupported type %s.", val)));
}
回答by P?r Eriksson
The accepted answer works well, but if you want to avoid creating a new stream with a temporary array you could use EnumSet.allOf()
.
接受的答案效果很好,但如果您想避免使用临时数组创建新流,您可以使用EnumSet.allOf()
.
EnumSet.allOf(Type.class)
.stream()
.filter(e -> e.s.equals(val))
.findFirst()
.orElseThrow(String.format("Unsupported type %s.", val));
回答by Marcell Rico
You need a getter for String s.
In the example below this method is getDesc()
:
你需要一个 String 的吸气剂。在下面的示例中,此方法是getDesc()
:
public static StatusManifestoType getFromValue(String value) {
return Arrays.asList(values()).stream().filter(t -> t.getDesc().equals(value)).findAny().orElse(null);
}
回答by Tomasz Linkowski
I know this question is old but I came here from a duplicate. My answer is not strictly answering the OP's question about how to solve the problem using Java Streams. Instead, this answer expands the Map
-based solution proposed in the accepted answerto become more (IMHO) manageable.
我知道这个问题很老,但我是从一个重复的人来到这里的。我的回答并不是严格回答 OP 关于如何使用 Java Streams解决问题的问题。相反,此答案扩展了Map
已接受答案中提出的基于 - 的解决方案,使其变得更加(恕我直言)易于管理。
So here it is: I propose to introduce a special helper class that I named EnumLookup
.
所以这里是:我建议引入一个特殊的帮助类,我命名为EnumLookup
.
Assuming the Type
enumeration is slightly better written (meaningful field name + getter), I inject an EnumLookup
constant to it like below:
假设Type
枚举写得稍微好一点(有意义的字段名称 + getter),我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;
}
}
The usage then becomes (again, IMO) really readable:
然后用法变得(再次,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
}
Finally, here's the code of the EnumLookup
helper class:
最后,这里是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
}
Note that:
注意:
I used Guava's
ImmutableMap
here, but a regularHashMap
orLinkedHashMap
can be used instead.If you mind the lack of lazy initialization in the above approach, you can delay building of the
EnumLookup
untilbyCode
method is first called (e.g. using the lazy-holder idiom, like in the accepted answer)
我
ImmutableMap
在这里使用了番石榴,但可以使用普通的HashMap
orLinkedHashMap
代替。如果您介意上述方法中缺少延迟初始化,您可以延迟构建
EnumLookup
直到byCode
方法第一次被调用(例如使用延迟持有者习语,就像在接受的答案中一样)
回答by Bastien Escouvois
I think the second answer of Alexis C. (Alexis C.'s answer) is the good one in term of complexity. Instead of searching in O(n) each time you look for a code using
我认为 Alexis C. 的第二个答案(Alexis C. 的答案)在复杂性方面是好的。每次使用以下代码查找代码时,不必在 O(n) 中进行搜索
return Arrays.stream(Type.values())
.filter(e -> e.s.equals(val))
.findFirst()
.orElseThrow(() -> new IllegalStateException(String.format("Unsupported type %s.", val)));
you could use O(n) time at the loading of the class by putting all elements into the map, and then access to the code of the type in constant time O(1) using the map.
您可以在加载类时使用 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);
}
}
回答by Myles Wehr
You'd need a getter for String s, but this is the pattern I use:
你需要一个用于 String 的 getter,但这是我使用的模式:
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);
}
No for loops, only streams. Quick lookup as opposed to building a stream every time the method is called.
没有 for 循环,只有流。快速查找,而不是每次调用该方法时都构建一个流。