Informatik-Test · Abstrakte Klassen, Interfaces, Exceptions
Java Prüfungstrainer
Erst die Theorie Schritt für Schritt, dann Praxisaufgaben wie im Test, am Ende ein Quiz mit Erklärungen. Alles basiert auf euren Folien.
1 · Vererbung wiederholenDie Basis, auf der alles andere aufbaut: extends, super, Überschreiben, Polymorphie.
2 · Abstrakte Klassenabstract, abstrakte Methoden, rein vs. partiell abstrakt.
3 · Interfacesimplements, Konstanten, default-Methoden, Subinterfaces.
4 · Assertions & Exceptionstry/catch/finally, throw/throws, eigene Exceptions, Chaining.
5 · PraxisAusgabe vorhersagen, „Kompiliert das?“, Code schreiben, Lückentexte.
6 · QuizViele Fragen mit sofortiger Erklärung. Falsche lassen sich wiederholen.
Vorschlag für heute Abend
- Theorie von oben nach unten lesen. Die Kästen Merke sind das, was im Test sicher drankommt.
- Bei Exceptions die Ablauf-Simulation ausprobieren: dort versteht man
try/catch/finally am schnellsten.
- Praxis durcharbeiten. Beim Programmieren erst selbst auf Papier schreiben, dann die Lösung aufklappen.
- Quiz machen, danach nur die falschen wiederholen, bis alles sitzt.
- Zum Schluss den Spickzettel einmal durchlesen.
Zur QuelleDie Seite benutzt die Begriffe aus euren Unterlagen (z. B. „Systemausnahmen“, „Benutzerausnahmen“, „partiell abstrakt“, „erweiterte Schnittstelle“). Alle Codebeispiele wurden mit Java 21 kompiliert und ausgeführt, die Lösungen stimmen also.
Dein Stand
Teil 1 · Grundlage
Vererbung kurz wiederholt
Abstrakte Klassen und Interfaces bauen darauf auf. Wenn das hier sitzt, wird der Rest leicht.
extends und die Ist-eine-Art-von-Beziehung
Eine Unterklasse erweitert eine Oberklasse mit extends und erbt deren sichtbare Eigenschaften. Jede Klasse ohne eigene extends-Angabe erbt automatisch von java.lang.Object (deshalb hat jede Klasse toString()). Java kennt keine Mehrfachvererbung: eine Klasse hat genau eine Oberklasse (Grund: Diamantenproblem).
class Auto { String name; }
class Cabrio extends Auto { boolean dachOffen; } // Cabrio ist eine Art von Auto
Konstruktoren und super()
- Konstruktoren werden nicht vererbt. Jede Unterklasse braucht eigene.
- Als allererste Anweisung ruft jeder Konstruktor den Konstruktor der Oberklasse auf. Schreibt man nichts, setzt Java
super(); ein.
- Hat die Oberklasse keinen parameterlosen Konstruktor, muss man
super(args) selbst schreiben, sonst gibt es einen Compilerfehler.
super(...) und this(...) müssen jeweils die erste Anweisung sein, deshalb kann man nicht beide im selben Konstruktor verwenden.
class Car {
public String name;
Car(String name) { this.name = name; }
}
class Cabriolet extends Car {
Cabriolet() { super("Volvo"); } // ohne diese Zeile: Compilerfehler
}
Überladen vs. Überschreiben
| Überladen (overload) | Überschreiben (override) |
| Wo? | in derselben Klasse (oder Unterklasse) | in einer Unterklasse |
| Name | gleich | gleich |
| Parameterliste | verschieden | gleich (gleiche Signatur) |
| Beispiel | add(int) und add(int,int) | toString() in der Unterklasse neu schreiben |
Die Signatur einer Methode ist Name + Parameterliste. Beim Überschreiben ist @Override eine Annotation vor der Methode: der Compiler prüft dann, dass wirklich etwas überschrieben wird.
Referenztyp und Objekttyp
Der Compiler kennt nur den Referenztyp (links vom =). Die JVM kennt zur Laufzeit den Objekttyp (rechts vom new).
Ferrari f = new Testarossa(); // erlaubt: Testarossa ist ein Ferrari (automatisch)
f.name; // ok, name steht in Ferrari
f.hubraum; // Compilerfehler: Ferrari kennt hubraum nicht
Testarossa t = (Testarossa) f; // explizite Umwandlung (Cast) nötig
t.hubraum; // jetzt ok
- Cast nach „oben“ (Unterklasse → Oberklasse) geht automatisch, nach „unten“ braucht man einen Cast.
- Passt der Cast nicht zum echten Objekttyp, kompiliert er zwar, wirft aber zur Laufzeit eine
ClassCastException.
x instanceof Typ prüft zur Laufzeit, ob x ungleich null ist und zu diesem Typ passt. Schützt vor der ClassCastException.
Polymorphie und dynamisches Binden
Welche überschriebene Methode läuft, entscheidet die JVM erst zur Laufzeit anhand des Objekttyps (spätes/dynamisches Binden).
Object o = new Testarossa();
System.out.println(o.toString()); // ruft toString() von Testarossa auf!
AchtungNicht dynamisch gebunden werden private, static und final Methoden. Sie können nicht überschrieben werden. Mit final bei einer Klasse kann man gar keine Unterklassen bilden (z. B. String, Math).
Merke
- Eine Klasse: ein
extends. Konstruktoren erbt man nicht.
super(...) immer als erste Zeile; ohne Angabe wird super() ergänzt.
- Compiler schaut auf den Referenztyp, Laufzeit auf den Objekttyp.
Teil 2
Abstrakte Klassen und Methoden
Eine abstrakte Klasse ist ein unfertiger Bauplan. Sie legt fest, was Unterklassen können müssen, aber nicht immer, wie.
Die Grundidee
Bisher waren alle Klassen konkret: man konnte mit new Objekte erzeugen. Manchmal ist das nicht sinnvoll. Ein allgemeines „Fahrzeug“ gibt es in der Realität nicht, nur Autos, Fahrräder usw. Eine Oberklasse soll dann nur als Modellierungsklasse dienen.
public abstract class Fahrzeug { // Modifier abstract bei der Klasse
protected String name;
public Fahrzeug(String name) { // Konstruktor ist erlaubt
this.name = name;
}
public abstract void fahre(double speed); // abstrakt: nur Kopf, kein Rumpf, endet mit ;
public void stehe() { // normale Methode mit Implementierung
System.out.println(name + " steht.");
}
}
public class Auto extends Fahrzeug {
public Auto(String name) { super(name); }
@Override
public void fahre(double speed) { // Pflicht: abstrakte Methode ausimplementieren
System.out.println(name + " fährt " + speed + " km/h.");
}
}
Regeln auf einen Blick
| Aussage | Stimmt? |
new Fahrzeug("x") bei abstrakter Klasse | Compilerfehler |
Variable vom abstrakten Typ: Fahrzeug f = new Auto("Fiat"); | erlaubt (Polymorphie) |
Abstrakte Methode hat einen Rumpf { } | Compilerfehler |
Klasse hat eine abstrakte Methode, ist aber nicht abstract | Compilerfehler |
| Abstrakte Klasse hat einen Konstruktor | erlaubt, wird per super(...) aufgerufen |
| Abstrakte Klasse hat Objektvariablen | erlaubt (anders als Interface) |
| Abstrakte Klasse ohne eine einzige abstrakte Methode | erlaubt |
| Unterklasse implementiert nicht alle abstrakten Methoden | Fehler, außer sie ist selbst abstract |
Rein abstrakt oder partiell abstrakt
Rein (pure) abstrakt
Enthält ausschließlich abstrakte Methoden. Gibt nur Signaturen vor, wie ein Vertrag.
Partiell abstrakt
Enthält abstrakte und normale Methoden mit Implementierung. Bietet eine Basisimplementierung an.
Merke
- Ist mindestens eine Methode abstrakt, ist die ganze Klasse abstrakt.
- Von abstrakten Klassen gibt es keine Objekte.
- Unterklassen müssen alle abstrakten Methoden ausimplementieren oder selbst abstrakt bleiben.
- UML: Klassenname kursiv oder
{abstract} unter den Namen schreiben.
Hinweis aus den FolienSoll nur verhindert werden, dass ein Objekt der Klasse gebildet wird, geht das auch mit einem private oder protected Konstruktor. Abstrakt ist dann sinnvoll, wenn Unterklassen etwas ausimplementieren müssen.
Polymorphie mit abstrakten Klassen
Fahrzeug[] flotte = { new Auto("Fiat"), new Fahrrad("BMX") };
for (Fahrzeug f : flotte) {
f.fahre(30); // jedes Objekt führt seine eigene fahre() aus
}
Teil 3
Interfaces (Schnittstellen)
Java erlaubt keine Mehrfachvererbung bei Klassen. Damit eine Klasse trotzdem mehrere „Typen“ haben kann, gibt es Interfaces.
Was vs. Wie
In der Objektorientierung versucht man, das Was vom Wie zu trennen. Abstrakte Klasse und Interface beschreiben das Was (leere Methodenrümpfe), die erbende bzw. implementierende Klasse liefert das Wie (ausimplementierte Methoden).
Deklaration und Implementierung
public interface Buyable { // interface statt class
String price(); // automatisch public abstract
}
public interface Paintable {
void paint(Color farbe);
}
// Eine Klasse: genau EIN extends, aber beliebig viele implements (mit Komma)
public class Testarossa extends Ferrari implements Buyable, Paintable {
@Override
public String price() { // public ist Pflicht!
return this.preis + "";
}
@Override
public void paint(Color farbe) { /* ... */ }
}
- Methoden im Interface haben nur den Kopf und schließen mit
; ab. Sie sind automatisch public abstract.
- Die implementierende Klasse muss sie
public schreiben. Weniger Sichtbarkeit ist ein Compilerfehler.
- Es gibt keine Konstruktoren und keine Objektvariablen.
- Variablen sind automatisch
public static final, also Konstanten. Zuweisen ist verboten.
- Namen enden oft auf -ble:
Runnable, Comparable, Buyable.
- In UML steht
«interface» über dem Namen.
Was darf in ein Interface?
| Attribut | Methode |
| Objektbezogen | nicht erlaubt | ja, üblicherweise abstrakt (kann aber auch ausimplementiert sein, siehe default) |
Statisch (static) | ja, als Konstante | ja, immer mit Implementierung |
Subinterfaces
Ein Interface kann ein anderes mit extends erweitern. Eine implementierende Klasse muss dann alle Methoden aus allen Interfaces der Hierarchie ausimplementieren. Oberinterface-Methoden dürfen im Unterinterface überschrieben werden.
public interface CHBuyable extends Buyable {
int MAX_PRICE = 500_000; // = public static final
static boolean isValidPrice(double price) { // statische Methode mit Rumpf
return price >= 0 && price < MAX_PRICE;
}
String price();
}
Default-Methoden: erweiterte Schnittstellen
Ändert man ein Interface im Nachhinein (neue abstrakte Methode), funktionieren alle implementierenden Klassen nicht mehr. Die Lösung ist eine default-Methode mit fertiger Implementierung. Ein Interface mit default-Methode heißt erweiterte Schnittstelle. Klassen dürfen die default-Methode überschreiben, müssen aber nicht.
public interface Paintable {
void paint(Color farbe); // abstrakt: Klasse muss implementieren
default void paintRed() { // default: fertig, darf überschrieben werden
paint(Color.RED);
}
}
Abstrakte Klasse oder Interface?
Abstrakte Klasse, wenn …
- die Unterklasse nur eine Klasse erweitern soll
- Gemeinsamkeiten in eine Oberklasse ausgelagert werden
- sie Objektzustände (Attribute) enthält
- eine Basisimplementierung vorgegeben wird
Interface, wenn …
- die Klasse mehrere Typen haben soll
- Vorschriften für Verhalten weitergegeben werden
- die Implementierung fast ganz in der Unterklasse liegt
Merke
- Klasse:
extends (1×), Interface: implements (beliebig viele, mit Komma). Interface erweitert Interface mit extends.
- Methoden:
public abstract (implizit). Variablen: public static final (implizit).
- Kein Konstruktor, keine Objektvariablen.
- Ein Interface-Typ darf als Variablentyp dienen:
Buyable b = new Testarossa();
Hinweis aus den FolienInterfaces, die nur statische Methoden enthalten, ersetzt man besser durch eine finale Klasse mit privatem Konstruktor.
Teil 4
Assertions und Exceptions
Programme müssen auf Fehler vorbereitet sein und kontrolliert reagieren. Java bietet dafür zwei Mittel: Assertions für die innere Logik und Exceptions für beliebige Randbedingungen.
Assertions
Eine Assertion (Zusicherung) ist ein boolescher Ausdruck, der immer zutreffen muss. Die JVM überwacht sie. Ist sie false, stoppt das Programm mit einem AssertionError, die nachfolgenden Anweisungen laufen nicht mehr.
assert a <= b && b <= c;
assert x >= 0 : "Negatives x"; // Text nach dem Doppelpunkt = Fehlermeldung
Aktivieren
Assertions kosten Rechenzeit und sind standardmäßig aus. Aktivieren ohne Neukompilieren:
java -ea Test // enable assertions
java -da Test // disable (Standard)
java -ea:Klassenname Test // nur in einer Klasse
java -ea:paketname... Test // nur in einem Paket (3 Punkte!)
// IntelliJ: Run → Edit Configurations → VM options: -ea
Wann verwenden?
geeignet
- Bedingungen, die das Programm selbst garantiert
- Rechenergebnisse am Methodenende (Postconditions)
- Schleifeninvarianten
- „Tote Zweige“ in if/switch:
assert false;
nicht geeignet
- Bedingungen, die von äußeren Einflüssen abhängen
- z. B. Parameter am Anfang einer öffentlichen Methode prüfen
- (Das macht man mit Exceptions.)
Exceptions: Grundprinzip
Fehlerinformationen werden in ein spezielles Objekt verpackt, eine Instanz von Exception (oder einer Unterklasse). Ausgelöst wird es automatisch oder mit throw, abgefangen mit catch an einer anderen Stelle.
Vorteile: (1) Fehler können nicht ignoriert werden, es muss Maßnahmen zur Behandlung geben. (2) Code für den normalen Ablauf und für die Fehlerbehandlung ist getrennt.
try – catch – finally
try {
// regulärer Code, der eine Ausnahme auslösen könnte
} catch (ArithmeticException e) { // spezifisch zuerst
// Fehlerbehandlung
} catch (RuntimeException e) { // dann allgemeiner
// Fehlerbehandlung
} finally {
// wird IMMER ausgeführt (Aufräumen, z. B. Datei schließen)
}
- Die catch-Klauseln werden der Reihe nach geprüft. Die erste passende läuft, die restlichen werden übersprungen.
- Eine catch-Klausel für Klasse E fängt auch alle Unterklassen von E. Deshalb: von spezifisch zu allgemein ordnen. Andersrum ist ein Compilerfehler.
- Findet sich keine passende catch-Klausel, geht die Exception nach oben und das Programm bricht ab.
- Termination Model: Java kehrt nach der Exception nie an die Fehlerstelle zurück. (Das Resumption Model, Fortsetzen an der Fehlerstelle, gibt es in Java nicht.)
Ablauf selbst ausprobieren
Was wird geworfen, während risiko() läuft?
Ausgabe:
Die Exception-Hierarchie
Alle Ausnahmen sind Objekte in einer Klassenhierarchie. Ganz oben steht java.lang.Throwable mit Methoden zur Fehleranalyse.
Throwable
Error JVM-Fehler, nicht behandeln
Exception Benutzerausnahmen: müssen behandelt werden
IOException MyException (eigene) …
RuntimeException Systemausnahmen: Behandlung freiwillig
ArithmeticException (Division durch 0)
NullPointerException (Zugriff auf null)
ArrayIndexOutOfBoundsException
ClassCastException
| Art | Auslöser | Muss behandelt werden? |
| Systemausnahmen (runtime exceptions, unchecked) | automatisch: illegale Instruktionen, Bugs im Programm | nein, kann abgefangen werden |
| Benutzerausnahmen (checked exceptions) | explizit vom Programmierer mit throw (oder Bibliothek, z. B. IOException) | ja: fangen oder mit throws weiterreichen. Sonst Compilerfehler. |
| Errors | Probleme der JVM, z. B. zu wenig Speicher | nein, darauf kann man nicht sinnvoll reagieren |
Wird eine Systemausnahme nicht behandelt, steigt sie im Stacktrace nach oben (Methode für Methode zurück bis zu main), bis die JVM stoppt und den Stacktrace ausgibt. Daran findet man die Fehlerstelle.
Nützliche Methoden von Throwable
String getMessage() | Fehlertext |
String toString() | Klassenname + getMessage() |
void printStackTrace() | gibt den Stacktrace aus |
throw und throws
throw | throws |
Anweisung im Methodenrumpf. Löst die Exception aus (throw new MyException();). Unterbricht den Code sofort. | Teil der Methodensignatur. Warnt, dass die Methode eine Exception weitergeben kann (int test(int x) throws IOException). Behandelt wird sie dann beim Aufrufer. |
Zwei Wege, eine Benutzerausnahme zu behandeln: try-catch umrahmt den Fehler und behandelt ihn direkt. throws reicht ihn nach oben weiter. Irgendwo muss er dann behandelt werden.
Eigene Exceptions
Eigene Ausnahmen erben von Exception. Wer eine Fehlermeldung mitgeben will, schreibt einen eigenen Konstruktor, der super(...) aufruft.
class MyException extends Exception {
public MyException() { }
public MyException(final String specialinfo) {
super(specialinfo); // Text landet in getMessage()
}
}
static void pruefe(int alter) throws MyException { // Warnung in der Signatur
if (alter < 0) {
throw new MyException("Falsche Eingabe"); // auslösen
}
}
try {
pruefe(-1);
} catch (MyException e) {
System.out.println(e.getMessage()); // Falsche Eingabe
} finally {
System.out.println("fertig");
}
finally
Der finally-Block läuft immer als Abschluss, egal ob eine Ausnahme auftrat oder nicht. Typisch: im try wird eine Datei geöffnet, im finally wird sie geschlossen. Sogar ein return im try-Block überspringt das finally nicht.
Exception-Chaining
Wenn Methoden Ausnahmen immer nur weiterreichen, werden die throws-Listen lang und unübersichtlich. Beim Chaining fängt man Ausnahmen von untergeordneten Aufrufen, verpackt sie in eine neue Exception und wirft diese weiter. Die ursprüngliche Ursache bleibt als cause erhalten.
try {
// Aufrufe, die E1 oder E2 werfen können
} catch (E1 ex) {
throw new MyException(ex);
} catch (E2 ex) {
throw new MyException(ex);
}
// In der eigenen Exception muss der Konstruktor passen:
MyException(final Throwable cause) {
super(cause);
}
// Alternative mit initCause():
catch (E1 ex) {
MyException m = new MyException();
m.initCause(ex);
throw m;
}
Merke
- Assertion:
assert bedingung : "Text";, Standard aus, Einschalten mit -ea, Fehler = AssertionError.
- catch-Reihenfolge: spezifisch → allgemein. finally läuft immer.
- Benutzerausnahme: fangen oder throws, sonst Compilerfehler. Systemausnahme: freiwillig.
throw löst aus, throws kündigt an.
- Eigene Exception:
extends Exception und super(text).
- Java folgt dem Termination Model.
Teil 5
Praxis
Im Test musst du Code lesen, Fehler erkennen und selbst Code schreiben. Hier alle drei Arten.
A · Was gibt das Programm aus?
Jedes Programm kompiliert. Überlege erst im Kopf, dann wähle.
B · Kompiliert das?
Entscheide, ob der Code ohne Compilerfehler übersetzt wird. Zu jedem Beispiel steht die Begründung dabei.
C · Code schreiben
Schreib die Lösung auf Papier oder in IntelliJ. Klappe sie erst danach auf und hake ab, was du richtig hattest.
D · Lückentexte
Zum Schluss
Spickzettel
Alles Wichtige auf einer Seite. Lies es kurz vor dem Test noch einmal.
Abstrakte Klasse vs. Interface
| Abstrakte Klasse | Interface |
| Schlüsselwort | abstract class, extends | interface, implements |
| Mehrfach? | nur 1 Oberklasse | beliebig viele |
| Objekt erzeugen | nein | nein |
| Konstruktor | ja | nein |
| Objektvariablen | ja | nein, nur Konstanten (public static final) |
| Methoden | abstrakte und normale gemischt | implizit public abstract; zusätzlich default und static mit Rumpf |
| Zweck | gemeinsamen Zustand und Basisimplementierung teilen | Verhalten vorschreiben (Was, nicht Wie) |
Exceptions in einem Bild
try {
... // kann Exception werfen
} catch (Spezifisch e) { // 1. passende catch-Klausel gewinnt
...
} catch (Allgemein e) { // Oberklasse ZULETZT
...
} finally {
... // immer
}
void m() throws MyException { // kündigt an
throw new MyException("Text"); // löst aus
}
assert x > 0 : "Text"; // java -ea → AssertionError
| Muss ich fangen / deklarieren? | |
Exception, IOException, eigene (extends Exception) | Ja (Benutzerausnahme) |
RuntimeException und Unterklassen (ArithmeticException, NullPointerException, …) | Nein (Systemausnahme) |
Error (OutOfMemoryError) | Nein |
Typische Compilerfehler
new auf abstrakte Klasse oder Interface
- Klasse mit abstrakter Methode, aber ohne
abstract
- Abstrakte Methode mit
{ }
- Interface-Methode ohne
public implementiert
- Nicht alle Interface-Methoden implementiert (und Klasse nicht
abstract)
- Interface-Konstante zuweisen
extends A, B (nur eine Oberklasse)
- catch für Oberklasse vor catch für Unterklasse
- Benutzerausnahme weder gefangen noch mit
throws deklariert
catch (IOException e), obwohl der try-Block keine werfen kann
super(...) nicht als erste Anweisung / Oberklasse ohne Standardkonstruktor
- Zugriff auf Unterklassen-Member über Oberklassen-Referenz ohne Cast
Typische Laufzeitfehler
ClassCastException: falscher Cast. Vorher mit instanceof prüfen.
ArithmeticException: ganzzahlig durch 0.
NullPointerException: Methode auf null.
ArrayIndexOutOfBoundsException: Index außerhalb.
AssertionError: Assertion war false (nur mit -ea).