Zum Inhalt
wannacode

Typprüfungen

Kurz gesagt: Eine Typprüfung mit instanceof fragt, ob ein Objekt zu einer bestimmten Klasse oder einer ihrer Unterklassen gehört, und ein Downcast gibt Zugriff auf die Methoden der Unterklasse.

Problem

Nur manche können laden

Eine Firma hat einen Fuhrpark aus normalen und elektrischen Autos. Jeden Abend müssen die elektrischen geladen werden. ElectricCar erbt von Car und fügt charge() hinzu, also stehen alle in einem Array Car[]:

Car[] fleet = {
new Car("Skoda"),
new ElectricCar("Tesla", 35),
new Car("Dacia"),
new ElectricCar("Nissan", 60)
};
for (Car c : fleet) {
c.charge();
}

Das Programm lässt sich nicht kompilieren:

„cannot find symbol: method charge()“.

Warum, weißt du aus der Lektion über Polymorphie: Der Typ der Variable entscheidet, was du aufrufen kannst. c ist ein Car, und Car kann nicht laden.

Ein normales Auto lässt sich wirklich nicht laden. Du musst jedes Auto fragen: „Bist du elektrisch?“

Die Idee

Frag das Objekt, was es ist

for (Car c : fleet) {
if (c instanceof ElectricCar e) {
e.charge();
System.out.println(e.getBrand() + ": geladen auf " + e.getBattery() + "%");
}
}
Ausgabe
Tesla: geladen auf 100%
Nissan: geladen auf 100%

c instanceof ElectricCar e macht zwei Dinge auf einmal:

  • Es prüft, ob das Objekt, auf das c zeigt, ein ElectricCar ist;
  • wenn ja, legt es eine Variable e vom Typ ElectricCar an, die auf dasselbe Objekt zeigt.

Über e kannst du jetzt charge() aufrufen. Die Autos bleiben dieselben. Du schaust das Objekt nur durch einen genaueren Typ an.

Das nennt man Typprüfung. Der Weg vom Typ der Basisklasse zum Typ der Unterklasse heißt Downcast: die Kette hinunter, Object → Car → ElectricCar.

e gibt es nur im if. Außerhalb ist das Objekt wieder nur ein Car.

for (Car c : fleet) {
if (c instanceof ElectricCar e) {
e.charge();
System.out.println(e.getBrand() + ": geladen auf " + e.getBattery() + "%");
}
}
Schritt für Schritt, in Worten
  1. fleet ist ein Array von Car: 2 normale und 2 elektrische Autos.
  2. c zeigt auf Skoda: instanceof sagt „nein“. Der Rumpf des if wird übersprungen.
  3. c zeigt auf Tesla: instanceof sagt „ja“. e ist ein zweiter Pfeil auf dasselbe Auto und lädt es.
  4. c zeigt auf Dacia: instanceof sagt „nein“. Der Rumpf des if wird übersprungen.
  5. c zeigt auf Nissan: instanceof sagt „ja“. e ist ein zweiter Pfeil auf dasselbe Auto und lädt es.
  6. Die Autos ändern sich nicht. Die Prüfung schaut das Objekt nur durch einen genaueren Typ an.

Probier es aus

Ist es ein Auto?

Was gibt das Programm aus?

Car c = new ElectricCar("Tesla", 35);
Car d = new Car("Skoda");
System.out.println(c instanceof Car);
System.out.println(c instanceof ElectricCar);
System.out.println(d instanceof ElectricCar);
Antwort

Die Idee

Andere Formen, denen du begegnen wirst

Das Muster mit Variable (instanceof ElectricCar e) kam mit Java 16. In älterem Code sind Prüfung und Umwandlung getrennt:

Car c = fleet[1];
if (c instanceof ElectricCar) {
ElectricCar e = (ElectricCar) c;
e.charge();
System.out.println(e.getBrand() + ": " + e.getBattery() + "%");
}
Ausgabe
Tesla: 100%

(ElectricCar) c ist eine Typumwandlung (Cast), mit denselben Klammern wie (double) aus dem Modul „Sprachgrundlagen“. Sie sagt dem Compiler: „Vertrau mir, hier ist ein Elektroauto.“ Ist keins da, bricht das Programm mit ClassCastException ab. Deshalb ist ein Cast ohne Prüfung davor ein Risiko.

Genauer, für Neugierige
  • Für null ist die Prüfung immer falsch: null instanceof Car ist false. Nach einem erfolgreichen instanceof hast du also nie null.
  • Haben die beiden Typen nichts gemeinsam (String und Car), lässt sich instanceof nicht kompilieren: Der Compiler sieht, dass die Antwort immer „nein“ ist.
  • Die Prüfung funktioniert auch mit Interfaces: a instanceof Swimmable s fragt, ob die Klasse des Objekts den Vertrag implementiert.

Probier es aus

Finde den Fehler

Das Programm zählt die normalen und die elektrischen Autos. Der Fuhrpark hat von beiden je zwei, aber es gibt aus:

Ausgabe
Normale: 4, elektrische: 0

In welcher Zeile ist der Fehler?

Wähle die Zeile mit dem Fehler.

Die Idee

Eine Prüfung oder eine Methode?

Du willst ausgeben, womit jedes Auto fährt. Eine Typprüfung kann das:

for (Car c : fleet) {
if (c instanceof ElectricCar) {
System.out.println(c.getBrand() + ": Strom");
} else {
System.out.println(c.getBrand() + ": Benzin");
}
}

Aber das ist das Gehalt mit einem if pro Rolle aus der Lektion über Polymorphie. Jedes Auto hat eine Energiequelle, nur eine andere. Das ist eine Aufgabe für eine Methode in Car, überschrieben in ElectricCar:

// in Car
public String energy() {
return "Benzin";
}
// in ElectricCar
@Override
public String energy() {
return "Strom";
}
for (Car c : fleet) {
System.out.println(c.getBrand() + ": " + c.energy());
}
Ausgabe
Skoda: Benzin
Tesla: Strom
Dacia: Benzin
Nissan: Strom

Morgen kommt ein Hybridauto dazu. Mit der Methode schreibst du eine neue Klasse. Mit den Prüfungen suchst du jedes if im Programm.

Die Regel:

  • Jedes Objekt tut es, jedes auf seine Weise: eine Methode in der Basisklasse und Überschreiben.
  • Nur manche Objekte können es (laden, in die Pedale treten): eine Typprüfung.

Probier es aus

Was fehlt?

for (Car c : fleet) {
    if (c  ElectricCar e) {
        e.charge();
    }
}

Kurz gesagt

Die vier Säulen der OOP

Mit dieser Lektion hast du jede Idee aus den Modulen „Klassen und Objekte“ und „Vererbung und Polymorphie“ gesehen. Die objektorientierte Programmierung ruht auf vier davon. Du benutzt sie schon alle:

  • Kapselung. Die Daten des Objekts sind versteckt, und von außen erreichst du sie nur über Methoden, die die Regeln bewachen. Account hält den Kontostand private und bietet nur deposit() und withdraw() an. Kapselung
  • Vererbung. Das Gemeinsame steht an einer Stelle, in der Basisklasse, und die Unterklasse fügt ihr Eigenes hinzu. Manager ist ein Employee mit Bonus. Vererbung
  • Polymorphie. Ein Aufruf, verschiedenes Verhalten je nach Objekt. e.monthlyPay() in einer Schleife berechnet das Gehalt eines Mitarbeiters, eines Managers und einer Praktikantin. Polymorphie
  • Abstraktion. Du sagst, was das Objekt können muss, und jede Klasse entscheidet, wie. Die abstrakte Shape verspricht eine Fläche, das Interface Payable verspricht einen zu zahlenden Betrag. Abstrakte Klassen, Interfaces

Wie die vier in einem Programm zusammenarbeiten, wann jede wehtut und was verwechselt wird, steht in der letzten Lektion des Moduls: OOP-Prinzipien.

Übung

Was ist es wirklich?

Löse die Aufgaben in deinem Editor.

1. Parkplatz. Ein Array SimpleCar[] mit normalen und Familienautos (FamilyCar erbt von SimpleCar). Gib für jedes seinen Typ aus. Achte auf die Reihenfolge der Prüfungen.

Ausgabe
Das ist ein SimpleCar
Das ist ein FamilyCar
Das ist ein SimpleCar

2. Tänze. Ein Array DanceMove[] mit normalen und Breakdance-Moves. Gib bei den Breakdance-Moves auch die Schwierigkeit aus. Dafür brauchst du die Variable aus der Prüfung.

Ausgabe
moonwalk: DanceMove
windmill: BreakDanceMove, Schwierigkeit 8
Salsa: DanceMove

3. Fuhrpark. Nimm Car und ElectricCar aus der Übung in der Lektion „Vererbung“. Leg in ein Array Car[] einen „Skoda“ mit 40 l, einen „Tesla“ mit Effizienz 5, einen „Dacia“ mit 5 l und einen „Nissan“ mit Effizienz 4. Jedes Auto fährt 100 km: die elektrischen mit driveElectric, die übrigen mit drive. Dann gib die Kilometer und den Kraftstoff oder den Akku aus.

Ausgabe
Skoda: 100 km, Kraftstoff 30 l
Tesla: 100 km, Akku 80%
Nicht genug Kraftstoff für 100 km
Dacia: 0 km, Kraftstoff 5 l
Nissan: 100 km, Akku 75%

4. Zoo. Eine abstrakte Klasse Animal mit sound() und Unterklassen Cat, Dog und Fish. Nur Fish implementiert Swimmable mit swim(). Ruf in einer Schleife über die Tiere den Laut jedes Tiers auf, und bei denen, die schwimmen können, auch swim().

Ausgabe
Miau, miau!
Wau, wau!
Der Fisch schweigt.
Der Fisch schwimmt.

Weitere Aufgaben

  • Pedale. In der Übung aus der Lektion „Abstrakte Klassen“ ließ sich garage[1].pedal() nicht kompilieren. Repariere es mit einer Typprüfung: In der Schleife über Vehicle[] fährt jedes Fahrzeug los, und das Fahrrad tritt zusätzlich in die Pedale.
  • Zahlungen nach Art. Aus der Lektion „Interfaces“: Das Array Payable[] enthält Mitarbeiter und Rechnungen. Gib die Summe für Gehälter und die Summe für Rechnungen getrennt aus. Für Ivan, Maria und die zwei Rechnungen: 5500 € Gehälter und 2140 € Rechnungen.

Test

Teste dich

1. c instanceof ElectricCar e ist wahr. Was ist e?
2. c zeigt auf ein ElectricCar. Was ist c instanceof Car?
3. Du prüfst, ob ein Auto ein Car oder ein ElectricCar ist. In welcher Reihenfolge?
4. Das Programm hat viele ifs wie „ist es ein ElectricCar, tu das eine, sonst das andere“. Was sagt dir das?
5. c zeigt auf ein normales Auto. Was passiert bei ElectricCar e = (ElectricCar) c;?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·