Jednim člankom potpuno savladajte Java obradu izuzetaka, zaista vrhunski
01. Šta je izuzetak
„Erge, danas učimo o izuzecima?” upita Sanmej.
„Da. Samo uz pravilno obrađivanje izuzetaka možemo garantovati pouzdanost programa, zato je učenje o izuzecima zaista neophodno.” rekoh.
„Pa šta zapravo jeste izuzetak?” upita Sanmej.
„Izuzetak je nepredvidiv događaj koji prekida normalno izvršavanje programa. Kada nastupi izuzetak, normalan tok izvršavanja programa biva prekinut. Uobičajeno, program se sastoji od mnogo naredbi; bez mehanizma za obradu izuzetaka, čim se u nekoj ranijoj naredbi pojavi izuzetak, sve kasnije naredbe ne mogu nastaviti izvršavanje.”
„Sa mehanizmom za obradu izuzetaka, program se ne prekida kada nastupi izuzetak; možemo uhvatiti izuzetak, a zatim promeniti tok izvršavanja programa.”
„Pored toga, mehanizam za obradu izuzetaka omogućava nam da korisnicima pružimo prijateljske poruke, umesto sirovih poruka izuzetka programa koje korisnici uopšte ne bi razumeli.”
„Međutim, iz perspektive programera, više bismo voleli da vidimo sirove informacije o izuzetku, jer nam to pomaže da brže pronađemo koren baga; s druge strane, previše »upakovani« izuzeci ometaju naš pregled.”
„Java je od samog početka pružala prilično potpun mehanizam za obradu izuzetaka, koji znatno snižava prag za pisanje pouzdanih programa; to je jedan od razloga zašto je Java postala toliko popularna.”
„A koji uzroci dovode do toga da program baci izuzetak?” upita Sanmej.
Na primer:
- Program pokušava da otvori datoteku koja ne postoji;
- Program naiđe na problem sa mrežnom konekcijom;
- Korisnik unese loše podatke;
- Program pri radu sa aritmetikom ne uzima u obzir slučaj deljenja nulom;
I tako dalje.
Uzmimo najjednostavniji uzrok.
public class Demo {
public static void main(String[] args) {
System.out.println(10/0);
}
}Izuzetak koji ovaj kod baci pri izvršavanju izgleda ovako:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.itwanger.s41.Demo.main(Demo.java:8)„Vidiš, Sanmej, ova sirova poruka o izuzetku očigledno nije lako razumljiva korisnicima, ali za nas programere je previše jasna — lako možemo locirati koren izuzetka.”
02. Razlika između Exception i Error
„O, razumem. Sledeće pitanje: često vidim u člancima kako se pominju Exception i Error, Erge, možeš li mi objasniti razliku između njih?” upita Sanmej.
„To je odlično pitanje, Sanmej!”
Sa stanovišta značenja reči, error je greška, exception je izuzetak, i nivo greške je očigledno viši od izuzetka.
I sa stanovišta programa, zaista je tako.
Pojava Error-a znači da je program naišao na ozbiljan problem koji ne treba prepuštati Java mehanizmu za obradu izuzetaka; program bi trebalo direktno da se sruši. Na primer, OutOfMemoryError — memorija je prelivena, što znači da program pri izvršavanju zahteva više memorije nego što sistem može da pruži; ova greška je fatalna po program.
Pojava Exception-a znači da je program naišao na problem u kontrolisanim granicama i da treba preduzeti mere da ga spasemo.
Na primer, gore pomenuti ArithmeticException je očigledno uzrokovan time što je delilac nula; možemo uhvatiti izuzetak i poručiti korisniku da ne bi trebalo deliti nulom. Naravno, bolji pristup je da direktno proverimo delilac: ako je nula, ne vršimo deljenje, nego tražimo od korisnika da unese broj različit od nule.
03. checked i unchecked izuzeci
„Sanmej, možeš li da se setiš još nekog pitanja?”
„Hmm, ne moram ni da razmišljam, Erge, već sam unapred pripremila lekciju,” reče Sanmej samouvereno. „Izuzeci se mogu podeliti na checked i unchecked; koja je razlika između njih?”
„O, Sanmej, opet odlično pitanje.”
checked izuzeci (izuzeci koji se proveravaju) moraju se u izvornom kodu eksplicitno uhvatiti ili baciti, inače će kompilator tražiti da izvršite odgovarajuću radnju; unchecked izuzeci (izuzeci koji se ne proveravaju) su takozvani izuzeci u vreme izvršavanja, obično se mogu izbeći dobrim kodiranjem i ne zahtevaju eksplicitno hvatanje ili bacanje.
„Nacrtaću ti jednu umnu mapu da osetiš kako to ide.”

Prvo, i Exception i Error nasleđuju klasu Throwable. Drugim rečima, samo objekti klase Throwable (ili njenih podklasa) mogu se baciti pomoću ključne reči throw, ili se koristiti kao tip parametra za catch.
Pitanje koje se često pojavljuje na intervjuima je: koja je razlika između NoClassDefFoundError i ClassNotFoundException?
„Sanmej, da li znaš?”
„Ne znam, Erge, objasni mi.”
Oba nastaju zato što sistem pri izvršavanju ne može da pronađe klasu koju treba učitati, ali se uzroci okidanja razlikuju.
- NoClassDefFoundError: Program pri kompilaciji može pronaći zavisnu klasu, ali pri izvršavanju ne može pronaći traženi fajl klase, što izaziva bacanje ove greške; uzrok može biti nedostajući jar paket ili poziv klase čija inicijalizacija nije uspela.
- ClassNotFoundException: Baca se kada se pri dinamičkom učitavanju Class objekta ne pronađe odgovarajuća klasa; uzrok može biti da klasa za učitavanje ne postoji ili je ime klase pogrešno napisano.
Drugo, klase poput IOException, ClassNotFoundException, SQLException pripadaju checked izuzecima; RuntimeException i njene podklase ArithmeticException, ClassCastException, ArrayIndexOutOfBoundsException, NullPointerException pripadaju unchecked izuzecima.
unchecked izuzeci ne moraju se eksplicitno obraditi u programu, kao što je već pomenuto za ArithmeticException; ali checked izuzeci se moraju eksplicitno obraditi.
Na primer, sledeća linija koda:
Class clz = Class.forName("com.itwanger.s41.Demo1");Ako se ne obradi, na primer u Intellij IDEA okruženju, dobijete poruku da ova linija koda može baciti java.lang.ClassNotFoundException.

Preporučujem da ili koristite try-catch za hvatanje:
try {
Class clz = Class.forName("com.itwanger.s41.Demo1");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}Obratite pažnju na metod printStackTrace() koji ispisuje stek izuzetka: ovaj metod štampa stek izuzetka na standardni konzolni izlaz. U testnom okruženju takav pristup je još u redu, ali u produkciji je neprihvatljiv; morate koristiti logerski frejmvork da biste izlaz steka izuzetka usmerili u sistem logova, inače ga možda nećete moći da pratite.
Ili na potpisu metode koristite ključnu reč throws da biste bacili izuzetak:
public class Demo1 {
public static void main(String[] args) throws ClassNotFoundException {
Class clz = Class.forName("com.itwanger.s41.Demo1");
}
}Prednost ovog pristupa je što ne morate hvatati i obrađivati izuzetak, već ga prepustite Java virtuelnoj mašini; mana je što ne možete preduzeti odgovarajuće korake za tu situaciju.
„Erge, povodom checked izuzetaka, na Zhihu-u sam videla objavu koja kaže da su checked izuzeci u Javi prilično nepotrebni: ovakav izuzetak se pri kompilaciji mora ili try-catch ili throws, a opet nije nužno da će se izuzetak pojaviti — da li misliš da ovakva konstrukcija ima smisla?” Sanmej je postavila veoma oštro pitanje.
„O, to je dobro postavljeno pitanje.” Nisam mogao a ne osetiti divljenje prema Sanmej.
„Zaista, checked izuzeci su u industriji sporni. Oni pretpostavljaju da smo uhvatili izuzetak i preduzeli odgovarajuće korake, ali ponekad jednostavno nema šta da se uradi.” rekoh. „Uzmimo gore pomenuti ClassNotFoundException: pretpostavimo da smo ga obuhvatili try-catch; ali kada se ClassNotFoundException zaista desi, nemamo mnogo mogućnosti za reagovanje — da ponovo pozovemo Class.forName()?”
Osim toga, checked izuzeci nisu kompatibilni sa funkcionalnim programiranjem; kasnije, kada budete pisali Lambda/Stream kod, osetićete tu gorčinu.
Naravno, checked izuzeci nisu potpuno bez vrednosti, posebno kada se radi o IO ili mrežnim izuzecima. Na primer, prilikom uspostavljanja Socket veze, napisao sam grubo ovako:
public class Demo2 {
private String mHost;
private int mPort;
private Socket mSocket;
private final Object mLock = new Object();
public void run() {
}
private void initSocket() {
while (true) {
try {
Socket socket = new Socket(mHost, mPort);
synchronized (mLock) {
mSocket = socket;
}
break;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}Kada nastupi IOException, socket ponovo pokušava da se poveže; u suprotnom, break izlazi iz petlje. To znači da, kada IOException nije checked izuzetak, ovakav način pisanja deluje pomalo iznenadno, jer se IOException ne može izbeći if naredbom koja proverava da li je delilac nula, kao ArithmeticException.
Drugo, obaveznost checked izuzetka nas tera da pri programiranju razmislimo kako da elegantnije obradimo izuzetak kada se desi. Očigledno, u Socket programiranju će se sigurno pojaviti IOException; kada bi IOException bio unchecked izuzetak, programeri bi mogli i da ga zanemare i prepuste ga Java virtuelnoj mašini, ali mislim da to svakako nije prikladnije.
04. O throw i throws
„Erge, možeš li mi reći razliku između ključnih reči throw i throws?” upita Sanmej.
„Ključna reč throw se koristi za aktivno bacanje izuzetka; normalno, kada je delilac 0, program sam baca ArithmeticException; ali ako želimo da i kada je delilac 1 bacimo ArithmeticException, možemo koristiti throw da bismo aktivno bacili izuzetak.” rekoh.
throw new exception_class("error message");Sintaksa je veoma jednostavna: iza ključne reči throw ide ključna reč new, zatim tip izuzetka i parametri.
Pogledajmo primer.
public class ThrowDemo {
static void checkEligibilty(int stuage){
if(stuage<18) {
throw new ArithmeticException("Mlađi od 18 godina, zabranjeno gledanje");
} else {
System.out.println("Molimo, pažljivo pratite film!!");
}
}
public static void main(String args[]){
checkEligibilty(10);
System.out.println("Prijetan vikend..");
}
}Ovaj kod pri izvršavanju baci sledeću grešku:
Exception in thread "main" java.lang.ArithmeticException: Mlađi od 18 godina, zabranjeno gledanje
at com.itwanger.s43.ThrowDemo.checkEligibilty(ThrowDemo.java:9)
at com.itwanger.s43.ThrowDemo.main(ThrowDemo.java:16)„Uloga ključne reči throws je potpuno drugačija,” rekoh. „U prethodnom odeljku već su objašnjeni checked i unchecked izuzeci, to jest izuzeci koji se proveravaju i oni koji se ne proveravaju; za checked izuzetke, ako ih ne obradite, kompilator će vas upozoriti.”
Metod Class.forName() pri izvršavanju može naići na izuzetak java.lang.ClassNotFoundException, što je checked izuzetak; ako se ne obradi, IDEA će vam sugerisati da ga deklarišete u potpisu metode ili da ga stavite u try-catch.

„A kada koristiti throws umesto try-catch?” upita Sanmej.
„Pretpostavimo da imamo metod myMethod() koji može baciti ArithmeticException, a može i NullPointerException. U tom slučaju možemo koristiti try-catch za obradu.” odgovorio sam.
public void myMethod() {
try {
// može baciti izuzetak
} catch (ArithmeticException e) {
// aritmetički izuzetak
} catch (NullPointerException e) {
// izuzetak null pokazivača
}
}„Ali pretpostavimo da imamo nekoliko metoda sličnih myMethod(); kada bismo za svaki dodali try-catch, postalo bi veoma naporno. Kod bi postao predug i teško čitljiv.” nastavio sam.
„Jedno rešenje je da koristite ključnu reč throws: u potpisu metode deklarišete izuzetke koji mogu biti bačeni, a onda na mestu poziva metode koristite try-catch za obradu.”
public static void main(String args[]){
try {
myMethod1();
} catch (ArithmeticException e) {
// aritmetički izuzetak
} catch (NullPointerException e) {
// izuzetak null pokazivača
}
}
public static void myMethod1() throws ArithmeticException, NullPointerException{
// deklaracija izuzetka u potpisu metode
}„Dobro, sumiraću razliku između throw i throws; Sanmej, zapiši.”
Ključna reč throws se koristi za deklaraciju izuzetka i slična je po ulozi try-catch; dok se throw koristi za eksplicitno bacanje izuzetka.
Iza ključne reči throws ide ime izuzetka; iza throw ide objekat izuzetka.
Primer.
throws ArithmeticException;throw new ArithmeticException("aritmetički izuzetak");Ključna reč throws pojavljuje se u potpisu metode, dok se throw pojavljuje u telu metode.
Pri deklaraciji, throws može pratiti više izuzetaka razdvojenih zarezima; dok throw može baciti samo jedan izuzetak u jednom trenutku.
05. O try-catch-finally
„Erge, ranije si objasnio mehanizam za obradu izuzetaka; o čemu će biti ovaj odeljak?” upita Sanmej.
„Vreme je za try-catch-finally,” rekoh. „Iza ključne reči try idu vitičaste zagrade {}; u njih stavljamo kod koji može izazvati izuzetak. Iza try bloka obično sledi catch blok koji obrađuje nastale izuzetke; naravno, izuzetak ne mora nužno da se desi, pa da bismo obezbedili da se određeni kod izvrši bez obzira na to, pridružujemo finally blok.”
„Kako se tačno koristi, Erge?” upita Sanmej.
„Ne brini, Sanmej, proći ću kroz sve redom.” rekoh.
Sintaksa try bloka je vrlo jednostavna:
try{
// kod koji može izazvati izuzetak
}„Pazi, Sanmej: ako ste sigurni da neki kod neće baciti izuzetak, nastojte da ga ne stavljate u try blok, jer kod sa obradom izuzetaka zahteva više vremena za izvršavanje od onog bez nje.”
Sintaksa catch bloka je takođe jednostavna:
try{
// kod koji može izazvati izuzetak
}catch (exception(type) e(object)){
// kod za obradu izuzetka
}Iza jednog try bloka može slediti više catch blokova koji hvataju različite tipove izuzetaka i vrše odgovarajuću obradu; kada neka linija u try bloku izazove izuzetak, preostali kod se ne izvršava, već se prelazi u catch blok koji odgovara tom izuzetku.
Kada za jedan try blok sledi više povezanih catch blokova, specifičniji izuzeci treba da idu ispred, a opštiji iza; u suprotnom će kompilator prijaviti grešku. Pogledajmo primer.
static void test() {
int num1, num2;
try {
num1 = 0;
num2 = 62 / num1;
System.out.println(num2);
System.out.println("poslednja naredba u try bloku");
} catch (ArithmeticException e) {
// prelazi ovde kada nastupi aritmetička greška
System.out.println("Delilac ne može biti nula");
} catch (Exception e) {
// opšti izuzetak može uhvatiti sve izuzetke; treba stajati na kraju
System.out.println("Izuzetak je nastao");
}
System.out.println("Kod van try-catch.");
}„Zašto Exception ne može ići ispred ArithmeticException?” upita Sanmej.
„Zato što je ArithmeticException podklasa klase Exception, specifičnija je — kada je vidimo, znamo da je nastupila aritmetička greška; dok je Exception opštiji, krije konkretne informacije o izuzetku, pa kada ga vidimo ne znamo tačno koji tip izuzetka se desio, što otežava traženje greške,” rekoh. „Osim toga, kada bismo opštiji izuzetak stavili ispred, ostali catch blokovi se nikada ne bi izvršili, pa kompilator odmah prijavljuje grešku.”
„Daću ti još jedan primer; pazi, Sanmej.”
static void test1 () {
try{
int arr[]=new int[7];
arr[4]=30/0;
System.out.println("kraj try bloka");
} catch(ArithmeticException e){
System.out.println("Delilac ne sme biti 0");
} catch(ArrayIndexOutOfBoundsException e){
System.out.println("Niz je van granica");
} catch(Exception e){
System.out.println("neki drugi izuzetak");
}
System.out.println("van try-catch");
}Pri izvršavanju ovog koda, izvršiće se prvi catch blok, jer je delilac nula; hajde da malo izmenim kod.
static void test1 () {
try{
int arr[]=new int[7];
arr[9]=30/1;
System.out.println("kraj try bloka");
} catch(ArithmeticException e){
System.out.println("Delilac ne sme biti 0");
} catch(ArrayIndexOutOfBoundsException e){
System.out.println("Niz je van granica");
} catch(Exception e){
System.out.println("neki drugi izuzetak");
}
System.out.println("van try-catch");
}„Znam, Erge, izvršiće se drugi catch blok, jer nije bilo aritmetičkog izuzetka, ali je niz izašao van granica,” Sanmej je izgovorila odgovor pre nego što sam uspeo da pokrenem kod.
„Sanmej, u pravu si; hajde da ponovo izmenim kod.”
static void test1 () {
try{
int arr[]=new int[7];
arr[9]=30/1;
System.out.println("kraj try bloka");
} catch(ArithmeticException | ArrayIndexOutOfBoundsException e){
System.out.println("Delilac ne sme biti 0");
}
System.out.println("van try-catch");
}„Kada ima više catch blokova, mogu se grupisati, razdvojeni uspravnom crtom |, kao gore,” rekoh.
„To je lepo, izgleda nedvosmislenije,” reče Sanmej.
Sintaksa finally bloka nije komplikovana.
try {
// kod koji može izazvati izuzetak
}catch {
// obrada izuzetka
}finally {
// kod koji se mora izvršiti
}Pre try-with-resources, finally blok se često koristio za zatvaranje resursa veze, poput socketa, veze sa bazom podataka, IO ulazno-izlaznih tokova itd.
OutputStream osf = new FileOutputStream( "filename" );
OutputStream osb = new BufferedOutputStream(opf);
ObjectOutput op = new ObjectOutputStream(osb);
try{
output.writeObject(writableObject);
} finally{
op.close();
}„Sanmej, pazi, pri korišćenju finally bloka treba poštovati ova pravila.”
- Ispred finally bloka mora postojati try blok; nemojte finally blok koristiti samostalno. Kompilator to ni ne dozvoljava.
- finally blok nije obavezan; kada postoji try blok, finally blok ne mora postojati.
- Ako kod u finally bloku može izazvati izuzetak, treba ga obuhvatiti try-catch.
- Čak i kada try blok izvrši naredbe skoka poput return, break, continue, finally blok će se izvršiti.
„Zaista, Erge?” Sanmej je bila u nedoumici oko poslednjeg pravila.
„Isprobajmo i videćeš,” rekoh.
static int test2 () {
try {
return 112;
}
finally {
System.out.println("Čak i kada try blok ima return, finally blok će se izvršiti");
}
}Pogledajmo izlaz:
Čak i kada try blok ima return, finally blok će se izvršiti„A da li postoje situacije kada se finally ne izvrši?” Sanmej je radoznalo pitala.
„Ima,” odgovorio sam odlučno.
- Naiđe na beskonačnu petlju.
- Izvrši se linija
System. exit().
System.exit() se razlikuje od return naredbe: prva služi za izlaz iz programa, a druga samo vraća kontrolu nadređenom pozivu metode.
„Sanmej, pogledaj dokumentacioni komentar u izvornom kodu i sve će ti biti jasno!”

Što se tiče vrednosti parametra status, lako je razumeti: za izlaz zbog greške, postavite vrednost različitu od nule, obično 1; za normalan izlaz iz programa, koristite 0.
06. Rezime
Obrada izuzetaka u Javi je važan mehanizam koji nam pomaže da obradimo greške ili izuzetke nastale tokom izvršavanja programa.
Izuzeci se dele u dve kategorije: Checked Exception i Unchecked Exception. Checked Exception se mora u kodu eksplicitno obraditi ili deklarisati za bacanje, dok Unchecked Exception ne mora. Obrada izuzetaka se obično vrši kroz try-catch-finally blokove, a može se koristiti i ključna reč throws da se izuzeci prebace na pozivaoca.
Evo nekoliko sažetaka o Java obradi izuzetaka:
- Upotrebom try-catch bloka za hvatanje i obradu izuzetaka možemo sprečiti pad programa zbog izuzetka.
- Može se koristiti više catch blokova za hvatanje različitih tipova izuzetaka i njihovu različitu obradu.
- finally blok se može koristiti za izvršavanje neophodnog čišćenja, bez obzira na to da li je izuzetak nastupio.
- Ključna reč throw omogućava ručno bacanje izuzetka, radi jasne naznake određenih izuzetnih situacija u programu.
- Ključna reč throws prebacuje izuzetke pozivaocu na obradu, te se koristi za deklaraciju izuzetaka koji se mogu pojaviti u potpisu metode.
- Checked Exception obično proizlazi iz spoljnih faktora i mora se u kodu eksplicitno obraditi ili deklarisati za bacanje.
- Unchecked Exception obično proizlazi iz unutrašnje logike programa ili anomalija u podacima; može se ne obraditi ili obraditi po potrebi.
- Pri obradi izuzetaka treba postupati prema konkretnom tipu izuzetka; na primer, možemo pokušati ponovo da otvorimo datoteku, ponovo uspostavimo mrežnu vezu i sl.
- Obrada izuzetaka treba da prati konkretne poslovne potrebe i principe dizajna; izbegavajte prekomerno hvatanje i obrađivanje izuzetaka, jer to smanjuje performanse i održivost programa.
