Throw/Throws — co to jest, operatory wyrzucania wyjątków i jak działają w programowaniu mobilnym

Autor: IT Sectr Opublikowano: 2026-05-26 Czas czytania: 9 min

Throw i Throws — mechanizmy wyrzucania i deklaracji wyjątków w językach programowania. Operator throw przerywa normalne wykonanie funkcji i przekazuje obiekt błędu w górę stosu wywołań. Słowo kluczowe throws w sygnaturze funkcji ostrzega stronę wywołującą o możliwości błędu, czyniąc kod przewidywalnym. Według Kotlin Documentation (2026), throw w Kotlin jest wyrażeniem (expression), a nie instrukcją, co pozwala na używanie go wewnątrz bloków when i operatora elvisa.

Najważniejsze

  • Throw — operator przerywający wykonanie funkcji i przekazujący obiekt błędu w górę stosu wywołań
  • Throws — słowo kluczowe w sygnaturze funkcji deklarujące, że funkcja może wyrzucić wyjątek
  • W Swift funkcja jest oznaczana throws, wywołanie wymaga try/try?/try!, a throw przyjmuje dowolny typ implementujący protokół Error
  • W Kotlin throw — wyrażenie typu Nothing, throws nie występuje (wszystkie wyjątki są unchecked)
  • Błędy użytkownika tworzone są przez enum (Swift) lub sealed class (Kotlin) dla wygodnej obsługi w catch

Co to jest Throw i Throws?

Throw — to operator, który generuje wyjątek w punkcie wykonania programu. Gdy napotkany jest throw, bieżący przepływ wykonania zostaje natychmiast przerwany, a sterowanie przekazywane jest najbliższemu obsługiwaczowi catch w stosie wywołań. Jeśli obsługiwacz nie zostanie znaleziony, aplikacja kończy się awaryjnie. Throw w programowaniu mobilnym używany jest do sygnalizowania błędów, których nie można obsłużyć na bieżącym poziomie abstrakcji — na przykład nieprawidłowa odpowiedź serwera, brak sieci lub niepoprawne argumenty.

Throws — modyfikator w sygnaturze funkcji (głównie w Swift), który deklaruje, że funkcja może wyrzucić błąd. Jest to część mechanizmu checked errors w Swift: strona wywołująca jest zobowiązana do obsłużenia błędu przez do-catch, try?, try! lub oznaczenia swojej funkcji throws do dalszego przekazywania (propagation). W Kotlin i Java throws również istnieje, ale Kotlin uważa go za zbędny — wszystkie wyjątki w Kotlin są unchecked, czyli mogą być nieobsłużone bez syntaktycznego przymusu. Według Apple Swift Documentation (2026), throws w Swift — to jedyny sposób jawnego zadeklarowania możliwości błędu w typie funkcji, co czyni kontrakty API przejrzystymi dla programisty.

Różnica między throw a throws jest fundamentalna: throw — to działanie (wyrzucenie wyjątku w runtime), throws — to deklaracja (kontrakt compile-time). Funkcja bez throws nie może użyć throw — kompilator Swift zgłosi błąd. Funkcja z throws może nie użyć throw — jest to dozwolone, ale bezcelowe. Takie rozdzielenie czyni throw/throws potężnym narzędziem projektowania API, gdzie kontrakt dotyczący błędu jest widoczny w sygnaturze funkcji przed jej wywołaniem.

Throw w Swift

W Swift operator throw przyjmuje dowolny typ implementujący protokół Error. Najczęściej jest to enum z case'ami dla różnych rodzajów błędów. Swift nie obsługuje checked exceptions w stylu Java — zamiast tego typ funkcji jest oznaczany throws, a obsługa spoczywa na stronie wywołującej. To czyni throw w Swift bardziej elastycznym, ale też bardziej odpowiedzialnym dla programisty.

Protokół Error i typy błędów

Dowolny typ zgodny z protokołem Error może być wyrzucony przez throw. Najczęściej programiści używają enum z case'ami bez powiązanych wartości (dla prostych błędów) lub z associated values (do przekazywania kontekstu). Swift nie wymaga, aby błąd był enum — można użyć struct lub class implementujących Error, ale enum jest preferowany dzięki exhaustive switch po stronie obsługiwacza. Kompilator sprawdza, czy wszystkie case'y zostały obsłużone w do-catch.

swift
enum AuthError: Error {
    case invalidCredentials
    case tokenExpired
    case accountLocked(remainingMinutes: Int)
}

func login(username: String, password: String) throws -> Session {
    guard isValid(username) else {
        throw AuthError.invalidCredentials
    }
    let response = try api.authenticate(username, password)
    if response.isLocked {
        throw AuthError.accountLocked(
            remainingMinutes: response.lockDuration
        )
    }
    return Session(token: response.token)
}

AuthError definiuje trzy scenariusze: nieprawidłowe dane uwierzytelniające, wygasły token i zablokowane konto z powiązaną wartością remainingMinutes. Funkcja login jest zadeklarowana jako throws — kompilator wymaga wywołania jej przez try. Wewnątrz funkcji throw jest używane w dwóch miejscach: przy nieprawidłowym username i przy zablokowanym koncie. Powiązana wartość accountLocked pozwala przekazać użytkownikowi konkretne dane — ile minut czekać do odblokowania. Takie podejście eliminuje potrzebę osobnych endpoints API do sprawdzania statusu blokady.

Throw w Kotlin

W Kotlin throw — to wyrażenie (expression) typu Nothing, a nie instrukcja. Oznacza to, że throw można użyć po prawej stronie przypisania, wewnątrz wyrażeń when i operatora elvisa ?:. Typ Nothing — specjalny podtyp wszystkich typów w Kotlin, co pozwala na użycie throw w miejscach, gdzie wymagana jest wartość dowolnego typu. Kompilator rozumie, że po throw wykonanie nie jest kontynuowane i nie wymaga gałęzi dla tego przypadku.

Typ Nothing i jego rola w kompozycji

Nothing — unikalny typ w Kotlin, który jest podtypem wszystkich możliwych typów. Funkcja zwracająca Nothing (na przykład TODO()) nigdy nie kończy się normalnie — albo zawsze wyrzuca wyjątek, albo przechodzi w nieskończoną pętlę. To czyni throw naturalnym kandydatem do użycia w miejscach, gdzie wymagana jest wartość: operator elvisa, when bez else, inicjalizacja zmiennej. Jeśli throw znajduje się w gałęzi when, kompilator rozumie, że gałąź prowadzi do Nothing i nie wymaga return ani else dla tej gałęzi.

kotlin
data class Config(val apiUrl: String, val timeoutSec: Int)

class ConfigParser {
    fun parse(json: String): Config {
        val obj = JSONObject(json)
        val url = obj.optString("apiUrl")
            ?: throw IllegalArgumentException("apiUrl is required")
        val timeout = obj.optInt("timeoutSec", 30)
        return Config(url, timeout)
    }

    fun getErrorMessage(code: Int): String {
        return when (code) {
            404 -> "Not found"
            500 -> "Server error"
            else -> throw IllegalArgumentException("Unknown code: $code")
        }
    }
}

W pierwszym przykładzie throw jest używane w operatorze elvisa ?:: jeśli pole apiUrl jest nieobecne w JSON, wyrażenie throw natychmiast przerywa wykonanie i wyrzuca IllegalArgumentException. Typ Nothing pozwala kompilatorowi wywnioskować typ prawej strony jako String (elvis oczekuje String, throw ma typ Nothing, Nothing jest podtypem String). W drugim przykładzie throw wewnątrz wyrażenia when: jeśli kod nie odpowiada żadnemu ze znanych, wyrzucany jest wyjątek. Kompilator rozumie, że po throw kod jest nieosiągalny, więc zwracany typ funkcji String nie jest naruszony.

Throws w sygnaturze funkcji i rethrows

W Swift throws jest podawane po liście parametrów i przed strzałką typu zwracanego. Funkcja z throws może wywoływać tylko inne funkcje throws wewnątrz do-catch lub z try?. Jeśli funkcja throws nie obsługuje błędu, przekazuje go stronie wywołującej. Swift obsługuje również rethrows — modyfikator dla funkcji wyższego rzędu, które przyjmują domknięcie z throws i przerzucają jego błąd. Rethrows oznacza, że funkcja wyrzuca błąd tylko jeśli przekazane domknięcie go wyrzuciło — sama funkcja nie generuje błędu.

swift
func mapValues<T>(
    _ array: [T],
    transform: (T) throws -> U
) rethrows -> [U] {
    var result = [U]()
    for element in array {
        result.append(try transform(element))
    }
    return result
}

// Użycie z throws-zamknięciem
let parsed = try mapValues(jsonStrings) { str in
    let data = Data(str.utf8)
    return try JSONDecoder().decode(Item.self, from: data)
}

Rethrows pozwala funkcji mapValues być elastyczną: przyjmuje zarówno domknięcia throws, jak i zwykłe. Jeśli przekazane jest domknięcie throws, wywołanie mapValues wymaga try; jeśli zwykłe — try nie jest potrzebny. To czyni rethrows idealnym dla funkcji wyższego rzędu, takich jak map, filter, reduce, w standardowej bibliotece Swift. Zalecenie Apple: używaj rethrows dla API, które przyjmują domknięcia throws i jedynym źródłem błędu jest to domknięcie. Jeśli funkcja może wyrzucić własny błąd, używaj throws.

Checked vs unchecked wyjątki

Swift throws jest bliższy checked exceptions (jak w Java) — wyrzucane błędy są deklarowane w sygnaturze. Kotlin i Dart używają unchecked wyjątków — throws w sygnaturze nie jest wymagane. Różnica jest zasadnicza: checked zmusza programistę do obsługi (bezpieczniejsze, ale bardziej rozwlekłe), unchecked daje swobodę, ale zwiększa ryzyko zapomnienia o obsłudze błędu. Swift wybrał checked dla throws, Kotlin — unchecked dla wszystkich wyjątków. Oba podejścia mają zalety: Swift jest bardziej niezawodny na poziomie języka, Kotlin jest bardziej zwięzły i wygodniejszy w łańcuchach przekształceń funkcjonalnych.

Niestandardowe typy błędów

Do strukturalnej obsługi błędów w aplikacjach mobilnych zaleca się tworzenie własnych typów błędów zamiast używania podstawowych Exception lub Error. W Swift używa się do tego enum z protokołem Error, w Kotlin — sealed class z dziedziczeniem po Throwable (lub po Exception), w Dart — class z dziedziczeniem po Exception. Niestandardowe typy pozwalają grupować błędy według kategorii i przekazywać powiązane dane.

JęzykTyp błęduCecha
Swiftenum: Error { ... }Powiązane wartości, exhaustive switch w catch
Kotlinsealed class : Throwable()Data class dla błędów z polami, wyrażenie when
Dartclass implements ExceptionMessage-pole, on-klauzula w catch
Javaclass extends ExceptionChecked vs unchecked, obowiązkowy throws w sygnaturze

Przy projektowaniu niestandardowych błędów przestrzegaj zasady: jeden błąd — jeden scenariusz. Nie łącz różnych przyczyn w jeden typ z flagą String message — twórz osobne case'y/podklasy dla każdego scenariusza. Pozwoli to stronie wywołującej obsługiwać każdy przypadek przez pattern matching (when/switch), a nie przez porównywanie stringów. W Swift daje to exhaustive checking — kompilator ostrzeże, jeśli nie został obsłużony jakiś case enum NetworkError.

Przykład niestandardowego błędu w Kotlin

kotlin
sealed class NetworkError(val message: String) : Throwable(message) {
    data class Timeout(val durationMs: Long) :
        NetworkError("Request timed out after ${durationMs}ms")
    data class HttpError(val code: Int, val body: String?) :
        NetworkError("HTTP $code")
    data class NoConnection(val cause: IOException) :
        NetworkError("No internet connection")
}

Sealed class NetworkError dziedziczy po Throwable (standardowy typ wyjątków w Kotlin). Każda podklasa to data class z własnymi polami: Timeout zawiera czas trwania timeoutu w milisekundach, HttpError — kod i treść odpowiedzi, NoConnection — oryginalny IOException. Taki design pozwala obsługiwać każdy błąd przez when z wyczerpującym przeglądem (gdy dodajesz nową podklasę, kompilator zmusi do aktualizacji wszystkich wyrażeń when).

Try, try? i try! w Swift

Swift oferuje trzy warianty wywoływania funkcji throws, każdy z własnym kontraktem bezpieczeństwa. try — standardowy sposób: wymaga do-catch lub znajdowania się wewnątrz funkcji throws. try? przekształca błąd w nil — wynik staje się opcjonalny, przy błędzie zwracany jest nil, typ zmienia się z T na T?. try! — wymuszone wykonanie bez obsługi: jeśli błąd zostanie wyrzucony, aplikacja ulega awarii. Używaj try! tylko gdy jesteś absolutnie pewien, że błąd jest niemożliwy (na przykład oczywiście poprawne dane).

swift
let configPath = Bundle.main.path(forResource: "config", ofType: "json")!

// try? — opcjonalny wynik
let data = try? Data(contentsOf: URL(fileURLWithPath: configPath))
let json = try? JSONSerialization.jsonObject(with: data ?? Data())

// try! — gwarantowany sukces (tylko gdy jesteś pewien)
let decoder = JSONDecoder()
let defaultConfig = try! decoder.decode(
    Config.self,
    from: Config.defaultJSON
)

// try — standardowa obsługa
do {
    let user = try fetchUser()
    showUser(user)
} catch let error as NetworkError {
    showRetryAlert(error.message)
}

W przykładzie try! jest używane dla oczywiście poprawnego JSON, wbudowanego w bundle aplikacji — błąd dekodowania jest niemożliwy przy poprawnym wydaniu. try? jest stosowane do odczytu pliku konfiguracyjnego — jeśli plik jest nieobecny lub uszkodzony, aplikacja używa wartości domyślnych, a nie ulega awarii. try w do-catch jest używane do żądań sieciowych, gdzie błąd jest oczekiwany i wymaga reakcji użytkownika. Zalecenie: unikaj try! w kodzie produkcyjnym — wyjątkowo tylko dla danych stałych, zweryfikowanych na etapie kompilacji.

Często zadawane pytania

Jaka jest różnica między throw a throws w Swift?

Throw — operator, który wyrzuca błąd podczas wykonywania programu, przerywając przepływ. Throws — modyfikator sygnatury funkcji deklarujący, że funkcja może wyrzucić błąd. Funkcja bez throws nie może użyć throw. Throws — to kontrakt compile-time, throw — działanie w runtime.

Dlaczego w Kotlin nie ma throws?

Kotlin kieruje się filozofią unchecked exceptions: wszystkie wyjątki mogą być nieobsłużone bez syntaktycznego przymusu. Twórcy Kotlin uważają, że throws w Java prowadzi do nadmiarowych bloków try-catch i ignorowania checked exceptions przez puste catch. Typ Nothing w Kotlin pozwala używać throw jako wyrażenia, zastępując throws bardziej elastycznym sposobem.

Kiedy używać try! w Swift?

try! jest dopuszczalne tylko gdy jesteś absolutnie pewien, że błąd jest niemożliwy: oczywiście poprawny JSON z bundle, dane stałe, poprawne schematy URL. W kodzie produkcyjnym try! — to wyjątek, a nie reguła. try? jest preferowane dla scenariuszy opcjonalnych z wartością domyślną, try z do-catch — do obowiązkowej obsługi błędów.

Co to jest rethrows w Swift?

Rethrows — modyfikator dla funkcji przyjmujących domknięcia throws. Funkcja z rethrows wyrzuca błąd tylko jeśli przekazane domknięcie go wyrzuciło. Pozwala to funkcjom wyższego rzędu (map, filter) działać zarówno z domknięciami throws, jak i non-throws bez wymuszonego try po stronie wywołującej.

Czy można wyrzucić błąd wewnątrz bloku catch?

Tak, wewnątrz catch można użyć throw do przerzucenia błędu wyżej w stosie, opakowując go w inny typ lub dodając kontekst. Nazywa się to error chaining lub rethrow. W Swift wystarczy kolejny throw wewnątrz catch, w Kotlin — throw wewnątrz bloku catch. Blok finally wykona się przed przekazaniem sterowania dalej.

Podsumowanie

  • Throw — operator wyrzucania wyjątku, przerywający bieżący przepływ i przekazujący obiekt błędu w górę stosu
  • Throws — deklaracja compile-time w sygnaturze funkcji o możliwości błędu, obowiązkowa w Swift, nieobecna w Kotlin
  • W Swift throw przyjmuje enum: Error, funkcja jest oznaczana throws, wywoływana przez try / try? / try!
  • W Kotlin throw — wyrażenie typu Nothing, dopuszczające użycie w when, operatorze elvisa i przypisaniach
  • Rethrows w Swift pozwala funkcjom wyższego rzędu przerzucać błędy tylko od przekazanych domknięć
  • Niestandardowe błędy tworzone są przez enum (Swift) lub sealed class (Kotlin) z powiązanymi wartościami dla szczegółowych informacji
  • Projektuj typy błędów według zasady „jeden case — jeden scenariusz” dla wygody obsługi przez pattern matching

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również