CodeGym /Kursy /JAVA 25 SELF /Tworzenie własnych wyjątków

Tworzenie własnych wyjątków

JAVA 25 SELF
Poziom 24 , Lekcja 1
Dostępny

1. Wprowadzenie

W standardowej bibliotece Javy istnieje już wiele wyjątków: NullPointerException, IllegalArgumentException, IOException i inne. Jednak czasem standardowych wyjątków nie wystarcza, aby jasno i czytelnie opisać błąd, który wystąpił w twoim programie.

Przykład z życia:
Piszesz aplikację bankową. Użytkownik próbuje wypłacić więcej pieniędzy, niż ma na koncie. Można rzucić IllegalArgumentException. Jednak znacznie czytelniej będzie użyć własnego wyjątku, na przykład InsufficientFundsException. Dzięki temu w kodzie od razu widać, co się stało.

Własne wyjątki to właściwy sposób dostosowania aplikacji. Pozwalają precyzyjnie obsługiwać problemy, a już sama ich nazwa (jeśli jest logiczna!) od razu mówi, co się stało. Ponadto są samoopisowe: metody z throws MyException w sygnaturze jasno wskazują, jakie błędy mogą wystąpić. A do tego – zawsze można dodać dodatkowe pola (np. saldo, kwotę operacji itd.).

2. Jak stworzyć własny wyjątek?

To bardzo proste: utwórz nową klasę, która dziedziczy po jednej ze standardowych klas wyjątków.

  • Dla sprawdzanych (checked) wyjątków – dziedzicz po Exception.
  • Dla niesprawdzanych (unchecked) – po RuntimeException.

Przykład: wyjątek sprawdzany

public class InvalidCredentialsException extends Exception {
    public InvalidCredentialsException(String message) {
        super(message); // Przekazujemy komunikat do klasy bazowej
    }
}

Teraz możesz rzucać ten wyjątek w swoim kodzie:

if (!login.equals("admin") || !password.equals("1234")) {
    throw new InvalidCredentialsException("Nieprawidłowy login lub hasło");
}

Przykład: wyjątek niesprawdzany

public class NegativeBalanceException extends RuntimeException {
    public NegativeBalanceException(String message) {
        super(message);
    }
}

Kiedy używać checked, a kiedy unchecked?

  • Checked – gdy błąd jest spodziewany i można go obsłużyć (np. błąd walidacji, brak pliku, niepoprawne dane użytkownika).
  • Unchecked – gdy błąd wynika z usterki w logice programu (np. dzielenie przez zero, naruszenie inwariantu).

3. Konstruktory: jak uczynić wyjątek informacyjnym

Zwykle w klasie wyjątku implementuje się przynajmniej jeden konstruktor z parametrem String message. Często dodaje się też inne:

public class ScoreLimitExceededException extends Exception {
    public ScoreLimitExceededException() {
        super();
    }
    public ScoreLimitExceededException(String message) {
        super(message);
    }
    public ScoreLimitExceededException(String message, Throwable cause) {
        super(message, cause);
    }
    public ScoreLimitExceededException(Throwable cause) {
        super(cause);
    }
}

Wyjaśnienie:

  • message – tekstowy opis błędu.
  • cause – przyczyna (inny wyjątek), jeśli chcesz „opakować” jeden błąd drugim.

Wskazówka: jeśli nie wiesz, których konstruktorów potrzebujesz – dodaj przynajmniej ten, który przyjmuje ciąg znaków.

4. Użycie własnych wyjątków w kodzie

Przeanalizujmy przykład: mamy użytkownika, użytkownik ma punkty i nie można dodać więcej niż 100.

public class User {
    private String name;
    private int score;

    public User(String name) {
        this.name = name;
        this.score = 0;
    }

    public void addScore(int points) throws ScoreLimitExceededException {
        if (score + points > 100) {
            throw new ScoreLimitExceededException("Przekroczono limit punktów! Próba dodania: " + points);
        }
        this.score += points;
    }
}

Klasa wyjątku:

public class ScoreLimitExceededException extends Exception {
    public ScoreLimitExceededException(String message) {
        super(message);
    }
}

Obsługa:

try {
    user.addScore(60);
    user.addScore(50); // Tutaj zostanie rzucony wyjątek!
} catch (ScoreLimitExceededException e) {
    System.out.println("Błąd: " + e.getMessage());
}

Wynik:

Błąd: Przekroczono limit punktów! Próba dodania: 50

Być może nasuwa się pytanie: co jeśli zamiast wyjątku po prostu użyć warunku if i na przykład zwracać false lub inną specjalną wartość, aby pokazać, że operacja się nie powiodła? Na przykład tak:

public boolean addScore(int points) {
    if (score + points > 100) {
        return false; // Albo rzucić jakiś RuntimeException, jeśli nie chcesz tego obsługiwać
    }
    this.score += points;
    return true;
}

Choć takie podejście może wydawać się prostsze, ma wady, gdy chodzi o poważne błędy lub naruszenie logiki aplikacji.

Po pierwsze, zwracanie false lub innej wartości w celu oznaczenia błędu zobowiązuje kod wywołujący do zawsze sprawdzania wartości zwracanej. Jeśli programista o tym zapomni, błąd może pozostać niezauważony, co prowadzi do nieprzewidywalnego zachowania programu. Wyjątki natomiast wymuszają obsługę (dla wyjątków sprawdzanych) lub przynajmniej wyraźnie sygnalizują problem, jeśli nie zostaną przechwycone.

Po drugie, wyjątki lepiej oddają semantykę błędu. Zwrócenie false może oznaczać cokolwiek: „nie udało się”, „nie dotyczy”, „niedostępne”. Wyjątek ScoreLimitExceededException jasno i jednoznacznie mówi: „przekroczono limit punktów”. To poprawia czytelność i łatwość utrzymania kodu.

Po trzecie, wyjątki pozwalają scentralizować obsługę błędów. Zamiast rozsiewania sprawdzeń if po całym kodzie, gdzie wywoływane jest addScore, możesz przechwycić wyjątek w jednym miejscu i podjąć odpowiednią decyzję: wyświetlić komunikat użytkownikowi, zapisać do logu lub wycofać transakcję.

Wreszcie, w przypadku takich problemów jak przekroczenie limitu, jest to rzeczywiście sytuacja wyjątkowa (stąd nazwa). Zwykły przepływ wykonania programu zakłada, że punkty zostaną dodane pomyślnie. Jeśli tak nie jest – to naruszenie logiki biznesowej lub inwariantów obiektu, a więc idealny scenariusz do użycia wyjątków.

5. Dodawanie własnych pól do wyjątków

Czasem warto dodać do własnego wyjątku dodatkowe dane, które pomogą obsłużyć błąd.

Przykład:

public class ScoreLimitExceededException extends Exception {
    private int currentScore;
    private int attemptedAdd;

    public ScoreLimitExceededException(String message, int currentScore, int attemptedAdd) {
        super(message);
        this.currentScore = currentScore;
        this.attemptedAdd = attemptedAdd;
    }

    public int getCurrentScore() { 
        return currentScore; 
    }
    public int getAttemptedAdd() { 
        return attemptedAdd; 
    }
}

Użycie:

if (score + points > 100) {
    throw new ScoreLimitExceededException(
        "Przekroczono limit punktów!",
        this.score,
        points
    );
}

6. Przydatne niuanse

Jak nazywać własne wyjątki?

W Javie przyjęto nazywać wyjątki użytkownika z sufiksem Exception: InvalidUserInputException, InsufficientFundsException, ScoreLimitExceededException.

Nie nazywaj swoich wyjątków po prostu Error ani Warning – może to wprowadzać w błąd innych programistów (a nawet ciebie samego po kilku tygodniach).

Gdzie i kiedy rzucać własne wyjątki?

  • Podczas walidacji danych użytkownika (np. pusta nazwa, ujemny wiek).
  • Przy naruszeniu reguł biznesowych (np. przekroczenie limitu, próba wypłaty większej kwoty niż saldo).
  • Przy błędach w pracy z usługami zewnętrznymi (np. usługa niedostępna, przekroczony czas oczekiwania).

7. Typowe błędy przy tworzeniu własnych wyjątków

Błąd nr 1: Dziedziczenie po niewłaściwej klasie.
Dziedzicz po Exception (lub RuntimeException), a nie po Throwable ani Error.

Błąd nr 2: Brak konstruktora z komunikatem.
Bez konstruktora przyjmującego łańcuch (String message) twoje wyjątki będą nieme i trudne do debugowania.

Błąd nr 3: Używanie standardowych wyjątków do logiki biznesowej.
Nie rzucaj NullPointerException ani IllegalArgumentException tam, gdzie potrzebny jest własny „mówiący” wyjątek.

Błąd nr 4: Nadużywanie własnych wyjątków.
Nie twórz osobnej klasy wyjątku dla każdej drobnostki. Jeśli błąd nie jest unikalny dla twojej domeny, użyj standardowych wyjątków.

Błąd nr 5: Brak serializacji (rzadko, ale się zdarza).
Jeśli twój wyjątek będzie przesyłany przez sieć lub zapisywany, warto dodać implements Serializable. Jednak w prostych aplikacjach nie jest to krytyczne.

1
Zadanie
JAVA 25 SELF, poziom 24, lekcja 1
Niedostępne
Strażnik bram gry: Sprawdzenie wyniku gracza
Strażnik bram gry: Sprawdzenie wyniku gracza
1
Zadanie
JAVA 25 SELF, poziom 24, lekcja 1
Niedostępne
Twierdza hasła: Ochrona danych użytkownika
Twierdza hasła: Ochrona danych użytkownika
1
Zadanie
JAVA 25 SELF, poziom 24, lekcja 1
Niedostępne
Kontrola wieku w parku rozrywki: Elastyczne komunikaty o błędach
Kontrola wieku w parku rozrywki: Elastyczne komunikaty o błędach
1
Zadanie
JAVA 25 SELF, poziom 24, lekcja 1
Niedostępne
Zarządzanie magazynem magicznych artefaktów: Dokładne informacje o przekroczeniu pojemności
Zarządzanie magazynem magicznych artefaktów: Dokładne informacje o przekroczeniu pojemności
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION