1. Records, Schnittstellen, Aufzählungen, Versiegelte Klassen

1.1. Records

Records sind in Java nützlich, weil sie eine einfache und kompakte Möglichkeit bieten, Datenobjekte mit einer festen Anzahl von Attributen zu definieren und zu verwenden.

1.1.1. Record für Komplexe Zahlen entwickeln ⭐

Komplexe Zahlen sind eine wichtige mathematische Konzeption, die in verschiedenen Bereichen wie Ingenieurwesen, Physik und Informatik verwendet werden. Eine komplexe Zahl besteht aus einem Realteil und einem Imaginärteil. Sie wird in der Form a + bi geschrieben, wobei dann a der Realteil und b der Imaginärteil ist. Die mathematischen Operationen werden wie folgt realisiert:

  • Addition und Subtraktion: Die Operation erfolgt elementweise, d. h., der Realteil/Imaginärteil einer Zahl wird mit dem Realteil/Imaginärteil der anderen Zahl addiert/subtrahiert.

  • Multiplikation: Die Operation erfolgt gemäß der Regel: (a + bi) × (c + di) = (ac - bd) + (ad + bc)i.

  • Betrag einer komplexen Zahl: Der Betrag von z = a + bi ist definiert als |z| = sqrt(a2 + b2).

Aufgabe:

  • Schreibe ein neues Record Complex mit den Record-Komponenten real und imaginary.

  • Lege eine Konstante I an, die die imaginäre Einheit repräsentiert, also die Quadratwurzel aus -1.

  • Implementiere die Methoden add(Complex other), subtract(Complex other), multiply(Complex other) und abs().

  • Überschreibe die toString()-Methode, die Real- und Imaginärteil auf drei Stellen hinter dem Komma rundet, sodass die Ausgabe zum Beispiel (-3,000 + 2,000i) oder (-2,000 - 3,000i) wird.

1.1.2. Record Patterns ⭐

Die tollkühnen Piraten haben nicht nur einen ausgeprägten Sinn für Beute, sondern auch eine Vorliebe für exotische Haustiere. Bisher repräsentiert eine Anwendung Haustiere in zwei Records:

record MischiefMonkey( String name, boolean isMutinous ) { }
record FeistyParrot( String name, String favoritePhrase, boolean isMutinous ) { }

Die Modellierung hat sich als starr erwiesen, wenn neue Tiertypen dazukommen und Eigenschaften sich ändern. Daher sollen die Daten aus den Records in java.util.Properties-Objekte konvertiert werden. Properties ist ein besonderer Assoziativspeicher, der einen String mit einem anderen String assoziiert. Die Methode setProperty(String key, String value) setzt ein neues Schlüssel-Werte-Paar in das Properties-Objekt.

Aufgabe:

  • Schreibe eine Methode Properties convertToProperties(Object), um Haustiere des Typs MischiefMonkey und FeistyParrot in Properties-Objekte umzuwandeln.

  • Haustiere, die meuterisch sind (Zustand isMutinous) sollen ignoriert werden und zu einem leeren Properties Objekt ohne Einträge führen.

  • Die Methode sollte mit null oder unbekannten Typen aufgerufen werden können und in den Fällen ein leeres Properties-Objekt zurückgeben.

Beispiele (Ausgabe zeigt die toString()-Repräsentation von Properties):

  • new MischiefMonkey( "Jack", true ){}

  • new FeistyParrot( "Captain Squawk", "Avast, ye scallywags!", false ){favoritePhrase=Avast, ye scallywags!, name=Captain Squawk}

  • new MischiefMonkey( "Barbossa", false ){name=Barbossa}

  • new FeistyParrot( "Polly", "Pieces of eight!", true ){}

  • new FeistyParrot( "Marauder", "Walk the plank!", false ){favoritePhrase=Walk the plank!, name=Marauder}

1.2. Schnittstellen

Abstrakte Klassen sind immer noch Klassen mit all ihren Möglichkeiten: Objektvariablen, Konstruktoren, Methoden, unterschiedliche Sichtbarkeiten. Oftmals reicht eine einfachere Form der Vorschrift, und hierfür bietet Java Schnittstellen. Sie haben keine Objektvariablen, können aber Konstanten, abstrakte Methoden, statische Methoden und default-Methoden haben — eine Objektvariable ist eine Art, wie man etwas speichert, was zur Klasse gehört, nicht zur Schnittstelle.

1.2.1. Pokopettos nach Energie vergleichen (NEU) ⭐

Captain CiaoCiao möchte seine Pokopettos nach ihrer Energie ordnen können, um zu sehen, welche am aktivsten sind.

Aufgabe Teil 1:

  • Ergänze in der toString()-Methode in Pokopetto den mood-Wert als Zahlwert. Ein String könnte etwa so aussehen: "Pokopetto is energetic and full, mood=80".

Aufgabe Teil 2:

  • Schreibe eine neue Klasse PokopettoEnergyComparator, die die Schnittstelle java.util.Comparator<Pokopetto> implementiert.

  • Die compare()-Methode soll Pokopettos nach ihrer Energie (Zustand mood) ordnen:

    • Ein Pokopetto ist "kleiner", wenn es weniger Energie hat. Das heißt, die Methode sollte einen negativen Wert zurückgeben, wenn das erste Pokopetto weniger Energie hat, einen positiven Wert, wenn es mehr Energie hat, und 0, wenn beide gleich viel Energie haben.

  • Füge eine Bildschirmausgabe wie System.out.println(p1 + " vs " + p2) in die compare(…​)-Methode ein, die zeigt, welche Pokopettos verglichen werden.

  • Ergänze in PokopettoDemo:

    Pokopetto p1 = new DefaultPokopetto();  // mood = 80
    Pokopetto p2 = new HyperPokopetto();    // mood = 100
    
    Comparator<Pokopetto> comp = new PokopettoEnergyComparator();
    System.out.println( comp.compare(p1, p2) );  // negativer Wert, da p1 weniger Energie hat
    System.out.println( comp.compare(p2, p1) );  // positiver Wert, da p2 mehr Energie hat

In einem UML-Diagramm sieht die Typbeziehung so aus:

ElectronicDeviceWattComparator UML
Abbildung 1. UML-Diagramm

1.2.2. Das energiegeladenste Pokopetto finden (NEU) ⭐

Captain CiaoCiao möchte aus seiner Sammlung schnell das Pokopetto mit der höchsten Energie finden. Dafür bietet die Klasse java.util.Collections eine hilfreiche statische Methode:

static T max(Collection coll, Comparator comp)

Diese Methode benötigt:

  1. Eine Collection-Implementierung (wie unsere ArrayList von Pokopettos)

  2. Einen Comparator (unseren PokopettoEnergyComparator)

Aufgabe:

  • Erzeuge in PokopettoDemo eine ArrayList mit mehreren Pokopettos.

  • Nutze die Collections.max(…​)-Methode zusammen mit dem PokopettoEnergyComparator, um aus dieser Liste das Pokopetto mit dem höchsten Energielevel zu bekommen.

    • Was passiert, wenn max(…​) mit einer leeren Liste aufgerufen wird?

1.2.3. Pokopettos nach Energie sortieren (NEU) ⭐

Captain CiaoCiao möchte seine Pokopetto-Sammlung immer schön sortiert halten, mit den energiegeladensten Pokopettos am Anfang der Liste.

Die List-Schnittstelle bietet eine Methode sort(Comparator<…​> c), mit der sich die Elemente einer Liste sortieren lassen. Dafür benötigt die Methode einen Comparator, der festlegt, wie zwei Elemente miteinander verglichen werden sollen.

Aufgabe:

  • Modifiziere das create-Kommando in der PokopettoGame-Klasse, dass nach dem Hinzufügen eines neuen Pokopettos die Liste automatisch mit der sort(…​)-Methode zusammen mit dem PokopettoEnergyComparator sortiert wird.

    • Achte auf die Reihenfolge, dass die energiegeladensten Pokopettos am Anfang der Liste stehen.

1.2.4. Erschöpfte Pokopettos entfernen (NEU) ⭐

Die List-Methode removeIf(Predicate<…​>filter) löscht alle Elemente, die ein Prädikat erfüllen. Die Klasse ArrayList ist eine Implementierung der Schnittstelle List, also steht die Methode bei einer ArrayList zur Verfügung.

Möchten wir z. B. aus einer List<String> aller leeren Strings löschen, können wir auf der Liste removeIf(new IsStringEmpty()) aufrufen, wobei IsStringEmpty wie folgt deklariert ist:

class IsStringEmpty implements Predicate<String> {
  @Override public boolean test( String t ) {
    return t.trim().isEmpty();
  }
}

Captain CiaoCiao möchte in seiner Sammlung nur aktive und energiegeladene Pokopettos behalten. Die trägen Exemplare sollen aussortiert werden.

Aufgabe:

  • Erzeuge in PokopettoDemo eine ArrayList mit mehreren Pokopettos.

    • Einige Pokopettos sollten erschöpft sein.

    • Andere sollten energiegeladen sein.

  • Definiere einen Schwellenwert MINIMUM_ENERGY und setze ihn auf 30.

  • Nutze removeIf(…​) mit einem passenden Predicate, um alle Pokopettos zu entfernen, deren Energie (mood) unter dem Schwellenwert liegt.

1.2.5. Pokopetto Stimmungswerte als eigener Typ (NEU) ⭐⭐⭐

Bei einem Code-Review bemerkt Captain CiaoCiao , dass primitive Datentypen wie int für die Stimmungswerte der Pokopettos problematisch sind:

  • Gültige Wertebereiche müssen ständig manuell überprüft werden

  • Die Bedeutung der Zahlen ist nicht selbsterklärend

  • Konvertierungen zwischen verschiedenen Darstellungen sind fehleranfällig

  • Primitive Typen können ungültige Werte nicht verhindern

Er beschließt, einen eigenen Typ Mood für Stimmungswerte einzuführen, der diese Probleme löst und die Datenintegrität schon auf Typebene sicherstellt.

Aufgabe:

  • Lege eine neue Schnittstelle Mood an.

  • Setze in Mood zwei statische Fabrikmethoden:

    • Mood ofPercentage(int value) erstellt einen Stimmungswert als Prozentangabe (0 bis 100)

    • Mood ofNormalized(double value) erstellt einen Stimmungswert als normalisierte Zahl (0.0 bis 1.0)

      Die Implementierungen müssen sicherstellen, dass nur Mood-Objekte erzeugt werden können.

  • Setze in Mood eine abstrakte Methode int percentage(), die den Prozentwert der Stimmung zurückgibt.

  • Setze eine Default-Methode double normalized() in die Schnittstelle, die den Prozentwert als normalisierte Zahl zwischen 0.0 und 1.0 zurückgibt.

Beispiel:

Mood highMood = Mood.ofPercentage( 80 );
System.out.printf( "Mood 80%% = %d%%, %.2f normalized%n",
                   highMood.percentage(), highMood.normalized() );

Mood lowMood = Mood.ofNormalized( 0.25 );
System.out.printf( "Mood 0.25 normalized = %d%%, %.2f normalized",
                   lowMood.percentage(), lowMood.normalized() );

Hinweis: Die Pokopetto-Klasse muss nicht refactored werden.

Mood Interface UML
Abbildung 2. UML-Diagramm der sichtbaren Mood-Schnittstelle und Implementierung

1.2.6. Pokopetto Evolutionszustände über State Pattern umsetzen (NEU) ⭐⭐

Nach langer Beobachtung seiner Pokopettos macht Captain CiaoCiao eine faszinierende Entdeckung: Die Evolution eines Pokopettos beeinflusst nicht nur sein Aussehen, sondern auch grundlegend sein Verhalten. Ein Baby-Pokopetto reagiert ganz anders auf Füttern und Spielen als ein erwachsenes Exemplar.

In einer früheren Version hatten wir bereits ein Enum für die Evolutionsstufen eingeführt:

enum Evolution {
  BABY, TEEN, ADULT
}

und die Methode getEvolution() in die Pokopetto-Klasse integriert.

Aufgabe:

Implementiere das State Pattern für die verschiedenen Evolutionsstufen eines Pokopettos. Nutze dafür das folgende Interface:

public interface PokopettoBehavior {
  boolean feed( Pokopetto pokopetto );
  boolean play( Pokopetto pokopetto );
  boolean sleep( Pokopetto pokopetto );
}
  • Erstelle für jede Evolutionsstufe eine eigene Implementierung des Interfaces:

    • BabyPokopettoBehavior

    • TeenPokopettoBehavior

    • AdultPokopettoBehavior

  • Implementiere das spezifische Verhalten für jede Evolutionsstufe:

    EvolutionSpielen (Mood)Hunger (pro Aktion)Schlafen (Mood)

    BABY

    -40

    +20

    +50

    TEEN

    -30

    +15

    +40

    ADULT

    -20

    +10

    +30

  • Wie genau muss die Verbindung zwischen Alter, Evolution und Verweis auf die PokopettoBehavior-Implementierung realisiert werden?

  • Erweitere die PokopettoDemo-Klasse um Testcode, der ein Pokopetto durch seine verschiedenen Evolutionsstufen begleitet und das unterschiedliche Verhalten demonstriert.

Pokopetto State UML
Abbildung 3. UML-Diagramm des Pokopetto State Patterns

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. Evolution erweitern (NEU) ⭐

Wir hatten schon eine Aufzählung Evolution mit den Konstanten BABY, TEEN und ADULT deklariert. Diesen Aufzählungstyp wollen wir nun erweitern.

Aufgabe:

  • Entwickle eine Methode static Optional<Evolution> fromName(String input) im Aufzählungstyp Evolution, die es ermöglicht, Text-Eingaben in die entsprechende Evolutionsstufe umzuwandeln.

    • Die Groß-/Kleinschreibung soll keine Rolle spielen

    • Bei ungültigen Eingaben soll Optional.empty() zurückgegeben werden.

    • Es dürfen keine Ausnahmen ausgelöst werden.

  • Ergänze im Aufzählungstyp Evolution eine statische Methode random(), die eine zufällige Evolutionsstufe zurückgibt.

    System.out.println( Evolution.random() );  // z. B. TEEN
    System.out.println( Evolution.random() );  // z. B. BABY
  • Teste die Implementierung in PokopettoDemo.

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

1.3.2. Evolutionsstärke und Weiterentwicklung (NEU) ⭐⭐

Aufgabe:

  • Verbinde mit jeder Aufzählungskonstante aus Evolution eine Evolutionsstärke (int):

    • BABY: 1

    • TEEN: 3

    • ADULT: 5

  • Zur Speicherung der Evolutionsstärke nutze einen Konstruktor im enum. Die Evolutionsstärke soll eine neue nichtstatische Methode evolutionPower() liefern.

  • Implementiere in Evolution eine neue Objektmethode next(), die die nächste Evolutionsstufe zurückgibt:

    • BABY entwickelt sich zu TEEN

    • TEEN entwickelt sich zu ADULT

    • ADULT bleibt ADULT

  • Teste die Implementierung in PokopettoDemo.

Beispiele:

Evolution.BABY.next()      // liefert TEEN
Evolution.TEEN.next()      // liefert ADULT
Evolution.ADULT.next()     // bleibt ADULT
Evolution Enum Constructor UML
Abbildung 5. UML-Diagramm vom Aufzählungstyp

1.3.3. Bewertung bei der Kanonenkugelprüfung (NEU) ⭐

Bald steht für die Piratencrew die gefürchtete Kanonenkugelprüfung an. Jeder Schuss wird bewertet, und aus der Treffgenauigkeit wird eine Prozentzahl ermittelt. Basierend darauf wird eine Note und eine Bewertung nach folgender Tabelle festgelegt:

Tabelle 1. Notentabelle
BewertungProzenteNote

sehr gut

≥ 95 bis < 100

1,0

sehr gut

≥ 90 bis 95

1,3

gut

≥ 85 bis < 90

1,7

gut

≥ 80 bis < 85

2,0

gut

≥ 75 bis < 80

2,3

befriedigend

≥ 70 bis < 75

2,7

befriedigend

≥ 65 bis < 70

3,0

befriedigend

≥ 60 bis < 65

3,3

ausreichend

≥ 55 bis < 60

3,7

ausreichend

≥ 50 bis < 55

4,0

nicht ausreichend

< 50

5,0

Gegeben ist ein Aufzählungstyp Grade mit folgenden Konstanten für die Noten:

enum Grade {
  _5_0, _4_0, _3_7, _3_3, _3_0, _2_7, _2_3, _2_0, _1_7, _1_3, _1_0;

  static Grade fromPercentage( double percentage ) { … }
}

Aufgabe:

  • Implementiere die Methode fromPercentage(…​), die eine Prozentzahl (nur im Bereich 0 bis 100) als Parameter bekommt und die entsprechende Aufzählungskonstante liefert.

  • Schreibe einen switch-Ausdruck, der für alle möglichen Aufzählungskonstanten die entsprechende Bewertung auf dem Bildschirm ausgibt. Zum Beispiel soll Grade.fromPercentage(49.999) die Ausgabe Nicht ausreichend liefern.

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 einer Aufzählungskonstante 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 eine weitere Aufzählungskonstante 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.4. Aufgaben aus 1. Auflage

1.4.1. Verbrauch von Elektrogeräten vergleichen ⭐

Jedes Elektrogerät hat eine Leistung, die in Watt gemessen wird.

Aufgabe Teil 1:

  1. Deklariere in ElectronicDevice eine private int-Objektvariable watt, und generiere mit der Entwicklungsumgebung Setter/Getter.

  2. Ergänze eine toString()-Methode, die etwa Folgendes zurückgibt: "ElectronicDevice[watt=12kW]". Einige Unterklassen hatten schon toString() überschrieben; sie sollten dann in ihrem toString()-Methoden ein super.toString() enthalten.

Aufgabe Teil 2:

  1. Schreibe eine neue Klasse ElectronicDeviceWattComparator, die die Schnittstelle java.util.Comparator<ElectronicDevice> implementiert.

  2. Die compare(…​)-Methode soll eine Ordnung der Elektrogeräte definieren, wobei ein Elektrogerät »kleiner« ist, wenn es weniger Leistung aufnimmt.

  3. Setzte zum besseren Verständnis ein println(…​) in die eigene compare(…​)-Methode, sodass zu sehen ist, welche Objekte verglichen werden.

Beispiel:

ElectronicDevice ea1 = new Radio(); ea1.setWatt( 200 );
ElectronicDevice ea2 = new Radio(); ea2.setWatt( 20 );
Comparator<ElectronicDevice> c = new ElectronicDeviceWattComparator();
System.out.println( c.compare(ea1, ea2) );
System.out.println( c.compare(ea2, ea1) );

1.4.2. Elektrogeräte mit dem höchsten Verbrauch finden ⭐

Die Klasse java.util.Collections hat eine statische Methode, die das größte Element einer Sammlung liefert (die Generics in den spitzen Klammern wurden für die einfache Darstellung entfernt):

static T max( Collection coll, Comparator comp )

Übergeben werden müssen der max(…​)-Methode folglich

  1. eine Collection-Implementierung wie ArrayList und

  2. eine Comparator-Implementierung. Hier lässt sich unser ElectronicDeviceWattComparator verwenden.

Aufgabe:

  • Setze in das Schiff eine Methode findMostPowerConsumingElectronicDevice(), die das Gerät mit dem größten Verbrauch liefert.

Beispiel:

  • Folgendes Programm liefert die Ausgabe 12000.

    Radio grannysRadio = new Radio();
    grannysRadio.volumeUp();
    grannysRadio.setWatt( 12_000 );
    
    TV grandpasTv = new TV();
    grandpasTv.setWatt( 1000 );
    
    Ship ship = new Ship();
    ship.load( grannysRadio );
    ship.load( grandpasTv );
    System.out.println( ship.findMostPowerConsumingElectronicDevice().getWatt() );

1.4.3. Schnittstelle Comparator zum Sortieren einsetzen ⭐

Möchte man Objekte einer Liste sortieren, kann man die sort(…​)-Methode auf List-Objekten nutzen. Wichtig ist, der sort(…​)-Methode mitzuteilen, wann ein Objekt etwa »kleiner« ist als ein anderes. Dazu lässt sich unser ElectronicDeviceWattComparator verwenden; er ist Voraussetzung für Objekte, die man sortieren möchte — das verrät auch schon die Signatur void sort(Comparator<…​> c).

Aufgabe:

  • Rufe in load(…​) des Ship-Objekts nach dem Hinzufügen in die eigene Datenstruktur sort(…​) auf, um nach dem Hinzufügen immer eine interne sortierte Liste zu haben.

1.4.4. Ausgewählte Elemente mit Predicate löschen ⭐⭐

Wollen wir ein Schiff energieeffizient gestalten, müssen wir alle Geräte mit einem zu hohen Verbrauch entfernen.

Die List-Methode removeIf(Predicate<…​>filter) löscht alle Elemente, die ein Prädikat erfüllen. Die Klasse ArrayList ist eine Implementierung der Schnittstelle List, also steht die Methode bei einer ArrayList zur Verfügung.

Möchten wir z. B. aus einer List<String> aller leeren Strings löschen, können wir auf der Liste removeIf(new IsStringEmpty()) aufrufen, wobei IsStringEmpty wie folgt deklariert ist:

class IsStringEmpty implements Predicate<String> {
  @Override public boolean test( String t ) {
    return t.trim().isEmpty();
  }
}
Find electronic devices with the highest power consumption

Aufgabe:

  • Setze in das Schiff eine neue Methode removePowerConsumingElectronicDevices(), die alle Geräte mit einer Leistungsaufnahme größer einer selbstgewählten Konstanten MAXIMUM_POWER_CONSUMPTION löscht.

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

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

Enumeration for candy

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 für die Umwandlung vom String in eine Aufzählungskonstante vom Typ CandyType eine neue Methode static Optional<CandyType> fromName(String input) im Aufzählungstyp CandyType ein. Ausnahmen durch falsche Eingaben darf die Methode nicht auslösen; unbekannte Namen führen zu einem Optional.empty().

1.4.6. Zufällige Süßwaren liefern ⭐

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

Deliver random candies

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
CandyType Enum random UML
Abbildung 6. UML-Diagramm des Aufzählungstyps mit statischen Methoden

1.4.7. Süßwaren mit Suchtfaktor auszeichnen ⭐⭐

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

Aufgabe:

  • Verbinde mit jeder Aufzählungskonstante 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 7. UML-Diagramm vom Aufzählungstyp

1.4.8. Statische und Default-Methoden in Schnittstellen ⭐⭐⭐

Schnittstellen können statische Methoden enthalten und als Fabrikmethoden dienen, also Instanzen von Klassen liefern, die diese Schnittstelle implementieren.

Aufgabe:

  1. Lege eine Schnittstelle Distance an.

  2. Setze in Distance zwei statische Methoden Distance ofMeter(int value) und Distance ofKilometer(int value), die ein neues Objekt vom Typ Distance liefern.

  3. Setze in Distance eine abstrakte Methode int meter(). Was muss man implementieren?

  4. Setze eine Default-Methode int kilometer() in die Schnittstelle Distance.

Beispiel in der Nutzung:

Distance oneKm = Distance.ofKilometer( 1 );
System.out.printf( "1 km = %d km, %d m%n", oneKm.kilometer(), oneKm.meter() );

Distance moreMeter = Distance.ofMeter( 12345 );
System.out.printf( "12345 m = %d km, %d m", moreMeter.kilometer(), moreMeter.meter() );