Trouver une valeur enum avec L'API Java 8 Stream

Supposons qu'il existe une énumération simple appelée Type définie comme ceci:

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

    private String s;

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

Trouver l'énumération correcte pour s donné est trivialement fait avec la méthode statique avec For-loop (supposons que la méthode est définie dans enum), par exemple:

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

Je pense que l'équivalent fonctionnel de ceci exprimé avec L'API Stream serait quelque chose comme ceci:

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

Comment pourrions-nous écrire cela mieux et plus simplement? Ce code se sent contraint et pas très clair. Le reduce() semble particulièrement maladroit et abusé car il n'accumule rien, n'effectue aucun calcul et renvoie toujours simplement t1 (à condition que le filtre renvoie une valeur - si ce n'est pas le cas, c'est clairement un désastre), sans parler de t2 est-il superflu et confus. Pourtant, je n'ai rien trouvé dans L'API Stream qui renvoie simplement directement un T à partir d'un Stream<T>.

Est-il un meilleur moyen?

26
demandé sur Stuart Marks 2015-01-07 00:10:48

7 réponses

J'utiliserais findFirst à la place:

return Arrays.stream(Type.values())
            .filter(e -> e.s.equals(val))
            .findFirst()
            .orElseThrow(() -> new IllegalStateException(String.format("Unsupported type %s.", val)));


Bien qu'un Map pourrait être meilleur dans ce cas:
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;
    }
}

J'ai appris cette astuce de cette réponse. Fondamentalement, le chargeur de classe initialise les classes statiques avant la classe enum, ce qui vous permet de remplir le Map dans le constructeur enum lui-même. Très pratique !

J'espère que ça aide ! :)

61
répondu Alexis C. 2017-05-23 11:55:03

La réponse acceptée fonctionne bien, mais si vous voulez éviter de créer un nouveau flux avec un tableau temporaire, vous pouvez utiliser EnumSet.allOf().

EnumSet.allOf(Type.class)
       .stream()
       .filter(e -> e.s.equals(val))
       .findFirst()
       .orElseThrow(String.format("Unsupported type %s.", val));
12
répondu Pär Eriksson 2016-05-16 15:24:31
Arrays.stream(Type.values()).filter(v -> v.s.equals(val)).findAny().orElseThrow(...);
4
répondu sprinter 2015-01-06 21:50:50

Que diriez-vous d'utiliser findAny() au lieu de 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)));
}
4
répondu Todd 2015-01-07 18:04:22

Je sais que cette question Est ancienne mais je suis venu ici d'un duplicata. Ma réponse ne répond pas strictement à la question de L'OP sur la façon de résoudre le problème en utilisant les flux Java . Au lieu de cela, cette réponse élargit la solution basée sur Map proposée dans la réponse acceptée pour devenir plus (à mon humble avis) gérable.

La voici donc: je propose d'introduire une classe d'aide spéciale que j'ai nommée EnumLookup.

En supposant que l'énumération Type est légèrement mieux écrite (significative nom du champ + getter), j'injecte une constante EnumLookup comme ci-dessous:

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

L'utilisation devient alors (encore une fois, IMO) vraiment lisible:

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
}

Enfin, voici le code de la classe d'aide 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
}

Notez que:

  1. J'ai utilisé de Goyave ImmutableMap ici, mais régulière HashMap ou LinkedHashMap peut être utilisé à la place.

  2. Si cela vous dérange le manque d'initialisation paresseuse dans l'approche ci-dessus, vous pouvez retarder la construction du EnumLookup jusqu'à byCode la méthode est d'abord appelée (par exemple en utilisant l'idiome lazy-holder , comme dans la réponse acceptée )

1
répondu Tomasz Linkowski 2018-09-13 15:59:53

Je ne peux pas encore ajouter de commentaire, donc je poste une réponse pour compléter la réponse ci-dessus, en suivant la même idée mais en utilisant l'approche 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
répondu Thiago 2017-05-23 12:10:43

Vous avez besoin d'un getter pour la chaîne S. Dans l'exemple ci-dessous cette méthode est getDesc():

public static StatusManifestoType getFromValue(String value) {
    return Arrays.asList(values()).stream().filter(t -> t.getDesc().equals(value)).findAny().orElse(null);
}
0
répondu Marcell Rico 2017-01-30 12:28:57