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?
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)));
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 ! :)
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));
Arrays.stream(Type.values()).filter(v -> v.s.equals(val)).findAny().orElseThrow(...);
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)));
}
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);
}
}
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:
J'ai utilisé le ImmutableMap
de Guava ici, mais un HashMap
ou LinkedHashMap
normal peut être utilisé à la place.
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 )
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);
}
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)));
}