Enum-Wert mit Java 8 Stream API ermitteln

Lesezeit: 9 Minuten

Enum Wert mit Java 8 Stream API ermitteln
Quanten

Angenommen, es gibt eine einfache Aufzählung namens Typ, die wie folgt definiert ist:

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

    private String s;

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

Finden der richtigen Aufzählung für gegeben s wird trivialerweise mit einer statischen Methode mit for-Schleife durchgeführt (angenommen, die Methode ist in enum definiert), zB:

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 dazu, 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 erzwungen und nicht sehr klar an. Der reduce() wirkt besonders klobig und missbraucht, da es nichts ansammelt, keine Berechnungen durchführt und immer einfach zurückkehrt t1 (vorausgesetzt, der Filter gibt einen Wert zurück – wenn nicht, ist das eindeutig eine Katastrophe), ganz zu schweigen davon t2 ist da überflüssig und verwirrend. Ich konnte jedoch in der Stream-API nichts finden, das einfach irgendwie direkt zurückkehrt a T von einem Stream<T>.

Gibt es einen besseren Weg?

  • 6

    Ich weiß, dass dieser Kommentar von niemandem positiv bewertet wird, aber so großartig wie Java 8 ist, müssen Sie es nicht verwenden Streams für jedes einzelne Problem. Ihr for-Schleifen-Ansatz ist klarer (und schneller) als jeder Ansatz mit StreamS.

    – Paul Boddington

    6. Januar ’15 um 21:24


  • 4

    @pbabcdefp Nun, ich denke, es ist ein guter Kommentar, aber wenn ich ihn positiv bewerten würde, wäre der erste Satz in Ihrem Kommentar falsch, was bedeutet, dass ich ihn erneut ablehnen müsste, dann würde ich ihn wieder für einen guten Kommentar halten, also dann müsste ich es positiv bewerten, aber dann wäre der erste Satz falsch … ich glaube, ich werfe gleich StackOverflowException

    – ajb

    6. Januar ’15 um 21:33


  • @pbabcdefp – das ist wahrscheinlich Ansichtssache, aber ich finde Lambdas immer mehr vorzuziehen gegenüber Iterationen und Klarheit übertrumpft fast immer die Effizienz. Ich war mir sicher, dass ich das probiert hatte findFirst() und habe einige seltsame Kompilierungsfehler in IDEA bekommen und das geschrieben reduce() Variante. Jedenfalls habe ich alle Ihre Antworten positiv bewertet, aber gefühlt first ist klarer als any also bin ich damit gefahren. Danke für Ihre Hilfe!

    – Quanten

    6. Januar ’15 um 21:55

Enum Wert mit Java 8 Stream API ermitteln
Alexis C.

ich würde … benutzen findFirst stattdessen:

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


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

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 Class Loader die statischen Klassen vor der Enum-Klasse, wodurch Sie die Map im Enumerationskonstruktor selbst. Sehr praktisch!

Ich hoffe es hilft ! 🙂

  • 5

    Das ist ein sehr raffinierter Trick, ich mag besonders, wie die JVM die serielle Kartenbestückung garantiert – ausgezeichnet. Nur ein kleiner Vorschlag – wir können den Code noch kompakter machen, indem wir das Feld entfernen s da es woanders ungenutzt ist.

    – Quanten

    6. Januar ’15 um 22:41


  • findAny() eher, als findFirst()? — wenn eine (oder null) Übereinstimmung garantiert ist, dann möglicherweise findAny() wird die Antwort früher bekommen (obwohl ich denke, dass es fraglich ist, ob eine Aufzählung jemals groß genug sein wird, um die Suche zu parallelisieren)

    – schlank

    2. März ’17 um 13:15


  • @slim Wie der Stream bestellt wird, findFirst() zeigt das gleiche Verhalten wie der ursprüngliche Code vor Java 8 bei mehreren Übereinstimmungen. Obwohl ich eine bijektive Zuordnung zwischen den Werten und Namen von enum vermute, erwarte ich keinen großen Unterschied in Bezug auf die Leistung zwischen findFirst() und findAny() (und wie gesagt, fragwürdig). Das heißt, ich würde den zweiten Ansatz verwenden. Es verbraucht mehr Speicher, aber es kann sich lohnen, da die Suchzeit besser ist 🙂

    – Alexis C.

    2. März ’17 um 17:39

  • Der zweite Ansatz wird in Big O (1) ausgeführt, während alle anderen Ansätze BigO (n) verwenden … Danke für die Antwort

    – Shridutt Kothari

    29. Dezember ’21 um 9:56

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

EnumSet.allOf(Type.class)
       .stream()
       .filter(e -> e.s.equals(val))
       .findFirst()
       .orElseThrow(String.format("Unsupported type %s.", val));

  • 8

    Wenn man sich die JDK-Quelle ansieht, Arrays.stream(Type.values()) klont intern ein Array und erstellt dann einen neuen Stream – während EnumSet.allOf(Type.class).stream() erstellt intern ein neues EnumSet, fügt ihm alle Enum-Werte hinzu und erstellt dann einen neuen Stream. Diese Lösung sieht für mich schöner aus, aber die Entscheidung, diese zu verwenden, sollte nicht nur auf Annahmen darüber basieren, wie viele Objekte erstellt werden.

    – kapex

    28. Februar ’17 um 10:47

  • 1

    @kapex seit enun sind Konstanten, die Sie immer noch kopieren, die ganze Zeit. Ein EnumSet auf der anderen Seite könnten Flaggen eingeführt werden wie DISTINCT oder NONNULL das könnte später vom Stream genutzt werden, ist es nicht einfach über die objekte

    – Eugen

    17. September ’18 um 20:21

  • @Eugene Richtig, EnumSet könnte Hinweise für optimiertes Streaming geben, das wäre ein besserer Grund für die Verwendung. Was meinst du mit “Da Enumerationen Konstanten sind, kopieren Sie immer noch die ganze Zeit.”? Aufzählungskonstanten definieren Aufzählungsinstanzen des Aufzählungstyps. Das Kopieren von Arrays von Enumerationen funktioniert meines Wissens genauso wie das Kopieren von Arrays anderer Objekttypen.

    – kapex

    21. September ’18 um 10:19

  • @kapex was ich meine ist das values gibt immer ein neues Array zurück, natürlich durch Kopieren, da das Zurückgeben eines Arrays, das durch das ursprüngliche Array unterstützt wird, die Möglichkeit bedeuten würde, die Aufzählung selbst zu ändern, und das kann offensichtlich nicht passieren.

    – Eugen

    21. September ’18 um 11:14

  • esequals(val) Was ist hier der Wert von s?

    – Girdhar Singh Rathore

    10. Januar ’19 um 17:28

1641585932 819 Enum Wert mit Java 8 Stream API ermitteln
Sprinter

Arrays.stream(Type.values()).filter(v -> v.s.equals(val)).findAny().orElseThrow(...);

1641585932 258 Enum Wert mit Java 8 Stream API ermitteln
Todd

Wie wäre es mit findAny() anstatt 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)));
}

photo
Bastien Escouvois

Ich denke, die zweite Antwort von Alexis C. (Antwort von Alexis C.) ist in Bezug auf die Komplexität die gute. Anstatt jedes Mal in O(n) zu suchen, wenn Sie mit . nach einem 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 mithilfe der Map in konstanter Zeit O(1) auf den Code des Typs zugreifen.

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

Ich weiß, dass diese Frage alt ist, aber ich bin von einem Duplikat hierher gekommen. Meine Antwort beantwortet nicht genau die Frage des OP, wie das Problem zu lösen ist Verwendung von Java-Streams. Stattdessen erweitert diese Antwort die Map-basierte Lösung, die in der akzeptierten Antwort vorgeschlagen wurde, um (IMHO) handhabbarer zu werden.

Hier ist es also: Ich schlage vor, eine spezielle Helferklasse einzuführen, die ich genannt habe EnumLookup.

Angenommen, die Type Aufzählung ist etwas besser geschrieben (sinnvoller Feldname + Getter), ich spritze an EnumLookup konstant dazu wie unten:

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
}

Hier ist endlich der Code des EnumLookup Helferklasse:

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 Guaven verwendet ImmutableMap hier, aber ein regelmäßiger HashMap oder LinkedHashMap kann stattdessen verwendet werden.

  2. Wenn Ihnen das Fehlen einer faulen Initialisierung im obigen Ansatz etwas ausmacht, können Sie den Aufbau des verzögern EnumLookup noch bis byCode Methode zuerst aufgerufen wird (zB mit dem Faule-Halter-Idiom, wie in der akzeptierten Antwort)

1641585932 524 Enum Wert mit Java 8 Stream API ermitteln
Myles Wehr

Sie benötigen einen Getter für String s, aber das ist das Muster, das ich verwende:

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

Keine for-Schleifen, nur Streams. Schnelle Suche im Gegensatz zum Erstellen eines Streams bei jedem Aufruf der Methode.

.

179000cookie-checkEnum-Wert mit Java 8 Stream API ermitteln

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy