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 noch 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 geht es um elektronische Konsumgeräte, und die meisten Aufgaben bauen aufeinander auf. Dabei werden wir erst einfache Elektrogeräte wie Radios und Fernsehgeräte aufbauen; später werden wir Abstraktionen realisieren und diese Elektrogeräte auf dem Schiff sammeln. Und wenn Captain CiaoCiao und Bonny Brain in Urlaub fahren, muss alles schön ausgeschaltet werden. Auf diese Weise können die Themen Assoziationen und Vererbung geübt werden. Zur Erinnerung: Eine Assoziation nennen wir umgangssprachlich Hat- oder Kennt-Beziehung; eine Vererbung ist eine Ist-eine-Art-von-Beziehung.
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. 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.
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. 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 1. 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.1.3. Private Parts: Objektvariablen privat machen ⭐
Die privaten Details einer Implementierung dürfen nicht öffentlich sein, damit man jederzeit das Innere ändern kann.
Aufgabe:
Mache alle Objektvariablen aus
Radio
privat.Überlege, ob die Methoden
public
werden können.Gibt es interne Methoden, die
private
sein sollten?
1.1.4. Setter und Getter anlegen ⭐
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:
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.
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. Sendernamen in Frequenzen konvertieren ⭐
Bisher hat das Radio nur Objekteigenschaften. Ergänzen wir eine statische Methode, die keine Beziehung zu einem konkreten Radio-Objekt hat.
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.2.2. 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.3. Einfache Aufzählungen
Aufzählungen sind geschlossene Mengen, die in Java über das Schlüsselwort enum
aufgebaut werden.
1.3.1. 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.3.2. Gültige Start- und Endfrequenz bei Modulation setzen ⭐
Für Rundfunk werden drei Frequenzbereiche (Frequenzband genannt) unterschieden, die über AM kodieren:
Langwelle: 148,5 kHz bis 283,5 kHz
Mittelwelle: 526,5 kHz bis 1606,5 kHz
Kurzwelle: Kurzwellenrundfunk nutzt mehrere Bänder zwischen 3,2 MHz und 26,1 MHz.
Über FM kodiert:
Ultrakurzwelle (UKW): 87,5 MHz bis 108 MHz
Aufgabe:
Füge zwei neue private Objektvariablen ein:
minFrequency
maxFrequency
Beim Aufruf von
setModulation(Modulation)
sollen die ObjektvariablenminFrequency
undmaxFrequency
auf ihre minimalen und maximalen Wertebereiche gesetzt werden, nämlich für AM 148,5 kHz bis 26,1 MHz und für FM 87,5 MHz bis 108 MHz.
1.4. Konstruktoren
Konstruktoren sind besondere Initialisierungsroutinen, die beim Anlegen eines Objektes 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. Anlegevarianten: Radio-Konstruktoren schreiben ⭐
Unser Radio hat bisher nur einen vom Compiler generierten Standardkonstruktor. Ersetzen wir diesen durch eigene Konstruktoren:
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.4.2. Copy-Konstruktor implementieren ⭐
Wird im Konstruktor einer Klasse ein Objekt des gleichen Typs als Vorlage angenommen, so sprechen wir von einem Copy-Konstruktor.
Aufgabe:
Implementiere für
Radio
einen Copy-Konstruktor.
1.4.3. Fabrikmethoden realisieren ⭐
Neben Konstruktoren bieten einige Klassen eine alternative Variante zu Anlegen, sogenannte Fabrikmethoden. Dabei gilt:
Es gibt prinzipiell Konstruktoren, aber die sind 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.
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 view 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.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. 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.5.2. 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.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. 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.6.2. 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.6.3. 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.6.4. 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.6.5. 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 2. 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.6. Feuermelder geht nicht aus: Überschreiben von Methoden ⭐
Feuer ist etwas, was Captain CiaoCiao auf seinen Schiffen überhaupt nicht gebrauchen kann. Wenn es brennt, muss 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.6.7. toString() überschreiben ⭐
Gib ElectronicDevice
und Radio
eine eigene toString()
-Methode.
1.6.8. 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.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. 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.7.2. 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.
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 sehr wichtig. 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. 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‹