Von Nova Trent, Junior Entwicklerin bei Java Fleet Systems Consulting

Schwierigkeit: 🔴 KOPFNUSS
Lesezeit: 40–45 Minuten
Voraussetzungen: Tag 1–9 abgeschlossen


📋 Kursübersicht: Java OOP in 10 Tagen

TagThemaStatus
1OOP-Konzepte & erste Klasse✅ Abgeschlossen
2Attribute & Methoden✅ Abgeschlossen
3Datenkapselung & Sichtbarkeit✅ Abgeschlossen
4Konstruktoren✅ Abgeschlossen
5Konstanten & Static✅ Abgeschlossen 🔴
6Vererbung – Grundlagen✅ Abgeschlossen
7Vererbung – Polymorphie & Abstrakte Klassen✅ Abgeschlossen
8Typumwandlung & instanceof✅ Abgeschlossen
9Interfaces & Enumerationen✅ Abgeschlossen
→ 10Ausnahmebehandlung📍 FINALE! 🔴

Voraussetzung: Tag 1–9 abgeschlossen


🔄 Kurz-Wiederholung: Challenge von Tag 9

Die Aufgabe war: Erstelle ein Zahlungssystem mit Interface und Enum.

Lösung:

public interface Zahlungsmittel {
    boolean bezahlen(double betrag);
    double getGuthaben();
}

public class Kreditkarte implements Zahlungsmittel {
    private double kreditlimit;
    private double genutzt = 0;
    
    public Kreditkarte(double kreditlimit) {
        this.kreditlimit = kreditlimit;
    }
    
    @Override
    public boolean bezahlen(double betrag) {
        if (genutzt + betrag > kreditlimit) {
            System.out.println("❌ Kreditlimit überschritten!");
            return false;
        }
        genutzt += betrag;
        System.out.println("✅ Zahlung: " + betrag + "€ (Limit: " + 
            (kreditlimit - genutzt) + "€ frei)");
        return true;
    }
    
    @Override
    public double getGuthaben() {
        return kreditlimit - genutzt;
    }
}

public class PayPal implements Zahlungsmittel {
    private String email;
    private double guthaben;
    
    public PayPal(String email, double guthaben) {
        this.email = email;
        this.guthaben = guthaben;
    }
    
    @Override
    public boolean bezahlen(double betrag) {
        if (betrag > guthaben) {
            System.out.println("❌ Nicht genug Guthaben!");
            return false;
        }
        guthaben -= betrag;
        System.out.println("✅ PayPal-Zahlung: " + betrag + "€");
        return true;
    }
    
    @Override
    public double getGuthaben() { return guthaben; }
}

public enum Waehrung {
    EUR("€"), USD("$"), GBP("£"), CHF("CHF");
    
    private final String symbol;
    
    Waehrung(String symbol) { this.symbol = symbol; }
    public String getSymbol() { return symbol; }
}

Du bist bereit für die finale KOPFNUSS! 💪


⚡ Das Wichtigste in 30 Sekunden

Das Problem: Fehler passieren. Datei nicht gefunden, Division durch Null, Server nicht erreichbar. Ohne Behandlung stürzt dein Programm ab.

Die Lösung: Exceptions! Java’s Mechanismus für kontrollierte Fehlerbehandlung.

Heute lernst du:

  • try-catch-finally — Fehler abfangen und behandeln
  • ✅ Checked vs. Unchecked Exceptions
  • throw vs. throws — Unterschied verstehen
  • ✅ Eigene Exceptions erstellen
  • ✅ Best Practices für Exception-Handling

Zeit-Investment: 40–45 Minuten


👋 Nova: „Exceptions haben mir den Job gerettet!“

Hey! 👋

Nova hier.

Real talk: Mein erstes Produktionsproblem war eine NullPointerException um 3 Uhr nachts. Der Server crashte, Kunden konnten nicht bestellen, mein Handy klingelte.

Seit dem Tag behandle ich Exceptions IMMER ordentlich. Nicht weil’s Spaß macht, sondern weil ich gelernt habe: Unbehandelte Exceptions = Schlaflose Nächte.

Los geht’s — das ist wichtig! 🚀


🟢 GRUNDLAGEN

Was ist eine Exception?

Eine Exception (Ausnahme) ist ein Ereignis, das den normalen Programmablauf unterbricht.

int[] zahlen = {1, 2, 3};
System.out.println(zahlen[99]);  // 💥 ArrayIndexOutOfBoundsException!

Ohne Behandlung: Programm stürzt ab!

try-catch — Fehler abfangen

try {
    // Code, der fehlschlagen könnte
    int[] zahlen = {1, 2, 3};
    System.out.println(zahlen[99]);
} catch (ArrayIndexOutOfBoundsException e) {
    // Was tun, wenn der Fehler auftritt
    System.out.println("Index existiert nicht!");
}

System.out.println("Programm läuft weiter!");  // ✅ Wird ausgeführt!

try-catch-finally

finally wird IMMER ausgeführt — ob Fehler oder nicht!

FileReader reader = null;
try {
    reader = new FileReader("datei.txt");
    // Datei lesen...
} catch (FileNotFoundException e) {
    System.out.println("Datei nicht gefunden!");
} finally {
    // Aufräumen — IMMER!
    if (reader != null) {
        try { reader.close(); } catch (IOException e) { }
    }
    System.out.println("Finally wird immer ausgeführt!");
}
Ausnahmebehandlung

Abbildung 1: Der Ablauf von try-catch-finally — finally läuft IMMER!

Mehrere catch-Blöcke

Du kannst verschiedene Exceptions unterschiedlich behandeln:

try {
    int zahl = Integer.parseInt(input);
    int ergebnis = 100 / zahl;
} catch (NumberFormatException e) {
    System.out.println("Keine gültige Zahl!");
} catch (ArithmeticException e) {
    System.out.println("Division durch Null!");
} catch (Exception e) {
    // Fängt alles andere
    System.out.println("Unbekannter Fehler: " + e.getMessage());
}

Wichtig: Spezifische Exceptions zuerst, allgemeine zuletzt!

Multi-Catch (Java 7+)

try {
    // ...
} catch (NumberFormatException | ArithmeticException e) {
    System.out.println("Eingabefehler: " + e.getMessage());
}

Exception-Informationen

Jede Exception hat nützliche Methoden:

try {
    int x = 1 / 0;
} catch (ArithmeticException e) {
    System.out.println("Message: " + e.getMessage());
    System.out.println("Klasse: " + e.getClass().getName());
    e.printStackTrace();  // Kompletter Stack-Trace
}

Checked vs. Unchecked Exceptions

Abbildung 2: Checked Exceptions MÜSSEN behandelt werden, Unchecked nicht.

Checked Exceptions:

  • Erben von Exception (aber nicht von RuntimeException)
  • MÜSSEN behandelt werden (try-catch ODER throws)
  • Beispiele: IOException, SQLException, FileNotFoundException

Unchecked Exceptions:

  • Erben von RuntimeException
  • Müssen NICHT behandelt werden (aber können)
  • Beispiele: NullPointerException, IllegalArgumentException
// Checked — Compiler zwingt zur Behandlung:
public void lesenDatei() throws IOException {
    Files.readString(Path.of("test.txt"));
}

// Unchecked — Keine Behandlung nötig:
public void berechne(int x) {
    if (x < 0) {
        throw new IllegalArgumentException("x muss positiv sein!");
    }
}

throw — Exception werfen

Mit throw wirfst du selbst eine Exception:

public void setAlter(int alter) {
    if (alter < 0) {
        throw new IllegalArgumentException("Alter darf nicht negativ sein!");
    }
    if (alter > 150) {
        throw new IllegalArgumentException("Unrealistisches Alter!");
    }
    this.alter = alter;
}

throws — Exception deklarieren

Mit throws sagst du: „Diese Methode KANN diese Exception werfen.“

public String lesenDatei(String pfad) throws IOException {
    return Files.readString(Path.of(pfad));
}

Der Aufrufer muss dann entscheiden:

  1. Selbst behandeln (try-catch)
  2. Weitergeben (throws)
throw vs throws

Abbildung 3: throw wirft, throws deklariert!

try-with-resources (Java 7+)

Für Ressourcen, die geschlossen werden müssen (Dateien, Verbindungen):

// ALT: Manuelles Schließen in finally
BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("test.txt"));
    // ...
} finally {
    if (reader != null) reader.close();
}

// NEU: try-with-resources — schließt automatisch!
try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"))) {
    String zeile = reader.readLine();
    System.out.println(zeile);
}  // reader wird automatisch geschlossen!

Funktioniert mit allem, was AutoCloseable implementiert.


🟡 PROFESSIONALS

Schon Exception-Erfahrung aus C#, C++, PHP? Hier sind die Java-Besonderheiten.

Für C#-Umsteiger

// C# — Keine Checked Exceptions!
public void ReadFile(string path) {
    File.ReadAllText(path);  // Keine throws-Deklaration nötig
}
// Java — Checked Exceptions!
public void lesenDatei(String pfad) throws IOException {
    Files.readString(Path.of(pfad));  // IOException ist checked!
}

Für C++-Umsteiger

// C++ — Exception-Spezifikation (deprecated)
void foo() throw(std::runtime_error);
// Java — throws ist Standard
void foo() throws RuntimeException;

Exception-Chaining

try {
    // Original-Exception
} catch (SQLException e) {
    // Neue Exception mit Original als Ursache
    throw new DataAccessException("Datenbankfehler", e);
}

Eigene Exceptions erstellen

// Checked Exception
public class KontoException extends Exception {
    public KontoException(String message) {
        super(message);
    }
    
    public KontoException(String message, Throwable cause) {
        super(message, cause);
    }
}

// Unchecked Exception
public class UngueltigerBetragException extends RuntimeException {
    private final double betrag;
    
    public UngueltigerBetragException(double betrag) {
        super("Ungültiger Betrag: " + betrag);
        this.betrag = betrag;
    }
    
    public double getBetrag() { return betrag; }
}

🔵 BONUS

Für Wissbegierige: Best Practices, Anti-Patterns, Logging.

Best Practices

1. Fange nur, was du behandeln kannst:

// ❌ SCHLECHT: Leerer catch-Block
try {
    riskanterCode();
} catch (Exception e) {
    // Nichts tun — BÖSE!
}

// ✅ GUT: Sinnvoll behandeln
try {
    riskanterCode();
} catch (IOException e) {
    logger.error("Fehler beim Lesen", e);
    throw new ServiceException("Daten nicht verfügbar", e);
}

2. Spezifische Exceptions fangen:

// ❌ SCHLECHT: Zu allgemein
catch (Exception e)

// ✅ GUT: Spezifisch
catch (FileNotFoundException e)

3. Exceptions nicht für Kontrollfluss:

// ❌ SCHLECHT: Exception als if-else
try {
    return liste.get(index);
} catch (IndexOutOfBoundsException e) {
    return defaultWert;
}

// ✅ GUT: Prüfen vor Zugriff
if (index < liste.size()) {
    return liste.get(index);
} else {
    return defaultWert;
}

4. Ressourcen immer schließen:

// ✅ try-with-resources
try (Connection conn = dataSource.getConnection()) {
    // ...
}

Anti-Patterns

Pokemon Exception Handling:

// ❌ "Gotta catch 'em all!"
try {
    // ...
} catch (Exception e) {
    // Ignorieren
}

Log and Throw:

// ❌ Doppelt gemoppelt
catch (Exception e) {
    logger.error("Fehler", e);
    throw e;  // Wird nochmal geloggt!
}

Logging statt printStackTrace()

// ❌ SCHLECHT: In Produktion
e.printStackTrace();

// ✅ GUT: Mit Logger
private static final Logger logger = LoggerFactory.getLogger(MyClass.class);

catch (Exception e) {
    logger.error("Fehler bei Verarbeitung", e);
}

💬 Real Talk

Tom fragt, Nova erklärt — Die Fragen, die sich jeder stellt.

Java Fleet Serverraum, 22:00 Uhr. (Warum auch nicht.)


Tom: Nova, checked vs. unchecked — ich check’s nicht. Wann was?

Nova: Einfache Regel: Checked für Dinge außerhalb deiner Kontrolle (Datei, Netzwerk, Datenbank). Unchecked für Programmierfehler (null, falscher Index).

Tom: Und warum muss ich checked behandeln?

Nova: Weil der Compiler dich zwingt, über Fehler nachzudenken. Eine Datei KANN nicht existieren. Das ist kein Bug, das ist Realität.

Tom: Okay… und wenn ich einfach throws Exception überall hinschreibe?

Nova: zuckt zusammen Dann hasst dich jeder, der deinen Code benutzt. Sei spezifisch! throws IOException sagt was. throws Exception sagt nichts.

Tom: Was ist mit finally? Wann brauch ich das?

Nova: Für Aufräumarbeiten. Aber ehrlich? Seit Java 7 nutze ich try-with-resources. Das macht’s automatisch.

Tom: Eine letzte Frage: Leere catch-Blöcke?

Nova: starrt Nein. Niemals. NIEMALS. Das ist wie Rauchmelder abschalten, weil sie nerven.


✅ Checkpoint: Hast du es verstanden?

Quiz

Frage 1: Was ist der Unterschied zwischen throw und throws?

Frage 2: Wird der finally-Block ausgeführt, wenn im try-Block ein return steht?

Frage 3: Ist NullPointerException checked oder unchecked?

Frage 4: Dieser Code:

try {
    throw new IOException("Fehler");
} catch (Exception e) {
    System.out.println("A");
} catch (IOException e) {
    System.out.println("B");
}

Kompiliert das? Warum (nicht)?

Frage 5: Was ist das Problem mit diesem Code?

try {
    riskanteOperation();
} catch (Exception e) {
    // TODO: Später behandeln
}

Abschluss-Challenge: Bankkonto mit Exceptions

Aufgabe: Erstelle ein robustes Bankkonto-System:

  1. Eigene Exception KontoException (checked):
    • Mit Nachricht und optionaler Ursache
  2. Eigene Exception UngueltigerBetragException (unchecked):
    • Speichert den ungültigen Betrag
  3. Klasse Bankkonto:
    • Attribute: kontonummer, inhaber, kontostand
    • Methode einzahlen(double betrag) — wirft UngueltigerBetragException bei negativem Betrag
    • Methode abheben(double betrag) — wirft KontoException bei zu wenig Guthaben
    • Methode ueberweisen(Bankkonto ziel, double betrag) — kombiniert beide
  4. Main-Methode:
    • Teste alle Fehlerfälle
    • Zeige saubere Fehlerbehandlung

Das ist deine Abschlussarbeit! 🎓


❓ FAQ — Häufig gestellte Fragen

Frage 1: Soll ich RuntimeException oder Exception erweitern?

RuntimeException für Programmierfehler (sollten durch besseren Code vermieden werden). Exception für erwartbare Fehler (Datei nicht gefunden, Netzwerkfehler).


Frage 2: Was passiert bei Exception im finally-Block?

Die ursprüngliche Exception geht verloren! Sehr gefährlich. Vermeide Exceptions in finally.


Frage 3: Kann ich mehrere Exceptions in throws deklarieren?

Ja! public void foo() throws IOException, SQLException


Frage 4: Was ist ein Stack-Trace?

Die Aufrufkette bis zum Fehler. Zeigt genau, wo das Problem ist. Immer lesen, von unten nach oben!


Frage 5: Warum gibt es checked Exceptions?

Java’s Design-Entscheidung: Zwingt Entwickler, über Fehler nachzudenken. Kontrovers, aber hat Gründe.


Frage 6: Wie behandle ich Exceptions in Streams/Lambdas?

Kompliziert! Checked Exceptions in Lambdas sind umständlich. Entweder wrappen oder Sneaky Throws (fortgeschritten).


Frage 7: Bernd sagt, er fängt nie Exceptions. Wie überlebt er?

lacht Bernd übertreibt. Er meint: Er fängt sie an der RICHTIGEN Stelle — nicht überall, sondern strategisch. In Service-Methoden, an API-Grenzen. Nicht in jeder Zeile.

Bernd hat auch 40 Jahre Erfahrung. Er weiß, wo Fehler auftreten können. Du und ich? Wir brauchen die Compiler-Hilfe noch. 😄


📚 Quiz-Lösungen

Frage 1: throw vs. throws?

Antwort:

  • throw: Wirft eine Exception (im Methodenkörper)
  • throws: Deklariert, dass eine Methode eine Exception werfen KANN (in der Signatur)
public void foo() throws IOException {  // throws = Deklaration
    throw new IOException("Fehler");    // throw = Werfen
}

Frage 2: finally bei return?

Antwort:

Ja! finally wird IMMER ausgeführt, auch bei return, break, oder Exception.

try {
    return "A";
} finally {
    System.out.println("Läuft!");  // Wird ausgegeben!
}

Frage 3: NullPointerException checked oder unchecked?

Antwort:

Unchecked! Es erbt von RuntimeException. Muss nicht behandelt werden (sollte durch null-Checks vermieden werden).


Frage 4: Kompiliert das?

} catch (Exception e) {
    System.out.println("A");
} catch (IOException e) {  // ❌ Unerreichbar!
    System.out.println("B");
}

Antwort:

Nein! Compilerfehler: „exception IOException has already been caught“

IOException ist Subtyp von Exception. Der erste catch fängt alles, der zweite ist unerreichbar.

Lösung: Spezifische zuerst!


Frage 5: Problem mit leerem catch?

Antwort:

Der Fehler wird verschluckt. Das Programm läuft weiter, aber du weißt nicht, dass etwas schiefging. Debugging-Albtraum!

Mindestens loggen:

catch (Exception e) {
    logger.error("Fehler", e);
}

🎉 KURS ABGESCHLOSSEN! 🎉

DU HAST ES GESCHAFFT! 🚀🎓

In 10 Tagen hast du gelernt:

TagThema
1OOP-Konzepte & erste Klasse
2Attribute & Methoden
3Datenkapselung & Sichtbarkeit
4Konstruktoren
5Konstanten & Static 🔴
6Vererbung – Grundlagen
7Polymorphie & Abstrakte Klassen
8Typumwandlung & instanceof
9Interfaces & Enumerationen
10Ausnahmebehandlung 🔴

Du beherrschst jetzt:

  • Klassen, Objekte, Konstruktoren
  • Kapselung und Sichtbarkeit
  • Vererbung und Polymorphie
  • Abstrakte Klassen und Interfaces
  • Enums und Typumwandlung
  • Exception-Handling

Das ist die Grundlage für ALLES in Java! 💪


🔮 Wie geht’s weiter?

Empfohlene nächste Schritte:

  1. Collections Framework — Listen, Maps, Sets
  2. Streams & Lambdas — Funktionale Programmierung
  3. Generics — Typsichere Collections
  4. Multithreading — Parallele Programmierung
  5. Spring Boot — Enterprise-Entwicklung

Schau auf java-developer.online für weitere Kurse! 🎯


📦 Downloads

Starter-Projekt für Tag 10:

⬇️ Tag10_Ausnahmebehandlung.zip — Komplettes Maven-Projekt

Inhalt:

Tag10_Ausnahmebehandlung/
├── pom.xml
├── README.md
└── src/main/java/
    └── de/javafleet/oop/
        ├── Main.java
        ├── model/
        │   ├── Bankkonto.java
        │   ├── Zahlungsmittel.java    (Lösung Tag 9)
        │   ├── Kreditkarte.java
        │   └── PayPal.java
        ├── exceptions/
        │   ├── KontoException.java
        │   └── UngueltigerBetragException.java
        └── enums/
            └── Waehrung.java

Grafiken:


🔧 Troubleshooting

Problem: „unreported exception; must be caught or declared“

error: unreported exception IOException; must be caught or declared to be thrown

Lösung: Checked Exception! Entweder try-catch oder throws hinzufügen.


Problem: „exception has already been caught“

error: exception IOException has already been caught

Lösung: Spezifische Exceptions VOR allgemeine!


Problem: NullPointerException zur Laufzeit

Lösung: Null-Checks! Oder Optional verwenden (fortgeschritten).


🔗 Resources & Links

🟢 Für Einsteiger

RessourceBeschreibungSprache
Oracle — ExceptionsOffizielle Doku🇬🇧
Baeldung — Exception HandlingPraktische Beispiele🇬🇧

🟡 Für Fortgeschrittene

RessourceBeschreibungSprache
Effective Java — Item 69-77Exception Best Practices🇬🇧

👋 Bis zum nächsten Kurs!

Du hast den OOP-Kurs gemeistert! 🎓

Ich bin stolz auf dich. Wirklich. OOP ist nicht einfach, und du hast durchgehalten.

Jetzt raus in die Praxis! Bau was. Mach Fehler. Lern daraus. Das ist der einzige Weg.

Wir sehen uns! 🚀


Nova Trent
Junior Entwicklerin bei Java Fleet Systems Consulting
„Exceptions sind nicht der Feind — ignorierte Exceptions sind es!“ 💎


Praxis-Challenge 🔴 KOPFNUSS

🎯 Bestellsystem mit Fehlerbehandlung

Schwierigkeit: 🔴 Fortgeschritten (KOPFNUSS)
Geschätzte Zeit: 90–120 Minuten
Voraussetzungen: Tag 1–10 abgeschlossen


Szenario

Du entwickelst ein Bestellsystem für einen Online-Shop. In der echten Welt geht ständig etwas schief: Produkte sind nicht auf Lager, Zahlungen schlagen fehl, Adressen sind ungültig. Dein System muss mit all diesen Fehlern robust umgehen.

Diese Aufgabe testet alle Aspekte der Ausnahmebehandlung:

  • Eigene Exceptions (checked & unchecked)
  • try-catch-finally
  • Multi-catch
  • throws deklarieren
  • Exception Chaining
  • try-with-resources
  • Best Practices

Teil 1: Eigene Exception-Klassen

1.1 BestellException (Checked Exception)

Die Basis-Exception für alle Bestellfehler.

public class BestellException extends Exception {
    // Konstruktoren für: message, message + cause
}

1.2 ProduktNichtVerfuegbarException extends BestellException

Wird geworfen, wenn ein Produkt nicht auf Lager ist.

ElementDetails
Zusätzliche AttributeproduktId (int), angefordert (int), verfuegbar (int)
KonstruktorAlle Parameter + generiert passende Message
GetterFür alle Attribute

Generierte Message: "Produkt #123: 5 angefordert, aber nur 2 verfügbar"


1.3 ZahlungFehlgeschlagenException extends BestellException

Wird geworfen, wenn eine Zahlung abgelehnt wird.

ElementDetails
Zusätzliche Attributegrund (String), betrag (double)
KonstruktorAlle Parameter
GetterFür alle Attribute

1.4 UngueltigeAdresseException extends BestellException

Wird geworfen, wenn die Lieferadresse ungültig ist.

ElementDetails
Zusätzliche Attributefeld (String, z.B. „PLZ“, „Straße“)
KonstruktorParameter: feld, message
GetterFür feld

1.5 SystemFehlerException (Unchecked Exception)

Für unerwartete technische Fehler (Datenbank, Netzwerk…).

public class SystemFehlerException extends RuntimeException {
    // Konstruktoren für: message, message + cause
}

Teil 2: Domänenklassen

2.1 Klasse Produkt

ElementDetails
Attributeid (int), name (String), preis (double), lagerbestand (int)
KonstruktorAlle Parameter
Methodevoid reduziereBestand(int menge) throws ProduktNichtVerfuegbarException
Getter/SetterFür alle Attribute

reduziereBestand():

  • Wirft ProduktNichtVerfuegbarException wenn menge > lagerbestand
  • Sonst: reduziert Bestand

2.2 Klasse Adresse

ElementDetails
Attributestrasse (String), hausnummer (String), plz (String), ort (String)
KonstruktorAlle Parameter
Methodevoid validieren() throws UngueltigeAdresseException
GetterFür alle Attribute

validieren():

  • Wirft Exception wenn strasse leer oder null → Feld: „Straße“
  • Wirft Exception wenn plz nicht genau 5 Ziffern → Feld: „PLZ“
  • Wirft Exception wenn ort leer oder null → Feld: „Ort“

2.3 Klasse BestellPosition

ElementDetails
Attributeprodukt (Produkt), menge (int)
KonstruktorBeide Parameter
Methodedouble getGesamtpreis()
GetterFür beide Attribute

2.4 Klasse Bestellung

ElementDetails
AttributebestellNummer (String), positionen (List<BestellPosition>), lieferadresse (Adresse), status (String)
KonstruktorParameter: bestellNummer, lieferadresse. Liste startet leer, Status = „NEU“
Methodevoid addPosition(BestellPosition pos)
Methodedouble getGesamtsumme()
GetterFür alle Attribute
SetterFür status

Teil 3: Service-Klassen

3.1 Klasse ZahlungsService

Simuliert die Zahlungsabwicklung.

public class ZahlungsService {
    
    /**
     * Verarbeitet eine Zahlung.
     * @param betrag Der zu zahlende Betrag
     * @param zahlungsart "KREDITKARTE", "PAYPAL", "RECHNUNG"
     * @throws ZahlungFehlgeschlagenException wenn Zahlung abgelehnt
     */
    public void verarbeiteZahlung(double betrag, String zahlungsart) 
            throws ZahlungFehlgeschlagenException {
        
        // Simulierte Regeln:
        // - KREDITKARTE: Abgelehnt wenn betrag > 1000
        // - PAYPAL: Immer erfolgreich
        // - RECHNUNG: Abgelehnt wenn betrag > 500
        // - Unbekannte Zahlungsart: Abgelehnt
    }
}

3.2 Klasse BestellLogger

Implementiert AutoCloseable für try-with-resources!

public class BestellLogger implements AutoCloseable {
    
    private String logDatei;
    private boolean istOffen;
    
    public BestellLogger(String logDatei) {
        this.logDatei = logDatei;
        this.istOffen = true;
        System.out.println("[LOG] Logger geöffnet: " + logDatei);
    }
    
    public void log(String nachricht) {
        if (!istOffen) {
            throw new IllegalStateException("Logger ist bereits geschlossen!");
        }
        System.out.println("[LOG] " + nachricht);
    }
    
    @Override
    public void close() {
        // Gibt Meldung aus und setzt istOffen = false
    }
}

3.3 Klasse BestellService

Die Hauptklasse — hier kommt alles zusammen!

public class BestellService {
    
    private ZahlungsService zahlungsService = new ZahlungsService();
    
    /**
     * Verarbeitet eine komplette Bestellung.
     * 
     * Diese Methode demonstriert:
     * - try-with-resources (Logger)
     * - Multi-catch
     * - Exception chaining
     * - finally-Block
     * 
     * @param bestellung Die zu verarbeitende Bestellung
     * @param zahlungsart Die gewählte Zahlungsart
     * @throws BestellException wenn die Bestellung nicht verarbeitet werden kann
     */
    public void verarbeiteBestellung(Bestellung bestellung, String zahlungsart) 
            throws BestellException {
        
        // 1. try-with-resources für Logger
        // 2. Adresse validieren
        // 3. Lagerbestand für alle Positionen prüfen und reduzieren
        // 4. Zahlung durchführen
        // 5. Status setzen
        // 6. Bei Fehlern: Exception mit Kontext werfen (Chaining)
        // 7. finally: Immer "Verarbeitung beendet" loggen (außerhalb try-with-resources)
    }
    
    /**
     * Verarbeitet mehrere Bestellungen und sammelt Fehler.
     * Stoppt NICHT bei der ersten fehlerhaften Bestellung!
     * 
     * @param bestellungen Liste der Bestellungen
     * @param zahlungsart Die Zahlungsart für alle
     * @return Map mit BestellNummer → Ergebnis ("OK" oder Fehlermeldung)
     */
    public Map<String, String> verarbeiteAlle(List<Bestellung> bestellungen, String zahlungsart) {
        // Verarbeitet jede Bestellung einzeln
        // Fängt Exceptions und speichert sie in der Map
        // Gibt am Ende die Map zurück
    }
}

Teil 4: Implementierungs-Details

verarbeiteBestellung() — Schritt für Schritt

public void verarbeiteBestellung(Bestellung bestellung, String zahlungsart) 
        throws BestellException {
    
    System.out.println("=== Verarbeite Bestellung " + bestellung.getBestellNummer() + " ===");
    
    try (BestellLogger logger = new BestellLogger("bestellung_" + bestellung.getBestellNummer() + ".log")) {
        
        // 1. Adresse validieren
        logger.log("Prüfe Lieferadresse...");
        bestellung.getLieferadresse().validieren();
        logger.log("Adresse OK");
        
        // 2. Lagerbestand prüfen und reduzieren
        logger.log("Prüfe Lagerbestand...");
        for (BestellPosition pos : bestellung.getPositionen()) {
            pos.getProdukt().reduziereBestand(pos.getMenge());
            logger.log("Produkt " + pos.getProdukt().getName() + ": " + pos.getMenge() + " reserviert");
        }
        
        // 3. Zahlung durchführen
        logger.log("Führe Zahlung durch...");
        double summe = bestellung.getGesamtsumme();
        zahlungsService.verarbeiteZahlung(summe, zahlungsart);
        logger.log("Zahlung erfolgreich: " + summe + " EUR");
        
        // 4. Status setzen
        bestellung.setStatus("ABGESCHLOSSEN");
        logger.log("Bestellung abgeschlossen!");
        
    } catch (UngueltigeAdresseException e) {
        // Exception Chaining: Ursprüngliche Exception als cause
        bestellung.setStatus("FEHLER_ADRESSE");
        throw new BestellException("Bestellung " + bestellung.getBestellNummer() + 
            " fehlgeschlagen: Ungültige Adresse (" + e.getFeld() + ")", e);
        
    } catch (ProduktNichtVerfuegbarException e) {
        bestellung.setStatus("FEHLER_LAGER");
        throw new BestellException("Bestellung " + bestellung.getBestellNummer() + 
            " fehlgeschlagen: " + e.getMessage(), e);
        
    } catch (ZahlungFehlgeschlagenException e) {
        bestellung.setStatus("FEHLER_ZAHLUNG");
        throw new BestellException("Bestellung " + bestellung.getBestellNummer() + 
            " fehlgeschlagen: Zahlung abgelehnt (" + e.getGrund() + ")", e);
        
    } finally {
        System.out.println("=== Verarbeitung " + bestellung.getBestellNummer() + " beendet ===\n");
    }
}

Test-Code für Main

public class Main {
    
    public static void main(String[] args) {
        
        BestellService service = new BestellService();
        
        // === Produkte anlegen ===
        Produkt laptop = new Produkt(1, "Laptop", 999.99, 5);
        Produkt maus = new Produkt(2, "Maus", 29.99, 100);
        Produkt tastatur = new Produkt(3, "Tastatur", 79.99, 3);
        Produkt monitor = new Produkt(4, "Monitor", 399.99, 0);  // Nicht auf Lager!
        
        // === Test 1: Erfolgreiche Bestellung ===
        System.out.println("########## TEST 1: Erfolgreiche Bestellung ##########\n");
        try {
            Adresse adresse1 = new Adresse("Hauptstraße", "42", "12345", "Berlin");
            Bestellung bestellung1 = new Bestellung("B-001", adresse1);
            bestellung1.addPosition(new BestellPosition(maus, 2));
            bestellung1.addPosition(new BestellPosition(tastatur, 1));
            
            service.verarbeiteBestellung(bestellung1, "PAYPAL");
            System.out.println("Status: " + bestellung1.getStatus());
            
        } catch (BestellException e) {
            System.out.println("FEHLER: " + e.getMessage());
        }
        
        // === Test 2: Ungültige Adresse ===
        System.out.println("\n########## TEST 2: Ungültige Adresse ##########\n");
        try {
            Adresse adresse2 = new Adresse("", "1", "123", "Hamburg");  // Straße leer, PLZ zu kurz
            Bestellung bestellung2 = new Bestellung("B-002", adresse2);
            bestellung2.addPosition(new BestellPosition(maus, 1));
            
            service.verarbeiteBestellung(bestellung2, "PAYPAL");
            
        } catch (BestellException e) {
            System.out.println("FEHLER: " + e.getMessage());
            if (e.getCause() != null) {
                System.out.println("Ursache: " + e.getCause().getClass().getSimpleName());
            }
        }
        
        // === Test 3: Produkt nicht auf Lager ===
        System.out.println("\n########## TEST 3: Produkt nicht auf Lager ##########\n");
        try {
            Adresse adresse3 = new Adresse("Nebenstraße", "7", "54321", "München");
            Bestellung bestellung3 = new Bestellung("B-003", adresse3);
            bestellung3.addPosition(new BestellPosition(monitor, 1));  // Lagerbestand = 0!
            
            service.verarbeiteBestellung(bestellung3, "PAYPAL");
            
        } catch (BestellException e) {
            System.out.println("FEHLER: " + e.getMessage());
        }
        
        // === Test 4: Zahlung abgelehnt ===
        System.out.println("\n########## TEST 4: Zahlung abgelehnt ##########\n");
        try {
            Adresse adresse4 = new Adresse("Parkweg", "99", "11111", "Köln");
            Bestellung bestellung4 = new Bestellung("B-004", adresse4);
            bestellung4.addPosition(new BestellPosition(laptop, 2));  // 2x 999.99 = 1999.98 > 1000!
            
            service.verarbeiteBestellung(bestellung4, "KREDITKARTE");  // Limit 1000!
            
        } catch (BestellException e) {
            System.out.println("FEHLER: " + e.getMessage());
        }
        
        // === Test 5: Mehrere Bestellungen auf einmal ===
        System.out.println("\n########## TEST 5: Batch-Verarbeitung ##########\n");
        
        List<Bestellung> batch = new ArrayList<>();
        
        // Gültige Bestellung
        Adresse a1 = new Adresse("Straße A", "1", "11111", "Stadt A");
        Bestellung b1 = new Bestellung("BATCH-001", a1);
        b1.addPosition(new BestellPosition(maus, 1));
        batch.add(b1);
        
        // Ungültige PLZ
        Adresse a2 = new Adresse("Straße B", "2", "ABC", "Stadt B");
        Bestellung b2 = new Bestellung("BATCH-002", a2);
        b2.addPosition(new BestellPosition(maus, 1));
        batch.add(b2);
        
        // Gültige Bestellung
        Adresse a3 = new Adresse("Straße C", "3", "33333", "Stadt C");
        Bestellung b3 = new Bestellung("BATCH-003", a3);
        b3.addPosition(new BestellPosition(tastatur, 1));
        batch.add(b3);
        
        Map<String, String> ergebnisse = service.verarbeiteAlle(batch, "PAYPAL");
        
        System.out.println("=== Batch-Ergebnisse ===");
        for (Map.Entry<String, String> entry : ergebnisse.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
        
        // === Test 6: Multi-catch Demo ===
        System.out.println("\n########## TEST 6: Multi-catch Demo ##########\n");
        demonstriereMultiCatch();
    }
    
    /**
     * Demonstriert Multi-catch (Java 7+).
     */
    private static void demonstriereMultiCatch() {
        try {
            // Simuliere verschiedene mögliche Fehler
            int zufallsFehler = (int) (Math.random() * 3);
            
            if (zufallsFehler == 0) {
                throw new IllegalArgumentException("Ungültiges Argument");
            } else if (zufallsFehler == 1) {
                throw new IllegalStateException("Ungültiger Zustand");
            } else {
                System.out.println("Kein Fehler aufgetreten");
            }
            
        } catch (IllegalArgumentException | IllegalStateException e) {
            // Multi-catch: Beide Exceptions gleich behandeln
            System.out.println("Gefangen mit Multi-catch: " + e.getClass().getSimpleName());
            System.out.println("Message: " + e.getMessage());
        }
    }
}

Erwartete Ausgabe (ungefähr)

########## TEST 1: Erfolgreiche Bestellung ##########

=== Verarbeite Bestellung B-001 ===
[LOG] Logger geöffnet: bestellung_B-001.log
[LOG] Prüfe Lieferadresse...
[LOG] Adresse OK
[LOG] Prüfe Lagerbestand...
[LOG] Produkt Maus: 2 reserviert
[LOG] Produkt Tastatur: 1 reserviert
[LOG] Führe Zahlung durch...
[LOG] Zahlung erfolgreich: 139.97 EUR
[LOG] Bestellung abgeschlossen!
[LOG] Logger geschlossen: bestellung_B-001.log
=== Verarbeitung B-001 beendet ===

Status: ABGESCHLOSSEN

########## TEST 2: Ungültige Adresse ##########

=== Verarbeite Bestellung B-002 ===
[LOG] Logger geöffnet: bestellung_B-002.log
[LOG] Prüfe Lieferadresse...
[LOG] Logger geschlossen: bestellung_B-002.log
=== Verarbeitung B-002 beendet ===

FEHLER: Bestellung B-002 fehlgeschlagen: Ungültige Adresse (Straße)
Ursache: UngueltigeAdresseException

########## TEST 3: Produkt nicht auf Lager ##########

=== Verarbeite Bestellung B-003 ===
[LOG] Logger geöffnet: bestellung_B-003.log
[LOG] Prüfe Lieferadresse...
[LOG] Adresse OK
[LOG] Prüfe Lagerbestand...
[LOG] Logger geschlossen: bestellung_B-003.log
=== Verarbeitung B-003 beendet ===

FEHLER: Bestellung B-003 fehlgeschlagen: Produkt #4: 1 angefordert, aber nur 0 verfügbar

########## TEST 4: Zahlung abgelehnt ##########

=== Verarbeite Bestellung B-004 ===
[LOG] Logger geöffnet: bestellung_B-004.log
[LOG] Prüfe Lieferadresse...
[LOG] Adresse OK
[LOG] Prüfe Lagerbestand...
[LOG] Produkt Laptop: 2 reserviert
[LOG] Führe Zahlung durch...
[LOG] Logger geschlossen: bestellung_B-004.log
=== Verarbeitung B-004 beendet ===

FEHLER: Bestellung B-004 fehlgeschlagen: Zahlung abgelehnt (Kreditkarten-Limit überschritten)

########## TEST 5: Batch-Verarbeitung ##########

[... Logs für jede Bestellung ...]

=== Batch-Ergebnisse ===
BATCH-001: OK
BATCH-002: Ungültige Adresse (PLZ)
BATCH-003: OK

########## TEST 6: Multi-catch Demo ##########

Gefangen mit Multi-catch: IllegalArgumentException
Message: Ungültiges Argument

Checkliste: Das wird getestet

Eigene Exceptions

  • [ ] BestellException extends Exception (checked)
  • [ ] SystemFehlerException extends RuntimeException (unchecked)
  • [ ] Spezialisierte Exceptions mit zusätzlichen Attributen
  • [ ] Konstruktor mit message und cause

try-catch-finally

  • [ ] try-Block für gefährlichen Code
  • [ ] Mehrere catch-Blöcke für verschiedene Exceptions
  • [ ] finally-Block wird IMMER ausgeführt

try-with-resources

  • [ ] BestellLogger implements AutoCloseable
  • [ ] close() wird automatisch aufgerufen
  • [ ] Funktioniert auch bei Exceptions

Multi-catch

  • [ ] catch (ExceptionA | ExceptionB e) Syntax
  • [ ] Gleiche Behandlung für mehrere Exception-Typen

Exception Chaining

  • [ ] throw new BestellException("...", originalException)
  • [ ] getCause() gibt ursprüngliche Exception zurück

throws Deklaration

  • [ ] Checked Exceptions in Signatur deklariert
  • [ ] Unchecked Exceptions nicht deklariert

Hinweise

  1. Checked vs. Unchecked:
    • Exception → Checked (muss behandelt oder deklariert werden)
    • RuntimeException → Unchecked (optional)
  2. try-with-resources: Der Logger wird automatisch geschlossen, auch bei Exceptions!
  3. Exception Chaining: Die cause ermöglicht Stack-Trace-Analyse
  4. finally vs. close(): finally läuft NACH close() bei try-with-resources

Projektstruktur

src/main/java/
└── de/javafleet/oop/
    ├── Main.java
    ├── service/
    │   ├── BestellService.java
    │   ├── ZahlungsService.java
    │   └── BestellLogger.java
    ├── model/
    │   ├── Produkt.java
    │   ├── Adresse.java
    │   ├── BestellPosition.java
    │   └── Bestellung.java
    └── exception/
        ├── BestellException.java
        ├── ProduktNichtVerfuegbarException.java
        ├── ZahlungFehlgeschlagenException.java
        ├── UngueltigeAdresseException.java
        └── SystemFehlerException.java

Bonus-Aufgaben

  1. Retry-Mechanismus: Implementiere automatische Wiederholung bei ZahlungFehlgeschlagenException (max. 3 Versuche)
  2. Rollback: Wenn die Zahlung fehlschlägt, mache die Lagerbestand-Reduktion rückgängig
  3. Exception-Report: Erstelle eine Methode, die alle Exceptions einer Batch-Verarbeitung als HTML-Report ausgibt

Lösung: Findest du in Tag10_Ausnahmebehandlung_Loesung.md


© 2025 Java Fleet Systems Consulting

Tags: #Java #OOP #Exceptions #TryCatch #Tutorial #Abschluss

© 2025 Java Fleet Systems Consulting | java-developer.online

Autor

  • Ensign Nova Trent

    24 Jahre alt, frisch von der Universität als Junior Entwicklerin bei Java Fleet Systems Consulting. Nova ist brilliant in Algorithmen und Datenstrukturen, aber neu in der praktischen Java-Enterprise-Entwicklung. Sie brennt darauf, ihre ersten echten Projekte zu bauen und entdeckt dabei die Lücke zwischen Uni-Theorie und Entwickler-Realität. Sie liebt Star Treck das ist der Grund warum alle Sie Ensign Nova nennen und arbeitet daraufhin das sie Ihren ersten Knopf am Kragen bekommt.