Objektno-orjentisano programiranje

Vežbe 6

Parsiranje stringa u proste tipove podataka

U programiranju, pojam “parser” se odnosi na softversku komponentu koji vrši analizu struktura podataka kako bi ih preoblikovao ili izvukao određene informacije.

Ugrađeni parseri u Javi koji prevode string u proste tipove su:

Scanner

Scanner klasa u programskom jeziku Java omogućava jednostavan unos podataka sa standardnog ulaza ili iz drugih izvora podataka, kao što su datoteke. Ova klasa se nalazi u paketu java.util i pruža različite metode za čitanje različitih tipova podataka, poput celih brojeva, decimalnih brojeva, nizova karaktera itd.

Evo nekoliko ključnih pojmova i primera korišćenja Scanner klase:

Na sledećem linku možete videti sve metode klase Scanner.

Uz to, Scanner klasa se može koristiti i za čitanje podataka iz datoteka pomoću konstruktora koji prima File objekat.

import java.io.File;

Scanner fileScanner = new Scanner(new File("putanja/do/datoteke.txt"));
    // Rad sa Scanner objektom za čitanje datoteke

Ovde je postavljen Scanner objekat da čita podatke iz datoteke “putanja/do/datoteke.txt”. Treba voditi računa o obradi izuzetaka, naročito kada radimo sa datotekama koje možda ne postoje.

Zadatak 1

Napisati jednostavan program u Javi koji sa standardnog ulaza čita dva realna broja i ispisuje njihov količnik.

Pitanje

U kojim slučajevima program, koji predstavlja rešenje zadatka 1, neće raditi i zašto?

Izuzeci

Pri pozivu nekog metoda, moguće je da dođe do različith vrsta neregularnih stanja.

Jedan način rukovanja takvim (izuzetnim) situacijama jeste proveravanje svih kritičnih uslova pre navodjenja dela koda koji može biti ‘ugrožen’ neproverenim greškama. Mana ovakvog rada jeste nečitak kod u kome se mešaju provera uslova za regularan tok programa i obrada u slučaju neispunjenosti uslova ikod koji se izvršava pri regularnom radu.

Da bi obezbedili razdvajanje koda koji se izvršava kada program teče glatko od obrade ‘neregularne’ situacije objektni jezici uvode:

  1. posebnu vrstu upravljačkog bloka – try/catch/finally i
  2. poseban mehanizam obaveštavanja da se i šta desilo – instanciranjem objekata specijalnog tipa i njegovim prosleđivanjem onom delu koda koji je u stanju da ga ‘obradi’

U Java programiranju, “izuzetak” (eng. exception) je događaj koji narušava normalan tok izvođenja programa. Kada dođe do izuzetka, Java generiše objekat koji predstavlja tu iznimku. Taj objekat sadrži informacije o tome šta je pošlo po zlu, uključujući i trag steka (stack trace), koji pokazuje gde se izuzetak dogodio.

Izuzeci u Javi se koriste kako bi se nosili sa neočekivanim situacijama ili greškama tokom izvođenja programa. To može uključivati situacije poput deljenja sa nulom, pristupa nepostojećem elementu niza ili bilo koje druge situacije koje bi mogle dovesti do problema tokom izvođenja programa.

Primer koda koji baca i hvata izuzetak u Javi može izgledati ovako:

public class PrimerIzuzetka {
    public static void main(String[] args) {
        try {
            // Blok koda u kojem može doći do izuzetka
            int a = 10;
            int b = 0;
            System.out.println(a / b);
        } catch (ArithmeticException e) {
            // Blok koda koji se izvršava ako dođe do izuzetka
            System.out.println("Došlo je do aritmetičke greške: " + e.getMessage());
        }
    }
}

U ovom primeru, JVM će prvo pokušati da podeli dva broja. Pošto deljenje nulom nije moguće, biće bačen izuzetak tipa ArithmeticException. Taj izuzetak će biti uhvaćen u catch delu i izvršiće se kod koji se tu nalazi.

throw

throw je ključna reč koja se koristi za ručno izazivanje (bacanje) izuzetka. Kada se throw koristi u programskom kodu, označava trenutnu tačku gde se programski izvršavanje zaustavlja i prenosi kontrolu do bloka koda koji je zadužen za hvatanje (catch) tog izuzetka.

Koristi se u sledećem kontekstu:

public class PrimerBacanjaIzuzetka {
    public static void main(String[] args) {
        try {
            // Izazivanje izuzetka
            throw new IllegalArgumentException("Ovo je poruka izuzetka");
        } catch (IllegalArgumentException e) {
            // Hvatanje i obrada izuzetka
            System.out.println("Uhvaćen izuzetak: " + e.getMessage());
        }
    }
}

U ovom primeru, throw se koristi za izazivanje izuzetka tipa IllegalArgumentException sa odgovarajućom porukom. Zatim se izuzetak hvata i obrađuje u bloku catch, gde se ispisuje poruka izuzetka. Nakon throw je neophodno proslediti objekat. Objekat kojim se opisuje greška ne može pripadati proizvoljnoj klasi. Klasa kojoj objekat pripada mora da se nađe u lancu nasleđivanja klase Throwable, jer sam Javin kompajler vrši proveru tipa objekta pri prevođenju throw komande.

Korišćenje throw je posebno korisno kada programer želi signalizirati da se desila određena greška ili neispravno stanje koje zahteva specifičnu reakciju. Ovo može uključivati proveru unosa korisnika, rukovanje s resursima, ili bilo koje druge situacije gde je potrebno obavestiti sistem o nečemu neočekivanom. Važno je odabrati odgovarajući tip izuzetka koji najbolje opisuje prirodu problema.

Exception i RuntimeException

Za skoro sve izuzetke koji su obuhvaćeni podklasama klase Exception potrebno je uključiti kod za njihovu obradu ili program neće proći kompajliranje, tj. prevodilac NE DOZVOLJAVA njihovo ignorisanje.

RunTimeException (nasleđuje klasu Exception) izuzeci se tretiraju drugačije, jer se u njih najčešće svrstavaju izuzeci koji se pojavljuju kao posledica ozbiljnijih grešaka u kodu, čija obrada ne bi mogla ništa značajno promenila. Prevodilac dozvoljava njihovo ignorisanje.

Neke podklase klase RuntimeException:

Proveravani i neproveravani izuzeci

Na osnovu toga da li prevodilac insistira njihovom proveravanju ili ne, izuzeci se dele u dve grupe:

public class Test {
    static void exceptionInMethod() throws Exception {
        throw new Exception("Exception");
    }

    // posto se Exception hvata unutar main metoda, main metod ne mora biti oznacen kljucnom reci throws
    public static void main(String[] args) {
        try {
            exceptionInMethod();
        }
        catch(Exception ex) {
            System.out.println(ex.getMessage());
        }
    }
}

Obrada izuzetaka i višestruki catch blokovi

U delu try/catch bloka

catch (ExceptionType1 identifier) {
 // …
}

kod koji obrađuje izuzetak se poziva za ExceptionType1 ili bilo koju njegovu podklasu.

Ako je u nekom try/catch bloku navedeno nekoliko catch blokova sa nekoliko tipova izuzetaka u istoj klasnoj hijearhiji, potrebno je blokove postaviti tako da se prvo hvata izuzetak najniže podklase, pa redom prema najvišoj superklasi.

// neispravna sekvenca catch blokova
// neće se prevesti
try {
// try block code
} catch(Exception e){ ... }
catch(ArithmeticException e) { ... 

// ispravna sekvenca catch blokova
try {
// try block code
} catch(ArithmeticException e){ ... }
catch(Exception e) { ...

Lanac hvatanja izuzetaka

Pretpostavimo da se u main() metodu u nekom trenutku poziva metod a() u kome se poziva metod b(), a u njemu metod c().

c() {

}

b() {
    c();
}

a() {
    b();
}

main() {
    a();
}

O čitavom lancu pozivanja i o tome koji je metod dokle stigao sa izvršavanjem vodi računa JVM. Struktura u kojoj beleži lanac pozivanja se naziva call stack. Zahvaljujući call stack-u JVM zna koja je prva naredna komanda koja treba da bude izvršena nakon regularnog završetka nekog pozvanog metoda ili kome treba da prosledi generisani izuzetak u slučaju neregularnog završetka pozvanog metoda. Ako JVM ne uspe ni u jednom metodu na call stack-u da pronađe obradu izuzetka program prekida rad.

Exception objekti

Klasa Throwable` je bazna klasa za sve Java izuzetke i ima:

public class Test {
    static void b() throws Exception {
        throw new Exception("Poruka");
    }

    static void a() throws Exception {
        try {
            a();
        }
        catch(Exception ex) {
            ex.printStackTrace();
            /**
             * b
             * a
             * main
            */
            ex.fillInStackTrace();
            throw ex;
        }
    }

    public static void main(String[] args) {
        try {
            a();
        }
        catch(Exception ex) {
            ex.printStackTrace();
            /**
             * a
             * main
            */
        }
    }
}

Definisanje novih izuzetaka

Dva osnovna razloga:

Primer:

// exception class – minimalna definicija
public class DreadfulProblemException extends Exception
{
    // Konstruktori
    public DreadfulProblemException(){ } // Default constructor
    public DreadfulProblemException(String s) {
        super(s); // Call the base class constructor
    }
}

Šta još dodati?

finally

Koristi se za pospremanje (clean-up) tj. oslobađanje resursa (npr. zatvaranje fajla). Asociran je sa određenim try blokom (kao i catch blokom) i izvršava se uvek nakon što se završi izvršavanje try i catch bloka. Može se koristiti i s try blokom koji sadrži kod koji ne baca nikakav izuzetak:

Zadatak 2

Iskoristite try/catch blokove kako bi uhvatili izuzetke koji se mogu pojaviti u zadatku 1 i obradite ih (ispišite neku poruku).

Zadatak 3

Uradite zadatak 2 samo što će se vrednosti čitati iz fajla umesto sa standardnog ulaza.

Zadatak 4

Kreirajte izuzetak BrojeviSuJednaki. Ukoliko je apsolutna vrednost razlike brojeva, koji se čitaju iz fajlu u zadatku 2, manja od 0.0001 potrebno je baciti izuzetak BrojeviSuJednaki i obraditi ga.