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-Komponentenreal
undimaginary
.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)
undabs()
.Ü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 TypsMischiefMonkey
undFeistyParrot
inProperties
-Objekte umzuwandeln.Haustiere, die meuterisch sind (Zustand
isMutinous
) sollen ignoriert werden und zu einem leerenProperties
Objekt ohne Einträge führen.Die Methode sollte mit
null
oder unbekannten Typen aufgerufen werden können und in den Fällen ein leeresProperties
-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.
Noch mehr Aufgaben findest du im Buch: ›Captain CiaoCiao erobert Java: Das Trainingsbuch für besseres Java. 300 Java-Workshops, Aufgaben und Übungen mit kommentierten Lösungen‹
1.2.1. Verbrauch von Elektrogeräten vergleichen ⭐
Jedes Elektrogerät hat eine Leistung, die in Watt gemessen wird.
Aufgabe Teil 1:
Deklariere in
ElectronicDevice
eine privateint
-Objektvariablewatt
, und generiere mit der Entwicklungsumgebung Setter/Getter.Ergänze eine
toString()
-Methode, die etwa Folgendes zurückgibt:"ElectronicDevice[watt=12kW]"
. Einige Unterklassen hatten schontoString()
überschrieben; sie sollten dann in ihremtoString()
-Methoden einsuper.toString()
enthalten.
Aufgabe Teil 2:
Schreibe eine neue Klasse
ElectronicDeviceWattComparator
, die die Schnittstellejava.util.Comparator<ElectronicDevice>
implementiert.Die
compare(…)
-Methode soll eine Ordnung der Elektrogeräte definieren, wobei ein Elektrogerät »kleiner« ist, wenn es weniger Leistung aufnimmt.Setzte zum besseren Verständnis ein
println(…)
in die eigenecompare(…)
-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) );
Ziel der Aufgabe: ElectronicDeviceWattComparator
als Implementierung der Schnittstelle Comparator
, wie im UML-Diagramm gezeigt:
1.2.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
eine
Collection
-Implementierung wieArrayList
undeine
Comparator
-Implementierung. Hier lässt sich unserElectronicDeviceWattComparator
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.2.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(…)
desShip
-Objekts nach dem Hinzufügen in die eigene Datenstruktursort(…)
auf, um nach dem Hinzufügen immer eine interne sortierte Liste zu haben.
1.2.4. 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:
Lege eine Schnittstelle
Distance
an.Setze in
Distance
zwei statische MethodenDistance ofMeter(int value)
undDistance ofKilometer(int value)
, die ein neues Objekt vom TypDistance
liefern.Setze in
Distance
eine abstrakte Methodeint meter()
. Was muss man implementieren?Setze eine Default-Methode
int kilometer()
in die SchnittstelleDistance
.
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() );
1.2.5. 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();
}
}
Aufgabe:
Setze in das Schiff eine neue Methode
removePowerConsumingElectronicDevices()
, die alle Geräte mit einer Leistungsaufnahme größer einer selbstgewählten KonstantenMAXIMUM_POWER_COMSUMPTION
löscht.
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ürCaramels
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 vomString
in ein Aufzählungselement vom TypCandyType
eine neue Methodestatic Optional<CandyType> fromName(String input)
im AufzählungstypCandyType
ein. Ausnahmen durch falsche Eingaben darf die Methode nicht auslösen; unbekannte Namen führen zu einemOptional.empty()
.
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 Methoderandom()
, die eine zufällige Süßware liefert.System.out.println( CandyType.random() ); // z. B. CHOCOLATE System.out.println( CandyType.random() ); // z. B. LOLLIPOPS
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 MethodeaddictiveQuality()
liefern.Da Captain CiaoCiao eine Abhängigkeit in Richtung Süßwaren mit größerem Suchtfaktor erreichen möchte, soll eine neue
CandyType
-Methodenext()
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()
istLOLLIPOPS
.CandyType.LOLLIPOPS.next()
ist z. B.LICORICE
.CandyType.LOLLIPOPS.next()
ist z. B.CHEWING_GUMS
.CandyType.CARAMELS.next()
istCARAMELS
.
1.3.4. 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:
Bewertung | Prozente | Note |
---|---|---|
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 das entsprechende Aufzählungselement liefert.Schreibe einen
switch
-Ausdruck, der für alle möglichen Aufzählungselemente die entsprechende Bewertung auf dem Bildschirm ausgibt. Zum Beispiel sollGrade.fromPercentage(49.999)
die AusgabeNicht ausreichend
liefern.
1.3.5. 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
, derDistance
implementiert mit genau einem AufzählungselementEUCLIDEAN
:enum Distances implements Distance { EUCLIDEAN }
Wer nun eine
Distance
-Implementierung für den euklidischen Abstand benötigt, kann sie überDistances.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ählungselementMANHATTAN
, damit es zwei KonstantenEUCLIDEAN
undMANHATTAN
gibt.
Die Manhattan-Distanz bildet sich aus der Summe der absoluten Differenzen der Einzelkoordinaten, für einen 2D-Punkt alsoMath.abs(x1 - x2) + Math.abs(y1 - y2)
.
Noch mehr Aufgaben findest du im Buch: ›Captain CiaoCiao erobert Java: Das Trainingsbuch für besseres Java. 300 Java-Workshops, Aufgaben und Übungen mit kommentierten Lösungen‹