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?
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 ! :)
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));
Arrays.stream(Type.values()).filter(v -> v.s.equals(val)).findAny().orElseThrow(...);
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)));
}
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:
J'ai utilisé de Goyave
ImmutableMap
ici, mais régulièreHashMap
ouLinkedHashMap
peut être utilisé à la place.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 )
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)));
}
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);
}