1. Eigene Klassen schreiben
Natürlich haben wir bis jetzt schon diverse Klassen geschrieben. Aber diese Klassen hatten bisher nur statische Eigenschaften, und wir haben unsere eigenen Klassen bisher nicht benutzt, um davon Exemplare zu bilden. Objekte selbst haben wir natürlich schon aufgebaut, und zwar aus Klassen der Standardbibliothek. Dieses Wissen wollen wir jetzt erweitern. Wir wollen Klassen schreiben und selbst von unseren eigenen Typen Exemplare bilden.
In diesem Kapitel entwickeln wir ein digitales Haustierspiel, inspiriert von den beliebten Tamagotchis der 90er Jahre. Wir beginnen mit einfachen Pokopettos, die gefüttert und gepflegt werden müssen. Im Laufe der Aufgaben erweitern wir das Spiel um verschiedene Pokopetto-Arten und komplexere Spielmechaniken.
Dabei üben wir wichtige Konzepte der Objektorientierung:
Wie Objekte Zustände und Verhalten haben
Wie Objekte miteinander in Beziehung stehen (Assoziationen oder "Hat"-Beziehungen)
Wie wir Gemeinsamkeiten in Hierarchien organisieren (Vererbung oder "Ist-eine-Art-von"-Beziehungen)
Captain CiaoCiao und Bonny Brain werden uns auf dieser Reise begleiten und ihre virtuelle Pokopetto-Sammlung immer weiter ausbauen und verbessern.
Voraussetzungen
neue Klassen anlegen können
Objektvariablen in Klassen setzen können
Methoden implementieren können
Sichtbarkeiten
private
, paketsichtbar undpublic
kennenstatische Eigenschaften programmieren können
Aufzählungstypen deklarieren und nutzen können
einfache und überladene Konstruktoren implementieren können
Arten von Assoziationen kennen
1:1-Assoziation implementieren können
einfache Listen nutzen können
Delegation von Operationen einsetzen können
Vererbungsbeziehungen mit
extends
realisieren könnenMethoden überschreiben können
zwei Bedeutungen von
super
verstehendynamisches Binden einsetzen können
Schnittstellen nutzen und deklarieren können
Default-Methoden in Schnittstellen einsetzen können
Verwendete Datentypen in diesem Kapitel:
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.1. Klassendeklaration und Objekteigenschaften
Für einen neuen Typ schreiben wir in Java eine neue Klasse. Legen wir in diesem Abschnitt ein paar Klassen an, und geben wir den Klassen Objektvariablen und Methoden.
1.1.1. Pokopetto aufbauen (NEU) ⭐
Captain CiaoCiao liebt es, neue Technologien zu erforschen. Als er in einem japanischen Hafen ein merkwürdiges elektronisches Spielzeug entdeckt — ein sogenanntes Pokopetto — ist er sofort fasziniert. Dieses virtuelle Haustier erinnert ihn an die Tamagotchis der 90er Jahre…
Wie sein Vorbild muss ein Pokopetto gefüttert und gepflegt werden, damit es gesund und glücklich bleibt. Der Zustand eines Pokopettos wird durch drei Zustände bestimmt:
seinen Hunger (0 = satt bis 100 = kurz vor dem Verhungern),
seine Stimmung (0 = erschöpft/traurig bis 100 = energiegeladen/glücklich) und
sein Alter (gemessen in erfolgreichen Interaktionen).
Aufgabe:
Lege eine neue Klasse
Pokopetto
an.Setze folgende Objektvariablen in die Klasse, sodass jedes Pokopetto einen eigenen Zustand haben kann:
hunger
(Hungergefühl des Pokopetto)mood
(Stimmung und Energielevel)age
(Alter des Pokopetto in Spielzügen)Welche Variablentypen sind sinnvoll?
Schreibe zusätzlich eine Klasse
PokopettoDemo
, die einPokopetto
-Objekt in ihrermain(…)
-Methode aufbaut. Belege und erfrage zum Test die Variablen. Wir wollen in dieser Klasse im Folgenden verschiedene Features testen.Setze in der
Pokopetto
-Klasse die Startwerte für die Objektvariablen.hunger
soll mit 30 starten (leicht hungrig)mood
soll mit 80 starten (gute Stimmung)
Berücksichtige die Namenskonventionen: Klassen beginnen mit einem Großbuchstaben, und Variablen sowie Methoden mit einem Kleinbuchstaben; nur Konstanten sind in Großbuchstaben. Wir schreiben alles auf Englisch und verzichten auf deutsche Bezeichner. |
1.1.2. Pokopetto steuern (NEU) ⭐
Nach seiner ersten Begegnung mit einem Pokopetto beschließt Captain CiaoCiao, die Funktionsweise genauer zu studieren. Er erstellt eine Liste der grundlegenden Aktionen, die ein Pokopetto ausführen kann. Nun sollen die Methoden implementiert werden, die das Verhalten des virtuellen Haustiers steuern.
Für die Werteänderungen steht folgende Hilfsmethode zur Verfügung:
int clampPercentage( int value ) {
return Math.clamp( value, 0, 100 );
}
Diese Methode stellt sicher, dass Werte im gültigen Bereich von 0 bis 100 bleiben. Kopiere die Methode in die Klasse Pokopetto
. Sie nutzt die clamp(…)
-Methode aus Java 21. Für ältere Java-Versionen verwende Math.min(Math.max(value, 0), 100)
.
Aufgabe:
Ergänze folgende Statusabfrage-Methoden:
boolean isHungry()
: Gibttrue
zurück, wennhunger
> 30boolean isStarving()
: Gibttrue
zurück, wennhunger
> 70boolean isExhausted()
: Gibttrue
zurück, wennmood
< 30boolean isTired()
: Gibttrue
zurück, wennmood
< 60
Implementiere die Aktionsmethoden:
void feed()
: Wenn hungrig, reduzierehunger
um 30 und erhöhemood
um 20void play()
: Wenn nicht erschöpft und nicht am Verhungern, reduzieremood
um 30 und erhöhehunger
um 15void sleep()
: Erhöhemood
um 40 undhunger
um 10Nutze intern
clampPercentage(…)
für alle Wertänderungen vonhunger
undmood
. Dasage
-Attribut soll nur erhöht werden, wenn eine Aktion erfolgreich durchgeführt wurde.
Implementiere eine Methode
public String toString()
, die den Status als Text zurückgibt. Das allgemeine Format soll sein:Pokopetto is [exhausted|tired|energetic] and [starving|hungry|full]
.Lege eine neue Klasse
PokopettoGame
mit einermain(…)
Methode an und kopiere Folgendes hinein, um die Objektmethoden zu testen und mit den Pokopettos zu spielen.Pokopetto pokopetto = new Pokopetto(); while ( !pokopetto.isStarving() && !pokopetto.isExhausted() ) { System.out.println( pokopetto ); System.out.println( "Choose an action: feed, play, sleep, or quit" ); String action = new Scanner( System.in ).nextLine().toLowerCase(); switch ( action ) { case "feed" -> pokopetto.feed(); case "play" -> pokopetto.play(); case "sleep" -> pokopetto.sleep(); case "quit" -> { System.out.println( "Goodbye!" ); return; } default -> System.out.println( "Invalid action. Try again." ); } } System.out.println( "Pokopetto is not feeling well! Game over." );
1.1.3. Private Parts: Eigenschaften aus Pokopetto
privat machen (NEU) ⭐
Die privaten Details einer Implementierung dürfen nicht öffentlich sein, damit man jederzeit das Innere ändern kann.
Aufgabe:
Mache alle Objektvariablen aus
Pokopetto
privat. Gibt es Compilerfehler? Wenn, kommentiere Programmstücke aus.Überlege, welche Methoden
public
werden können, welcheprivate
.
1.1.4. Setter und Getter für Pokopetto
einführen (NEU) ⭐
Getter und Setter sind in der Java-Welt oft anzutreffen. Sie werden benutzt, um sogenannte Properties zu definieren. Viele Frameworks greifen automatisch auf die Properties über die Getter/Setter zu.
Aufgabe:
Ergänze eine neue private Objektvariable
String name
.Implementiere einen Getter
getName()
.Implementiere einen Setter
setName(String name)
, der eineNullPointerException
wirft, wenn der Parameternull
ist. Der Name soll ohne Weißraum am Anfang und Ende gespeichert werden.
Ergänze für das bestehende Attribut
age
nur einen GettergetAge()
. Einen Setter darf es nicht geben, da das Alter von außen nicht verändert werden darf.
Setter und Getter sind eine wichtige Namenskonvention. Ist eine Property |
1.2. Statische Eigenschaften
Klassenvariablen und statische Methoden führen bei Programmiereinsteigern oft zur Verwirrung. Dabei ist es ganz einfach: Entweder können Zustände bei den individuellen Objekten gespeichert werden oder bei der Klasse selbst. Haben wir verschiedene Objekte mit Objektvariablen, können die Objektmethoden auf die individuellen Eigenschaften zurückgreifen. Eine statische Methode kann ohne explizite Angabe eines Objekts nur auf Klassenvariablen zurückgreifen.
1.2.1. Konstanten und statische Hilfsmethoden in Pokopetto
einführen (NEU) ⭐
Bisher hat das Pokopetto nur Objekteigenschaften. Ergänzen wir statische Eigenschaften, die keine Beziehung zu einem konkreten Pokopetto-Objekt haben.
Aufgabe:
Die Startwerte 30 und 80 sind an dieser Stelle nicht optimal:
int hunger = 30; int mood = 80;
Führen neue private statische finale Variablen
INITIAL_HUNGER
undINITIAL_MOOD
ein, die für die Startbelebung verwendet werden können. Für den Startwert 0 beiage
benötigen wir keine Konstante.Kann
clampPercentage(…)
statisch sein? Was spricht dafür, was dagegen?Implementiere eine neue statische Methode
String generateRandomName()
, die einen zufälligen Namen aus vorgegebenen Namensbestandteilen generiert. Die Methode soll aus jedem der drei Arrays zufällig ein Element auswählen und es konkatenieren:String[] parts1 = { "Piko", "Mochi", "Neko", "Kawa", "Yuki", "Haru", "Saku", "Mimi", "Tama" }; String[] parts2 = { "ni", "mo", "ka", "chi", "yu", "ri", "mi", "sa", "ta", "ko" }; String[] parts3 = { "chan", "kun", "san", "chi", "tan", "rin", "pi", "nyan", "maru", "ko" };
Gültig wäre zum Beispiel "Piko" + "mo" + "chi" = "Pikomochi".
Teste die Methode in
PokopettoDemo
.
1.3. Einfache Aufzählungen
Aufzählungen sind geschlossene Mengen, die in Java über das Schlüsselwort enum
aufgebaut werden.
1.3.1. Evolutionsstufen eines Pokopetto (NEU) ⭐
Aufgabe:
Führe einen neuen Aufzählungstyp (Enum) für die verschiedenen Entwicklungsstufen eines Pokopetto ein.
enum Evolution { BABY, TEEN, ADULT }
Implementiere eine neue Methode
getEvolution()
in derPokopetto
Klasse, die basierend auf dem Alter die aktuelle Entwicklungsstufe zurückgibt.BABY
: Alter 0–5TEEN
: Alter 6–15ADULT
: Alter ab 16
Teste die Methode in
PokopettoDemo
.
1.4. Konstruktoren
Konstruktoren sind besondere Initialisierungsroutinen, die beim Anlegen eines Objekts automatisch von der virtuellen Maschine aufgerufen werden. Wir nutzen Konstruktoren oft, um bei der Erzeugung von Objekten Zustände zuzuweisen, die wir uns dann im Objekt merken.
1.4.1. Pokopetto über verschiedene Konstruktoren aufbauen (NEU) ⭐
Unser Pokopetto hat bisher nur einen vom Compiler generierten Standardkonstruktor. Ersetzen wir diesen durch eigene Konstruktoren.
Aufgabe:
Folgende drei Konstruktoren sollen erstellt werden:
Parameterloser Konstruktor: Soll einen zufälligen Namen über
generateRandomName()
generieren und den initialen Hunger-WertINITIAL_HUNGER
und die initiale StimmungINITIAL_MOOD
verwenden.Konstruktor mit zwei Parametern
(name, hunger)
soll den übergebenen Namen und Hunger-Wert verwenden und die initiale StimmungINITIAL_MOOD
verwenden. VerwendeclampPercentage(…)
zur Sicherstellung gültiger Werte fürhunger
.Konstruktor mit drei Parametern
(name, hunger, mood)
kann alle Objektwerte direkt aus den Parametern setzen. VerwendeclampPercentage(…)
zur Sicherstellung gültiger Werte fürhunger
undmood
.
Konstruktoren können mit
this(…)
aufeinander verweisen. Implementiere damit eine Konstruktor-Hierarchie nach dem Telescoping Constructor Pattern. Die Konstruktoren sollen aufeinander aufbauen, wobei der Konstruktor mit den meisten Parametern die eigentliche Arbeit macht. Die anderen Konstruktoren rufen diesen mit Standardwerten auf, anstatt die Werte selbst zu setzen.Teste den Aufbau in
PokopettoDemo
.
1.4.2. Copy-Konstruktor für Pokopetto implementieren (NEU) ⭐
Wird im Konstruktor einer Klasse ein Objekt des gleichen Typs als Vorlage angenommen, so sprechen wir von einem Copy-Konstruktor.
Aufgabe:
Implementiere für
Pokopetto
einen Copy-Konstruktor. Kopierename
,hunger
,mood
undage
.
1.4.3. Pokopetto-Fabrik einrichten (NEU) ⭐
Neben Konstruktoren bieten einige Klassen eine alternative Variante zum Anlegen, sogenannte Fabrikmethoden. Dabei gilt:
Es gibt prinzipiell Konstruktoren, aber die sind oftmals privat, und folglich können Außenstehende keine Instanzen erzeugen.
Damit Objekte aufgebaut werden können, gibt es (statische) Methoden, die intern den Konstruktor aufrufen und die Instanz zurückgeben.
Captain CiaoCiao stellt fest, dass die Erstellung neuer Pokopettos mit mehreren Zahlenwerten verwirrend sein kann. Was bedeutet new Pokopetto("Mimimi", 30, 80)
— ist das ein hungriges oder glückliches Pokopetto?
Aufgabe:
Erstelle eine neue Klasse
PokopettoFactory
mit folgenden statischen Methoden (nicht angegebe Werte sollen auf StandardwerteINITIAL_HUNGER
/INITIAL_MOOD
gesetzt werden):Pokopetto createHungryDefaultPokopetto(String name, int hungerValue)
Pokopetto createHappyDefaultPokopetto(String name, int moodValue)
Zähle jedes erstellte Pokopetto mit. Jedes 10. Pokopetto soll mit maximaler Stimmung (mood = 100) erstellt werden.
1.5. Assoziationen
Eine Assoziation ist eine dynamische Verbindung von zwei oder mehreren Objekten. Assoziation können wir auf verschiedene Arten charakterisieren:
Kennt nur eine Seite die andere, oder kennen sich beide Seiten?
Ist die Lebensdauer eines Objektes an die Lebensdauer eines Objektes gebunden?
Mit wie vielen anderen Objekten hat ein Objekt eine Verbindung? Gibt es eine Verbindung zu nur einem anderen Objekt oder zu mehreren? Bei 1:n- oder n:m-Beziehungen benötigen wir Container, wie Arrays oder dynamische Datenstrukturen wie die
java.util.ArrayList
.
1.5.1. Mehrere Pokopettos im Spiel verwalten (NEU) ⭐⭐
Die Mannschaft ist so begeistert von Pokopettos, dass bald jeder ein eigenes haben möchte. Um den Überblick nicht zu verlieren, benötigt der Captain ein System zur Verwaltung mehrerer Pokopettos. Um dies umzusetzen, ersetzen wir das einzelne Pokopetto durch eine Sammlung von Pokopettos.
Aufgabe:
Refactor in
PokopettoGame
den Variablennamenpokopetto
durch eine Umbenennung in den Pluralpokopettos
. Und ändere den Typ der Referenzvariablen zu einerArrayList
; die Variable kannfinal
werden:final ArrayList<Pokopetto> pokopettos = new ArrayList<Pokopetto>();
Schreibe ein neues Kommando
create
, das ein neues Pokopetto Objekt erzeugt und der Liste hinzufügt.Schreibe ein neues Kommando
list
, das eine Liste aller Pokopettos mit Index anzeigt.Damit unsere Pokopettos nicht verhungern, wollen wir ihren Zustand überprüfen können.
Ergänze ein neues Kommando
check
, das ausgibt, wie viele Pokopettos in der Liste am Verhungern sind. Nutze zum Testen die MethodeisStarving()
derPokopetto
-Klasse.Die Beispielausgabe könnte sein:
2 Pokopettos at index 0, 2 are starving
.
Passe die drei Kommandos
feed
,play
,sleep
an, sodass sie einen Index benötigen. Das Format kann etwa sein:<command> <index>
, z. B.feed 0
oderplay 2
. Fehlerhafte Indizes müssen erkannt werden und dürfen nicht zum Spielabbruch führen.Da einzelne Pokopettos unabhängig voneinander sterben können, müssen diese aus der Liste entfernt werden. Das Spiel endet, wenn keine Pokopettos mehr in der Liste sind.
1.6. Vererbung
Vererbung modelliert eine Ist-eine-Art-von-Beziehung und verbindet zwei Typen sehr direkt. Die Modellierung ist sehr wichtig, um Gruppen von zusammenhängenden Dingen zu bilden.
1.6.1. Einführung einer Abstraktionshierarchie für Pokopettos (NEU) ⭐
Während einer langen Seereise beobachtet Captain CiaoCiao, dass sich einige Pokopettos anders verhalten als andere. Er beschließt, seine Forschungen zu systematisieren und verschiedene Pokopetto-Arten zu klassifizieren. Mit dieser Erweiterung führen wir eine objektorientierte Hierarchie für verschiedene Pokopetto-Arten ein. Die gemeinsamen Eigenschaften werden in einer Basisklasse gebündelt. Die neue Klassenhierarchie ermöglicht es, verschiedene Pokopetto-Arten mit unterschiedlichem Verhalten zu erstellen, während gemeinsame Eigenschaften in der Basisklasse gebündelt sind.
Aufgabe:
Lege eine neue Klasse
DefaultPokopetto
für Pokopettos an, die vonPokopetto
abgeleitet ist. Durch die Vererbungsbeziehung verhält sichDefaultPokopetto
genau wie die bisherigen Pokopettos.Lege eine weitere Klasse
HyperPokopetto
als Unterklasse vonPokopetto
an. Nach dem Anlegen einer Instanz soll es automatisch einenmood
von 100 haben.Welche Konstruktoren stehen in den neuen Klassen zur Verfügung? Erstelle verschiedene Pokopetto-Arten und überprüfe ihre Werte in
PokopettoDemo
.
1.6.2. Konstruktor-Verkettung (NEU) ⭐
Die neuen Pokopetto-Arten haben ein Problem: Sie können nicht so flexibel erstellt werden wie die Original-Pokopettos, da Konstruktoren nicht vererbt werden.
Aufgabe:
Sieh dir die verfügbaren Konstruktoren in
Pokopetto
an und implementiere die gleichen Konstruktoren inDefaultPokopetto
. Alle drei Konstruktor-Varianten sollen verfügbar sein.Nutze
super(…)
, um den passenden Konstruktor der Oberklasse aufzurufen.
1.6.3. Objekttyp und Referenztyp (NEU) ⭐
Aufgabe:
In der Klasse PokopettoGame
werden Objekte bisher mit new Pokopetto()
erzeugt. Ändere dies, sodass die Objekterstellung stattdessen mit new DefaultPokopetto()
erfolgt.
Beantworte die folgenden Fragen:
Was ist der Unterschied zwischen Referenztyp und Objekttyp?
Welche Bedeutung hat der Typ auf der linken Seite einer Zuweisung?
Welche Bedeutung hat der Typ auf der rechten Seite einer Zuweisung?
Beeinflusst diese Änderung das Verhalten des Spiels?
1.6.4. Erschöpfte Pokopettos erkennen (NEU) ⭐
Bei der täglichen Inspektion seiner Pokopetto-Sammlung stellt Captain CiaoCiao fest, dass einige seiner digitalen Haustiere erschöpft wirken. Er möchte schnell erkennen können, welche Pokopettos eine Pause benötigen.
Mit Vererbung können wir für beliebige Pokopettos, also egal welcher konkreten Unterklasse, bestimmte Eigenschaften prüfen.
Aufgabe:
Setze in der Klasse
Pokopetto
eine neue statische Methode:public static int numberOfExhaustedPokopettos( Pokopetto... pokopettos ) { // Liefert die Anzahl erschöpfter Pokopettos zurück, // die der Methode übergeben wurden }
Für die Implementierung kann man auf die
isExhausted()
-Methode zurückgreifen, die für jede Art von Pokopetto existiert.Teste die Methode in
PokopettoDemo
etwa so:Pokopetto p1 = ... // erschöpftes Pokopetto aufbauen Pokopetto p2 = ... // erschöpftes Pokopetto aufbauen Pokopetto p3 = ... // energiegeladenes Pokopetto aufbauen int exhausted = Pokopetto.numberOfExhaustedPokopettos( p1, p2, p3 );
Wenn
p1
undp2
zwei erschöpfte Pokopettos sind undp3
ein nicht erschöpftes Pokopetto, soll die Methode die Rückgabe2
liefern.
1.6.5. Spezielle Interaktionen mit Pokopetto-Arten (NEU) ⭐
Während seiner Pokopetto-Forschungen bemerkt Captain CiaoCiao, dass HyperPokopettos andere Erschöpfungsanzeichen zeigen als gewöhnliche Pokopettos.
Aufgabe:
Schließe bei der Zählmethode
Pokopetto.numberOfExhaustedPokopettos(…)
explizit alle Objekte vom TypHyperPokopetto
aus.Teste die Funktionalität in
PokopettoDemo
.
1.6.6. Gleichwertigkeitstest mit Pattern-Variable lösen ⭐
Java 14 führt das Pattern matching for instanceof ein, das Code angenehm verkürzen kann.
Aufgabe:
Gegeben ist die ältere Klasse
Toaster
mit einerequals(…)
-Methode für einen Test auf Gleichwertigkeit:Listing 1. com/tutego/exercise/oop/Toaster.javapublic class Toaster { int capacity; boolean stainless; boolean extraLarge; @Override public boolean equals( Object o ) { if ( !(o instanceof Toaster) ) return false; Toaster toaster = (Toaster) o; return capacity == toaster.capacity && stainless == toaster.stainless && extraLarge == toaster.extraLarge; } @Override public int hashCode() { return Objects.hash( capacity, stainless, extraLarge ); } }
Schreibe die
equals(…)
-Methode so um, dassinstanceof
mit einer Pattern-Variable verwendet wird.
1.6.7. HyperPokopetto kennt keine Müdigkeit (NEU) ⭐
Während Captain CiaoCiao seine verschiedenen Pokopetto-Arten studiert, macht er eine erstaunliche Entdeckung: HyperPokopettos scheinen niemals müde zu werden! Egal, wie viel sie spielen, ihre Energie bleibt konstant hoch.
Aufgabe:
Die Methode
play()
soll beiHyperPokopetto
überschrieben werden, sodass sich dermood
-Wert nicht verringert, denn HyperPokopettos werden beim Spielen einfach nicht müde.hunger
undage
sollen wie üblich aktualisiert werden.Auch die Methode
isTired()
soll überschrieben werden und immerfalse
zurückgeben.Zum Testen setze Folgendes in
PokopettoDemo
ein:HyperPokopetto poko = new HyperPokopetto(); System.out.println( poko.getMood() ); // 100 poko.play(); System.out.println( poko.getMood() ); // immer noch 100 System.out.println( poko.isTired() ); // false
1.6.8. Refactoring der HyperPokopetto play()
-Methode (NEU) ⭐⭐
Captain CiaoCiao möchte seinen Code eleganter gestalten. Er hat bemerkt, dass die überschriebene play()
-Methode in HyperPokopetto
viel Code aus der Oberklasse dupliziert. Als erfahrener Programmierer weiß er: Code-Duplikation sollte vermieden werden.
Aufgabe:
Komprimiere die
play()
-Methode in der KlasseHyperPokopetto
, indem die Implementierung der Oberklasse über einensuper
-Aufruf wiederverwendet wird.Damit kann die Methode weiterhin den Hunger wie gewohnt erhöhen (über die Oberklassen-Implementierung), und der
mood
-Wert wird beim Aufruf vonplay()
nicht gesenkt, sondern bleibt beim Ausgangswert konstant (Spezialverhalten der Unterklasse).
1.7. Polymorphie und dynamisches Binden
Eine zentrale Eigenschaft von objektorientierten Programmiersprachen ist die dynamische Auflösung von Methodenaufrufen. Diese Form von Aufrufen kann dabei nicht zur Compilezeit entschieden werden, sondern zur Laufzeit, wenn der Objekttyp bekannt ist.
1.7.1. Polymorphie beim Spielen (NEU) ⭐
Captain CiaoCiao experimentiert gerne: Er möchte beim Start des Spiels zufällig ein normales oder ein hyperaktives Pokopetto erstellt bekommen. So kann er besser die verschiedenen Verhaltensweisen studieren.
Aufgabe:
Implementiere die Pokopetto-Erzeugung so, dass mit 50 % Wahrscheinlichkeit entweder ein
DefaultPokopetto
oder einHyperPokopetto
erstellt wird.Untersuche das polymorphe Verhalten, was sich durch diese Zeile ergibt:
case "play" -> pokopetto.play(); // <-- Hier findet Polymorphie statt
Der Aufruf von
play()
verhält sich unterschiedlich, je nachdem, ob es sich um ein normales Pokopetto handelt (wird müde vom Spielen) oder um ein HyperPokopetto (bleibt immer energiegeladen).Setze einen Breakpoint in die
play()
-Methode derDefaultPokopetto
undHyperPokopetto
-Klasse.Starte das Spiel mehrmals und beobachte, welche
play()
-Implementierung aufgerufen wird.
1.7.2. Pokopetto-Steckbriefe erstellen (NEU) ⭐
Captain CiaoCiao möchte für seine Pokopetto-Sammlung einen illustrierten Katalog erstellen. Seine Crew liebt ASCII-Art, daher soll für jedes Pokopetto in der Liste ein passendes Symbol mit Status angezeigt werden.
Aufgabe:
Kopiere die folgende Klasse
PokopettoArt
als geschachtelte Klasse in die KlassePokopettoGame
:public static class PokopettoArt { public static final String ENERGETIC = """ \\(^o^)/ |===| / \\"""; public static final String TIRED = """ (-.-) |===| / \\"""; public static final String HUNGRY = """ (o.o) |===| / \\"""; public static final String HYPER = """ *\\(^O^)/* ===== / \\"""; }
Implementiere ein neues Kommando
catalog
, das für alle Pokopettos in der Liste das passende ASCII-Art ausgibt. Die Logik ist wie folgt:Für
HyperPokopetto
wird immerHYPER
ausgegeben.Für
DefaultPokopetto
wird je nach Zustand ausgegeben: beiisExhausted()
dieTIRED
Zeichenfolge, beiisStarving()
dieHUNGRY
Zeichenfolge, sonstENERGETIC
. Es hat die Rückgabe vonisExhausted()
Vorrang vorisStarving()
.
Nutze Pattern Matching for switch, um den Typ und Zustand zu prüfen.
1.8. Abstrakte Klassen und abstrakte Methoden
Abstrakte Klassen sind auf den ersten Blick etwas Komisches: Was soll man mit Klassen, von denen man keine Objekte bilden kann? Und was ist mit abstrakten Methoden? Eine Klasse ohne implementierte Methoden hat doch nichts zu bieten!
Beide Konzepte sind essenziell. Obertyp und Untertyp haben immer einen Vertrag; ein Untertyp muss mindestens das besitzen, was ein Obertyp vorschreibt, und darf die Semantik nicht brechen. Ist eine Oberklasse abstrakt bzw. sind Methoden abstrakt, so geben die Unterklassen, von denen man Objekte bilden kann, ein Versprechen ab, diese Funktionalität bereitzustellen.
1.8.1. Pokopetto als abstrakte Klasse? (NEU) ⭐
Captain CiaoCiao grübelt über seine Pokopetto-Architektur. Er stellt sich folgende Fragen:
Ergibt es Sinn, mit
new Pokopetto()
ein "reines" Pokopetto Objekt zu erstellen? Oder sollte jedes konkete Pokopetto von einer speziellen Art sein (Default oder Hyper)?Welche Konsequenzen hätte es, wenn die Klasse
Pokopetto
abstract
wäre?Für die Erstellung neuer Pokopettos?
Für die bestehenden Methoden?
Für die Vererbungshierarchie?
1.8.2. Pokopetto-Persönlichkeiten (NEU) ⭐
Captain CiaoCiao bemerkt, dass jedes Pokopetto eine einzigartige Persönlichkeit entwickelt und sich beim Spielen anders verhält.
Aufgabe:
Mache die Klasse
Pokopetto
abstrakt und füge eine abstrakte MethodeString getPersonality()
hinzu.Gibt es Compilerfehler? Wenn ja, warum?
Die Persönlichkeit soll sich wie folgt entwickeln:
Bei
DefaultPokopetto
soll die Persönlichkeit vom Alter abhängen: Bis Alter 5: schüchtern ("shy"
), bis Alter 15: verspielt ("playful"
), ab Alter 15: erwachsen ("mature"
).Bei
HyperPokopetto
soll immer die Persönlichkeit"energetic"
zurückgegeben werden.
Erweitere die
toString()
-Methode in der Basis-Klasse, dass ein Fragment wie+ " and has a " + getPersonality() + " personality"
ergänzt wird. Ist das ein Beispiel für einen polymorphen Aufruf?
1.8.3. TimerTask als Beispiel für eine abstrakte Klasse ⭐⭐
Captain CiaoCiao nimmt jeden Überfall per Video auf und analysiert die Abläufe in der Nachbesprechung. Allerdings füllen die Videos in feinster 8K-Qualität schnell die Festplatte. Er möchte rechtzeitig feststellen, ob er eine neue Festplatte kaufen muss.
Ein java.util.Timer
kann Aufgaben wiederholt durchführen. Dazu hat die Timer
-Klasse eine Methode schedule(…)
zum Hinzufügen einer Aufgabe. Die Aufgabe ist vom Typ java.util.TimerTask
.
Aufgabe:
Schreibe eine Unterklasse von
TimerTask
, die immer dann eine Meldung auf dem Bildschirm ausgibt, wenn die Anzahl freier Bytes auf dem Dateisystem unter eine gewisse Grenze fällt (z. B. weniger als 1000 MB).Die freien Bytes liefert:
long freeDiskSpace = java.io.File.listRoots()[0].getFreeSpace();
Der
Timer
soll diesenTimerTask
alle 2 Sekunden ausführen.
TimerTask
und eigener UnterklasseBonus: Integriere eine Nachricht in der Tray mit:
import javax.swing.*;
import java.awt.*;
import java.awt.TrayIcon.MessageType;
import java.net.URL;
try {
String url =
"https://cdn4.iconfinder.com/data/icons/common-toolbar/36/Save-16.png";
ImageIcon icon = new ImageIcon( new URL( url ) );
TrayIcon trayIcon = new TrayIcon( icon.getImage() );
SystemTray.getSystemTray().add( trayIcon );
trayIcon.displayMessage( "Warning", "Hard drive full", MessageType.INFO );
}
catch ( Exception e ) { e.printStackTrace(); }
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.9. Aufgaben aus 1. Auflage
1.9.1. Radio mit Objektvariablen und ein Hauptprogramm deklarieren ⭐
Der erste Typ unserer Sammlung von Elektrogeräten ist ein Radio. Ein Radio hat einen Zustand, den wir speichern wollen.
Aufgabe:
Lege eine neue Klasse
Radio
an.Gib dem Radio die folgenden Objektvariablen:
isOn
, ist das Radio an oder aus?volume
, wie laut spielt das Radio Musik ab?
Welche Variablentypen sind sinnvoll? Achte darauf, dass die Objektvariablen nicht statisch sind!
Schreibe zusätzlich eine Klasse
Application
, die einRadio
-Objekt in ihrermain(…)
-Methode aufbaut. Belege und erfrage zum Test die Variablen.
1.9.2. Methoden eines Radios implementieren ⭐
In die neue Klasse Radio
sollen Methoden gesetzt werden, damit das Objekt etwas »kann«.
Aufgabe:
Ergänze folgende nichtstatische Methoden:
void volumeUp()
/void volumeDown()
: verändern die Objektvariablevolume
um 1 bzw. -1. Die Lautstärke darf nur im Bereich von 0 bis 100 liegen.void on()
/void off()
/boolean isOn()
: Greifen auf die ObjektvariableisOn
zurück; es ist in Ordnung, wenn eine Methode so heißt wie eine Objektvariable. Die Methodenon()/off()
sollen Meldungen wie "an"/"aus" auf dem Bildschirm ausgeben.public String toString()
: Sie soll Informationen über den internen Zustand als String zurückgeben, wobei die Zeichenkette eine Form wieRadio[volume=2, is on]
annehmen sollte.
In der
main(…)
-Methode der KlasseApplication
können die Objektmethoden des Radios zum Beispiel so getestet werden:Listing 2. Ausschnitt aus Application.javaRadio grandmasOldRadio = new Radio(); System.out.println( grandmasOldRadio.isOn() ); // false grandmasOldRadio.on(); System.out.println( grandmasOldRadio.isOn() ); // true System.out.println( grandmasOldRadio.volume ); // 0 grandmasOldRadio.volumeUp(); grandmasOldRadio.volumeUp(); grandmasOldRadio.volumeDown(); grandmasOldRadio.volumeDown(); grandmasOldRadio.volumeDown(); grandmasOldRadio.volumeDown(); grandmasOldRadio.volumeDown(); grandmasOldRadio.volumeDown(); grandmasOldRadio.volumeDown(); grandmasOldRadio.volumeDown(); grandmasOldRadio.volumeDown(); grandmasOldRadio.volumeDown(); grandmasOldRadio.volumeDown(); grandmasOldRadio.volumeUp(); System.out.println( grandmasOldRadio.volume ); // 2 System.out.println( grandmasOldRadio.toString() ); // Radio[volume=2, is on] System.out.println( grandmasOldRadio ); // Radio[volume=2, is on] grandmasOldRadio.off();
1.9.3. Private Parts: Objektvariablen privat machen ⭐
Aufgabe:
Mache alle Objektvariablen aus
Radio
privat.Überlege, ob die Methoden
public
werden können.Gibt es interne Methoden, die
private
sein sollten?
1.9.4. Setter und Getter anlegen ⭐
Aufgabe:
Gib dem
Radio
eine neue privatedouble
-Objektvariablefrequency
, sodass man das Radio auf eine Frequenz einstellen kann.Passe die
toString()
-Methode so an, dass sie die Frequenz berücksichtigt.Das Schreiben dieser Setter und Getter ist oft langweilig, daher werden sie entweder über eine Entwicklungsumgebung automatisch generiert oder mithilfe von Werkzeugen automatisch in den Bytecode gesetzt. Generiere mithilfe der IDE Setter und Getter für die Frequenz.
Es gibt Getter ohne Setter, wenn man Nur-lesen-Operationen realisieren möchte und verhindern will, dass Properties von außen verändert werden. Wenn eine Variable
final
ist, funktionieren auch nur Getter. Generiere für den Zustandvolume
nur einen Getter.
1.9.5. Sendernamen in Frequenzen konvertieren ⭐
Aufgabe:
Implementiere in die Klasse
Radio
eine statische Methodedouble stationNameToFrequency(String)
, die einem Sender als Zeichenkette eine Frequenz zuordnet (zum Beispiel hat der bekannte Piratensender »Walking the Plank« die Frequenz 98.3).Wird der Methode
null
übergeben, dann soll die Rückgabe0.0
sein. Auch bei unbekannten Sendernamen soll die Rückgabe0.0
sein.
Beispiel:
Im Hauptprogramm können wir schreiben:
System.out.println( Radio.stationNameToFrequency( "Walking the Plank" ) ); // 98.3
Stringvergleiche mit den Sendern können mit |
1.9.6. Logausgaben mit einer Tracer-Klasse schreiben ⭐
Man verwendet Logger, um Programmausgaben zu protokollieren und sie später nachvollziehen zu können — ganz ähnlich wie Captain CiaoCiao in seinem Logbuch festhält, was alles auf den Meeren, in den Häfen und innerhalb der Besatzung passiert ist.
Aufgabe:
Lege eine neue Klasse
Tracer
an.Ergänze eine statische Methode
void trace(String)
, die einen an sie überreichten String auf dem Bildschirm ausgibt.Erweitere das Programm um zwei statische Methoden
on()
undoff()
, die sich in einem internen Zustand merken, obtrace(String)
zu einer Ausgabe führt oder nicht. Am Anfang soll derTracer
abgeschaltet sein.Optional: Füge eine Methode
trace(String format, Object... args)
hinzu, die intern aufSystem.out.printf(format, args)
geht, wenn das Tracing eingeschaltet ist.
Beispiel:
Wir können die Klasse dann so verwenden:
Tracer.on();
Tracer.trace( "Start" );
int i = 2;
Tracer.off();
Tracer.trace( "i = " + i );
// Tracer.trace( "i = %d", i );
Tracer.on();
Tracer.trace( "End" );
Die erwartete Ausgabe ist:
Start End
1.9.7. Radio eine AM-FM-Modulation geben ⭐
Bei Radioübertragungen ist die Modulation wichtig; es gibt AM (Amplitudenmodulation) und FM (Frequenzmodulation)[1].
Aufgabe:
Deklariere einen neuen Aufzählungstyp
Modulation
mit den WertenAM
undFM
als eigene Datei.Füge in
Radio
eine private ObjektvariableModulation modulation
ein, in der sich das Radio die Modulation merkt.Setze die
Modulation
über eine neueRadio
-Methodevoid setModulation(Modulation modulation)
um, einen Getter kann es auch geben.Passe die
toString()
-Methode inRadio
an.
1.9.8. Anlegevarianten: Radio-Konstruktoren schreiben ⭐
Aufgabe:
Schreibe einen Konstruktor für die Klasse
Radio
, sodass man ein Radio mit einer Frequenz (double
) initialisieren kann. Man sollte Radios aber immer noch mit dem parameterlosen Konstruktor anlegen können!Alternativ soll ein
Radio
-Objekt mit einem Sender (alsString
) initialisiert werden können (nutze dazu internstationNameToFrequency(…)
). Der Sendername wird nicht gespeichert, nur die Frequenz.Wie können wir die Konstruktorweiterschaltung mit
this(…)
nutzen?
Beispiel: Auf folgende Weise soll man Radios aufbauen können:
Radio r1 = new Radio();
Radio r2 = new Radio( 102. );
Radio r3 = new Radio( "BFBS" );
1.9.9. Copy-Konstruktor implementieren ⭐
Aufgabe:
Implementiere für
Radio
einen Copy-Konstruktor.
1.9.10. Fabrikmethoden realisieren ⭐
Aufgabe:
Lege eine neue Klasse
TreasureChest
für eine Schatztruhe an.Eine Schatztruhe kann Golddublonen und Edelsteine enthalten; lege zwei öffentliche finale Objektvariablen
int goldDoubloonWeight
undint gemstoneWeight
an. Das Objekt ist also immutable, die Zustände lassen sich später nicht mehr ändern. Getter sind nicht nötig.Schreibe vier statische Fabrikmethoden, die ein
TreasureChest
-Objekt liefern:TreasureChest newInstance()
TreasureChest newInstanceWithGoldDoubloonWeight(int)
TreasureChest newInstanceWithGemstoneWeight(int)
TreasureChest newInstanceWithGoldDoubloonAndGemstoneWeight(int, int)
Wo wäre hier das Problem mit einem üblichen Konstruktor?
1.9.11. Bildröhre mit Fernsehgerät verbinden ⭐
Bisher haben wir ein Elektrogerät: Radio. Es wird Zeit, ein zweites Elektrogerät hinzuzunehmen und eine 1:1-Assoziation einzubauen.
Aufgabe:
Lege eine neue Klasse
TV
an.Das Fernsehgerät soll Methoden
on()
/off()
bekommen, die kurze Meldungen auf die Konsole schreiben (eine Objektvariable ist für das Beispiel erst einmal nicht nötig).Lege in der
main(…)
-Methode vonApplication
einTV
an.Lege eine neue Klasse
MonitorTube
(Bildröhre) an.Die
MonitorTube
soll ebenfallson()
/off()
-Methoden mit Konsolenmeldungen bekommen.
Ein
TV
soll eineMonitorTube
über eine private Objektvariable referenzieren. Wie kann das in Java Quellcode aussehen?Implementiere eine unidirektionale Beziehung zwischen dem Fernsehgerät und der Bildröhre. Zum Lebenszyklus: Wenn das Fernsehgerät aufgebaut wird, soll auch die Bildröhre mit erzeugt werden, man muss die Bildröhre nicht auswechseln können.
Wenn das Fernsehgerät ein-/ausgeschaltet wird, so soll auch die Bildröhre ein-/ausgeschaltet werden.
Optional: Wie können wir eine bidirektionale Beziehung implementieren? Wo könnte ein Problem lauern?
Am Ende soll diese Relation implementiert werden:
1.9.12. Radios mit einer 1:n-Assoziation auf das Schiff aufnehmen ⭐⭐
Captain CiaoCiao besitzt eine ganze Flotte von Schiffen, und sie können Ladung aufnehmen. Am Anfang möchte Captain CiaoCiao nur Radios auf sein Schiff laden.
Aufgabe:
Lege eine neue Klasse
Ship
(ohnemain(…)
-Methode) an.Damit das
Ship
Radios aufnehmen kann, greifen wir auf die Datenstrukturjava.util.ArrayList
zurück. Als private Objektvariable kommt inShip
:ArrayList<Radio> radios = new ArrayList<Radio>();
Schreibe in
Ship
eine neueload(…)
-Methode, damit das Schiff ein Radio aufnehmen kann.Baue in der
main(…)
-Methode vonApplication
zwei Schiffe auf.Weise einem
Ship
in dermain(…)
-Methode mehrere Radios zu.Schreibe eine
Ship
-Methodeint countDevicesSwitchedOn()
, die liefert, wie viele Radios eingeschaltet sind. Achtung: Es geht nicht um die Gesamtanzahl Radios auf dem Schiff, sondern um die Anzahl eingeschalteter Radios!Optional: Gib dem Schiff ebenfalls eine
toString()
-Methode.Was müssen wir tun, wenn das Schiff auch andere Elektrogeräte laden möchte, etwa Eismaschinen oder Fernsehgeräte?
Ziel der Implementierung: Ein Schiff referenziert Radios.
1.9.13. Abstraktion in Elektrogeräte über Vererbung einführen ⭐
Bisher waren Radios und Fernsehgeräte ohne Verbindung. Doch es gibt eine Gemeinsamkeit: Sie sind alle elektronische Konsumgeräte.
Aufgabe:
Lege eine neue Klasse
ElectronicDevice
für Elektrogeräte an.Leite die Klasse
Radio
von der KlasseElectronicDevice
ab —TV
lassen wir erst einmal außen vor.Ziehe in die Oberklasse die Gemeinsamkeiten der möglichen Elektrogeräte.
Schreibe eine neue Klasse
IceMachine
, die ebenfalls ein Elektrogerät ist.
Eine Entwicklungsumgebung kann heutzutage über ein Refactoring automatisch Eigenschaften in die Oberklasse verschieben; finde heraus, wie das geht. |
Ziel der Aufgabe: Implementierung der folgenden Vererbungsbeziehung.
1.9.14. Anzahl eingeschalteter Elektrogeräte ermitteln ⭐
Mit Vererbung lässt sich erreichen, dass ein Parameter mit einem Obertyp deklariert wird, der dann eine ganze Gruppe von Typen damit anspricht, nämlich auch alle Untertypen.
Aufgabe:
Setze in der Klasse
ElectronicDevice
eine statische Methode:public static int numberOfElectronicDevicesSwitchedOn( ElectronicDevice... devices ) { // Liefert die Anzahl eingeschalteter Geräte zurück, // die der Methode übergeben wurden }
Beispiel:
Wenn etwa
r1
undr2
zwei eingeschaltete Radios sind undice
eine ausgeschaltete Eismaschine, kann inmain(…)
zum Beispiel stehen:int switchedOn = ElectronicDevice.numberOfElectronicDevicesSwitchedOn( r1, ice, r2 ); System.out.println( switchedOn ); // 2
1.9.15. Schiff soll jedes Elektrogerät aufnehmen ⭐
Das Schiff kann bisher nur den Typ Radio
speichern. Nun sollen allgemeine Elektrogeräte gespeichert werden.
Aufgabe:
Ändere den Typ der dynamischen Datenstruktur von
Radio
inElectronicDevice
:private ArrayList<ElectronicDevice> devices = new ArrayList<ElectronicDevice>();
Auch die Methode zum Hinzufügen ändert sich — wieso und warum?
Ziel der Aufgabe: Schiffe speichern alle Arten von Elektrogeräten.
1.9.16. Funktionierende Radios auf das Schiff nehmen ⭐
Captain CiaoCiao will den GEZ-Gott nicht erzürnen, und so soll bei der Aufnahme von Radios auf das Schiff eine Meldung auf der Konsole erscheinen. Außerdem mag Captain CiaoCiao keine kaputten Radios auf das Schiff aufnehmen.
Aufgabe:
Wenn der Hinzufügemethode
load(…)
ein Radio übergeben wird, dann soll geprüft werden, ob es die Lautstärke 0 hat; in dem Fall soll es nicht in die Datenstruktur aufgenommen werden.Wenn ein Radio hinzugefügt wird, soll folgende Konsolenausgabe erfolgen:
Remember to pay license fee!
.
1.9.17. Feuermelder geht nicht aus: Überschreiben von Methoden ⭐
Feuer ist etwas, was Captain CiaoCiao auf seinen Schiffen überhaupt nicht gebrauchen kann. Wenn es brennt, muss es schnellstmöglich gelöscht werden.
Aufgabe:
Implementiere eine Klasse
Firebox
für einen Feuermelder als Unterklasse vonElectronicDevice
.Feuermelder sollen nach dem Erzeugen immer eingeschaltet sein.
Die Methode
off()
soll mit leerem Rumpf oder mit Konsolenausgabe implementiert werden, sodass sich ein Feuermelder nicht ausschalten lässt.
Beispiel:
Ziel der Aufgabe: eine überschriebene
off()
-Methode, die den ZustandisOn
nicht ändert. Das lässt sich so testen:Firebox fb = new Firebox(); System.out.println( fb.isOn() ); // true fb.off(); System.out.println( fb.isOn() ); // true
1.9.18. toString() überschreiben ⭐
Gib ElectronicDevice
und Radio
eine eigene toString()
-Methode.
1.9.19. Aufruf der Methoden der Oberklasse ⭐⭐
Ein Radio hat Methoden on()/off()
, und auch die TV
-Klasse hat schon Methoden on()/off()
. Allerdings ist das TV
noch kein ElectronicDevice
. Der Grund ist, dass das Fernsehgerät wegen der Bildröhre (MonitorTube
) eine Sonderbehandlung benötigt.
Erweitert auch TV
die Klasse ElectronicDevice
, überschreibt ein Fernsehgerät somit die Methoden der Oberklasse ElectronicDevice
. Es ergibt sich aber ein Problem:
Wenn wir die beiden Methoden weglassen, würde die Röhre nicht ausgeschaltet werden, das Fernsehgerät aber bei Vererbung als Elektrogerät durchgehen.
Wenn wir die Methoden in der Klasse lassen, wird nur die Röhre ausgeschaltet, das Gerät wird aber nicht mehr ein- oder ausgeschaltet. Diesen Zustand verwaltete ja die Oberklasse über die Methoden
on()/off()
.
Aufgabe:
Löse das Problem, dass ein
TV
einElectronicDevice
ist, aber die Bildröhre ein-/ausgeschaltet wird.
1.9.20. Urlaub! Alle Geräte ausschalten ⭐
Bevor sich Captain CiaoCiao mit einem Tropical Storm-Cocktail in die Hängematte legt und seinen Urlaub genießt, müssen alle Elektrogeräte auf dem Schiff ausgeschaltet werden.
Aufgabe:
Implementiere in der
Ship
-Klasse eine Methodeholiday()
, die alle Elektrogeräte der Liste ausschaltet.public void holiday() { // rufe off() für alle Elemente in der Datenstruktur auf }
In der
main(…)
-Methode vonApplication
kann zum Beispiel stehen:Radio bedroomRadio = new Radio(); bedroomRadio.volumeUp(); Radio cabooseRadio = new Radio(); cabooseRadio.volumeUp(); TV mainTv = new TV(); Radio crRadio = new Radio(); Firebox alarm = new Firebox(); Ship ship = new Ship(); ship.load( bedroomRadio ); ship.load( cabooseRadio ); ship.load( mainTv ); ship.load( crRadio ); ship.load( alarm ); ship.holiday();
1.9.21. Der große Umzug ⭐
Der furchtlose Captain CiaoCiao hat beschlossen, sein altes Schiff zu verlassen und auf einen frischen Kahn umzusteigen. Da nicht alle Piratenkameraden des Lesens mächtig sind, soll eine grafische Ladeliste erstellt werden, in der kleine Bildchen die verschiedenen Gegenstände darstellen.
Aufgabe:
Kopiere die folgende Klasse
AsciiArt
als geschachtelte Klasse in die KlasseShip
:public static class AsciiArt { public static final String RADIO = " .-.\n|o.o|\n|:_:|"; public static final String BIG_TV = """ .---..--------------------------------------..---. | ||.------------------------------------.|| | |.-.||| |||.-.| | o ||| ||| o | |`-'||| |||`-'| |.-.||| |||.-.| | O ||| ||| O | |`-'||`------------------------------------'||`-'| `---'`--------------------------------------'`---' _||_ _||_ /____\\ /____\\"""; public static final String TV = " \\ /\n _\\/_\n| |\n|____|"; public static final String SOCKET = """ ____ ____| \\ (____| `._____ ____| _|___ (____| .' |____/"""; }
Implementiere in
Ship
eine neue MethodeprintLoadingList()
, die über alle Geräte des Schiffs iteriert und bei der Ausgabe die folgenden Regeln umsetzt:Wenn das Gerät ein
Radio
ist und die Wattzahl positiv ist, wird ein Radio auf dem Bildschirm ausgegeben durch Zugriff aufAsciiArt.RADIO
. Kaputte Radios haben 0 Watt und dürfen nicht ausgegeben werden.Ist das Gerät ein
TV
und liegt die Wattzahl über 10.000, wird das Bild eines großen Fernsehers (AsciiArt.BIG_TV
) ausgegeben.Wenn das Gerät ein
TV
ist (unabhängig von der Wattzahl), wird das Bild eines normalen Fernsehers (AsciiArt.TV
) ausgegeben.Wenn keines der oben genannten Fälle zutrifft, wird das Bild einer Steckdose (
AsciiArt.SOCKET
) ausgegeben.
Löse die Aufgabe mit dem Sprachfeature Pattern Matching for switch.