使用 Java 8 Stream API 查找枚举值

Finding enum value with Java 8 Stream API

假设有一个名为 Type 的简单枚举定义如下:

enum Type{
    X("S1"),
    Y("S2");

    private String s;

    private Type(String s) {
        this.s = s;
    }
}

为给定的 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));
}

我认为用 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() 尤其显得笨拙和滥用,因为它不累积任何东西,不执行任何计算并且总是简单地 returns t1 (提供过滤器 returns 一个值 - 如果它不'这显然是一场灾难),更不用说 t2 是否多余且令人困惑。然而,我在 Stream API 中找不到任何东西,只是以某种方式 returns 直接来自 Stream<T>.

T

有没有更好的方法?

我会用 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;
    }
}

我从这个 answer 中学到了这个技巧。基本上 class 加载程序在枚举 class 之前初始化静态 classes,这允许您在枚举构造函数本身中填充 Map。非常方便!

希望对您有所帮助! :)

Arrays.stream(Type.values()).filter(v -> v.s.equals(val)).findAny().orElseThrow(...);

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

我还不能添加评论,所以我发布了一个答案来补充上述 ,只是遵循相同的想法,但使用 java 8 方法:

public static Type find(String val) {
    return Optional
            .ofNullable(Holder.MAP.get(val))
            .orElseThrow(() -> new IllegalStateException(String.format("Unsupported type %s.", val)));
}

接受的答案效果很好,但如果您想避免使用临时数组创建新流,您可以使用 EnumSet.allOf().

EnumSet.allOf(Type.class)
       .stream()
       .filter(e -> e.s.equals(val))
       .findFirst()
       .orElseThrow(String.format("Unsupported type %s.", val));

您需要一个 getter 用于 String 。 在下面的示例中,此方法是 getDesc():

public static StatusManifestoType getFromValue(String value) {
    return Arrays.asList(values()).stream().filter(t -> t.getDesc().equals(value)).findAny().orElse(null);
}

我知道这个问题很老,但我是从重复的问题来的。我的回答并不是严格回答 OP 关于如何使用 Java Streams 解决问题 的问题。相反,此答案扩展了 中提出的基于 Map 的解决方案,使其更易于(恕我直言)管理。

所以这里是:我建议介绍一个我命名为EnumLookup的特殊助手class。

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

然后用法变得(再次,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 助手的代码 class:

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
}

注意:

  1. 我这里用的是Guava的ImmutableMap,但是可以用普通的HashMapLinkedHashMap代替。

  2. 如果您介意上述方法中缺少惰性初始化,您可以延迟 EnumLookup 的构建,直到 byCode 方法首次被调用(例如使用 lazy-holder 成语,如)

我认为 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)));

你可以在加载 class 时使用 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);
}
}

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

没有 for 循环,只有流。快速查找,而不是每次调用方法时都构建一个流。