Java-Klassen und Objekte
toString-Methode und entwirf, implementiere und teste zum Abschluss eine vollständige Klasse anhand einer kurzen Beschreibung.AnfängerEinheit 5 von 11 in Kurs: Java-Programmierung – Klassen, Vererbung, Collections und Streams
Was du lernst
- Erkläre, wie eine Klasse einen neuen Datentyp mit eigenem Verhalten definiert, und erzeuge und verwende Objekte dieses Typs in einem Programm.
- Erkläre, was eine Klasse und ein Objekt sindBeschreibe eine Klasse als Definition eines Typs, ein Objekt als eine Instanz dieses Typs sowie Zustand und Verhalten als das, was Objekte speichern und tun.
- Erzeuge Objekte und rufe ihre Methoden aufErzeuge Objekte mit
new, rufe ihre Methoden mit Punktnotation auf und sage voraus, wie sich verschiedene Objekte verhalten.
- Erkläre, was eine Klasse und ein Objekt sind
- Deklariere die Instanzvariablen, die den Zustand eines Objekts speichern, und schreibe Konstruktoren, die jedem neuen Objekt gültige Anfangswerte geben.
- Deklariere InstanzvariablenDeklariere Felder mit passenden Typen, kenne ihre Standardwerte und unterscheide sie von lokalen Variablen und Parametern.
- Schreibe Konstruktoren, die Objekte initialisierenSchreibe Konstruktoren, die jedes Feld setzen, verwende
this, um Felder von Parametern zu unterscheiden, und vermeide Fehler, durch die Felder nicht gesetzt werden. - Stelle mehrere Konstruktoren bereit und verkette sieÜberlade Konstruktoren für verschiedene Arten, ein Objekt zu erzeugen, und verwende
this(...), damit der Initialisierungscode nur einmal geschrieben wird.
- Deklariere Instanzvariablen
- Schreibe Instanzmethoden, die den Zustand eines Objekts lesen und ändern, verfolge ihre Wirkung und entscheide, wann etwas zu einem Objekt oder zur Klasse als Ganzes gehört.
- Schreibe Instanzmethoden, die den Zustand verwenden und ändernSchreibe Methoden, die Informationen über die Felder eines Objekts liefern oder sie ändern, rufe andere Methoden desselben Objekts auf und verfolge den Zustand von Objekten Schritt für Schritt.
- Unterscheide Instanzmitglieder von statischen MitgliedernVerwende
thisfür das aktuelle Objekt, entscheide, was statisch sein sollte, und behebe Fehler, die durch das Vermischen von statischem und Instanzcode entstehen.
- Schreibe Instanzmethoden, die den Zustand verwenden und ändern
- Verberge Felder mithilfe von Zugriffsmodifikatoren, ermögliche kontrollierten Zugriff über Getter, Setter und aussagekräftige Methoden und halte jedes Objekt in einem gültigen Zustand.
- Verberge Daten mit ZugriffsmodifikatorenVerwende
private, Paketzugriff,protectedundpublic, um zu steuern, welcher Code die Mitglieder einer Klasse verwenden kann, und erkläre, warum Felder privat gehalten werden. - Schreibe Getter und SetterSchreibe Accessor- und Mutator-Methoden mit den üblichen Namen und entscheide, welche Felder sie brauchen und wann eine aussagekräftige Methode besser ist.
- Sichere gültige Objektzustände durch PrüfungenDefiniere die Regeln für den Zustand eines Objekts, setze sie in Konstruktoren und Methoden durch und vermeide die Weitergabe interner Daten, mit der sich diese Prüfungen umgehen lassen.
- Verberge Daten mit Zugriffsmodifikatoren
- Gib Objekten mit
toStringeine lesbare Textform und entwirf, schreibe und teste anschließend eine vollständige Klasse anhand einer kurzen Beschreibung.- Schreibe eine toString-MethodeSchreibe eine
toString-Methode, die eine lesbare Beschreibung eines Objekts zurückgibt, und lerne, wann Java sie automatisch aufruft. - Entwirf, implementiere und teste eine vollständige KlasseSetze eine kurze Beschreibung in eine Klasse mit Feldern, Konstruktoren, Methoden und Regeln um und teste anschließend jedes Verhalten von einem Hauptprogramm aus.
- Schreibe eine toString-Methode
Java-Klassen und Objekte einfach erklärt
1Klassen, Objekte und Methodenaufrufe
Ein Programm muss oft mehrere zusammengehörige Werte zusammenhalten, zum Beispiel Name, Tierart und Alter eines Haustiers. Eine Klasse gibt dieser Gruppe einen Typ und bündelt die Aktionen, die sie verwenden, an einer Stelle. Du wirst Klassendefinitionen mit den Objekten verbinden, die während der Programmausführung entstehen, und anschließend Methodenaufrufe verfolgen, um zu sehen, welche Objektdaten sich ändern. Die Unterscheidung zwischen Klasse, Objekt und Variable hilft dir, den Code zu lesen, den du als Nächstes schreibst.
1.1 Ein Typ mit Zustand und Verhalten
Eine Klasse definiert einen Typ: die Daten, die sich seine Objekte merken, und die Operationen, die sie anbieten. Ein Objekt, auch Instanz genannt, ist ein Wert dieses Typs, der während der Programmausführung erzeugt wird. Das Schreiben einer Klassendefinition erzeugt kein Objekt.
Zustand und Verhalten
Name, Tierart und Alter eines Haustiers bilden seinen Zustand. Diese aktuellen Werte stehen in Instanzvariablen, die auch Felder genannt werden. Eine Operation wie das Feiern eines Geburtstags ist Verhalten und wird als Methode geschrieben. Die Geburtstagsmethode gehört zu dem Alter, das sie ändert, sodass die Regel an einer Stelle steht.
Stell dir eine Klasse als Bauplan vor. Viele Häuser können nach einem Plan entstehen, aber jedes Haus hat seinen eigenen Inhalt. Ebenso können zwei Haustiere derselben Definition folgen und unterschiedlich alt sein. Du schreibst die Feldnamen einmal; jedes Objekt erhält eigene Werte.
Lies eine kleine Klasse
public class Pet {
private String name;
private String species;
private int age;
public Pet(String name, String species, int age) {
this.name = name;
this.species = species;
this.age = age;
}
public void celebrateBirthday() {
this.age++;
}
}
Lies dies von außen nach innen. public class Pet führt den Typ ein und die äußeren geschweiften Klammern enthalten seine Definition. Die ersten drei Deklarationen beschreiben den Zustand. Der Block namens Pet ist ein Konstruktor: Er gibt einem neuen Objekt seine ersten Werte. celebrateBirthday ist eine Methode: Sie erhöht das Alter des aktuellen Objekts. Die Wörter private und public steuern den Zugriff; spätere Beispiele verwenden sie, um den Zustand zu schützen.
Die Parameter des Konstruktors sind Werte, die beim Erzeugen eines Objekts übergeben werden. In this.age = age; bezeichnet die linke Seite das Feld des Objekts und die rechte Seite den übergebenen Parameter. Der Konstruktor speichert diese Eingabe, damit sich das Objekt nach dem Ende der Konstruktion daran erinnern kann.
Eine Klasse einsetzen
Du hast bereits Bibliotheksklassen verwendet. Scanner beschreibt Objekte, die Eingaben lesen, ArrayList beschreibt Listen und ein String-Objekt bietet Operationen wie length() an. Eine selbst geschriebene Klasse folgt derselben Grundidee: Sie stellt dem Rest deines Programms einen nützlichen Typ bereit.
Ohne Pet müsste ein Programm Name, Tierart und Alter möglicherweise getrennt an viele Methoden übergeben. Es muss jede Gruppe korrekt zusammenhalten. Ein Pet-Objekt trägt die zusammengehörigen Werte als eine Einheit. Methoden auf diesem Objekt wissen, welches Alter sie verwenden sollen.
Code organisieren und benennen
Bei gewöhnlichen Quelldateien wird eine öffentliche Klasse namens Pet in Pet.java gespeichert, wobei die Groß- und Kleinschreibung übereinstimmen muss. Eine separate Klasse Main kann die main-Methode des Programms enthalten, Haustiere erzeugen und ihre Methoden aufrufen. Pet braucht keine eigene main-Methode. Klassen im selben unbenannten Paket können einander ohne Importdeklarationen verwenden; die einfachen Beispiele halten diese Klassen in einem Ordner.
Verwende für Klassen Substantive in UpperCamelCase, etwa LibraryBook. Verwende lowerCamelCase für Felder und Methoden: pageCount bezeichnet Daten; giveBack() bezeichnet eine Aktion. Diese Konventionen helfen beim Lesen, die einzelnen Teile zu erkennen, obwohl der Compiler viele andere Namen erlaubt.
Wichtige Erkenntnisse
- Eine Klasse definiert einen Typ; ein Objekt ist eine Instanz, die während der Programmausführung erzeugt wird.
- Felder speichern den Zustand und Methoden stellen das Verhalten bereit.
- Objekte einer Klasse folgen derselben Definition und speichern ihre eigenen Werte in Instanzfeldern.
- Eine öffentliche Klasse in einer gewöhnlichen Quelldatei verwendet den passenden Klassennamen als Dateinamen.
1.2 Neue Objekte und ihre Referenzen
Ein Objekt zu erzeugen und eine Variable zu deklarieren sind verschiedene Vorgänge. Eine Variable gibt dir einen Namen, über den du auf ein Objekt zugreifen kannst; new erzeugt das Objekt, auf das der Name verweisen wird.
Lies eine Anweisung zur Objekterzeugung
Pet luna = new Pet("Luna", "cat", 3);
Pet ist der Typ der Variable und luna ihr Name. new Pet(...) erzeugt ein neues Objekt und ruft einen passenden Konstruktor mit drei Argumenten auf. Der Konstruktor speichert Name, Tierart und Alter. Die entstehende Referenz wird luna zugewiesen.
Eine Referenz sagt Java, welches Objekt es verwenden soll. Es ist hilfreich, eine Variable als Namen mit einem Pfeil zu einem Objekt zu zeichnen. Die Variable ist keine zweite Kopie aller Felder des Objekts.
Rufe eine Methode auf einem bestimmten Objekt auf
Der Punkt in luna.celebrateBirthday() verbindet eine Referenz mit einer Methode, die auf ihrem Objekt ausgeführt wird. Eine Methode mit Rückgabewert kann einen Wert für einen anderen Ausdruck liefern:
luna.celebrateBirthday();
int years = luna.getAge();
System.out.println(years); // 4
Hier ist getAge() eine öffentliche Methode, die das Alter des Haustiers zurückgibt. Der Geburtstagsaufruf ändert den Zustand; der Getter liest ihn. Pet.celebrateBirthday() schlägt fehl, weil diese Instanzmethode ein bestimmtes Haustier braucht. Der Klassenname allein beantwortet nicht, wessen Geburtstag es ist.
Getrennte Objekte und gemeinsam genutzte Referenzen
Pet max = new Pet("Max", "dog", 5);
luna.celebrateBirthday();
System.out.println(luna.getAge()); // 5
System.out.println(max.getAge()); // 5
Luna war bereits 4 Jahre alt, also wird sie beim nächsten Geburtstag 5. Max bleibt 5. Das gleiche Alter macht sie nicht zu demselben Objekt: Die beiden new-Ausdrücke haben getrennte Haustiere erzeugt.
Betrachte nun eine Zuweisung ohne new:
Pet other = luna;
other.celebrateBirthday();
System.out.println(luna.getAge()); // 6
Die Zuweisung kopiert die Referenz auf Luna. Beide Namen verweisen auf ein Haustier, sodass eine Änderung über other auch über luna sichtbar ist. Um ein zweites Haustier zu erzeugen, rufe new erneut auf.
Achte auf die Parameter des Konstruktors
Die Argumente müssen in Anzahl, Reihenfolge und Typ zu einem deklarierten Konstruktor passen. Nimmt der einzige verfügbare Konstruktor (String, String, int) entgegen, wird new Pet() nicht kompiliert. Java errät keine fehlenden Werte. Werden die beiden Strings vertauscht, wird der Code dagegen trotzdem kompiliert: new Pet("cat", "Luna", 3) liefert die richtigen Typen, aber die falschen Bedeutungen. Lies die Parameternamen, um die Reihenfolge zu prüfen.
Diese beiden unvollständigen Versuche scheitern aus unterschiedlichen Gründen:
Pet a; // declaration only
// System.out.println(a.getAge()); // a has no assigned value
// Pet b = Pet("Bo", "cat", 2); // missing new
Einer lokalen Variable muss vor ihrer Verwendung ein Wert zugewiesen werden. Ohne new sucht Java nach einer Methode namens Pet, nicht nach einem Ausdruck zur Objekterzeugung. Ist ein Objekt vorhanden, kann seine Ausgabe Text wie Pet@8bcc55f zeigen, bis du eine lesbare toString()-Methode bereitstellst; diese Ausgabe bedeutet nicht, dass die Konstruktion fehlgeschlagen ist.
Wichtige Erkenntnisse
- Verwende new mit Argumenten, die zu einem verfügbaren Konstruktor passen.
- Die Punktnotation ruft eine Instanzmethode auf dem Objekt auf, auf das eine Referenz verweist.
- Ein weiterer new-Ausdruck erzeugt ein weiteres Objekt; eine Referenzzuweisung macht ein vorhandenes Objekt gemeinsam nutzbar.
- Argumente desselben Typs können ohne Kompilierfehler vertauscht werden. Prüfe deshalb ihre Bedeutung.
- Eine lokale Deklaration allein erzeugt kein Objekt und gibt der Variable keinen verwendbaren Wert.
2Felder und der Anfangszustand eines Objekts
Objekte brauchen einen Ort für ihre Daten und eine zuverlässige Möglichkeit, ihre ersten Werte zu erhalten. Felder stellen den Speicher bereit; Konstruktoren initialisieren jedes neue Objekt. Du wählst Feldtypen, trennst dauerhaften Zustand von vorübergehenden lokalen Arbeiten und verfolgst die Konstruktion von den Standardwerten bis zum fertigen Objekt. Anschließend bietest du mehrere sinnvolle Möglichkeiten an, dieselbe Art von Objekt zu erzeugen, und hältst dabei gemeinsame Initialisierung und Prüfungen an einer Stelle.
2.1 Daten, die sich ein Objekt merkt
Ein Objekt braucht für jeden Wert, den es sich zwischen Methodenaufrufen merken muss, ein Feld. Wähle diese Werte, bevor du Methoden schreibst: Sie bilden den dauerhaften Zustand des Objekts.
Deklariere Felder mit passenden Typen
Ein Feld steht innerhalb der geschweiften Klammern der Klasse, aber außerhalb aller Konstruktoren und Methoden. Eine Deklaration braucht einen Typ und einen Namen; ein Zugriffsmodifikator und ein Initialisierer können ebenfalls vorkommen.
private String title;
private int pageCount;
private boolean lentOut = false;
String eignet sich für Text, int für eine ganzzahlige Anzahl und boolean für eine Ja-Nein-Bedingung. Felder können auch Klassentypen wie ArrayList<String> verwenden. Diese Beispiele verwenden private; lässt du den Modifikator weg, gilt Paketzugriff und kein privater Zugriff.
Jedes Objekt erhält eine eigene Kopie jedes Instanzfeldes. Zwei Rectangle-Objekte haben jeweils ein width- und ein height-Feld, obwohl die Deklarationen nur einmal in der Klasse stehen.
Standardwerte sind nicht immer brauchbare Anfangswerte
| Feldtyp | Standardwert |
|---|---|
int, long | 0 |
double | 0.0 |
boolean | false |
Klassentypen, darunter String und ArrayList | null |
Ein Feld erhält einen Standardwert, wenn Speicherplatz für das Objekt angelegt wird. Er muss nicht zum Zweck des Objekts passen. Ein Listenfeld mit dem Wert null ist keine leere Liste: Du musst die Liste erst erzeugen, bevor du ein Element hinzufügen kannst.
Lokale Variablen erhalten keinen solchen Standardwert. Innerhalb einer Methode wird int total; gefolgt von System.out.println(total); nicht kompiliert, weil total kein Wert zugewiesen wurde. Halte die Regel für Felder und die für lokale Variablen auseinander.
Entscheide, was bestehen bleiben muss
| Art der Variable | Wo sie deklariert wird | Wofür sie dient |
|---|---|---|
| Instanzfeld | In der Klasse, außerhalb von Methoden | Werte, die sich ein Objekt merkt |
| Parameter | Im Kopf eines Konstruktors oder einer Methode | Eingaben für diesen Aufruf |
| Lokale Variable | Innerhalb eines Methoden- oder Konstruktorblocks | Vorübergehende Arbeit für diesen Aufruf |
Ein Feld bleibt bei seinem Objekt. Ein Parameter ist während seines Aufrufs verfügbar und eine lokale Variable in ihrem Block, während dieser Aufruf ausgeführt wird. Muss ein Zähler seinen Zählerstand von einem Aufruf zum nächsten behalten, gehört der Stand in ein Feld. Eine vorübergehende Zwischensumme für eine Berechnung kann lokal sein.
Speichere die Eingaben; berechne abgeleitete Werte
public class Rectangle {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
public double area() {
return this.width * this.height;
}
}
Die Abmessungen sind in Zentimetern angegeben, daher gibt area() eine Fläche in Quadratzentimetern zurück. Ein separates Feld für die Fläche würde einen weiteren Wert schaffen, der bei jeder Änderung einer Abmessung aktualisiert werden müsste. Die Berechnung aus den Abmessungen hält das Ergebnis konsistent.
Verwende ein final-Instanzfeld für einen Wert, der einmal zugewiesen wird, etwa private final String title;. Gib ihm einen Wert in seiner Deklaration oder in jedem Konstruktor. final verhindert eine Neuzuweisung. static hat eine andere Bedeutung: Es erzeugt ein gemeinsam genutztes Klassenfeld. Würde der Name eines Haustiers static erhalten, würden verschiedene Haustiere eine Namensvariable gemeinsam nutzen.
Wichtige Erkenntnisse
- Instanzfelder werden außerhalb von Methoden deklariert und speichern den Zustand jedes Objekts.
- Felder erhalten Standardwerte; lokalen Variablen muss vor der Verwendung ein Wert zugewiesen werden.
- Wähle Felder für dauerhafte Daten und lokale Variablen für vorübergehende Arbeit.
- Berechne Werte, die sich aus gespeicherten Feldern ergeben, um inkonsistente Kopien zu vermeiden.
- Ein final-Instanzfeld erhält einmal einen Wert; ein statisches Feld wird von der Klasse gemeinsam genutzt.
- Ein Listenfeld mit null braucht noch ein Listenobjekt, bevor es verwendet werden kann.
2.2 Von Argumenten zu initialisierten Feldern
Ein Konstruktor initialisiert ein Objekt, wenn new es erzeugt. Sein Name muss mit dem der Klasse übereinstimmen und er hat keinen Rückgabetyp. Selbst void würde die Deklaration zu einer Methode machen.
Übertrage Eingaben in den Zustand
public Pet(String name, String species, int age) {
this.name = name;
this.species = species;
this.age = age;
}
Ein Konstruktor nimmt üblicherweise Eingaben als Parameter entgegen und speichert sie in Feldern. Hier hat jeder Parameter denselben Namen wie sein Feld. Der bloße Name age bezeichnet den Parameter; this.age bezeichnet das Feld des Objekts, das gerade erzeugt wird. Die Zuweisung liest rechts den übergebenen Wert und schreibt links in das Feld.
Bei new Pet("Luna", "cat", 3) erhalten die Felder des neuen Objekts zuerst Standardwerte: null, null und 0. Der Konstruktor erhält dann die drei Argumente und ersetzt diese Standardwerte. Wenn die Konstruktion endet, liefert new die Referenz, die der Aufrufer speichert.
Zwei Fehler, die kompiliert werden
public Pet(String name, String species, int age) {
name = name;
this.species = species;
this.age = age;
}
Beide Vorkommen von name beziehen sich auf den Parameter, sodass die erste Zeile diesen Parameter sich selbst zuweist. Der Name des Objekts bleibt null. Der Code ist zulässig, weshalb es wichtig ist, die Werte des neuen Objekts zu prüfen.
Eine Deklaration innerhalb des Konstruktors verursacht ein verwandtes Problem:
String nick = initialNick;
Existiert bereits ein Feld namens nick, erzeugt diese Zeile eine lokale Variable mit demselben Namen. Die lokale Variable verschwindet, wenn die Konstruktion endet. Schreibe this.nick = initialNick;, um die Eingabe im Feld zu speichern; wiederhole den Typ nicht.
Erkenne, wann es einen parameterlosen Konstruktor gibt
Deklariert eine Klasse keine Konstruktoren, stellt Java einen Standardkonstruktor ohne Parameter bereit. Bei einer einfachen Klasse behalten die Felder ihre deklarierten Anfangswerte oder Standardwerte. Sobald du irgendeinen Konstruktor deklarierst, stellt Java diesen Standardkonstruktor nicht mehr bereit.
Eine Klasse mit nur Pet(String name, String species, int age) kann daher nicht mit new Pet() erzeugt werden. Ergänze einen ausdrücklichen parameterlosen Konstruktor nur, wenn es dafür einen sinnvollen Anfangszustand gibt. Achte auch auf public void Pet(...): Das ist eine Methode und stellt den beabsichtigten Konstruktor daher überhaupt nicht bereit.
Setze auch interne Anfangswerte
Nicht jedes Feld muss aus einem Argument stammen. Ein neues Buch kann immer mit this.lentOut = false; beginnen. Eine Klasse, die eine Liste besitzt, kann sie bei importiertem ArrayList so initialisieren:
private ArrayList<String> tracks;
public Playlist() {
this.tracks = new ArrayList<>();
}
Die leere Liste ist für add bereit. Bleibt tracks auf null, schlägt ein späteres tracks.add(...) mit einer NullPointerException fehl.
Ein Feldinitialisierer wird vor dem Konstruktorrumpf ausgeführt. Folgt auf private int score = 10; im Konstruktor this.score = 5;, ist der Wert von score im fertigen Objekt 5. Prüfe die Werte nach der Konstruktion und nicht nur die Werte neben den Felddeklarationen. Konstruktoren brauchen außerdem dieselben Validierungsregeln wie spätere Änderungen, damit der erste Zustand gültig ist.
Wichtige Erkenntnisse
- Ein Konstruktor hat den Namen der Klasse und keinen Rückgabetyp.
- Verwende this.field = parameter, wenn die Namen übereinstimmen.
- Weder die Selbstzuweisung eines Parameters noch eine neue lokale Variable initialisiert das beabsichtigte Feld.
- Java stellt nur dann einen Standardkonstruktor bereit, wenn die Klasse keine Konstruktoren deklariert.
- Initialisiere internen Zustand wie leere Listen und wende die Validierung bei der Konstruktion an.
2.3 Mehrere Wege, eine Art von Objekt zu konstruieren
Eine Klasse kann mehrere sinnvolle Möglichkeiten anbieten, ein Objekt zu erzeugen. Überladen von Konstruktoren bedeutet, Konstruktoren mit unterschiedlichen Parameterlisten zu deklarieren.
Unterscheide Konstruktoren anhand ihrer Parameter
Java unterscheidet Konstruktoren anhand von Anzahl, Typen und Reihenfolge ihrer Parameter. Die Parameternamen unterscheiden sie nicht. Box(String label) und Box(String name) beschreiben dieselbe Parameterliste und können daher nicht beide in einer Klasse deklariert werden.
Ein Haustier kann mit einer Altersangabe oder als Neugeborenes erzeugt werden:
public Pet(String name, String species, int age) {
this.name = name;
this.species = species;
this.age = age;
}
public Pet(String name, String species) {
this(name, species, 0);
}
new Pet("Pip", "rabbit") wählt den Konstruktor mit zwei Parametern. Dieser Konstruktor setzt 0 als fehlendes Alter ein und ruft den Konstruktor mit drei Parametern auf. Das neue Objekt hat am Ende den Zustand "Pip", "rabbit" und 0.
Verknüpfe die Initialisierung desselben Objekts
Innerhalb eines Konstruktors ruft this(...) einen anderen Konstruktor derselben Klasse auf. Der kürzere Konstruktor wählt Standardwerte; der umfassendere Konstruktor übernimmt die gemeinsame Initialisierung. Prüfungen in dieser Initialisierung gelten für beide Konstruktionswege.
Vergleiche zwei ähnlich aussehende Zeilen:
| Zeile innerhalb eines Konstruktors | Wirkung |
|---|---|
this(name, species, 0); | Initialisiert das aktuelle Objekt über einen anderen Konstruktor |
new Pet(name, species, 0); | Erzeugt ein separates Haustier |
Die zweite Zeile füllt nicht die Felder des aktuellen Objekts. Wird ihr Ergebnis nie gespeichert, wird das zusätzliche Haustier verworfen. Die Felder des aktuellen Haustiers können weiterhin null enthalten.
Verfolge eine Kette, bevor du späteren Code nachvollziehst
Eine Uhr kann Mitternacht als Standard anbieten:
public Clock() {
this(0, 0);
}
public Clock(int hours, int minutes) {
this.hours = hours;
this.minutes = minutes;
}
Gehe bei new Clock() in den parameterlosen Konstruktor, folge this(0, 0) und weise beide Felder im Konstruktor mit zwei Parametern zu. Danach kehrt die Ausführung zum parameterlosen Konstruktor zurück. So entsteht eine Uhr, nicht zwei. Ein Aufruf von new Clock(9, 5) beginnt direkt im umfassenderen Konstruktor.
Halte die Kette zulässig und verständlich
Ein Konstruktor kann höchstens einen this(...)-Aufruf enthalten. Die Kette muss schließlich eine Initialisierung erreichen, die nicht in die Kette zurückruft: Zwei Konstruktoren, die aneinander delegieren, verursachen beim Kompilieren einen Fehler wegen eines rekursiven Konstruktoraufrufs.
this(...) an den Anfang zu setzen, ist ein klarer Stil, der über Java-Versionen hinweg funktioniert. In Java 24 und früher muss der Aufruf ohne Vorschaufunktionen an erster Stelle stehen. Java 25 erlaubt vorherige Anweisungen für Aufgaben wie das Berechnen oder Prüfen von Argumenten. Diese Anweisungen dürfen weder Instanzfelder des aktuellen Objekts lesen noch dessen Instanzmethoden aufrufen; Zuweisungen an Felder, die in der Klasse ohne Initialisierer in der Deklaration deklariert wurden, sind erlaubt.
Biete Überladungen an, weil Aufrufer unterschiedliche sinnvolle Eingaben haben. Eine Uhr auf Mitternacht einzustellen ist sinnvoll; ein Buch ohne Titel muss es nicht sein. Ein vollständiger Konstruktor erzeugt nicht automatisch kürzere Überladungen.
Wichtige Erkenntnisse
- Überladene Konstruktoren unterscheiden sich durch ihre Parameterlisten, nicht durch die Parameternamen.
- Der Aufruf this(...) delegiert an einen anderen Konstruktor für dasselbe Objekt.
- Halte gemeinsame Initialisierung und Prüfungen in einem Konstruktionsweg.
- Eine Konstruktorkette darf nicht zu einem Konstruktor zurückführen, der bereits in dieser Kette steht.
3Methoden, die den Zustand lesen und ändern
Ein nützliches Objekt kann Fragen über sich beantworten und Aktionen ausführen, die seinen Zustand ändern. Instanzmethoden stellen diese Operationen neben die Felder, die sie verwenden. Du verfolgst Änderungen eine Anweisung nach der anderen, behandelst den Überlauf einer Uhr und gibst Werte zurück, die anderer Code verwenden kann. Außerdem entscheidest du, welche Daten zu jedem einzelnen Objekt und welche Daten oder Operationen zur Klasse gehören, damit getrennte Objekte getrennt bleiben.
3.1 Berechnungen, Änderungen und Zustandsprotokolle
Eine Instanzmethode wird für ein bestimmtes Objekt ausgeführt. Deklariere sie ohne static und verwende Felder dieses Objekts über Namen wie this.width. Entscheide, was der Aufrufer braucht: eine Zustandsänderung, ein zurückgegebenes Ergebnis oder beides.
Gib Berechnungen zurück; mache Zustandsänderungen ausdrücklich
Für ein Rechteck, dessen Abmessungen in Zentimetern gespeichert sind:
public double area() {
return this.width * this.height;
}
public double perimeter() {
return 2 * (this.width + this.height);
}
public void scale(double factor) {
this.width = this.width * factor;
this.height = this.height * factor;
}
Ein Rechteck mit den Seiten 3,0 und 4,0 hat eine Fläche von 12,0 Quadratzentimetern und einen Umfang von 14,0 Zentimetern. scale(2) ändert die Seiten auf 6,0 und 8,0. Die nächsten Aufrufe geben die Fläche 48,0 und den Umfang 28,0 zurück. Jede Methode liest die aktuellen Felder, sodass keine gespeicherte Fläche aktualisiert werden muss.
area() gibt eine Zahl zurück, die gespeichert oder in einer Berechnung verwendet werden kann. Würde die Methode die Zahl nur ausgeben und den Rückgabetyp void haben, könnte der Aufrufer nicht double twice = r.area() * 2; schreiben. Überlasse die Ausgabe dem Aufrufer, wenn eine Methode einen Wert berechnen soll.
Aktualisiere zusammengehörige Felder der Reihe nach
Die Methode advance() einer Uhr muss sowohl den Minuten- als auch den Stundenüberlauf behandeln:
public void advance() {
this.minutes++;
if (this.minutes == 60) {
this.minutes = 0;
this.hours++;
if (this.hours == 24) {
this.hours = 0;
}
}
}
Bei 23:58 Uhr erreicht der erste Aufruf 23:59 Uhr und überspringt beide Blöcke zum Zurücksetzen. Der nächste Aufruf erreicht kurz Minute 60, setzt sie auf 0 zurück, erhöht die Stunde auf 24 und setzt auch diese auf 0 zurück. Der endgültige Zustand ist 00:00 Uhr. Ein weiterer Aufruf ergibt 00:01 Uhr.
Verfolge Objekte getrennt
Schreibe nach jeder Anweisung eine Zeile und für jedes Feld jedes Objekts eine Spalte. Ändere nur das Objekt, auf dem die Methode aufgerufen wird. Bei Haustieren im Alter von 2 und 7 Jahren ergeben zwei Geburtstage beim ersten Haustier und setAge(1) beim zweiten die Alterswerte 4 und 1. Führe den Rumpf einer Methode nicht für jede Instanz aus, nur weil sie derselben Klasse angehören.
Eine lokale Deklaration kann das Feld verdecken, das du aktualisieren wolltest. public void reset() { int count = 0; } erzeugt nur eine temporäre Variable. this.count = 0; setzt den dauerhaft gespeicherten Zählerstand zurück.
Verwende Methoden wieder und vervollständige Rückgabepfade
Innerhalb einer Instanzmethode rufen area() und this.area() die Flächenmethode desselben Objekts auf. Eine Methode zur Beschreibung kann diese Berechnung wiederverwenden, statt ihren Ausdruck zu kopieren. Eine Methode kann auch den Zustand ändern und einen Erfolg zurückgeben, wie lend(), wenn sie ein Buch als ausgeliehen markiert und true meldet.
Eine Methode mit einem anderen Rückgabetyp als void darf nicht einfach das Ende erreichen, ohne einen kompatiblen Wert zurückzugeben. Wenn get() nur innerhalb von if (count > 0) zurückkehrt, was geschieht dann, wenn der Zählerstand 0 ist? Ergänze den nötigen Rückgabepfad oder wirf für einen Wert, den die Methode ablehnt, eine Ausnahme. Ein Pfad, der eine Ausnahme wirft, kehrt nicht zum Aufrufer zurück und braucht keinen Rückgabewert.
Wichtige Erkenntnisse
- Instanzmethoden lesen oder ändern den Zustand des Objekts, auf dem sie aufgerufen werden.
- Gib berechnete Werte zurück, damit Aufrufer sie in Ausdrücken verwenden können.
- Verfolge Anweisungen der Reihe nach und halte die Felder jedes Objekts auseinander.
- Methoden zur Behandlung eines Überlaufs müssen alle zugehörigen Felder in ihren erlaubten Bereich zurücksetzen.
- Verwende andere Methoden desselben Objekts wieder, indem du sie direkt oder mit this aufrufst.
- Eine Methode mit Rückgabewert braucht auf jedem Pfad, der regulär zurückkehrt, einen Wert.
3.2 Objektbezogene und gemeinsam genutzte Klassenmitglieder
Frag dich, zu wem ein Wert oder eine Operation gehört, bevor du dich für oder gegen static entscheidest. Das Alter eines Haustiers gehört zu einem Haustier. Die Anzahl aller erzeugten Haustiere gehört zur Klasse als Ganzes.
Das aktuelle Objekt ist this
Innerhalb eines Konstruktors oder einer Instanzmethode verweist this auf das Objekt, das gerade konstruiert wird oder den Aufruf empfängt. In this.age++ bezeichnet es, wessen Alter sich ändert. In this.name = name unterscheidet es das Feld von einem gleichnamigen Parameter. this(...) ist die Form zur Konstruktorverkettung.
Eine statische Methode hat kein aktuelles Objekt, daher kann this dort nicht verwendet werden. Sie kann dennoch ein Objekt erzeugen oder eine Referenz erhalten und damit Instanzmethoden aufrufen.
Gemeinsam genutzte und objektbezogene Felder
public class Counter {
private int count;
private static int created = 0;
public Counter() {
this.count = 0;
created++;
}
public void increment() {
this.count++;
}
public int getCount() {
return this.count;
}
public static int getCreated() {
return created;
}
}
Nach zwei Aufrufen von new Counter() gibt es zwei getrennte count-Felder, die beide anfangs 0 sind, und ein created-Feld mit dem Wert 2. Erhöhst du den ersten Zähler, ändert sich sein Zählerstand auf 1; der zweite bleibt bei 0. Counter.getCreated() gibt weiterhin 2 zurück, weil kein neues Objekt konstruiert wurde.
Arbeite von main aus über ein Objekt
public static void main(String[] args) {
Counter c = new Counter();
c.increment();
System.out.println(c.getCount());
}
Die klassische main-Methode ist statisch. Ein bloßer Aufruf von increment() hätte dort kein Zielobjekt und würde nicht kompiliert. Die Variable c liefert dieses Ziel. Würdest du count statisch machen, um den Fehler zu beseitigen, würde sich der Entwurf ändern: Jeder Zähler würde dann einen Zählerstand gemeinsam nutzen.
| Art der Methode | Verfügbarer direkter Zugriff |
|---|---|
| Instanzmethode | Ihre Instanzmitglieder und die statischen Mitglieder der Klasse |
| Statische Methode | Statische Mitglieder; Instanzmitglieder über eine Objektreferenz |
Java 25 unterstützt auch Instanzformen von main, aber der klassische statische Methodenkopf bleibt gültig. Diese Beispiele verwenden ihn durchgehend.
Wähle Methoden und Konstanten bewusst
Eine Operation, die die Felder eines Objekts braucht, sollte eine Instanzmethode sein. Eine Operation, die nur von ihren Parametern abhängt, kann statisch sein, etwa eine Hilfsmethode, die eine Gesamtzahl von Minuten in Stunden umrechnet. Statische Aufrufe verwenden normalerweise den Klassennamen: Math.sqrt(16) oder Counter.getCreated().
Ein gemeinsamer fester Wert wird üblicherweise als Konstante deklariert:
public static final int MAX_CELSIUS = 30;
public macht sie für Aufrufer verfügbar, static gibt der Klasse ein gemeinsam genutztes Feld und final verhindert eine Neuzuweisung. UPPER_SNAKE_CASE kennzeichnet nach Konvention eine Konstante. Dies ist eine häufige Ausnahme von der Regel, Felder privat zu machen. Verwende static nicht für gewöhnliche Namen, Abmessungen oder andere Zustandswerte, die sich zwischen Objekten unterscheiden müssen.
Wichtige Erkenntnisse
- Die Referenz this bezeichnet innerhalb von Instanzcode das aktuelle Objekt.
- Statische Mitglieder gehören zur Klasse; Instanzmitglieder gehören zu einzelnen Objekten.
- Eine statische Methode braucht eine Objektreferenz, um Instanzmitglieder zu verwenden.
- Behebe Fehler zum statischen Kontext, indem du das richtige Objekt bereitstellst, wenn der Zustand zu einem Objekt gehört.
- Ein public static final-Feld stellt häufig eine gemeinsam genutzte Konstante bereit.
4Kontrollierter Zugriff und gültiger Zustand
Eine Klasse sollte kontrollieren, welche Werte in ihren Zustand aufgenommen werden können. Private Felder und öffentliche Methoden ermöglichen es der Klasse, jede Änderung zu prüfen und Aufrufern nützliche Operationen anzubieten. Du wählst Zugriffsebenen, schreibst Getter und Setter, wo sie helfen, und schützt Regeln wie einen erlaubten Temperaturbereich. Du siehst außerdem, warum eine private Liste über einen Getter trotzdem nach außen gelangen kann und wie du verhinderst, dass Code außerhalb der Klasse ihre Prüfungen umgeht.
4.1 Privater Zustand und öffentliche Schnittstelle
Kapselung hält den Zustand eines Objekts mit dem Code zusammen, der ihn kontrolliert. Felder sind üblicherweise privat, während Aufrufer öffentliche Operationen verwenden. Das verbirgt Details der Speicherung und gibt der Klasse eine Stelle, an der sie ihre Regeln durchsetzen kann.
Wähle eine Zugriffsebene
| Zugriff | Wer das Mitglied verwenden kann |
|---|---|
private | Code in derselben Klasse |
| Kein Modifikator | Code im selben Paket |
protected | Das gleiche Paket sowie Zugriff für Unterklassen nach den Java-Regeln für Unterklassen |
public | Code, der auf den deklarierenden Typ zugreifen kann |
Ein Paket gruppiert Klassen. Die einfachen Beispiele legen gewöhnliche Klassen ohne package-Deklaration zusammen in einem unbenannten Paket ab. Paketzugriff entsteht, wenn du den Modifikator weglässt; es gibt kein Zugriffsschlüsselwort package, das du einem Feld hinzufügen könntest.
Beginne mit dem restriktivsten sinnvollen Zugriff. Mache Felder privat, die von anderen Klassen benötigten Konstruktoren und Operationen öffentlich und interne Hilfsmethoden privat. Öffentliche Konstanten sind eine häufige Ausnahme bei Feldern.
Gib der Klasse die Kontrolle über Änderungen
Deklariert Pet ein Feld private int age;, kann eine separate Klasse Main nicht luna.age = -5; zuweisen. Der Compiler weist den Zugriff zurück. Ein Aufruf wie luna.setAge(-5) kann erlaubt sein, weil die öffentliche Methode zu Pet gehört und entscheiden kann, ob sie das Argument speichert.
Ein öffentliches Feld würde es jedem Aufrufer erlauben, diese Methode zu umgehen. Sobald alle Änderungen über den Code der Klasse erfolgen, können die Prüfungen an einer Stelle stehen. Privater Zugriff allein validiert ein Alter nicht; die Methoden müssen die Prüfungen ebenfalls durchführen.
Halte die öffentliche Schnittstelle stabil
Angenommen, eine Uhr speichert zunächst separate Felder hours und minutes. Aufrufer verwenden getHours() und getMinutes(). Später ersetzt du die Felder durch einen einzigen Wert minutesSinceMidnight:
public int getHours() {
return this.minutesSinceMidnight / 60;
}
public int getMinutes() {
return this.minutesSinceMidnight % 60;
}
Bei 615 gespeicherten Minuten ergibt die Division 10 und der Divisionsrest ist 15. Die Uhr meldet über dieselben Methoden 10:15 Uhr. Die Aufrufer müssen nicht umgeschrieben werden, weil sie von den öffentlichen Operationen abhingen und nicht davon, wie der Zustand gespeichert wurde. Diese Freiheit, Interna zu ändern, ist das Geheimnisprinzip.
Zugriff gehört zu Klassen, nicht zu einzelnen Objekten
Code innerhalb von Pet kann ein privates Feld eines anderen Pet-Objekts lesen. Die Zugriffsberechtigung ergibt sich daraus, dass sich der Code innerhalb der Klasse befindet. Eine andere Klasse auf oberster Ebene erhält keine Berechtigung, indem sie eine Referenz auf ein Haustier hält oder sich im selben Ordner befindet.
Für gewöhnliche Klassendeklarationen auf oberster Ebene stehen public oder kein Modifikator zur Wahl. Ein private class Pet auf oberster Ebene ist unzulässig. Eine öffentliche Klasse auf oberster Ebene gehört in die passende Quelldatei und eine gewöhnliche Quelldatei enthält höchstens eine öffentliche Klasse auf oberster Ebene.
Private Felder bilden eine Grenze im Programmentwurf. Sie verhindern, dass anderer gewöhnlicher Code direkt von internen Feldern abhängt, sind aber kein Mittel, um einen Wert vor einer Person geheim zu halten, die das Programm untersuchen kann. Verwende sie, um die Schnittstelle deiner Klasse zu steuern und ihren Zustand zu schützen.
Wichtige Erkenntnisse
- Private Felder und öffentliche Operationen halten die Kontrolle über den Zustand innerhalb der Klasse.
- Lässt du einen Zugriffsmodifikator weg, gilt Paketzugriff.
- Öffentliche Methoden ermöglichen Änderungen an der internen Speicherung, ohne die Aufrufer zu ändern.
- Privater Zugriff wird pro Klasse geprüft, sodass Code innerhalb der Klasse auf die Felder einer anderen Instanz zugreifen kann.
- Gewöhnliche Klassen auf oberster Ebene verwenden öffentlichen Zugriff oder Paketzugriff.
4.2 Zugriffsmethoden, die zur Klasse passen
Eine Klasse kann Aufrufern erlauben, ausgewählte Teile ihres Zustands über Methoden zu lesen oder zu ändern. Entscheide, welche Operationen die Klasse anbieten soll, bevor du für jedes Feld Zugriffsmethoden erstellst.
Gib Werte mit Gettern zurück
Ein Getter, auch Accessor genannt, gibt Informationen zurück, ohne das Objekt zu ändern. Ein gängiger Getter liest ein Feld:
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
Der Rückgabetyp passt zu den bereitgestellten Informationen. Ein Getter, der etwas ausgibt und void zurückgibt, kann keinen Wert für einen Ausdruck wie pet.getAge() + 1 liefern. Der Aufrufer sollte den Wert erhalten und entscheiden, wie er ihn verwendet.
Ändere ausgewählte Werte mit Settern
Ein Setter, auch Mutator genannt, nimmt einen neuen Wert entgegen und aktualisiert den Zustand:
public void setName(String name) {
this.name = name;
}
this.name ist das Feld. name ist der Parameter. name = name; wird kompiliert, ändert aber kein Feld. Es gibt hier keinen Rückgabewert, weil dieser Setter eine Änderung durchführen soll.
Für ein Haustier im Alter von 3 Jahren:
luna.setName("Lulu");
System.out.println(luna.getName() + " is " + luna.getAge());
// Lulu is 3
Der Aufrufer greift nie direkt auf ein Feld zu. Der Setter ändert den Namen und die Getter geben die aktuellen Werte zurück, die der Aufrufer verbinden kann.
Halte dich an Namen, die anderer Java-Code erwartet
| Feld | Getter | Setter, falls nötig |
|---|---|---|
pageCount | getPageCount() | setPageCount(int pageCount) |
name | getName() | setName(String name) |
lentOut | isLentOut() | setLentOut(boolean lentOut) |
Schreibe den ersten Buchstaben des Feldnamens nach get, set oder dem üblichen boolean-Präfix is groß. Ein Name wie getname() wird zwar möglicherweise kompiliert, folgt aber nicht der Konvention, die Werkzeuge und andere Programmierer erwarten.
Biete nur nützliche Operationen an
Der Titel eines Buches kann einen Getter und keinen Setter haben, weil Aufrufer ihn lesen, aber nicht ersetzen sollen. Ein privates final-Feld kann durchsetzen, dass ihm einmal ein Wert zugewiesen wird. Ein interner Zähler braucht möglicherweise weder Getter noch Setter, wenn nur die Klasse ihn verwendet.
Ein Setter ohne Prüfung kann denselben schädlichen Wert annehmen wie ein öffentliches Feld. Ergänze Prüfungen, wenn eine Änderung Einschränkungen unterliegt. Ziehe auch eine Aktionsmethode in Betracht: celebrateBirthday() drückt aus, dass das Alter um eins steigt, und lend() steht für einen Ausleihversuch. lend() kann den aktuellen Zustand prüfen und eine zweite Ausleihe ablehnen. Auch ein Setter kann diese Prüfung durchführen; der Name der Aktion macht dem Aufrufer die Operation aber klarer.
Ein Getter kann ein Ergebnis berechnen. getArea() kann das Produkt aus Breite und Höhe zurückgeben, ohne dass es ein Feld area gibt.
Wichtige Erkenntnisse
- Ein Getter gibt Informationen zurück; ein Setter ändert einen ausgewählten Wert.
- Halte dich an das Benennungsmuster getName, setName und das übliche boolean-Muster isName.
- Gib Feldern nur die Zugriffsmethoden, die Aufrufer brauchen.
- Aktionsmethoden wie lend drücken erlaubtes Verhalten klar aus und können Zustandsübergänge prüfen.
4.3 Regeln, die bei jeder Änderung erhalten bleiben
Eine Invariante ist eine Regel für den Zustand eines Objekts, die nach der Konstruktion und nach jeder öffentlichen Operation gilt. Ein Thermostat könnte Zieltemperaturen von 5 °C bis einschließlich 30 °C erlauben. Jede Möglichkeit, seinen Zielwert zu setzen, muss diesen Bereich einhalten.
Entscheide, was eine ungültige Eingabe bedeutet
Drei sinnvolle Reaktionen sind, den alten Wert zu behalten, den Wert auf eine Bereichsgrenze zu begrenzen oder eine Ausnahme zu werfen. Sie haben unterschiedliche Auswirkungen, wähle also für jede Operation eine klare Reaktion.
| Reaktion | Ergebnis eines ungültigen Aufrufs |
|---|---|
| Ignorieren | Der alte Feldwert bleibt unverändert |
| Begrenzen | Die nächstgelegene erlaubte Bereichsgrenze wird gespeichert |
| Ausnahme werfen | Der Aufruf bricht mit einem Fehler wie IllegalArgumentException ab |
Für ein nicht negatives Alter eines Haustiers kann ein Setter, der ungültige Werte ignoriert, die Zuweisung nur innerhalb von if (age >= 0) vornehmen. Ist das gespeicherte Alter 4, belässt setAge(-2) es bei 4. Das Alter wird nicht zu 0.
Schreibe Bereichsprüfungen in die Klasse
public static final int MIN_CELSIUS = 5;
public static final int MAX_CELSIUS = 30;
private int target;
public void setTarget(int celsius) {
if (celsius < MIN_CELSIUS) {
this.target = MIN_CELSIUS;
} else if (celsius > MAX_CELSIUS) {
this.target = MAX_CELSIUS;
} else {
this.target = celsius;
}
}
public void raise() {
setTarget(this.target + 1);
}
Ausgehend von 20 °C speichert setTarget(35) den Wert 30. setTarget(-3) speichert 5. Nach setTarget(29) speichert ein Aufruf von raise() den Wert 30; ein weiterer Aufruf speichert weiterhin 30. Durch die Wiederverwendung des Setters kann raise() die Obergrenze nicht umgehen.
Prüfungen allein in Main können den Thermostat nicht schützen. Ein anderer Aufrufer könnte sie vergessen. Die Klasse muss die Eingabe dort prüfen, wo sie die Speicherung kontrolliert.
Validiere auch den ersten Zustand
public Thermostat(int target) {
setTarget(target);
}
Mit diesem Konstruktor, der auf den Wertebereich begrenzt, endet new Thermostat(50) bei 30 °C. Eine direkte Zuweisung this.target = target; würde den ungültigen Anfangszustand 50 zulassen. Eine gemeinsam verwendete private Prüfmethode ist eine weitere Möglichkeit, die Validierung in Konstruktoren und späteren Methoden wiederzuverwenden.
Das Werfen einer Ausnahme ist nützlich, wenn ein ungültiges Argument einen Programmierfehler anzeigt. Ein Konstruktor kann zum Beispiel eine negative Anzahl mit throw new IllegalArgumentException("count must not be negative"); ablehnen. Das Objekt wird nicht erfolgreich konstruiert, sodass der Aufrufer kein fertiges ungültiges Objekt erhält. Eine Eingabe zu ignorieren ist einfacher, geschieht aber ohne Meldung; der Aufrufer braucht möglicherweise eine andere Möglichkeit zu bemerken, dass sich nichts geändert hat.
Halte veränderliche interne Daten im Objekt
Eine private Liste kann dennoch nach außen gelangen, wenn ein Getter die ursprüngliche Liste zurückgibt. Ein Aufrufer könnte dann Einträge hinzufügen oder entfernen, ohne die Methoden der Klasse zu verwenden, und so eine Regel zur Höchstgröße umgehen.
Für ein ArrayList<String>-Feld namens tracks liefert return new ArrayList<>(this.tracks); eine separate Liste mit denselben Strings. Eine Änderung an der zurückgegebenen Liste ändert die interne Liste nicht. Operationen wie addTrack(String title) können Listenänderungen innerhalb der Klasse halten und deren Regeln prüfen, bevor sie die Änderungen annehmen.
Wichtige Erkenntnisse
- Eine Invariante gilt nach der Konstruktion und nach jeder öffentlichen Operation.
- Ignorieren, Begrenzen und das Werfen einer Ausnahme sind unterschiedliche Reaktionen auf ungültige Eingaben.
- Jeder Konstruktor und jede zustandsändernde Methode muss dieselben Regeln einhalten.
- Verwende die Validierung wieder, damit verschiedene Änderungswege sie nicht umgehen können.
- Gibt eine Klasse eine interne veränderliche Liste zurück, können Aufrufer ihre Prüfungen umgehen.
- Eine Kopie einer internen String-Liste schützt die ursprüngliche Liste vor Listenänderungen von außen.
5Lesbare Objekte und vollständige Klassen
Lesbare Objektbeschreibungen erleichtern es, eine Klasse zu verwenden und zu prüfen. Eine toString-Methode liefert diesen Text überall dort, wo ein Objekt ausgegeben oder mit einem String verknüpft wird. Du schreibst die Methode mit dem richtigen Methodenkopf und fügst dann Felder, Konstruktoren, Methoden und Validierung zu einer vollständigen Klasse zusammen. Ausgehend von einer kurzen Beschreibung setzt du Anforderungen in Code um und testest gewöhnliche Aufrufe, wiederholte Aktionen und Grenzwertfälle.
5.1 Eine Objektbeschreibung als Rückgabewert
Ein nützliches Objekt sollte sich leicht als Text betrachten lassen. Das eingebaute toString() von Java gibt einen Klassennamen, ein @-Zeichen und einen hexadezimalen Hashcode zurück, etwa Pet@8bcc55f. Ein benanntes Paket wird Teil des Klassennamens. Der Hashcode als Text variiert und sagt üblicherweise wenig über den Zustand des Objekts aus.
Gib eine lesbare Beschreibung zurück
@Override
public String toString() {
return this.name + " (" + this.species + ", "
+ this.age + " years)";
}
Für Luna, eine 3 Jahre alte Katze, ist der zurückgegebene String "Luna (cat, 3 years)". Die Methode liest Felder und baut einen String auf. Sie ändert das Haustier nicht und gibt nichts auf dem Bildschirm aus.
@Override weist den Compiler an zu prüfen, ob die Deklaration tatsächlich eine vorhandene Methode ersetzt. Das erkennt Fehler wie tostring mit einem kleinen s. Die Annotation formatiert das Ergebnis nicht; der Rückgabeausdruck bestimmt den Text.
Erkenne automatische Aufrufe
System.out.println(luna);
String label = "Pet: " + luna;
String details = luna.toString();
Die erste Zeile gibt die lesbare Beschreibung des Haustiers aus. Die zweite erzeugt "Pet: Luna (cat, 3 years)". Die dritte fordert dieselbe Beschreibung ausdrücklich an und speichert sie, ohne sie auszugeben. Wird eine ArrayList von Haustieren ausgegeben, verwendet ihre Darstellung innerhalb der eckigen Klammern das toString() jedes Haustiers.
Wegen der automatischen Umwandlung ist es wichtig, Text zurückzugeben. Der Aufrufer gibt das Ergebnis möglicherweise aus, speichert es oder verknüpft es. Eine Methode, die nur etwas ausgibt, kann den String nicht liefern, den all diese Aufrufer brauchen.
Achte auf den richtigen Methodenkopf
| Deklaration | Was geschieht |
|---|---|
public String toString() | Liefert die Objektbeschreibung |
public String tostring() | Deklariert eine andere Methode; @Override erkennt dies |
public String toString(int width) | Deklariert eine Überladung; die automatische Ausgabe ruft sie nicht auf |
public void toString() | Schlägt fehl: Die Methode steht im Konflikt mit der vorhandenen Methode, die einen String zurückgibt |
Verwende den genauen parameterlosen Methodenkopf und ergänze die Annotation. Eine Überladung mit Parametern kann eine gültige separate Methode sein, aber die Ausgabe eines Objekts liefert diese Parameter nicht.
Formatiere Werte, ohne sie zu ändern
Eine Uhr braucht zweistellige Stunden und Minuten. Innerhalb ihrer toString()-Methode:
return String.format(java.util.Locale.US,
"%02d:%02d", this.hours, this.minutes);
Für die Stunde 9 und die Minute 5 ist der String "09:05". Jeder Formatspezifizierer %02d formatiert eine Ganzzahl mit mindestens zwei Stellen und verwendet bei Bedarf eine führende Null. String.format(java.util.Locale.US, "%.2f", 7.5) gibt "7.50" zurück. Das angegebene Gebietsschema macht die Ausgabe mit Dezimalpunkt unabhängig vom standardmäßigen Formatierungsgebietsschema des Computers.
Halte die Beschreibung kurz genug, damit du sie bei der Fehlersuche lesen kannst. Sie kann ungesetzten Zustand aufdecken: Wird ein Namensfeld mit dem Wert null mit einem String verknüpft, entsteht der Text "null", der dich zur Initialisierung zurückführt. Die Beschreibung sollte den aktuellen Zustand wiedergeben. Daher sollte die zweimalige Ausgabe des Objekts weder eine Uhr vorstellen noch einen Geburtstag feiern.
Wichtige Erkenntnisse
- Schreibe public String toString() ohne Parameter und gib lesbaren Text zurück.
- Die Annotation @Override lässt den Compiler prüfen, ob die Methode eine vorhandene Methode ersetzt.
- Die Ausgabe eines Objekts und seine Verknüpfung mit einem String verwenden automatisch toString.
- Halte toString frei von Zustandsänderungen und überlasse dem Aufrufer die Entscheidung, wohin der Text geht.
- String.format kann Werte auffüllen und für eine vorhersagbare Ausgabe ein ausdrücklich angegebenes Gebietsschema verwenden.
5.2 Von einer Anforderung zur getesteten Klasse
Beginne damit, was sich ein Objekt merken muss, welche Aktionen Aufrufer von ihm verlangen können und welche Zustände erlaubt sein müssen. Setze dann jeden Teil in Felder, Konstruktoren und Methoden um.
Leite den Entwurf aus einer Beschreibung ab
„Ein Bibliotheksbuch hat einen Titel, einen Autor und eine Seitenzahl. Es kann ausgeliehen und zurückgegeben werden. Solange es bereits ausgeliehen ist, kann es nicht erneut ausgeliehen werden.“
Verwende LibraryBook als Klassennamen. Speichere die drei beschreibenden Werte und einen booleschen Ausleihstatus. Titel, Autor und Seitenzahl bleiben unverändert, mache sie also final. Der Ausleihstatus ändert sich durch aussagekräftige Aktionen. Ein Setter für jedes Feld ist unnötig.
Entwickle die vollständige Klasse
public class LibraryBook {
private final String title;
private final String author;
private final int pages;
private boolean lentOut;
public LibraryBook(String title, String author, int pages) {
this.title = title;
this.author = author;
this.pages = pages;
this.lentOut = false;
}
public boolean isLentOut() {
return this.lentOut;
}
public boolean lend() {
if (this.lentOut) {
return false;
}
this.lentOut = true;
return true;
}
public void giveBack() {
this.lentOut = false;
}
@Override
public String toString() {
String status = this.lentOut ? "lent out" : "available";
return this.title + " by " + this.author + ", "
+ this.pages + " pages, " + status;
}
}
Der Konstruktor legt fest, dass das Buch verfügbar ist. lend() prüft den vorhandenen Zustand, bevor es ihn ändert; die Rückgabe von false bei einer zweiten Ausleihe erhält die Regel. giveBack() macht das Buch verfügbar. Der Getter meldet den Status und toString() beschreibt das ganze Buch, ohne es zu ändern.
Schreibe erwartete Ergebnisse vor den Aufrufen auf
In der klassischen main-Methode einer separaten Klasse Main:
LibraryBook b = new LibraryBook("Night Train", "A. Rivera", 320);
System.out.println(b); // Night Train by A. Rivera, 320 pages, available
System.out.println(b.lend()); // true
System.out.println(b.lend()); // false
b.giveBack();
System.out.println(b.lend()); // true
Der zweite Ausleihversuch ist entscheidend: Eine Methode, die immer das Flag setzt und true zurückgibt, würde den ersten Aufruf bestehen, aber an dieser Prüfung scheitern. Teste mit isLentOut() sowohl den Zustand als auch die Rückgabewerte. Probiere außerdem ein zweites Buch aus, um zu prüfen, ob das andere Buch verfügbar bleibt, wenn eines ausgeliehen wird.
Teste dort, wo sich das Verhalten ändert
Decke jeden Konstruktor und jede öffentliche Methode ab. Probiere bei Bereichsprüfungen beide Endpunkte und Werte knapp außerhalb davon. Prüfe bei einer Uhr den Minutenüberlauf und den Übergang zu Mitternacht. Prüfe bei einem Setter, der ungültige Eingaben ablehnt oder ignoriert, ob sie die versprochene Wirkung auf den alten Zustand haben. Schreibe das erwartete Ergebnis zuerst auf, damit eine unerwartete Ausgabe nicht unbemerkt zu deiner neuen Erwartung wird.
Entwickle in kleinen Schritten: zuerst Felder und Konstruktor, dann eine lesbare Beschreibung, dann jeweils eine Operation und ihre Prüfungen. Speichere gewöhnliche öffentliche Klassen in passenden Dateien, kompiliere sie zusammen und starte die Klasse, die main enthält. Trennst du die Operationen des Modells von der Bildschirmausgabe, kann dieselbe Klasse verschiedenen Programmen dienen.
Wichtige Erkenntnisse
- Mache Daten, die gespeichert bleiben müssen, zu privaten Feldern und erlaubte Aktionen zu Methoden.
- Schreibe die Zustandsregeln auf, bevor du Konstruktoren und Änderungen implementierst.
- Verwende final für feste Werte einzelner Objekte und vermeide unnötige Setter.
- Teste Rückgabewerte und resultierenden Zustand, auch bei wiederholten Aufrufen und getrennten Objekten.
- Prüfe neben gewöhnlichen Werten auch Bereichsgrenzen und ungültige Eingaben.
- Entwickle und teste jeweils einen nützlichen Teil.
- Trenne das Verhalten des Modells vom Hauptprogramm, das Ergebnisse ausgibt.
Java-Klassen und Objekte: Beispiele
1Von einer Klasse zu zwei Haustieren
10 Schritte
- 1EinführungEin Typ für die Dinge in deinem Programm
Ein Haustierprogramm muss sich Name, Tierart und Alter zusammen merken. Eine Klasse gibt dieser Gruppe einen Typ und bietet Methoden an, die sie verwenden. Jedes Objekt ist ein Haustier, das aus dieser Definition erzeugt wird.
Verfolge den Unterschied zwischen der Definition und den Haustieren, die sie hervorbringt. Verfolge dann zwei Haustierobjekte und sieh, warum eine zusätzliche Variable nicht immer ein zusätzliches Objekt bedeutet.
- 2AnalogieEin Plan und seine Instanzen
Stell dir eine Klasse wie einen Bauplan und ihre Objekte wie danach gebaute Häuser vor. Der Plan beschreibt, was jedes Haus enthält, ist aber selbst kein Haus.
Eine
Pet-Definition kann viele Haustiere beschreiben. Jedes hat Felder für dieselben Arten von Daten, aber eines kann eine drei Jahre alte Katze und ein anderes ein fünf Jahre alter Hund sein. Die Definition der Felder erstellt den Plan; die Objekterzeugung liefert konkrete Instanzen. - 3BeispielBestimme Zustand und Verhalten
Die Felder
name,speciesundageeines Haustiers speichern seinen Zustand. Für Luna sind ihre Werte"Luna","cat"und3.Die Methode
celebrateBirthday()stellt Verhalten bereit. Ihr Rumpf kannthis.age++;ausführen und damit das Alter des empfangenden Haustiers ändern. Die Methode ist Teil der Klassendefinition; der Wert, den sie ändert, gehört zu einem bestimmten Haustier. - 4HerausforderungErzeugt eine Definition ein Haustier?
Ein Programmierer schreibt eine Klasse
Petmit drei Feldern und einer Geburtstagsmethode. Es wurde kein Ausdruck zur Objekterzeugung ausgeführt. Wie viele Haustierobjekte hat diese Definition erzeugt?---reveal---
Keines. Die Klasse definiert einen Typ sowie dessen möglichen Zustand und Verhalten. Objekte entstehen, wenn das Programm sie erzeugt, zum Beispiel mit
new Pet(...). Drei Felddeklarationen beschreiben drei Daten pro Haustier, nicht drei Haustiere. - 5BeispielErzeuge das erste Objekt
Lies
Pet luna = new Pet("Luna", "cat", 3);in Teilen.Pet lunadeklariert eine Variable.new Pet(...)erzeugt ein Haustier und führt den Konstruktor mit den drei angegebenen Argumenten aus.Der Konstruktor speichert die Anfangswerte. Die von
newerzeugte Referenz wirdlunazugewiesen und gibt späterem Code damit eine Möglichkeit, auf dieses Objekt zuzugreifen. Die DeklarationPet luna;allein würde kein Objekt für einen Aufruf bereitstellen. - 6ErklärungRufe eine Methode auf ihrem Empfängerobjekt auf
In
luna.celebrateBirthday()bezeichnet der Ausdruck vor dem Punkt das Empfängerobjekt. Die Geburtstagsmethode wird für Luna ausgeführt und erhöht ihr Alter von 3 auf 4.int age = luna.getAge();ruft dann das aktuelle Alter zur weiteren Verwendung ab.Pet.celebrateBirthday()kann kein Haustier auswählen: Dies ist eine Instanzmethode und ein Klassenname liefert kein bestimmtes Objekt. - 7BeispielErzeuge ein zweites Haustier
Führe nun
Pet max = new Pet("Max", "dog", 5);aus. Ein zweiternew-Ausdruck erzeugt ein zweites Objekt mit eigenen Feldern.Nach einem weiteren
luna.celebrateBirthday()ist Luna 5 und Max immer noch 5. Ihr gleiches Alter macht sie nicht zu demselben Objekt. Ein späterer Geburtstag bei Luna ändert nur Lunas Alter, weil das Alter von Max in den Feldern eines anderen Objekts steht. - 8KernaussageEine Zuweisung kann ein Objekt gemeinsam nutzbar machen
Pet other = luna;kopiert die Referenz auf Luna. Es ruft keinen Konstruktor auf und kopiert ihre Felder nicht in ein anderes Haustier.Die Namen
otherundlunaverweisen nun auf dasselbe Objekt. Ein Aufruf vonother.celebrateBirthday()ändert das Alter dieses Haustiers von 5 auf 6 und auchluna.getAge()zeigt 6. Zähle Objekterzeugungen getrennt von Variablendeklarationen. - 9HerausforderungVerfolge eine gemeinsame Referenz und ein separates Haustier
Zwei separate Haustiere beginnen mit dem Alter 1 für
aund 8 fürb. Der Code führtPet c = a; c.celebrateBirthday(); b.celebrateBirthday();aus. Welches Alter meldenaundb?---reveal---
Sie melden 2 und 9.
cnutzt das Objekt vonagemeinsam, sodass sein Aufruf das Alter dieses Haustiers erhöht. Der Aufruf überberhöht das Alter des separaten Haustiers. Die Klasse stellt beiden dasselbe Geburtstagsverhalten bereit, aber jeder Aufruf hat ein eigenes Empfängerobjekt. - 10ZusammenfassungLies Erzeugung und Aufrufe bewusst
Halte drei Rollen auseinander: Die Klasse definiert den Typ, jedes Objekt speichert seinen eigenen Instanzzustand und eine Referenzvariable verweist auf ein Objekt.
Markiere beim Lesen von Code jedes
new, gleiche seine Argumente mit dem Konstruktor ab und bestimme das Empfängerobjekt, bevor du einen Methodenaufruf verfolgst. Eine einfache Referenzzuweisung ergänzt einen Zugriffsweg auf ein vorhandenes Objekt. Ein zweitesnewerzeugt ein separates Objekt mit eigenem Zustand.
2Erzeuge den ersten Zustand korrekt
14 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungGib jedem neuen Objekt brauchbare Daten
- 2ErklärungWähle dauerhaften Zustand
- 3ErklärungUnterscheide Standardwerte von brauchbaren Werten
- 4HerausforderungWähle ein Feld oder eine lokale Variable
- 5ErklärungLies den Konstruktorkopf
- 6BeispielÜbertrage Parameter in Felder
- 7TippEine zulässige Zuweisung, die nichts speichert
- 8HerausforderungFinde das unveränderte Feld
- 9VerbindungErzeuge auch interne Objekte
- 10BeispielBiete einen sinnvollen Weg mit Standardwerten an
- 11ErklärungVerfolge ein Objekt durch die Kette
- 12HerausforderungUntersuche einen irreführenden new-Ausdruck
- 13HerausforderungKombiniere ein festes Feld mit einer Überladung
- 14ZusammenfassungPrüfe jeden Weg zum fertigen Objekt
3Verfolge Zustandsänderungen und gemeinsam genutzten Zustand
13 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungVerfolge, was jeder Aufruf ändert
- 2ErklärungGib dem Aufrufer einen Wert zurück
- 3BeispielSkaliere die gespeicherten Abmessungen
- 4HerausforderungPrüfe das zurückgegebene Ergebnis nach einer Änderung
- 5BeispielSchreite über Mitternacht hinaus
- 6TippKorrigiere ein Zurücksetzen, das den Zustand nicht ändert
- 7KernaussageBerücksichtige jede reguläre Rückkehr
- 8ErklärungUnterscheide zwei Zählerstände von einem gemeinsamen Zähler
- 9BeispielStelle in statischem Code ein Objekt bereit
- 10VerbindungErkenne eine Konstante
- 11HerausforderungVerfolge die beiden Arten von Zustand
- 12HerausforderungFinde heraus, warum sich getrennte Objekte gemeinsam ändern
- 13ZusammenfassungOrdne jede Operation dem zu, wozu sie gehört
4Schütze jeden Zugriffsweg auf den Zustand
14 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungMache die Klasse für ihre Regeln verantwortlich
- 2ErklärungVerberge das Feld hinter einer Operation
- 3TippWähle den restriktivsten sinnvollen Zugriff
- 4HerausforderungBestimme, wo privater Zugriff funktioniert
- 5BeispielGib Informationen mit einem Getter zurück
- 6VerbindungBiete Aktionen an, die zum Objekt passen
- 7HerausforderungLesen erlauben, ohne Ersetzen zuzulassen
- 8ErklärungWähle eine Regel für ungültige Eingaben
- 9BeispielVerfolge die Begrenzung an der Obergrenze
- 10BeispielPrüfe den ersten Zustand
- 11HerausforderungVerfolge das Ignorieren statt des Begrenzens
- 12KernaussageEine private Liste kann dennoch nach außen gelangen
- 13HerausforderungSchließe beide ungeprüften Wege
- 14ZusammenfassungPrüfe die öffentliche Schnittstelle
5Entwickle und teste eine nützliche Klasse
12 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungSetze eine Anforderung in sichtbares Verhalten um
- 2ErklärungVerwende den genauen Methodenkopf für die Beschreibung
- 3BeispielGib den aktuellen Zustand als Text zurück
- 4TippFormatiere eine Uhr, ohne sie zu ändern
- 5HerausforderungKorrigiere die automatische Ausgabe
- 6BeispielWähle Felder anhand der Anforderung
- 7BeispielLege den Anfangsstatus fest
- 8BeispielImplementiere die Regel vor der Änderung
- 9HerausforderungSage eine Folge vor ihrer Ausführung voraus
- 10VerbindungTeste die Fälle, die ein einfacher Durchlauf übersieht
- 11HerausforderungUnterscheide Auskunft von Änderung
- 12ZusammenfassungEntwickle mit einer überprüfbaren öffentlichen Zusicherung
Java-Klassen und Objekte: Übungsaufgaben
Set 1: Erzeuge Objekte und lies ihren Zustand10 Übungsfragen
Frage 1 von 10
Parcel, um mehrere Pakete zu erzeugen. Wie heißt ein Paket, das während der Programmausführung erzeugt wird?Antwort zeigenAntwort ausblenden
Parcel. Die Klassendefinition beschreibt die Daten und Operationen für alle diese Objekte; sie ist kein anderer Name für ein bestimmtes Paket. Eine Methode ist eine Operation, die das Objekt ausführen kann.Frage 2 von 10
Bell-Objekt ist in bell gespeichert und ring() ist eine Instanzmethode. Wie soll das Programm diese Glocke läuten?Antwort zeigenAntwort ausblenden
ring() auf dem Objekt auf, auf das bell verweist. Die Variable vor dem Punkt liefert das Empfängerobjekt, das eine Instanzmethode braucht. Bell.ring() nennt dagegen die Klasse und kann daher diese Glocke nicht für einen Instanzaufruf auswählen.Frage 3 von 10
private int attempts; ohne Initialisierer oder Zuweisung im Konstruktor. Welchen Wert speichert ein neues Objekt in diesem Feld?Antwort zeigenAntwort ausblenden
int-Instanzfeld erhält vor einer ausdrücklichen Initialisierung den Standardwert 0. Die Regel, dass einer nicht initialisierten lokalen Variable vor dem Lesen ein Wert zugewiesen werden muss, gilt nicht für Felder. null ist der Standardwert für Felder von Klassentypen, nicht für ein int-Feld.Frage 4 von 10
new Badge("Visitor") gibt getText() den Wert null zurück. Das Feld lautet private String text;, der Getter gibt es zurück und der Konstruktorrumpf lautet text = text;. Wie sollte der Konstruktor seinen Parameter text speichern?Antwort zeigenAntwort ausblenden
this.text = text; schreibt die Eingabe in das aktuelle Objekt. Die umgekehrte Zuweisung liest dagegen das noch auf null stehende Feld in den Parameter, und eine Zuweisung des Feldes an sich selbst lässt es ebenfalls ungesetzt.Frage 5 von 10
Ticket() { this(4); } und Ticket(int zones) { this.zones = zones + 1; }. Welchen Wert speichert new Ticket() in zones?Antwort zeigenAntwort ausblenden
Frage 6 von 10
MinuteClock speichert Stunden von 0–23 und Minuten von 0–59. advance() fügt eine Minute hinzu und behandelt den Stundenüberlauf sowie den Übergang zu Mitternacht. isMidnight() prüft auf 00:00 Uhr. Führe ausgehend von 23:57 Uhr advance(); advance(); advance(); if (isMidnight()) { advance(); } advance(); aus. Wie lautet die endgültige Uhrzeit?Antwort zeigenAntwort ausblenden
Frage 7 von 10
Lamp muss sich ihren eigenen Ein- oder Ausschaltzustand merken. Wie sollte dieser Zustand deklariert werden?Antwort zeigenAntwort ausblenden
Frage 8 von 10
Bin wird ohne Zugriffsmodifikator als int capacity; geschrieben. Welchen Zugriff hat es?Antwort zeigenAntwort ausblenden
private.Frage 9 von 10
Session hat private boolean active;. Aufrufer müssen den Wert prüfen können, dürfen ihn aber nur über start() und stop() ändern. Welcher Accessor passt zu diesem Entwurf?Antwort zeigenAntwort ausblenden
Frage 10 von 10
public String toString() { return "Ready"; }. Welcher Text entsteht durch System.out.println("State: " + object);?Antwort zeigenAntwort ausblenden
"Ready" zu "State: Ready" verbunden werden. Ein eigenes parameterloses toString() ersetzt die eingebaute Beschreibung mit Klasse und Hashcode; Java stellt diesen eingebauten Text nicht zusätzlich voran.Set 2: Prüfe Initialisierung und kontrollierte Änderungen10 Übungsfragen
Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.
Frage 1 von 10
Locker hat ein Feld occupied und eine Methode release(). Welcher Teil dieser Klasse beschreibt Verhalten?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 2 von 10
Panel speichert Breite und Höhe. Seine Breite kann sich ändern und getArea() muss immer die aktuelle Fläche melden. Wie sollte die Fläche dargestellt werden?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 3 von 10
public Sensor(int channel). Ein Aufrufer verwendet new Sensor(). Warum wird das nicht kompiliert?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 4 von 10
private int reading = 9;. Seine Methode lautet public void reset() { int reading = 0; }. Was meldet ein Getter, der this.reading zurückgibt, nach reset()?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 5 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 6 von 10
minutesSinceMidnight, während ihre Getter weiterhin dieselben Werte zurückgeben. Warum können Aufrufer ihre vorhandenen Getter-Aufrufe beibehalten?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 7 von 10
setValue(25) und anschließend setValue(8) jeweils gespeichert?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 8 von 10
Group darf höchstens vier Namen enthalten. addName setzt die Begrenzung durch, aber Group(ArrayList<String> names) speichert die übergebene Liste direkt und getNames() gibt die interne Liste zurück. Welcher Entwurf schützt die Begrenzung vor Änderungen durch Aufrufer?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 9 von 10
toString() verwenden?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 10 von 10
use() gibt false zurück, wenn er bereits benutzt wurde; andernfalls setzt die Methode das Flag und gibt true zurück. reset() setzt das Flag zurück. toString() meldet es nur. Was geben die use-Aufrufe in use(); println(pass); use(); reset(); use(); use(); zurück?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Set 3: Verfolge den Objektzustand durch längere Folgen10 Übungsfragen
Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.
Frage 1 von 10
Marker hat einen Namen und setName. Führe Marker a = new Marker("One"); Marker b = a; a = new Marker("Two"); b.setName("Three"); aus. Was geben a.getName() und b.getName() zurück?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 2 von 10
private int level = 6;. Ihr Konstruktor lautet public Beacon(int level) { this.level = level + 2; }. Was speichert new Beacon(3)?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 3 von 10
public Tag(String text). Welcher zusätzliche Konstruktor stellt eine eigenständige Überladung dar?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 4 von 10
scale(k) multipliziert beide Abmessungen mit k; area() gibt ihr Produkt zurück. Führe scale(2); double saved = area(); scale(0.5); aus. Was sind saved und die endgültige Rückgabe von area()?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 5 von 10
Dial hat das Instanzfeld value und das statische Feld created, beide anfangs 0. Jeder Konstruktor erhöht created einmal; turn() erhöht nur den Wert seines Empfängerobjekts. Führe Dial a = new Dial(); Dial b = a; b.turn(); b = new Dial(); b.turn(); b.turn(); aus. Was sind a.value, b.value und created?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 6 von 10
private String label; gespeicherte Beschriftung melden. Welche Implementierung liefert diesen Wert an den Aufrufer?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 7 von 10
setLevel auf den Bereich von 0 bis einschließlich 10 begrenzt. Der Konstruktor ruft diesen Setter auf und raise() ruft setLevel(this.level + 1) auf. Führe ausgehend von new Level(13) die Aufrufe raise(); setLevel(-2); raise(); raise(); aus. Was ist der endgültige Wert?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 8 von 10
String.format(java.util.Locale.US, "%02d:%02d", 4, 7) innerhalb einer Uhrbeschreibung zurück?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 9 von 10
Seat muss eine zweite Reservierung bis zur Stornierung ablehnen. Ein Test prüft bisher nur den ersten erfolgreichen Aufruf von reserve(). Welcher nächste Aufruf prüft die Ablehnungsregel am besten?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 10 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Set 4: Untersuche Entwürfe und kombiniere die Regeln10 Übungsfragen
Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.
Frage 1 von 10
Pump dies darstellen?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 2 von 10
Token hat einen numerischen Wert und addOne() erhöht ihn. Führe Token a = new Token(2); Token b = a; b.addOne(); Token c = new Token(7); a = c; a.addOne(); b.addOne(); aus. Welche Werte melden a, b und c?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 3 von 10
add(int amount) deklariert derzeit int total = amount; innerhalb der Methode. Welche Änderung der Speicherung ermöglicht das Aufsummieren?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 4 von 10
Ein Bucket deklariert private int capacity = 2; und eine leere Instanzliste items. Sein Konstruktor lautet Bucket(int capacity) { capacity = capacity; }. add gibt false zurück, ohne etwas hinzuzufügen, wenn die Listengröße mindestens der gespeicherten Kapazität entspricht; andernfalls fügt die Methode das Element hinzu und gibt true zurück. snapshot() gibt new ArrayList<>(this.items) zurück.
Führe Bucket b = new Bucket(4); b.add("A"); ArrayList<String> view = b.snapshot(); view.add("B"); aus. Rufe dann b.add("C") und b.add("D") auf. Was geben diese beiden letzten Aufrufe zurück und wie viele Elemente enthält b am Ende?
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 5 von 10
Route hat das final-Feld code und das statische Feld created, das anfangs 0 ist. Ihre Konstruktoren lauten Route() { this("local"); created++; } und Route(String code) { this.code = code; created++; }. Nach je einem Aufruf beider Konstruktoren soll created zwei Objekte zählen. Welche Korrektur ergibt die richtige Anzahl?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 6 von 10
advance() fügt eine Minute hinzu und behandelt den Übergang nach 24 Stunden. getHour() liest die aktuelle Stunde. Führe int h = getHour(); advance(); if (getHour() != h) { advance(); } advance(); aus. Was sind h und die endgültige Uhrzeit?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 7 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 8 von 10
scale. Sein Getter gibt this.width * this.height zurück und es gibt kein Feld für die Fläche. Warum ist dies ein gültiger Getter-Entwurf?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 9 von 10
Album ist auf drei Bildunterschriften begrenzt. Sein Konstruktor lehnt mehr als drei Bildunterschriften ab und speichert new ArrayList<>(captions). getCaptions() gibt eine weitere Kopie zurück; addCaption lehnt bei erreichter Grenze weitere Einträge ab. Ein Aufrufer übergibt zwei Bildunterschriften, fügt der ursprünglichen Liste eine dritte hinzu, ergänzt eine Bildunterschrift im Ergebnis des Getters und ruft dann einmal addCaption auf. Wie viele Bildunterschriften enthält das Album?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 10 von 10
Door muss open() ablehnen, wenn sie abgeschlossen ist. Eine neue Tür ist anfangs geschlossen und nicht abgeschlossen. lock() gelingt nur bei geschlossener Tür; unlock() schließt sie auf. Welche Testfolge prüft die Ablehnung im abgeschlossenen Zustand und anschließend ein erfolgreiches Öffnen nach dem Aufschließen?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Hängst du bei deiner eigenen Aufgabe fest? Hausaufgabenhilfe
Schlüsselbegriffe
A
Aktionsmethode
lend() prüft den aktuellen Ausleihstatus, lehnt eine erneute Ausleihe ab und meldet, ob die Ausleihe gelungen ist.Argument
new Clock(9, 5) liefern die Argumente 9 und 5 die Stunde und die Minute für die neue Uhr.B
Begrenzung auf den Wertebereich
D
Defensive Kopie
new ArrayList<>(this.tracks) erstellte defensive Kopie hält Listenänderungen von außen von der internen Liste fern.E
Empfängerobjekt
book.lend() ist das Empfängerobjekt das Buch, auf das book verweist und dessen Ausleih-Flag die Methode prüft.Erwartetes Ergebnis
lend() bei einem ausgeliehenen Buch ist false bei unverändertem Ausleih-Flag.F
final-Feld
G
Geheimnisprinzip
getHours() für vorhandene Aufrufer gleichbleibend zu halten.Getter
get beginnt.getArea() kann Breite mal Höhe berechnen, selbst wenn die Klasse kein separates Feld für die Fläche speichert.Grenzwerttest
Gültigkeitsbereich
if-Blocks deklarierten lokalen Variable endet mit diesem Block, sodass späterer Code ihren Namen nicht verwenden kann.I
IllegalArgumentException
IllegalArgumentException ist eine Ausnahme, die eine Methode oder ein Konstruktor werfen kann, um ein Argument abzulehnen, das gegen die Eingaberegeln verstößt.IllegalArgumentException geworfen, gibt die Konstruktion kein ungültiges Objekt zurück.Initialisierung
new ArrayList<>(), damit anschließend Einträge hinzugefügt werden können.Inkrementelle Entwicklung
Instanz
Pet, wenn jedes separat erzeugt wird.Instanzmethode
scale ist eine Instanzmethode: a.scale(2) ändert das Rechteck a, während ein separates Rechteck unverändert bleibt.Instanzvariable
age jedes Haustiers behält ihren eigenen Wert, obwohl die Deklaration nur einmal in Pet steht.Invariante
K
Kapselung
Pet verbindet ein privates Altersfeld mit prüfenden Methoden, die ungültige Änderungen ablehnen.Klasse
Pet hält Name, Tierart und Alter mit Operationen wie celebrateBirthday() zusammen.Klassenentwurf
Konstante
static final-Feld bereitgestellt wird, oft öffentlich ist und in UPPER_SNAKE_CASE benannt wird.MAX_CELSIUS liefert der Klasse und ihren Aufrufern dieselbe obere Temperaturgrenze.Konstruktor
Pet-Konstruktor kopiert den übergebenen Namen, die Tierart und das Alter in die Felder des neuen Haustiers.Konstruktorverkettung
this(...)-Aufruf ein anderer Konstruktor derselben Klasse auf demselben Objekt ausgeführt.this(name, species, 0) die Initialisierung mit einem Standardalter wiederverwenden.L
Lebensdauer
Lokale Variable
total innerhalb einer Berechnungsmethode speichert ein vorübergehendes Ergebnis, das sich das Objekt nicht merken muss.N
new
new beginnt einen Ausdruck zur Objekterzeugung, der einen Konstruktor ausführt und eine Referenz auf das Objekt liefert.new Pet("Luna", "cat", 3) erzeugt ein Haustier und liefert drei Argumente für seinen Anfangszustand.null
null bedeutet, dass eine Referenz auf kein Objekt verweist; Felder von Klassentypen erhalten ihn, sofern sie nicht anders initialisiert werden.add auf einem Listenfeld, das noch null enthält, schlägt fehl, weil kein Listenobjekt erzeugt wurde.O
Objekt
new-Ausdrücken erzeugt werden, können unterschiedlich alt sein und dabei einer Klassendefinition folgen.Ö
Öffentliche Schnittstelle
lend(), giveBack() und isLentOut() anbieten, während sein Ausleih-Flag privat bleibt.P
Paketzugriff
int count; erlaubt einer anderen Klasse im selben unbenannten Paket, das Feld zu lesen oder zu ändern.Parameter
age in einem Haustierkonstruktor erhält 3, wenn der Aufruf an dieser Position 3 übergibt.Parameterloser Konstruktor
Clock() kann mit this(0, 0) delegieren, um die Uhr bei Mitternacht zu starten.private
private beschränkt den Zugriff auf ein Mitglied auf Code innerhalb seiner Klasse, sodass separate Aufrufer auf oberster Ebene die öffentliche Schnittstelle verwenden müssen.pet.age = -1; in einer separaten Klasse Main fehlschlagen, sodass ein kontrollierter Methodenaufruf nötig ist.protected
protected erlaubt Zugriff innerhalb desselben Pakets und eingeschränkten Zugriff aus Unterklassencode außerhalb dieses Pakets.public
public erlaubt den Zugriff auf ein Mitglied überall dort, wo der deklarierende Typ zugänglich ist, sodass Aufrufer die angebotene Operation verwenden können.lend() erlaubt Main, eine Ausleihe anzufordern, während das private Statusfeld des Buches verborgen bleibt.Punktnotation
luna.getAge() teilt Java mit, dass das über luna erreichte Haustier sein Alter melden soll.R
Referenz
Pet other = luna; enthalten die beiden Variablen Referenzen auf dasselbe Haustier, sodass beide darauf zugreifen können.Rekursiver Konstruktoraufruf
Box() und Box(int) gegenseitig aneinander delegieren.Rückgabewert
r.area() kann in einem Ausdruck verdoppelt werden; die bloße Ausgabe der Fläche liefert keinen Wert.S
Schreibgeschützte Eigenschaft
getTitle() haben, aber keine Operation, um den Titel zu ersetzen.Setter
setAge(int age) kann negative Eingaben ignorieren, sodass das gespeicherte Alter im erlaubten Bereich bleibt.Standardkonstruktor
new Pet() eine ausdrückliche Überladung benötigt.Standardwert
false; einer lokalen boolean-Variable muss vor dem Lesen ein Wert zugewiesen werden.Statische Methode
main kann Counter c erzeugen und dann über diese Referenz c.increment() aufrufen.Statischer Kontext
pet.getAge() stellt dagegen ein bestimmtes Objekt bereit.Statisches Feld
created kann jedes Mal erhöht werden, wenn der Konstruktor eine neue Instanz der Klasse initialisiert.String.format
String.format baut Text aus einem Formatstring und Werten auf und unterstützt feste Breiten und Dezimalstellen.String.format mit java.util.Locale.US und "%.2f" formatiert 7,5 unabhängig vom Standardgebietsschema als "7.50".T
this
this verweist innerhalb von Instanzcode auf das aktuelle Objekt und unterscheidet dessen Felder von gleichnamigen Parametern.this.name = name; die neue Eingabe in das Feld dieses bestimmten Haustiers.toString
toString() gibt die Textdarstellung eines Objekts zurück, sodass Ausgabe und String-Konkatenation lesbare Details zeigen können.toString-Methode einer Uhr gibt "09:05" zurück, sodass die Ausgabe der Uhr eine nützliche Zeit statt Text mit Klasse und Hashcode zeigt.Ü
Überladen von Konstruktoren
V
Validierung
Verhalten
lend() und giveBack() an, damit Aufrufer seinen Ausleihstatus verwalten können.Verschattung
this.age = age; nützlich: Links steht das Feld und rechts die Eingabe.void
void deklariert, dass eine Methode keinen Rückgabewert liefert, selbst wenn sie den Zustand ändert oder etwas ausgibt.void zurückgeben, weil sie das gespeicherte Alter ihres Empfängerobjekts erhöhen soll.Z
Zugriffsmodifikator
private, protected oder public, das den Zugriff auf eine Deklaration steuert.private hält eine Hilfsmethode innerhalb ihrer Klasse verfügbar, ohne sie anderen Klassen zugänglich zu machen.Zustand
Zustandsprotokoll
#
@Override
@Override weist den Compiler an zu prüfen, ob eine Methodendeklaration eine bereits von einem Obertyp bereitgestellte Methode ersetzt.@Override über tostring() deckt den Fehler in der Groß- und Kleinschreibung auf, statt die automatische Ausgabe unverändert zu lassen.- 1Java-Programme kompilieren und starten
- 2Bedingungen und Schleifen in Java
- 3Java-Methoden und Problemzerlegung
- 4Listen, Arrays und Strings in Java
- 5Java-Klassen und Objekte
- 6Objektreferenzen in Java
- 7HashMaps und Collections in Java
- 8Vererbung in Java
- 9Ausnahmen und Dateien in Java
- 10Modultests in Java
- 11Lambda-Ausdrücke und Streams in Java
Über diese Einheit
Erstelle deine eigenen Lernmaterialien
Mach aus deinen Notizen, Folien und Dokumenten Lernleitfäden, Übungsfragen, Karteikarten und 6 weitere Tools.