web-dev-qa-db-fra.com

Recherche d'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 la bonne énumération 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 Stream API ressemblerait à 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 ceci mieux et plus simplement? Ce code semble contraignant 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 clairement un désastre), sans compter que t2 est superflu et déroutant. Pourtant, je ne pouvais rien trouver dans Stream API qui renvoie simplement d'une manière ou d'une autre une T d'un Stream<T>.

Y a-t-il un meilleur moyen?

31
quantum

J'utiliserais plutôt findFirst:

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


Bien qu'une Map puisse être meilleure 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 classes initialise les classes statiques avant la classe enum, ce qui vous permet de remplir la variable Map dans le constructeur d’énum lui-même. Très utile !

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

69
Alexis C.

La réponse acceptée fonctionne bien, mais si vous souhaitez é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));
14
Pär Eriksson
Arrays.stream(Type.values()).filter(v -> v.s.equals(val)).findAny().orElseThrow(...);
4
sprinter

Comment 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
Todd

Je pense que la deuxième réponse d’Alexis C. ( La réponse d’Alexis C. ) est la bonne en termes de complexité. Au lieu de rechercher dans O(n) chaque fois que vous recherchez un code en utilisant 

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

vous pouvez utiliser le temps O(n) lors du chargement de la classe en mettant tous les éléments dans la carte, puis accéder au code du type à temps constant O(1) à l'aide de la carte.

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

}

1
Bastien Escouvois

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 du PO sur la façon de résoudre le problème en utilisant Java Streams. 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 (IMHO) gérable.

Voici donc ceci: je propose d’introduire une classe d’aide spéciale que j’ai nommée EnumLookup.

En supposant que l'énumération Type soit légèrement mieux écrite (nom de champ significatif + 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 (à nouveau, 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’assistance 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é le ImmutableMap de Guava ici, mais un HashMap ou LinkedHashMap normal peut être utilisé à la place.

  2. Si vous vous inquiétez du manque d'initialisation paresseuse dans l'approche ci-dessus, vous pouvez différer la construction de la méthode EnumLookup jusqu'à ce que la méthode byCode soit d'abord appelée (par exemple, en utilisant le idiome du titulaire paresseux , comme dans le réponse acceptée )

1
Tomasz Linkowski

Vous avez besoin d'un getter pour String 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
Marcell Rico

Je ne peux pas encore ajouter de commentaire. Je poste donc une réponse pour compléter ce qui précède answer , 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
Thiago