I am currently working on an English translation. If you like to help to proofread please contact me: ullenboom ät g m a i l dot c o m.

Java Videotraining Werbung

1. Besondere Typen aus der Java-Bibliothek

Die Klassenbibliothek umfasst Tausende von Typen, eine riesige Anzahl kommt über die Java Enterprise Frameworks und quelloffene Bibliotheken hinzu. Zum Glück muss man nicht alle diese Typen kennen, um erfolgreich Software schreiben zu können. Vieles in der Java SE ist auch sehr low level und eher für Entwickler von Frameworks gedacht.

Einige allgemeine Typen sind besonders eng mit der Sprache verbunden, sodass selbst der Compiler sie kennt. Sie müssen wir verstehen, damit wir die Möglichkeiten der Sprache optimal nutzen können. Es geht daher in diesem Kapitel um die absolute Oberklasse Object, welche Methoden für uns relevant sind, um Ordnungsprinzipien, die Umwendung von primitiven Typen und Wrapper-Typen (Autoboxing) und um Aufzählungstypen, eine Art spezielle Klassendeklaration.

Voraussetzungen

  • Unterschied zwischen == (Identität) und equals(…​) (Gleichwertigkeit) kennen

  • equals(…​) und hashCode() implementieren können

  • Comparator und Comparable für Ordnungskriterien implementieren

  • Funktion von Autoboxing verstehen

  • Aufzählungstypen deklarieren und mit Eigenschaften ausstatten

Verwendete Datentypen in diesem Kapitel:

1.1. Absolute Oberklasse java.lang.Object

Aus Object gibt es drei Methoden, die Unterklassen in der Regel überschreiben: toString(), equals(Object) und hashCode(). Während die Identität mit == und != getestet wird, ist für die Gleichwertigkeit die Methode equals(Object) zuständig. equals(Object) und hashCode() werden immer zusammen implementiert, sodass beide zueinanderpassen; wenn zum Beispiel der Hashcode von zwei Objekten nicht gleich ist, muss auch equals(…​) false ergeben und wenn equals(…​) true ergibt, müssen auch die beiden Hashcodes gleich sein. Bei der Implementierung sind gewisse Regeln zu beachten, weshalb die nächsten beiden Aufgaben equals(Object) und hashCode() in den Fokus setzen.

1.1.1. equals(Object) und hashCode() generieren lassen ⭐

Jede moderne Java-Entwicklungsumgebung kann diverse Methoden automatisch generieren, zum Beispiel toString(), aber auch equals(Object) und hashCode().

Die Entwicklungsumgebungen haben etwas andere Menüpunkte und Dialoge. equals(Object)/hashCode() lassen sich für die drei bekannten IDEs wie folgt erzeugen:

IntellIJ

In dieser IDE drücken wir den Shortcut Alt+Einfg. Es folgt eine Liste von Dingen, die generiert werden können, und darunter steht equals() and hashCode() aufgeführt. Aktivieren wir den Eintrag, öffnet sich zunächst ein Dialog, in dem wir verschiedene Vorlagen auswählen können. IntelliJ kann die Methoden auf unterschiedliche Arten generieren. Wir bleiben bei der Default-Einstellung und wechseln auf den nächsten Dialog mit Next. Wir wählen jetzt die Objektvariablen aus, die für die equals(…​)-Methode verwendet werden: standardmäßig sind das alle. Wir gehen auf Next. Im nächsten Schritt kommt der gleiche Dialog, doch nun wählen wir die Objektvariablen für die hashCode()-Methode aus; standardmäßig sind wieder alle vorausgewählt. Wir drücken Next und kommen in den nächsten Dialog, wo wir noch bestimmen dürfen, ob der Name null sein darf oder nicht. Da wir annehmen, er könnte null sein, wählen wir das Feld nicht an, und gehen auf Finish.

Eclipse

Bei Eclipse setzen wir den Cursor in den Rumpf der Klasse, aktivieren das Kontextmenü, navigieren auf den Menüpunkt Source, und gehen auf Generate hashCode() and equals(). Anders als in IntelliJ werden in Eclipse die Objektvariablen nur einmal angezeigt, und für die equals(Object)- sowie hashCode()-Methode gleichzeitig verwendet. Die Codegeneration startet mit einem Klick auf Generate.

NetBeans

Gehe im Menüpunkt unter Source (oder aktiviere das Kontextmenü im Editor), dann wähle Insert Code; alternativ aktiviere über die Tastatur Alt+Einfg. Es folgt ein kleiner Dialog, in dem man equals() and hashCode()…​ auswählen kann. Auch andere Dinge wie Setter, Getter, Konstruktor, toString() lassen sich so generieren.

Aufgabe:

  • Kopiere folgende Klasse in das Projekt:

    public class Person {
      public long id;
      public int age;
      public double income;
      public boolean isDrugLord;
      public String name;
    }
  • Erzeuge mit der IDE für die Klasse Person die Methoden equals(Object) und hashCode().

  • Studiere die generierten Methoden ganz genau.

1.1.2. Existierende equals(Object)-Implementierungen ⭐⭐

Was sagt die Javadoc, bzw. wie sehen die equals(Object)-Implementierungen bei den folgenden Klassen aus?

  • java.awt.Rectangle (Modul java.desktop)

  • java.lang.String (Modul java.base)

  • java.lang.StringBuilder (Modul java.base)

  • java.net.URL (Modul java.base)

Online lässt sich der Code vom OpenJDK unter https://github.com/openjdk/jdk/tree/master/src/ für die einzelnen Module einsehen; die Klassen finden sich unter share/classes.

1.2. Schnittstellen Comparator und Comparable

Ein Vergleich mit equals(…​) sagt aus, ob zwei Objekte gleichwertig sind, aber besagt nichts über die Ordnung, welches Objekt größer oder kleiner ist. Dafür gibt es in Java zwei Schnittstellen:

  • Comparable wird von den Typen implementiert, die eine natürliche Ordnung haben, für die es also in der Regel ein übliches Ordnungskriterium gibt. Bei zwei Datumswerten ist klar, welches vorher und welches nachher lag oder ob beide Datumswerte gleich sind.

  • Von der Schnittstelle Comparator gibt es für jedes Ordnungskriterium eine Implementierung. Personen können wir nach dem Namen sortieren, aber auch nach dem Alter: Das wären zwei Implementierungen von Comparator.

Comparator Comparable UML
Abbildung 1. UML-Diagramm von Comparator und Comparable

1.2.1. Superhelden verarbeiten

Bonny Brain interessiert sich schon seit ihrer Kindheit für Superhelden. Und es gibt so viele spannende Dinge zu wissen. Damit Bonny Brain Antworten auf ihre Fragen bekommt, soll zunächst die Datenbasis definiert werden.

Aufgabe: Kopiere folgende Klassendeklaration in das eigene Java-Projekt:[1]

Listing 1. com/tutego/exercise/util/Heroes.java
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Heroes {

  private Heroes() { }

  public static class Hero {

    public enum Sex { MALE, FEMALE }

    public final String name;
    public final Sex    sex;
    public final int    yearFirstAppearance;

    public Hero( String name, Sex sex, int yearFirstAppearance ) {
      this.name = Objects.requireNonNull( name );
      this.sex  = Objects.requireNonNull( sex );
      this.yearFirstAppearance = yearFirstAppearance;
    }

    @Override public String toString() {
      return String.format( "Hero[name=%s, sex=%s, yearFirstAppearance=%s]",
                            name, sex, yearFirstAppearance );
    }
  }

  public static class Universe {
    private final String name;
    private final List<Hero> heroes;

    public Universe( String name, List<Hero> heroes ) {
      this.name   = Objects.requireNonNull( name );
      this.heroes = Objects.requireNonNull( heroes );
    }

    public String name() { return name; }
    public Stream<Hero> heroes() { return heroes.stream(); }
  }

  // https://github.com/fivethirtyeight/data/tree/master/comic-characters
  private static final Hero DEADPOOL = new Hero( "Deadpool (Wade Wilson)", Hero.Sex.MALE, 1991 );
  private static final Hero LANA_LANG = new Hero( "Lana Lang", Hero.Sex.FEMALE, 1950 );
  private static final Hero THOR = new Hero( "Thor (Thor Odinson)", Hero.Sex.MALE, 1950 );
  private static final Hero IRON_MAN = new Hero( "Iron Man (Anthony 'Tony' Stark)", Hero.Sex.MALE, 1963 );
  private static final Hero SPIDERMAN = new Hero( "Spider-Man (Peter Parker)", Hero.Sex.MALE, 1962 );
  private static final Hero WONDER_WOMAN = new Hero( "Wonder Woman (Diana Prince)", Hero.Sex.FEMALE, 1941 );
  private static final Hero CAPTAIN_AMERICA = new Hero( "Captain America (Steven Rogers)", Hero.Sex.MALE, 1941 );
  private static final Hero SUPERMAN = new Hero( "Superman (Clark Kent)", Hero.Sex.MALE, 1938 );
  private static final Hero BATMAN = new Hero( "Batman (Bruce Wayne)", Hero.Sex.MALE, 1939 );

  public static final List<Hero> DC =
      Collections.unmodifiableList( Arrays.asList( SUPERMAN, LANA_LANG, WONDER_WOMAN, BATMAN ) );

  public static final List<Hero> MARVEL =
      Collections.unmodifiableList( Arrays.asList( DEADPOOL, CAPTAIN_AMERICA, THOR, IRON_MAN, SPIDERMAN ) );

  public static final List<Hero> ALL =
      Collections.unmodifiableList( Stream.concat( DC.stream(), MARVEL.stream() ).collect( Collectors.toList() ) );

  public static final List<Universe> UNIVERSES =
      Collections.unmodifiableList( Arrays.asList(
          new Universe( "DC", DC ), new Universe( "Marvel", MARVEL ) ) );
}

Wer die Klasse nun in das Aufgabenprojekt gesetzt hat, ist mit der Aufgabe auch schon fertig! Die Klassendeklaration ist eine Vorbereitung für die nächsten Aufgaben. Zum Inhalt der Klasse: Heroes deklariert die zwei geschachtelten Klassen Hero und Universe und zudem Sammlungen mit Helden. Mit welcher Java-API die Variablen initialisiert werden und welche privaten Variablen es gibt, ist für die Lösung nicht relevant. Wir kommen im Rahmen der Java-Stream-API noch einmal auf die Klasse Heroes zurück.

1.2.2. Superhelden vergleichen ⭐⭐

Nicht alle Helden sind gleich! Einige erscheinen früher oder haben eine Glatze. Wir können Comparator-Objekte nutzen, um individuell die Ordnung zwischen Helden zu bestimmen.

Aufgabe:

  • Baue als Erstes eine veränderbare Liste mit allen Helden auf:

    List<Hero> allHeroes = new ArrayList<>( Heroes.ALL );
  • Schreibe einen Comparator, damit Helden nach dem Erscheinungsjahr in eine Reihe gebracht werden. Nutze zur Implementierung:

    1. eine lokale Klasse

    2. eine anonyme Klasse

    3. einen Lambda-Ausdruck

  • Die Schnittstelle List hat eine sort(…​)-Methode. Sortiere mit dem neuen Comparator die Liste allHeroes.

  • Erweitere den einen Comparator, sodass bei gleichem Erscheinungsjahr zusätzlich nach dem Namen verglichen wird. Bewerte den Ansatz, dass der Comparator mehrere Kriterien gleichzeitig prüft.

1.2.3. Helden-Comparatoren verketten ⭐⭐

Die Sortierung erfolgt oftmals nicht nur nach einem Kriterium, sondern nach mehreren. Ein typisches Beispiel ist das Telefonbuch — falls das heute noch bekannt ist …​ Zunächst werden die Einträge nach Nachnamen sortiert und bei einer Gruppe von Personen mit den gleichen Nachnamen anschließend nach Vornamen.

Oftmals sind bei der Ordnung mehrere Kriterien involviert. Die Verkettung der Comparator-Exemplare müssen wir nicht selbst übernehmen, sondern wir können auf die Default-Methode thenComparing(…​) zurückgreifen.

Aufgabe:

  • Studiere die API-Dokumentation (oder Implementierung) zur Comparator-Methode thenComparing(Comparator<? super T> other).

  • Einige Helden haben das gleiche Erscheinungsjahr.

    1. Schreibe eine Comparator-Implementierung, die nur die Helden nach ihren Namen vergleicht.

    2. Schreibe einen zweiten Comparator, der die Helden nur nach ihrem Erscheinungsjahr vergleicht.

    3. Sortiere alle Helden im ersten Kriterium nach dem Erscheinungsjahr, dann nach dem Namen. Implementiere den zusammengesetzten Comparator mit thenComparing(…​).

1.2.4. Mit einem Key-Extraktor schnell zum Comparator ⭐⭐

Ein Comparator »extrahiert« in aller Regel Kernelemente und vergleicht sie. Damit kann der Comparator aber eigentlich zwei Dinge: erstens die relevanten Informationen extrahieren und zweitens diese Werte vergleichen. Nach guter objektorientierter Programmierung sollen diese zwei Schritte getrennt werden. Das ist das Ziel der statischen comparingXXX(…​)-Methoden der Schnittstelle Comparator. Denn diesen Methoden wird ein lediglich ein Key-Extraktor übergeben, und den Vergleich der extrahierten Werte übernehmen die comparingXXX(…​) Methoden selbst.

Schauen wir uns drei Implementierungen an, und beginnen wir mit der Implementierung der Methode comparing(Function):

Listing 2. OpenJDK-Implementierung aus java.util.Comparator
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
       Function<? super T, ? extends U> keyExtractor)
{
   Objects.requireNonNull(keyExtractor);
   return (Comparator<T> & Serializable)
       (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}

Am Anfang findet der obligatorische null-Test statt. Anschließend holt sich keyExtractor.apply(…​) den Wert aus dem ersten Objekt und dem zweiten Objekt. Da beide Objekte eine natürliche Ordnung haben (sie sind Comparable), liefert compareTo(…​) diese Ordnung zurück. comparing(Function) liefert einen Comparator zurück, hier als Lambda-Ausdruck.

Der Key-Extraktor ist eine Funktion, die einen Wert liefert, und genau dieser Wert wird intern verglichen. comparing(Function) kann verwendet werden, wenn die Objekte eine natürliche Ordnung haben. Es gibt nun unterschiedliche Fabrikmethoden für Comparator-Exemplare, die neben dem Vergleich von Comparable-Objekten ausgewählte primitive Datentypen extrahieren und diese vergleichen. Schauen wir uns die zweite Methode comparingInt(ToIntFunction) an, wenn zwei Ganzzahlen extrahiert werden über eine ToIntFunction:

Listing 3. OpenJDK-Implementierung aus java.util.Comparator
public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
   Objects.requireNonNull(keyExtractor);
   return (Comparator<T> & Serializable)
       (c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
}

Der Key-Extraktor extrahiert aus den zu vergleichenden Objekten einen Ganzzahlwert und geht dann an Integer.compare(…​), um diese beiden Ganzzahlen zu vergleichen.

Schauen wir uns noch die letzte Funktion an. Sie verbindet einen Key-Extraktor mit einem Comparator. Das ist dann praktisch, wenn die Objekte keine natürliche Ordnung haben, sondern ein fremder Comparator die Ordnung feststellen muss.

Listing 4. OpenJDK-Implementierung aus java.util.Comparator
public static <T, U> Comparator<T> comparing(
       Function<? super T, ? extends U> keyExtractor,
       Comparator<? super U> keyComparator)
{
   Objects.requireNonNull(keyExtractor);
   Objects.requireNonNull(keyComparator);
   return (Comparator<T> & Serializable)
       (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
                                         keyExtractor.apply(c2));
}

Zunächst einmal wird der Key-Extraktor für die beiden Objekte c1 und c2 die Werte extrahieren. Danach kommen die Werte in die compare(…​)-Methode der übergebenen Comparator-Instanz. Der Lambda-Ausdruck liefert einen neuen Comparator zurück.

Vergleicht man das mit eigenen Comparator-Implementierungen, dann wird man im Allgemeinen das Gleiche tun, nämlich von zwei Objekten die Werte extrahieren und vergleichen. Genau das übernehmen die Fabrikfunktionen! Wir müssen lediglich mitgeben, wie ein Schlüssel extrahiert werden muss, und dann wird dieser Key-Extraktor auf die beiden Werte, die verglichen werden sollen, angewendet.

Variiere die vorherige Aufgabe wie folgt:

  1. Erzeuge mit der statischen Methode Comparator.comparingInt(ToIntFunction<? super T> keyExtractor) und einem Lambda-Ausdruck einen Comparator für Helden-Erscheinungsjahre und sortiere damit die Liste.

  2. Verwende für den Namensvergleich ebenfalls eine Comparator-Methode, die einen Key-Extractor benutzt.

  3. Sortiere nach Namen und anschließend nach Alter, wieder mit thenComparing(…​). Ändere dann die Verkettungsmethode, und verwende thenComparingInt(…​) anstelle von thenComparing(…​).

  4. Schreibe einen Comparator<Hero>, der sich auf CASE_INSENSITIVE_ORDER aus String stützt, um den Heldennamen unabhängig von der Groß-/Kleinschreibung. Greife auf die Comparator-Methode comparing(Function, Comparator) zurück.

1.2.5. Punkte nach Abstand zum Zentrum sortieren ⭐

Captain CiaoCiao betreibt am Nordpol seine Absolutus Zero-Zero Vodka-Destillerie. Auf einer gedachten rechteckigen Karte befindet sich die Brennerei genau auf dem Nullpunkt. Ein java.awt.Point ist durch x/y-Koordinaten repräsentiert, was für die Speicherung für Ortsangaben durchaus geeignet ist. Nun ist die Frage, ob gewisse Orte näher oder ferner der Brennerei liegen.

Aufgabe:

  • Schreibe einen Vergleichs-Comparator PointDistanceToZeroComparator für Point-Objekte. Für den Vergleich soll die Distanz zum Nullpunkt verwendet werden. Ist der Abstand eines Punktes p1 vom Nullpunkt kleiner als der Abstand eines Punktes p2, so soll gelten p1 < p2.

  • Baue ein Array von Point-Objekten auf und sortiere sie mit der Arrays-Methode sort(T[] a, Comparator<? super T> c).

Beispiel:

Point[] points = { new Point( 9, 3 ), new Point( 3, 4 ), new Point( 4, 3 ), new Point( 1, 2 ) };
Arrays.sort( points, new PointDistanceToZeroComparator() );
System.out.println( Arrays.toString( points ) );

Die Ausgabe ist:

[java.awt.Point[x=1,y=2], java.awt.Point[x=3,y=4], java.awt.Point[x=4,y=3], java.awt.Point[x=9,y=3]]

Die Klasse java.awt.Point bietet diverse Klassen- und Objektmethoden zur Berechnung des Abstandes. Schaue dafür in die API-Dokumenation.

1.2.6. Geschäfte in der Nähe ermitteln ⭐⭐

Für die Spirituosen der Absolutus Zero-Zero Vodka-Destillerie baut Bonny Brain die Vertriebswege auf und plant Läden an verschiedenen Orten.

Aufgabe:

  1. Lege eine neue Klasse Store an.

  2. Gib dem Store zwei Objektvariablen Point location und String name.

  3. Sammle diverse Store-Objekte in einer Liste.

  4. Schreibe eine Methode List<Store> findStoresAround(Collection<Store> stores, Point center), die eine Liste zurückliefert, nach Abständen zum center sortiert; vorne in der Liste stehen diejenigen, die der Destillerie am nächsten sind.

1.3. Aufzählungstypen (enum)

Aufzählungstypen (enum) repräsentieren abgeschlossene Mengen und sind in Java recht leistungsfähig; Sie erlauben nicht nur zusätzliche Objekt- und Klassenvariablen, neue private Konstruktoren, sondern können auch Schnittstellen implementieren, Methoden überschreiben, und sie haben einige Standardmethoden. Die kommenden Aufgaben adressieren diese schönen Möglichkeiten.

1.3.1. Aufzählung für Süßwaren ⭐

Captain CiaoCiao will eine jüngere Käuferschicht ansprechen und experimentiert statt mit Rum in seinem Labor mit Süßwaren.

Aufgabe:

  • Deklariere eine Aufzählung CandyType mit Konstanten für

    • Caramels

    • Chocolate

    • Gummies

    • Licorice

    • Lollipops

    • Chewing Gums

    • Cotton Candy

  • Achte auf die übliche Namenskonvention.

  • Benutzer sollen von der Konsole eine Süßware eingeben können. Für die Eingabe soll das passende enum-Objekt gesucht werden, die Groß-/Kleinschreibung soll keine Rolle spielen. Führe eine eigene Methode ein: static Optional<CandyType> fromName(String input).

CandyType Enum UML
Abbildung 2. UML-Diagramm vom Aufzählungstyp

1.3.2. Zufällige Süßwaren liefern ⭐

Captain CiaoCiao startet seine Verköstigungstour und wählt immer zufällige Süßwaren aus.

Aufgabe:

  • Gib dem Aufzählungstyp CandyType eine Methode random(), die eine zufällige Süßware liefert.

    System.out.println( CandyType.random() );  // z. B. CHOCOLATE
    System.out.println( CandyType.random() );  // z. B. LOLLIPOPS
  • Verschiebe die fromName(String)-Methode aus der letzten Aufgabe in den Aufzählungstyp hinein.

CandyType Enum random UML
Abbildung 3. UML-Diagramm des Aufzählungstyps mit statischer Methode

1.3.3. Süßwaren mit Suchtfaktor auszeichnen ⭐⭐

Wir wissen, dass Süßwaren süchtig machen, manche mehr, manche weniger.

Aufgabe:

  • Verbinde mit jedem Aufzählungselement aus CandyType einen Suchtfaktor (int):

    • Caramels: 9

    • Chocolate: 5

    • Gummies: 4

    • Licorice: 3

    • Lollipops: 2

    • Chewing Gums: 3

    • Cotton Candy: 1

    Zur Speicherung des Suchtfaktors nutze einen Konstruktor im enum. Den Suchtfaktor soll eine neue nichtstatische Methode addictiveQuality() liefern.

  • Da Captain CiaoCiao eine Abhängigkeit in Richtung Süßwaren mit größerem Suchtfaktor erreichen möchte, soll eine neue CandyType-Methode next() die Süßware mit der nächsthöheren Abhängigkeit liefern. Lollipops hat zwei potenzielle Nachfolger, hier soll die Auswahl zufällig auf Chewing Gums und Licorice gehen. Caramels hat keinen »Nachfolger«, und es bleibt bei Caramels.

Beispiele:

  • CandyType.COTTON_CANDY.next() ist LOLLIPOPS.

  • CandyType.LOLLIPOPS.next() ist z. B. LICORICE.

  • CandyType.LOLLIPOPS.next() ist z. B. CHEWING_GUMS.

  • CandyType.CARAMELS.next() ist CARAMELS.

AddictiveQualityCandy Enum UML
Abbildung 4. UML-Diagramm vom Aufzählungstyp

1.3.4. Schnittstellen-Implementierungen über ein enum ⭐⭐

Ein Aufzählungstyp kann Schnittstellen implementieren, aber keine Klassen erweitern.

Gegeben ist eine Schnittstelle Distance:

interface Distance {
  double distance( double x1, double y1, double x2, double y2 );
  double distance( double x1, double y1, double z1, double x2, double y2, double z2 );
}

Aufgabe:

  • Übernimm die Schnittstelle Distance in das eigene Projekt.

  • Deklariere einen Aufzählungstyp Distances, der Distance implementiert mit genau einem Aufzählungselement EUCLIDEAN:

    enum Distances implements Distance {
      EUCLIDEAN
      ...
    }

    Wer nun eine Distance-Implementierung für den euklidischen Abstand benötigt, kann sie über Distances.EUCLIDEAN bekommen.

  • Füge die Implementierung hinzu, dass der euklidische Abstand von zwei Punkten berechnet wird; zur Erinnerung, für einen 2D-Punkt:

    Math.sqrt( (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) )
  • Erweitere den Aufzählungstyp Distances um ein weiteres Aufzählungselement MANHATTAN, damit es zwei Konstanten EUCLIDEAN und MANHATTAN gibt.
    Die Manhattan-Distanz bildet sich aus der Summe der absoluten Differenzen der Einzelkoordinaten, für einen 2D-Punkt also Math.abs(x1 - x2) + Math.abs(y1 - y2).

1.3.5. Aufzählungen vereinigen ⭐⭐⭐

Im ersten Entwurf einer Software wurde ein Aufzählungstyp EssentialJob mit Berufen deklariert:

enum EssentialJob {
  CAPTAIN, QUARTERMASTER, SAILINGMASTER, BOATSWAIN, SURGEON, CARPENTER, MASTER_GUNNER
}

Nun ist aufgefallen, dass Jobs fehlen. Allerdings kann in dem Projekt der Aufzählungstyp EssentialJob nicht mehr nachträglich ergänzt werden. Es wird ein neuer Aufzählungstyp deklariert:

enum NonEssentialJob {
  MATE, ABLE_BODIED_SAILOR, CABIN_BOY
}

In Java ist es nicht möglich, dass ein enum von einem anderem enum erbt. Wohl kann aber über eine Schnittstelle eine gewisse Gemeinsamkeit geschaffen werden.

Aufgabe:

  • Schaffe über eine gemeinsame Schnittstelle für die Aufzählungstypen eine Möglichkeit, eine eigene apply(XXX job) Methode zu deklarieren, die Aufzählungen entweder von EssentialJob oder NonEssentialJob aufnehmen kann. Überlege, wie der Typ XXX beschaffen sein muss. Es reicht, wenn apply(…​) den Namen der Aufzählung nutzen kann.


1. Quelle: https://github.com/fivethirtyeight/data/tree/master/comic-characters