Enums (ab Java 5)

+ andere TechDocs
+ Generics
+ Annotations
+


Zu den Neuerungen in Java 5 gehören die im JSR 201 beschriebenen Enums (= Enumerations = Aufzählungstypen).

Der folgende Text beschreibt nur Grundlagen zu Enums. Weitergehende Informationen finden Sie zum Beispiel unter:



Inhalt

  1. Einfaches Enum
  2. Enum mit spezifischen Daten und gemeinsamer Berechnungsmethode
  3. Enum mit spezifischer Berechnungsmethode pro Enumerationskonstante
  4. Spezifische Daten zusammen mit spezifischen Methoden
  5. Ein Interface implementierendes Enum
  6. Dynamisch zur Laufzeit ausgewählte Enumerationskonstante
  7. Enum mit Interface als generischer Methodenparameter
  8. EnumSet (statt Bit-Feld)
  9. EnumMap



Einfaches Enum

Das in folgendem Beispiel gezeigte Enum Farbe könnte zum Beispiel als Diskriminator dienen:

public class EnumTest1
{
   public static void main( String[] args )
   {
      for( Farbe f : Farbe.values() )
         System.out.println( f );
   }
}

enum Farbe            // Enum-Typ
{
   ROT, GRUEN, BLAU;  // Enumerationskonstanten
}

Enum-Klassen exportieren pro Enumerationskonstante eine Instanz. Das Beispiel-Enum Farbe exportiert die drei Instanzen ROT, GRUEN und BLAU. Enum-Instanzen sind Singletons.

Ein konkretes Anwendungsbeispiel für ein Diskriminator-Enum finden Sie weiter unten im EnumMap-Beispiel.



Enum mit spezifischen Daten und gemeinsamer Berechnungsmethode

Bei diesem Enum-Beispiel werden im Konstruktor pro Enumerationskonstante spezifische Daten übergeben (alkoholgehaltProzent). Für alle Enumerationskonstanten gemeinsame Methoden (alkoholgehalt() und abbauzeit()) ermöglichen Abfragen und Berechnungen.

import java.text.DecimalFormat;

public class EnumTest2
{
   public static void main( String[] args )
   {
      System.out.println( "Alkoholgehalt von Bananen: " + Alkohol.BANANE.alkoholgehalt() + " %" );
      System.out.println( Alkohol.BIER.abbauzeit( 0.5, 80 ) );
      System.out.println( Alkohol.WEIN.abbauzeit( 0.2, 70 ) );
   }
}

enum Alkohol
{
   WEIN( 12 ), BIER( 4.5 ), BANANE( 1 ), WASSER( 0 );

   private final double alkoholgehaltProzent;

   // Konstruktor mit Parameteruebergabe:
   Alkohol( double alkoholgehaltProzent ) { this.alkoholgehaltProzent = alkoholgehaltProzent; }

   public double alkoholgehalt() { return alkoholgehaltProzent; }

   // Gemeinsame Berechnungsmethode fuer alle Enumerationskonstanten:
   public String abbauzeit( double getraenkMenge, double koerpergewicht ) {
      double mengeAlkoholKg = getraenkMenge * alkoholgehaltProzent / 100;
      double abbauzeit = 9090.9 * mengeAlkoholKg / koerpergewicht; // 0,11g / (kg * h)
      return "Alkoholabbau dauert " + (new DecimalFormat( "0.0" )).format( abbauzeit ) + " h bei " +
             getraenkMenge + " Liter " + toString() + " bei " + koerpergewicht + " kg Koerpergewicht";
   }
}

Die Ausgabe sieht folgendermaßen aus:

Alkoholgehalt von Bananen: 1.0 %
Alkoholabbau dauert 2,6 h bei 0.5 Liter BIER bei 80.0 kg Koerpergewicht
Alkoholabbau dauert 3,1 h bei 0.2 Liter WEIN bei 70.0 kg Koerpergewicht


Enum mit spezifischer Berechnungsmethode pro Enumerationskonstante

In letztem Beispiel wurde eine gemeinsame Methode für alle Enumerationskonstanten implementiert, welche durch im Konstruktor übergebene Enumerationskonstanten-spezifische Daten parametrisiert wurde.
In diesem Beispiel gibt es pro Enumerationskonstante eine eigene spezifische Methode, die durch die abstrakte Methode deklariert ist.
a und b sind die Seitenlängen des die Figur umschließenden Rechtecks.
flaeche() berechnet den Flächeninhalt der Figur.

public class EnumTest3
{
   public static void main( String[] args )
   {
      double a = 3, b = 4;
      for( Figur3 f : Figur3.values() )
         System.out.println( f + ": Flaeche (" + a + ", " + b + ") = " + f.flaeche( a, b ) );
   }
}

enum Figur3
{
   // Pro Enumerationskonstante eine eigene spezifische Methode:
   RECHTECK { public double flaeche( double a, double b ) { return a * b; } },
   DREIECK  { public double flaeche( double a, double b ) { return a * b / 2; } },
   ELLIPSE  { public double flaeche( double a, double b ) { return a * b * Math.PI / 4; } };

   abstract double flaeche( double a, double b );
}

Die Ausgabe sieht folgendermaßen aus:

RECHTECK: Flaeche (3.0, 4.0) = 12.0
DREIECK:  Flaeche (3.0, 4.0) =  6.0
ELLIPSE:  Flaeche (3.0, 4.0) =  9.42


Spezifische Daten zusammen mit spezifischen Methoden

Parameterübergabe im Konstruktor und Enumerationskonstanten-spezifische Methoden können natürlich auch kombiniert werden:

public class EnumTest4
{
   public static void main( String[] args )
   {
      double a = 4, b = 5;
      for( Figur4 f : Figur4.values() )
         System.out.println( f.kuerzel() + "-Flaeche(" + a + ", " + b + ") = " + f.flaeche( a, b ) );
   }
}

enum Figur4
{
   RECHTECK( "RE" ) { public double flaeche( double a, double b ) { return a * b; } },
   DREIECK(  "DR" ) { public double flaeche( double a, double b ) { return a * b / 2; } },
   ELLIPSE(  "EL" ) { public double flaeche( double a, double b ) { return a * b * Math.PI / 4; } };

   private final String kuerzel;

   Figur4( String kuerzel ) { this.kuerzel = kuerzel; }

   public String kuerzel() { return kuerzel; }

   abstract double flaeche( double a, double b );
}


Ein Interface implementierendes Enum

In den letzten beiden Beispielen wurde die Enumerationskonstanten-spezifische Methode durch eine interne abstrakte Methode im Enum deklariert.
In diesem Beispiel fehlt eine solche abstrakte Methode. Stattdessen wird die Enumerationskonstanten-spezifische Methode in einem externen Interface deklariert, welches das Enum implementiert.

public class EnumTest5
{
   public static void main( String[] args )
   {
      double a = 5, b = 6;
      for( Flaeche f : Figur5.values() )
         System.out.println( f + ": Flaeche (" + a + ", " + b + ") = " + f.flaeche( a, b ) );
   }
}

enum Figur5 implements Flaeche
{
   RECHTECK { public double flaeche( double a, double b ) { return a * b; } },
   DREIECK  { public double flaeche( double a, double b ) { return a * b / 2; } },
   ELLIPSE  { public double flaeche( double a, double b ) { return a * b * Math.PI / 4; } };
}

// Implementiertes Interface:
interface Flaeche
{
   double flaeche( double a, double b );
}


Dynamisch zur Laufzeit ausgewählte Enumerationskonstante

Das folgende Beispiel ermittelt die zu verwendende Enumerationskonstante dynamisch zur Laufzeit über einen Kommandozeilenparameter.

import java.util.Arrays;

public class EnumTest6
{
   public static void main( String[] args )
   {
      if( args.length != 3 )
         System.out.println( "Bitte drei Kommandozeilenparameter uebergeben:\n" +
                             "  - Figurart " + Arrays.toString( Figur6.values() ) + "\n" +
                             "  - Zwei Kantenlaengen des umschliessenden Rechtecks\n" +
                             "Zum Beispiel so:\n" +
                             "  java EnumTest6 DREIECK 10 20" );
      else {
         // Figurart dynamisch aus Kommandozeilenparameter:
         Flaeche6 f = Figur6.valueOf( args[0] );
         double   a = Double.parseDouble( args[1] );
         double   b = Double.parseDouble( args[2] );
         System.out.println( f + ": Flaeche (" + a + ", " + b + ") = " + f.flaeche( a, b ) );
      }
   }
}

enum Figur6 implements Flaeche6
{
   RECHTECK { public double flaeche( double a, double b ) { return a * b; } },
   DREIECK  { public double flaeche( double a, double b ) { return a * b / 2; } },
   ELLIPSE  { public double flaeche( double a, double b ) { return a * b * Math.PI / 4; } };
}

interface Flaeche6
{
   double flaeche( double a, double b );
}

java EnumTest6 liefert die Ausgabe:

Bitte drei Kommandozeilenparameter uebergeben:
  - Figurart [RECHTECK, DREIECK, ELLIPSE]
  - Zwei Kantenlaengen des umschliessenden Rechtecks
Zum Beispiel so:
  java EnumTest6 DREIECK 10 20

Und java EnumTest6 DREIECK 6 7:

DREIECK: Flaeche (6.0, 7.0) = 21.0


Enum mit Interface als generischer Methodenparameter

Um per Interface zugängliche Enums als Methodenparameter zu übergeben, bieten sich zwei gleichwertige Varianten an:

Informationen zu Generics finden Sie unter java-generics.htm.

import java.util.*;

public class EnumTest7
{
   public static void main( String[] args )
   {
      double a = 7, b = 8;
      berechneAlleFlaechen1( Figur7.class, a, b );
      berechneAlleFlaechen2( Arrays.asList( Figur7.values() ), a, b );
   }

   // Variante 1:
   static <T extends Enum<T> & Flaeche7> void berechneAlleFlaechen1( Class<T> calcs, double a, double b )
   {
      for( Flaeche7 f : calcs.getEnumConstants() )
         System.out.println( f + ": Flaeche (" + a + ", " + b + ") = " + f.flaeche( a, b ) );
   }

   // Variante 2:
   static void berechneAlleFlaechen2( Collection<? extends Flaeche7> calcs, double a, double b )
   {
      for( Flaeche7 f : calcs )
         System.out.println( f + ": Flaeche (" + a + ", " + b + ") = " + f.flaeche( a, b ) );
   }
}

enum Figur7 implements Flaeche7
{
   RECHTECK { public double flaeche( double a, double b ) { return a * b; } },
   DREIECK  { public double flaeche( double a, double b ) { return a * b / 2; } },
   ELLIPSE  { public double flaeche( double a, double b ) { return a * b * Math.PI / 4; } };
}

interface Flaeche7
{
   double flaeche( double a, double b );
}


EnumSet (statt Bit-Feld)

Vor Java 5 wurden Flag-Sammlungen meistens in Bit-Feldern mit int- oder long-Variablen implementiert.
Als bessere Alternative gibt es seit Java 5 das EnumSet, welches Flag-Sammlungen typsicher speichert.
Das folgende Beispiel deutet die Verwendung von EnumSet an.

import java.util.*;

public class EnumTest8
{
   public static void main( String[] args )
   {
      Text text = new Text( "Mein Text" );
      EnumSet<Text.Stil> textStile = EnumSet.of( Text.Stil.FETT, Text.Stil.KURSIV );
      text.verwendeTextStile( textStile );
      // ...
   }
}

class Text
{
   public enum Stil { FETT, KURSIV, UNTERSTRICHEN, DURCHGESTRICHEN; }

   private String s;

   public Text( String s ) { this.s = s; }

   public void verwendeTextStile( EnumSet<Stil> textStile )
   {
      System.out.println( "Gesetzte Textstile: " + textStile );
      System.out.println( "Ist KURSIV gesetzt? " + textStile.contains( Stil.KURSIV ) );
      // ...
   }

   // ...
}


EnumMap

Um eine Menge von Objekten sortiert nach einem Diskriminator-Enum zu speichern, bietet sich die EnumMap an.
Die ursprünglich unsortierte Menge von Fahrzeugen wird darin nach Fahrzeugart sortiert gespeichert.

import java.util.*;

public class EnumTest9
{
   public static void main( String[] args )
   {
      Fahrzeug[] fahrzeuge = new Fahrzeug[] {
         new Fahrzeug( "Roller",    Fahrzeug.Art.ZWEIRAD ),
         new Fahrzeug( "Cabriolet", Fahrzeug.Art.PKW ),
         new Fahrzeug( "Fahrrad",   Fahrzeug.Art.ZWEIRAD ),
         new Fahrzeug( "Limousine", Fahrzeug.Art.PKW ),
         new Fahrzeug( "Lastwagen", Fahrzeug.Art.LKW ),
         new Fahrzeug( "SUV",       Fahrzeug.Art.PKW ),
         new Fahrzeug( "Moped",     Fahrzeug.Art.ZWEIRAD ),
         new Fahrzeug( "Bus",       Fahrzeug.Art.LKW ) };

      Map<Fahrzeug.Art, Set<Fahrzeug>> fahrzeugeNachArt =
         new EnumMap<Fahrzeug.Art, Set<Fahrzeug>>( Fahrzeug.Art.class );

      for( Fahrzeug.Art a : Fahrzeug.Art.values() )
         fahrzeugeNachArt.put( a, new HashSet<Fahrzeug>() );
      for( Fahrzeug f : fahrzeuge )
         fahrzeugeNachArt.get( f.art() ).add( f );

      System.out.println( fahrzeugeNachArt );
   }
}

class Fahrzeug
{
   public enum Art { ZWEIRAD, PKW, LKW; }

   private final String name;
   private final Art    art;

   Fahrzeug( String name, Art art )
   {
      this.name = name;
      this.art  = art;
   }

   public Art art() { return art; }

   @Override public String toString() { return name; }
}

In der Ausgabe der EnumMap erscheinen die Fahrzeuge sortiert nach Fahrzeugart:

{ZWEIRAD=[Moped, Roller, Fahrrad], PKW=[Limousine, SUV, Cabriolet], LKW=[Bus, Lastwagen]}



Weitere Themen: andere TechDocs | Generics | Annotations
© 2008-2009 Torsten Horn, Aachen