webentwicklung-frage-antwort-db.com.de

Suche nach Aufzählungswerten mit der Java 8 Stream API

Angenommen, es gibt ein einfaches Enum namens Typ, das wie folgt definiert ist:

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

    private String s;

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

Das Finden der richtigen Enumeration für gegebene s erfolgt trivial mit einer statischen Methode mit for-Schleife (vorausgesetzt, die Methode ist in enum definiert), z.

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

Ich denke, das funktionale Äquivalent davon, das mit der Stream-API ausgedrückt wird, wäre etwa so:

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

Wie könnten wir das besser und einfacher schreiben? Dieser Code fühlt sich gezwungen und nicht sehr klar. Die reduce() scheint besonders unbeholfen und missbraucht zu sein, da sie nichts ansammelt, keine Berechnung durchführt und immer einfach t1 zurückgibt (vorausgesetzt, der Filter gibt einen Wert zurück - wenn dies nicht eindeutig ein Desaster ist), ganz zu schweigen davon, t2 ist überflüssig und verwirrend. Ich konnte jedoch nichts in der Stream-API finden, das einfach irgendwie T von einem Stream<T> zurückgibt.

Gibt es einen besseren Weg?

31
quantum

Ich würde stattdessen findFirst verwenden:

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


Obwohl eine Map in diesem Fall besser sein könnte:

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

Ich habe diesen Trick aus dieser Antwort gelernt. Grundsätzlich initialisiert der Klassenlader die statischen Klassen vor der Enumenklasse, sodass Sie die Map im Enumenkonstruktor selbst füllen können. Sehr praktisch

Ich hoffe es hilft ! :)

69
Alexis C.

Die akzeptierte Antwort funktioniert gut. Wenn Sie jedoch vermeiden möchten, dass ein neuer Stream mit einem temporären Array erstellt wird, können Sie EnumSet.allOf() verwenden.

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

Wie wäre es mit findAny() anstelle von 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

Ich denke, die zweite Antwort von Alexis C. ( Alexis C.'s Antwort ) ist im Hinblick auf die Komplexität die gute. Statt in O(n) zu suchen, jedes Mal, wenn Sie einen Code suchen 

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

sie könnten O(n) Zeit beim Laden der Klasse verwenden, indem Sie alle Elemente in die Map einfügen und dann in konstanter Zeit auf den Code des Typs zugreifen. O(1) mit der Karte.

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

Ich weiß, dass diese Frage alt ist, aber ich bin hier aus einem Duplikat gekommen. Meine Antwort ist keine strikte Antwort auf die Frage des OP, wie das Problem mit Java Streams gelöst werden soll. Stattdessen erweitert diese Antwort die Map-basierte Lösung, die in akzeptierte Antwort vorgeschlagen wird, um (IMHO) besser zu verwalten.

Also hier ist es: Ich schlage vor, eine spezielle Hilfsklasse einzuführen, die ich EnumLookup nannte.

Angenommen, die Aufzählung von Type ist etwas besser geschrieben (aussagekräftiger Feldname + Getter), füge ich eine EnumLookup-Konstante dazu hinzu:

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

Die Verwendung wird dann (wieder IMO) wirklich lesbar:

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
}

Zum Schluss noch der Code der Helfer-Klasse 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
}

Beachten Sie, dass:

  1. Ich habe hier Guavas ImmutableMap verwendet, aber stattdessen kann eine reguläre HashMap oder LinkedHashMap verwendet werden.

  2. Wenn Sie bei dem oben beschriebenen Ansatz auf das Fehlen einer langsamen Initialisierung stoßen, können Sie die Erstellung der EnumLookup so lange verzögern, bis die byCode-Methode zuerst aufgerufen wird (z. B. mit dem Lazy-Holder-Idiom , wie in der akzeptierten Antwort )

1

Sie brauchen einen Getter für String s, aber das ist das Muster, das ich benutze:

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

Nein für Schleifen, nur Streams. Schnelle Suche im Gegensatz zum Erstellen eines Streams bei jedem Aufruf der Methode.

0
Myles Wehr

Sie benötigen einen Getter für String s . Im folgenden Beispiel lautet diese Methode getDesc():

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

Ich kann noch keinen Kommentar hinzufügen. Daher poste ich eine Antwort, um die obige answer zu ergänzen, und zwar mit der gleichen Idee, jedoch unter Verwendung des Java 8-Ansatzes:

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