Izuzeci u Javi: try, catch, finally i throw

Lekcija 13 od 16 · Java

Izuzetak (exception) je greška koja nastaje dok program radi: deljenje nulom, pogrešan unos, pristup nepostojećem elementu niza, nepostojeća datoteka. Ako se izuzetak ne obradi, program se prekida. Ovde je objašnjeno kako se izuzeci hvataju, kako se sami prijavljuju i kako se pravi sopstveni izuzetak.

Pre nego što počneš: potrebno predznanje – metode, nasleđivanje.
Kako da učiš: svaki primer prekucaj i pokreni, a zadatke na kraju prvo reši sam/a; uputstvo i rešenje otvori tek posle toga. Svi programi su u Javi (Java 17 ili novija); svaki je preveden i pokrenut i daje navedeni izlaz.

Šta se dešava kada izuzetak nije obrađen

Sledeći program pada već u drugom redu metode main, a poslednja naredba se nikada ne izvrši:

public class Main {
    public static void main(String[] args) {
        int[] niz = {10, 20, 30};
        System.out.println(niz[3]);          // indeks 3 ne postoji
        System.out.println("Kraj programa");
    }
}

Na ekranu se pojavljuje poruka sa imenom izuzetka i redom u kome je nastao:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
    at Main.main(Main.java:4)

Ovaj ispis (stack trace) je najkorisnija informacija pri traženju greške: kaže šta se desilo i gde.

IzuzetakKada nastaje
ArithmeticExceptioncelobrojno deljenje nulom
ArrayIndexOutOfBoundsExceptionindeks niza van opsega
NullPointerExceptionpoziv metode preko reference koja je null
NumberFormatExceptionInteger.parseInt("12a")
InputMismatchExceptionScanner očekuje broj, a unet je tekst
FileNotFoundExceptiondatoteka ne postoji

Hvatanje izuzetka: try i catch

Kod koji može da izazove izuzetak stavlja se u blok try. Ako izuzetak nastane, izvršavanje se prebacuje u odgovarajući blok catch, a program nastavlja dalje.

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String[] unosi = {sc.next(), sc.next(), sc.next()};
        for (String s : unosi) {
            try {
                int broj = Integer.parseInt(s);      // može baciti NumberFormatException
                int rezultat = 100 / broj;           // može baciti ArithmeticException
                System.out.println("100 / " + broj + " = " + rezultat);
            } catch (NumberFormatException e) {
                System.out.println("'" + s + "' nije ceo broj");
            } catch (ArithmeticException e) {
                System.out.println("Deljenje nulom: " + e.getMessage());
            }
        }
        System.out.println("Program je zavrsen normalno.");
    }
}

Blokova catch može biti više; proveravaju se redom. Više vrsta izuzetaka može se uhvatiti i jednim blokom: catch (NumberFormatException | ArithmeticException e).

Primer: siguran unos broja

Česta primena: program traži broj sve dok korisnik ne unese ispravnu vrednost.

import java.util.Scanner;

public class Main {
    static int ucitajOcenu(Scanner sc) {
        while (true) {
            System.out.print("Ocena (1-5): ");
            String s = sc.next();
            try {
                int x = Integer.parseInt(s);
                if (x >= 1 && x <= 5)
                    return x;
                System.out.println("Ocena mora biti od 1 do 5.");
            } catch (NumberFormatException e) {
                System.out.println("To nije broj.");
            }
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int ocena = ucitajOcenu(sc);
        System.out.println("Uneta ocena: " + ocena);
    }
}

Blok finally i try-with-resources

Blok finally se izvršava uvek – i kada je izuzetak nastao i kada nije. Koristi se za oslobađanje resursa (zatvaranje datoteke, veze sa bazom).

public class Main {
    static int podeli(int a, int b) {
        try {
            return a / b;
        } catch (ArithmeticException e) {
            System.out.println("greska: deljenje nulom");
            return 0;
        } finally {
            System.out.println("finally za " + a + "/" + b);   // izvršava se i posle return
        }
    }

    public static void main(String[] args) {
        System.out.println(podeli(10, 2));
        System.out.println(podeli(10, 0));
    }
}

Za resurse je kraći oblik try-with-resources: objekat napravljen u zagradi posle try automatski se zatvara na kraju bloka.

import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class Main {
    public static void main(String[] args) {
        // FileWriter se sam zatvara na kraju bloka try
        try (FileWriter f = new FileWriter("rezultat.txt")) {
            f.write("Prvi red\n");
            f.write("Drugi red\n");
        } catch (IOException e) {
            System.out.println("Greska pri pisanju: " + e.getMessage());
        }

        try {
            for (String red : Files.readAllLines(Path.of("rezultat.txt")))
                System.out.println("> " + red);
            Files.readAllLines(Path.of("nepostojeca.txt"));
        } catch (IOException e) {
            System.out.println("Ne mogu da procitam: " + e.getMessage());
        }
    }
}

Prijavljivanje greške: throw i throws

Metoda može sama da prijavi grešku naredbom throw. Tako se, na primer, sprečava da objekat dobije nedozvoljeno stanje:

public class Main {
    static class Racun {
        private double stanje;

        void uplati(double iznos) {
            if (iznos <= 0)
                throw new IllegalArgumentException("Iznos mora biti pozitivan");
            stanje += iznos;
        }

        void isplati(double iznos) {
            if (iznos > stanje)
                throw new IllegalStateException("Nema dovoljno novca (stanje " + stanje + ")");
            stanje -= iznos;
        }
    }

    public static void main(String[] args) {
        Racun r = new Racun();
        r.uplati(1000);
        try {
            r.isplati(300);
            r.isplati(900);
            System.out.println("Ovo se ne ispisuje");
        } catch (IllegalStateException e) {
            System.out.println("Odbijeno: " + e.getMessage());
        }
        try {
            r.uplati(-50);
        } catch (IllegalArgumentException e) {
            System.out.println("Odbijeno: " + e.getMessage());
        }
    }
}

Java razlikuje dve vrste izuzetaka:

  • neprovereni (unchecked, naslednici RuntimeException) – greške u programu, kao NullPointerException; ne moraju se hvatati;
  • provereni (checked, npr. IOException) – očekivane spoljne greške; prevodilac zahteva da se uhvate ili da metoda u zaglavlju navede throws IOException.

Sopstveni izuzetak

public class Main {
    // sopstveni izuzetak: nasleđuje Exception (proveren izuzetak)
    static class NeispravanJmbgException extends Exception {
        private static final long serialVersionUID = 1L;   // oznaka verzije (preporuka za klase izuzetaka)

        NeispravanJmbgException(String poruka) {
            super(poruka);
        }
    }

    // throws: metoda najavljuje da može baciti ovaj izuzetak
    static void proveri(String jmbg) throws NeispravanJmbgException {
        if (jmbg.length() != 13)
            throw new NeispravanJmbgException("JMBG mora imati 13 cifara, a ima " + jmbg.length());
        for (char c : jmbg.toCharArray())
            if (!Character.isDigit(c))
                throw new NeispravanJmbgException("JMBG sadrzi znak koji nije cifra: " + c);
    }

    public static void main(String[] args) {
        String[] primeri = {"0101010710001", "12345", "01010107A0001"};
        for (String j : primeri) {
            try {
                proveri(j);
                System.out.println(j + ": ispravan oblik");
            } catch (NeispravanJmbgException e) {
                System.out.println(j + ": " + e.getMessage());
            }
        }
    }
}

Zadaci za vežbu

Za svaki zadatak postoji primer, uputstvo i rešenje. Rešenja su provereno tačna na primerima iz teksta.

ZADATAK 01Težina: lako

Zbir ispravnih

Učitava se n, pa n reči. Neke su celi brojevi, a neke nisu. Ispisati zbir svih reči koje su celi brojevi i, u drugom redu, broj neispravnih.

Primer

Ulaz

6
12 abc 5 -3 7x 100

Izlaz

114
2
ZADATAK 02Težina: srednje

Kalkulator

Svaki red ulaza ima oblik a op b, gde je op jedan od + - * /. Za svaki red ispisati rezultat celobrojne operacije ili poruku: GRESKA: deljenje nulom, GRESKA: nije broj ili GRESKA: nepoznata operacija. Prvi red sadrži broj izraza n.

Primer

Ulaz

5
7 + 5
9 / 0
8 * x
20 / 3
4 ^ 2

Izlaz

12
GRESKA: deljenje nulom
GRESKA: nije broj
6
GRESKA: nepoznata operacija
Česte greške i saveti
  • Ne hvataj sve izuzetke praznim blokom catch (Exception e) { } – greška se tada „proguta“ i teško se pronalazi.
  • Redosled blokova catch: specifičniji izuzetak pre opštijeg (npr. NumberFormatException pre IllegalArgumentException), inače program neće da se prevede.
  • Izuzeci nisu zamena za obične provere: ako možeš da proveriš if (b != 0), uradi to.
  • Pročitaj poruku i broj reda iz stack trace-a pre nego što menjaš kod.

Proveri znanje

Otvori kviz

Izuzeci

1. Šta se dešava sa izuzetkom koji niko ne uhvati?




2. Kada se izvršava blok finally?




3. Koji izuzetak baca Integer.parseInt("12a")?




4. Čemu služi reč throws u zaglavlju metode?