Erindid, veatöötlus Iga vähegi suurema programmi juures tekib mittestandartseid olukordi, mis tuleb programmi sujuva töö jätkumiseks lahendada. Näiteks sisestab kasutaja sobimatu numbri või pole kettal piisavalt ruumi vaheandmete salvestamiseks. Üksikjuhtudel võib probleemi lahenduse kirjutada kahtlase olukorra juurde ning tillukeste programmide juures on see päris mõistlik. Kui aga sarnaseid veaohtlikke kohti on palju, siis tuleb sellise lähenemise juures ka kontrollimehhanisme palju kirjutada. See võib omakorda programmi kohmakaks, väheülevaatlikuks ning raskesti muudetavaks teha. Ka on olukord keerulisem, kui vastavalt olukorrale tuleks samale eksitusele erinevalt reageerida. Näiteks sünniaasta ekslikul sisestamisel võib kasutaja tähelepanu sellele juhtida ning aastat kohe uuesti küsida. Kui aga parooli sisestamisel eksitakse, siis tuleks enne uut sisestusluba mõnda aega oodata, et poleks võimalik lühikese ajaga suurt hulka paroole läbi proovida. Traditsiooniliselt on sellistest vigadest teada andmiseks kasutatud muutujaid, mis informeerivad paranduslõiku vajaliku paranduse iseloomust või saadavad vea kirjelduse mingit teed pidi tema jaoks loodud töötlemise koha juurde (nagu näiteks veakood Pacali numbriloomisprotseduuris val). Java keelde loodi ootamatustega toime tulemiseks omaette mehhanism aitamaks probleemiinfot transportida lahenduskohani. Probleemi tekkimisel saab luua ning "lendu lasta" probleemiinfot kandva isendi. Sellest kohast alates programmi täitmine katkestatakse. Kui programmilõigu ümber on pandud neid töötlev püünis, siis jätkatakse programmi täitmist püünisele järgnevalt käsurealt. Püünise puudumisel lendab probleemisend programmist välja, jättes kogu teel töö tegemata. Lihtprogrammide puhul tähendab see programmi töö lõppu. Kui erind aga tekkis näiteks rakendi ülejoonistamisel meetodis paint(), siis jääb see joonistuskord lõpetamata. Probleem tüübimuundel: public class Erind1{ public static void main(String argumendid[]){ String s="aabits"; int nr=Integer.parseInt(s); System.out.println(nr); } } annab töö tulemuseks: C:\TEMP\java>java Erind1 Exception in thread "main" java.lang.NumberFormatException: aabits at java.lang.Integer.parseInt(Integer.java:409) at java.lang.Integer.parseInt(Integer.java:458) at Erind1.main(Erind1.java:4) Lahtiseletatult tähendab arvuti poolt tulnud vastus seda, et lõimes nimega main tekkis erind java.lang.NumberFormatException, kuna sõna aabits ei sobi numbriks. Allpool on näha, et viga tekkis klassi Integer meetodis parseInt failis Integer.java koodireal nr. 409, see meetod oli omakorda välja kutsutud samast failist realt 458 ning see omakorda minu loodud klassi Erind main-meetodis, real nr. 4. Kuna sõna "aabits" pole võimalik numbriks muundada, siis kohas, kus seda üritatakse (Integer.parseInt(s)) lastakse lendu erind. Kuna siin näites pole erindipüünist, siis jäävad read alates tekkinud probleemist täitmata ning numbri väärtust välja ei trükita. import java.io.*; public class Erind2{ public static void main(String argumendid[]){ String s="aabits"; try{ int nr=Integer.parseInt(s); System.out.println(nr); } catch(NumberFormatException ex){ System.out.println("Vigane number"); } System.out.println("Programmi ots"); } } Käivitamisel paistab: C:\TEMP\java>java Erind2 Vigane number Programmi ots Siinses näites satub tekkinud erind püünisesse (try{ ... } catch ...) ning töödeldakse. Kasutajale teatatakse, et number on vigane. Kui erind on kinni püütud, jätkub programmi töö oma tavalist rada pidi järjekorras mööda käsklauseid. Järgnevas näites palutakse kasutajal senikaua numbrit sisestada, kuni ta sellega arvuti jaoks loetavalt hakkama saab. import java.io.*; public class Erind3{ public static void main(String argumendid[]){ boolean korrata=true; while(korrata){ try{ BufferedReader sisse=new BufferedReader( new InputStreamReader(System.in) ); System.out.println("Palun number:"); String rida=sisse.readLine(); int nr=Integer.parseInt(rida); System.out.println("Number "+nr+" sisestati korralikult."); korrata=false; } catch (IOException sisendierind){ System.out.println("Probleem klaviatuuriga"); korrata=false; } catch (NumberFormatException numbriformaadierind){ System.out.println("Valesti sisestatud number. "+ numbriformaadierind.getMessage()+ "\n Proovi veel!"); } } } } Eeltoodud näites võib erind tekkida nii klaviatuurilt lugemisel kui sisestatud sõne numbriks muundamisel. Katsendilause try{ ... } kogub tekkinud erindid kokku ning seejärel saab neid püünistes töötlema hakata. Iga püünis püüab kinni erindi sellest tüübist, mis on kirjutatud püünise parameetriks, või tema alatüübist. Muud liiki erindi püüdmiseks peab olema talle vastav püünis. Erindiklasside hierarhia Erindiklassid on samuti hierarhilised nagu muudki klassid javas. Kõige üldisem on ehk kõige kõrgemal asub klass Throwable (kuid seegi on Objecti alamklass nagu muudki), tema alamklassideks on Error ning Exception. Esimene enamjaolt parandamatute vigade jaoks, teine eriolukordade jaoks, mida on enam lootust lahendada. Erindi suuremateks alamklassides on IOException ning RuntimeException. Esimese alla kuuluvad kõik sisendi-väljundi (Input-Output) erindid, RuntimeExceptioni alla kuuluvad erindid, mille tekkimise võimalusi on programmis palju, peaaegu igas lauses, kus omistatakse või arvutatakse midagi. Näiteks ruutjuur negatiivsest arvust annab erindi tüübist ArithmeticException, kui püütakse viietähelisest sõnast leida seitsmendat tähte, siis tekib IndexOutOfBoundsException, need mõlemad aga on RuntimeExceptioni alamklassid. Kui muud eriolukorra tekkimise võimalused tuleb alati katsendibloki abil kinni püüda või kirjutada meetodi päisesse, et meetodist võib väljuda vastav erind, siis klassi RuntimeException või mõnda tema alamklassi püüda pole kohustuslik. Selline kohustus lihtsalt muudaks programmi kohmakamaks. Vajadusel saab neid aga püüda nagu muidki erindeid nagu eelpool toodud näiteprogrammides Erind1 ja Erind2. Kui tahta ühe catch-püünisega püüda kinni näiteks nii massiivi rajaerindit (ArrayIndexOutOfBoundsException) kui ka tüübimuunduserindit (ClassCastException), siis piisab kui püünise parameetriks kirjutada RuntimeException. Kuna nad mõlemad on viimase alaliigid, siis sobivad nad RuntimeExceptioni kohale samuti nagu inimese kohale sobis nii vanaema, lendur kui insener. Kui kirjutame püüniseks catch(Exception erind), siis jäävad sinna kinni kõik erindid ning catch(Throwable probleem) püüab kinni kõik erindid ja vead. Kui tahame, et mõne erindiklassi puhul töödeldaks selle isendit ühtemoodi, kõiki muid erindeid aga teisiti, siis tuleb spetsiifilisem püünis ettepoole panna. Näiteks: try{ lisaMassiivi(); //varem valmis olev meetod } catch (ArrayIndexOutOfBoundsException me){ System.out.println("Massiiv täis"); } catch(Exception e){ System.out.println("Eriolukord: "+e.getMessage()); e.printStackTrace(); } Erindi meetod printStackTrace() kirjutab välja, millises alamprogrammis (või ka omakorda alamprogrammi alamprogrammis) probleem tekkis. Järgnevalt valik sagedamini kasutust leidvatest vea- ning erindiklassidest koos hierarhilise struktuuri ning kommentaaridega. Klass Seletus Throwable Igasugune probleem Exception Erind ehk parandatav eriolukord ClassNotFoundException Programmi ühte klassi ei leita IOException Sisendi-väljundi erind FileNotFoundException Faili ei leita SocketException Interneti ühenduse erind RuntimeException Igasugu erind, mida ei pea töötlema ArithmeticException Arvutuserind (nt. jagamine nulliga) ClassCastException Tüübimuunduserind IllegalArgumentException Lubamatu argument NumberFormatException Vigane numbriformaat IndexOutOfBoundsException Rajaerind (nt. masiivi olematu element) NullPointerException Muutuja ei osuta isendile, kuigi peaks NoSuchElementException Otsitud elementi ei leita Error Tõsisem viga LinkageError Viga programmi käivitamisel ClassFormatError Klassifail vigane VerifyError Programmi sees lubamatud operatsioonid VirtualMachineError Viga intepreteerimisel OutOfMemoryError Mälu otsas Erindeid saab programmi töö käigus ka ise lendu lasta. Siis saab eriolukorra teate saata töötleva püüniseni ilma selle jaoks muid vahendeid kasutamata. Näiteks: if(nr>100) throw new ArithmeticException("Liiga suur number"); Konstruktori parameetriks antud tekst kuulub loodud erindi juurde kogu tema "eluajaks" ning selle teksti sisu saab kätte erindile saadetava teatega getMessage(). Seda sisu saab vajaduse korral töötlemise juures arvestada. Nagu eelnevalt kirjas, peab peale RuntimeExceptioni alla kuuluvate erinditüüpide kõikidele muudele erinditekkevõimalustele tähelepanu pöörama. Niimoodi kompilaator ühlasi hoolitseb, et kahtlased programmilõigud ei jääks tähelepanuta. Nende ümber peab olema katsendiblokk koos püünisega, kuhu vastavat tüüpi erind sobib, või peab meetod lubama vastavat tüüpi erindi enesest välja: public void trykiKriipse(int hulk) throws Exception{ if(hulk<0)throw new Exception("negatiivne arv"); else for(int i=0; i