Eine Anleitung zu Java Enums

Eine Anleitung zu Java Enums

1. Überblick

In diesem Artikel werden wir sehen, was Java-Enums sind, welche Probleme sie lösen und wie einige der Entwurfsmuster in der Praxis verwendet werden können.

The enum keyword was introduced in Java 5. Bezeichnet einen speziellen Klassentyp, der die Klassejava.lang.Enumimmer erweitert. Die offizielle Dokumentation zu ihrer Verwendung finden Sie indocumentation.

Auf diese Weise definierte Konstanten verbessern die Lesbarkeit des Codes, ermöglichen die Überprüfung der Kompilierungszeit, dokumentieren die Liste der akzeptierten Werte im Voraus und vermeiden unerwartetes Verhalten aufgrund der Übergabe ungültiger Werte.

Hier ist ein kurzes und einfaches Beispiel für eine Aufzählung, die den Status einer Bestellung für eine Pizza definiert. Der Auftragsstatus kannORDERED,READY oderDELIVERED sein:

public enum PizzaStatus {
    ORDERED,
    READY,
    DELIVERED;
}

Darüber hinaus bieten sie viele nützliche Methoden, die Sie sonst selbst schreiben müssten, wenn Sie herkömmliche öffentliche statische Endkonstanten verwenden würden.

2. Benutzerdefinierte Aufzählungsmethoden

OK, also jetzt, da wir ein grundlegendes Verständnis dafür haben, was Aufzählungen sind und wie Sie sie verwenden können, lassen Sie unser vorheriges Beispiel auf die nächste Ebene nehmen, indem wir einige zusätzliche API-Methoden auf dem Enum definieren:

public class Pizza {
    private PizzaStatus status;
    public enum PizzaStatus {
        ORDERED,
        READY,
        DELIVERED;
    }

    public boolean isDeliverable() {
        if (getStatus() == PizzaStatus.READY) {
            return true;
        }
        return false;
    }

    // Methods that set and get the status variable.
}

3. Vergleichen von Aufzählungstypen mit dem Operator "=="

Da Aufzählungstypen sicherstellen, dass nur eine Instanz der Konstanten in der JVM vorhanden ist, können wir den Operator "==" verwenden, um zwei Variablen wie im obigen Beispiel zu vergleichen. Darüber hinaus bietet der Operator "==" Kompilierungs- und Laufzeitsicherheit.

Schauen wir uns zunächstat run-time safety im folgenden Snippet an, in dem der Operator "==" zum Vergleichen von Status verwendet wird undNullPointerException nicht ausgelöst werden, wenn einer der Wertenull ist. Umgekehrt würde anNullPointerException geworfen, wenn die Methode equals verwendet würde:

if(testPz.getStatus().equals(Pizza.PizzaStatus.DELIVERED));
if(testPz.getStatus() == Pizza.PizzaStatus.DELIVERED);

Wascompile time safety betrifft, schauen wir uns ein anderes Beispiel an, in dem eine Aufzählung eines anderen Typs mit der Methodeequals als wahr bestimmt wird - weil die Werte der Aufzählung und dergetStatusDie Methode ist zufällig dieselbe, aber logischerweise sollte der Vergleich falsch sein. Dieses Problem wird durch die Verwendung des Operators "==" vermieden.

Der Compiler kennzeichnet den Vergleich als Inkompatibilitätsfehler:

if(testPz.getStatus().equals(TestColor.GREEN));
if(testPz.getStatus() == TestColor.GREEN);

4. Verwenden von Aufzählungstypen in Switch-Anweisungen

Aufzählungstypen können auch in den Anweisungen vonswitchverwendet werden:

public int getDeliveryTimeInDays() {
    switch (status) {
        case ORDERED: return 5;
        case READY: return 2;
        case DELIVERED: return 0;
    }
    return 0;
}

5. Felder, Methoden und Konstruktoren in Enums

Sie können Konstruktoren, Methoden und Felder in Aufzählungstypen definieren, die es sehr leistungsfähig machen.

Lassen Sie uns das obige Beispiel erweitern und den Übergang von einer Stufe einer Pizza zu einer anderen implementieren und sehen, wie wir die Anweisungif undswitch entfernen können, die zuvor verwendet wurde:

public class Pizza {

    private PizzaStatus status;
    public enum PizzaStatus {
        ORDERED (5){
            @Override
            public boolean isOrdered() {
                return true;
            }
        },
        READY (2){
            @Override
            public boolean isReady() {
                return true;
            }
        },
        DELIVERED (0){
            @Override
            public boolean isDelivered() {
                return true;
            }
        };

        private int timeToDelivery;

        public boolean isOrdered() {return false;}

        public boolean isReady() {return false;}

        public boolean isDelivered(){return false;}

        public int getTimeToDelivery() {
            return timeToDelivery;
        }

        PizzaStatus (int timeToDelivery) {
            this.timeToDelivery = timeToDelivery;
        }
    }

    public boolean isDeliverable() {
        return this.status.isReady();
    }

    public void printTimeToDeliver() {
        System.out.println("Time to delivery is " +
          this.getStatus().getTimeToDelivery());
    }

    // Methods that set and get the status variable.
}

Das Test-Snippet unten zeigt, wie das funktioniert:

@Test
public void givenPizaOrder_whenReady_thenDeliverable() {
    Pizza testPz = new Pizza();
    testPz.setStatus(Pizza.PizzaStatus.READY);
    assertTrue(testPz.isDeliverable());
}

6. EnumSet undEnumMap

6.1. EnumSet

EnumSet ist eine spezialisierteSet-Implementierung, die mitEnum-Typen verwendet werden soll.

Es ist eine sehr effiziente und kompakte Darstellung eines bestimmtenSet vonEnum Konstanten im Vergleich zu einemHashSet aufgrund der verwendeten internenBit Vector Representation. Und es bietet eine typsichere Alternative zu herkömmlichenint-basierten „Bit-Flags“, mit denen wir präzisen Code schreiben können, der besser lesbar und wartbar ist.

EnumSet ist eine abstrakte Klasse mit zwei Implementierungen, die alsRegularEnumSet undJumboEnumSet bezeichnet werden. Eine davon wird abhängig von der Anzahl der Konstanten in der Aufzählung zum Zeitpunkt der Instanziierung ausgewählt.

Daher ist es immer eine gute Idee, diese Menge zu verwenden, wenn wir in den meisten Szenarien mit einer Sammlung von Enum-Konstanten arbeiten möchten (z. B. Teilmenge, Hinzufügen, Entfernen und für Massenoperationen wiecontainsAll undremoveAll. s) und verwenden SieEnum.values(), wenn Sie nur alle möglichen Konstanten durchlaufen möchten.

Im folgenden Codefragment können Sie sehen, wieEnumSet zum Erstellen einer Teilmenge von Konstanten und deren Verwendung verwendet wird:

public class Pizza {

    private static EnumSet undeliveredPizzaStatuses =
      EnumSet.of(PizzaStatus.ORDERED, PizzaStatus.READY);

    private PizzaStatus status;

    public enum PizzaStatus {
        ...
    }

    public boolean isDeliverable() {
        return this.status.isReady();
    }

    public void printTimeToDeliver() {
        System.out.println("Time to delivery is " +
          this.getStatus().getTimeToDelivery() + " days");
    }

    public static List getAllUndeliveredPizzas(List input) {
        return input.stream().filter(
          (s) -> undeliveredPizzaStatuses.contains(s.getStatus()))
            .collect(Collectors.toList());
    }

    public void deliver() {
        if (isDeliverable()) {
            PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy()
              .deliver(this);
            this.setStatus(PizzaStatus.DELIVERED);
        }
    }

    // Methods that set and get the status variable.
}

Die Ausführung des folgenden Tests demonstrierte die Leistungsfähigkeit derEnumSet-Implementierung derSet-Schnittstelle:

@Test
public void givenPizaOrders_whenRetrievingUnDeliveredPzs_thenCorrectlyRetrieved() {
    List pzList = new ArrayList<>();
    Pizza pz1 = new Pizza();
    pz1.setStatus(Pizza.PizzaStatus.DELIVERED);

    Pizza pz2 = new Pizza();
    pz2.setStatus(Pizza.PizzaStatus.ORDERED);

    Pizza pz3 = new Pizza();
    pz3.setStatus(Pizza.PizzaStatus.ORDERED);

    Pizza pz4 = new Pizza();
    pz4.setStatus(Pizza.PizzaStatus.READY);

    pzList.add(pz1);
    pzList.add(pz2);
    pzList.add(pz3);
    pzList.add(pz4);

    List undeliveredPzs = Pizza.getAllUndeliveredPizzas(pzList);
    assertTrue(undeliveredPzs.size() == 3);
}

6.2. EnumMap

EnumMap ist eine spezialisierte Implementierung vonMap, die mit Enum-Konstanten als Schlüssel verwendet werden soll. Es ist eine effiziente und kompakte Implementierung im Vergleich zu seinem GegenstückHashMap und wird intern als Array dargestellt:

EnumMap map;

Schauen wir uns ein kurzes Beispiel an, das zeigt, wie es in der Praxis eingesetzt werden kann:

public static EnumMap>
  groupPizzaByStatus(List pizzaList) {
    EnumMap> pzByStatus =
      new EnumMap>(PizzaStatus.class);

    for (Pizza pz : pizzaList) {
        PizzaStatus status = pz.getStatus();
        if (pzByStatus.containsKey(status)) {
            pzByStatus.get(status).add(pz);
        } else {
            List newPzList = new ArrayList();
            newPzList.add(pz);
            pzByStatus.put(status, newPzList);
        }
    }
    return pzByStatus;
}

Die Ausführung des folgenden Tests demonstrierte die Leistungsfähigkeit derEnumMap-Implementierung derMap-Schnittstelle:

@Test
public void givenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped() {
    List pzList = new ArrayList<>();
    Pizza pz1 = new Pizza();
    pz1.setStatus(Pizza.PizzaStatus.DELIVERED);

    Pizza pz2 = new Pizza();
    pz2.setStatus(Pizza.PizzaStatus.ORDERED);

    Pizza pz3 = new Pizza();
    pz3.setStatus(Pizza.PizzaStatus.ORDERED);

    Pizza pz4 = new Pizza();
    pz4.setStatus(Pizza.PizzaStatus.READY);

    pzList.add(pz1);
    pzList.add(pz2);
    pzList.add(pz3);
    pzList.add(pz4);

    EnumMap> map = Pizza.groupPizzaByStatus(pzList);
    assertTrue(map.get(Pizza.PizzaStatus.DELIVERED).size() == 1);
    assertTrue(map.get(Pizza.PizzaStatus.ORDERED).size() == 2);
    assertTrue(map.get(Pizza.PizzaStatus.READY).size() == 1);
}

7. Implementieren Sie Entwurfsmuster mit Enums

7.1. Singleton-Muster

Normalerweise ist die Implementierung einer Klasse mithilfe des Singleton-Musters nicht trivial. Enums bieten eine einfache und schnelle Möglichkeit, Singletons zu implementieren.

Da die enum-Klasse dieSerializable-Schnittstelle unter der Haube implementiert, wird die Klasse außerdem von der JVM als Singleton garantiert, was im Gegensatz zur herkömmlichen Implementierung, bei der sichergestellt werden muss, dass währenddessen keine neuen Instanzen erstellt werden Deserialisierung.

Im folgenden Codeausschnitt sehen wir, wie wir Singleton-Muster implementieren können:

public enum PizzaDeliverySystemConfiguration {
    INSTANCE;
    PizzaDeliverySystemConfiguration() {
        // Initialization configuration which involves
        // overriding defaults like delivery strategy
    }

    private PizzaDeliveryStrategy deliveryStrategy = PizzaDeliveryStrategy.NORMAL;

    public static PizzaDeliverySystemConfiguration getInstance() {
        return INSTANCE;
    }

    public PizzaDeliveryStrategy getDeliveryStrategy() {
        return deliveryStrategy;
    }
}

7.2. Strategiemuster

Herkömmlicherweise wird das Strategiemuster mit einer Schnittstelle geschrieben, die von verschiedenen Klassen implementiert wird.

Das Hinzufügen einer neuen Strategie bedeutete das Hinzufügen einer neuen Implementierungsklasse. Mit Enums wird dies mit weniger Aufwand erreicht. Wenn Sie eine neue Implementierung hinzufügen, müssen Sie nur eine weitere Instanz mit einer bestimmten Implementierung definieren.

Das folgende Code-Snippet zeigt, wie das Strategy-Pattern implementiert wird:

public enum PizzaDeliveryStrategy {
    EXPRESS {
        @Override
        public void deliver(Pizza pz) {
            System.out.println("Pizza will be delivered in express mode");
        }
    },
    NORMAL {
        @Override
        public void deliver(Pizza pz) {
            System.out.println("Pizza will be delivered in normal mode");
        }
    };

    public abstract void deliver(Pizza pz);
}

Fügen Sie der KlassePizzadie folgende Methode hinzu:

public void deliver() {
    if (isDeliverable()) {
        PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy()
          .deliver(this);
        this.setStatus(PizzaStatus.DELIVERED);
    }
}
@Test
public void givenPizaOrder_whenDelivered_thenPizzaGetsDeliveredAndStatusChanges() {
    Pizza pz = new Pizza();
    pz.setStatus(Pizza.PizzaStatus.READY);
    pz.deliver();
    assertTrue(pz.getStatus() == Pizza.PizzaStatus.DELIVERED);
}

8. Java 8 und Enums

Die KlassePizza kann in Java 8 neu geschrieben werden, und Sie können sehen, wie die MethodengetAllUndeliveredPizzas() undgroupPizzaByStatus() mit der Verwendung von Lambdas und den APIsStream so präzise werden:

public static List getAllUndeliveredPizzas(List input) {
    return input.stream().filter(
      (s) -> !deliveredPizzaStatuses.contains(s.getStatus()))
        .collect(Collectors.toList());
}
public static EnumMap>
  groupPizzaByStatus(List pzList) {
    EnumMap> map = pzList.stream().collect(
      Collectors.groupingBy(Pizza::getStatus,
      () -> new EnumMap<>(PizzaStatus.class), Collectors.toList()));
    return map;
}

9. JSON-Darstellung von Enum

Mit Jackson-Bibliotheken ist es möglich, eine JSON-Darstellung von Aufzählungstypen zu haben, als wären sie POJOs. Das folgende Code-Snippet zeigt die Jackson-Annotationen, die für dasselbe verwendet werden können:

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum PizzaStatus {
    ORDERED (5){
        @Override
        public boolean isOrdered() {
            return true;
        }
    },
    READY (2){
        @Override
        public boolean isReady() {
            return true;
        }
    },
    DELIVERED (0){
        @Override
        public boolean isDelivered() {
            return true;
        }
    };

    private int timeToDelivery;

    public boolean isOrdered() {return false;}

    public boolean isReady() {return false;}

    public boolean isDelivered(){return false;}

    @JsonProperty("timeToDelivery")
    public int getTimeToDelivery() {
        return timeToDelivery;
    }

    private PizzaStatus (int timeToDelivery) {
        this.timeToDelivery = timeToDelivery;
    }
}

Wir können den Pizza und PizzaStatus wie folgt verwenden:

Pizza pz = new Pizza();
pz.setStatus(Pizza.PizzaStatus.READY);
System.out.println(Pizza.getJsonString(pz));

um die folgende JSON-Darstellung desPizzas-Status zu generieren:

{
  "status" : {
    "timeToDelivery" : 2,
    "ready" : true,
    "ordered" : false,
    "delivered" : false
  },
  "deliverable" : true
}

Weitere Informationen zum JSON-Serialisieren / Deserialisieren (einschließlich Anpassen) von Aufzählungstypen finden Sie inJackson – Serialize Enums as JSON Objects.

10. Fazit

In diesem Artikel haben wir uns mit der Java-Enumeration befasst, von den Sprachgrundlagen bis hin zu fortgeschritteneren und interessanteren Anwendungsfällen aus der Praxis.

Codefragmente aus diesem Artikel befinden sich im Repository vonGithub.