web-dev-qa-db-ja.com

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()は、特に何も蓄積せず、計算を実行せず、常に単に_t1_を返すため、不格好で乱用されているようです(フィルターが1つの値を返す場合-それが明らかに災害ではない場合) )、_t2_は言うまでもなく、余計で紛らわしいものがあります。しかし、Stream APIには、単に_Stream<T>_からTを直接返すようなものは何も見つかりませんでした。

もっと良い方法はありますか?

36
quantum

受け入れられた答えはうまく機能しますが、一時配列で新しいストリームを作成したくない場合は、EnumSet.allOf()を使用できます。

EnumSet.allOf(Type.class)
       .stream()
       .filter(e -> e.s.equals(val))
       .findFirst()
       .orElseThrow(String.format("Unsupported type %s.", val));
15
Pär Eriksson
Arrays.stream(Type.values()).filter(v -> v.s.equals(val)).findAny().orElseThrow(...);
5
sprinter

reduceの代わりにfindAny()を使用してはどうですか?

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

Alexis Cの2番目の答え( 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)));

すべての要素をマップに配置することにより、クラスのロード時にO(n) timeを使用し、一定時間で型のコードにアクセスすることができます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);
}
}
2

私はこの質問が古いことを知っていますが、私はここから重複して来ました。私の答えは、問題を解決する方法についてのOPの質問に厳密に答えているわけではありませんJava Streamsを使用して。代わりに、この答え 受け入れられた答え で提案されているMapベースのソリューションを拡張して、より(IMHO)管理しやすくします。

EnumLookupという名前の特別なヘルパークラスを導入することを提案します。

Type列挙がわずかに記述されていると仮定すると(意味のあるフィールド名+ゲッター)、以下のように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
}

ご了承ください:

  1. ここではGuavaの ImmutableMap を使用しましたが、代わりに通常のHashMapまたはLinkedHashMapを使用できます。

  2. 上記のアプローチで遅延初期化の欠如を気にする場合は、EnumLookupメソッドが最初に呼び出されるまでbyCodeの構築を遅らせることができます(たとえば、 lazy-holder idiom =、 受け入れられた回答 )のように

1

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

Forループではなく、ストリームのみ。メソッドが呼び出されるたびにストリームを構築するのではなく、クイックルックアップ。

0
Myles Wehr

Stringのゲッターが必要です。以下の例では、このメソッドはgetDesc()です:

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

私はまだコメントを追加することができないので、上記の answer を補完する回答を投稿します。同じアイデアに従いますが、Java 8アプローチを使用します。

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