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 — 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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ęzyk | Typ błędu | Cecha |
|---|---|---|
| Swift | enum: Error { ... } | Powiązane wartości, exhaustive switch w catch |
| Kotlin | sealed class : Throwable() | Data class dla błędów z polami, wyrażenie when |
| Dart | class implements Exception | Message-pole, on-klauzula w catch |
| Java | class extends Exception | Checked 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.
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).
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).
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
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.
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.
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.
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.
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
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.
Przeczytaj również