Struktuursed andmetüübid Struktuurtüüp Kaheksa lihttüüpi on keelde sisse ehitatud, programmeerija neid muuta ei saa. Vajadusel saab lihttüüpidest luua stuktuurse andmetüübi. Näide: class Punkt{ int x, y; } Niimoodi kirjeldame vaid tüübi. Tüübist saab luua isendeid. public class Punktid1{ public static void main(String argumendid[]){ Punkt a=new Punkt(); a.x=5; a.y=3; System.out.println("a="+a+" a.x="+a.x+" a.y="+a.y); } } annab oma töö tulemuseks rea a=Punkt@1fa4d40b a.x=5 a.y=3 Nagu näha, peitub meie jaoks mõistlik info Punkti a väljadel x ja y, a enese väljatrükkimisel näeme vaid tema räsikoodi. Struktuurse tüübi muutuja näitab kohale, kust leida tema välju. See on sarnane Pascali ja C viidatüüpi muutujale, mille väärtuseks oli mäluaadress. Kuna java-programm töötab virtuaalmasinas ning ta pole otseselt seotud arvuti enese füüsilise mäluga, siis käib ka tehniline pool teisiti. Struktuurse tüübi muutujat nimetatakse osutitüüpi muutujaks ehk osutiks. Java keeles saab nii väärtus- kui osutimuutjaid kasutada ilma probleemideta ka meetodite parameetritena ning tagastusväärtustena. Näite juures võib imelikuna tunduda rida Punkt a=new Punkt(); mille juures luuakse uus isend tüübist Punkt ning pannakse talle osutama muutuja a. Kui kirjutaksime vaid Punkt a; , siis kirjeldatakse ainult muutuja a, ilma tema jaoks mälu eraldamata. Sõna Punkt kaks korda kirjutamine võib tunduda liiasus, kuid ei ole. Edaspidi selgub, et juhul, kui oleme kirjeldanud PuutePunkti erinevused Punktist, siis on tähendus ka lausel Punkt ristmik=new PuutePunkt(asfalttee, kruusatee); Konstruktor Loodud isendile aitab väärtusi sisestada konstruktor. class Punkt2{ int x, y; public Punkt2(int uus_x, int uus_y){ x=uus_x; y=uus_y; } } Konstruktoril peab olema klassiga sama nimi. Ta käivitatakse isendi loomisel new abil. Piiritleja public näitab, et konstruktorit on võimalik käivitada ka väljastpoolt klassi. public class Punktid2{ public static void main(String argumendid[]){ Punkt2 a=new Punkt2(5, 3); Punkt2 b=new Punkt2(1, 1); System.out.println(b.x-a.x); } } annab käivitamisel vastuseks -4 (ehk 1-5). Nagu ennist kirjas oli, "sisaldavad" vaid lihttüüpi (8 tk) muutujad neisse pandud väärtusi. Ülejäänud muutujad osutavad vastavat tüüpi isendile (või ei osuta kuhugi, sellisel juhul on selle muutuja väärtuseks null (sõnana)). Järgnevas näites pannakse kaks osutit osutama ühele isendile. public class Punktid3{ public static void main(String argumendid[]){ Punkt2 a=new Punkt2(5, 3); Punkt2 b=new Punkt2(1, 1); a=b; // ka a hakkab osutama b jaoks loodud kohale b.x=7; System.out.println(a.x+" "+a.y); } } väljastab a väljade väärtusteks 7 ja 1. Esialgu luuakse kaks isendit. Ühe väljade väärtusteks saavad 5 ja 3, teisele 1 ja 1. Esimesele pannakse osutama a, teisele b. Siis pannakse ka a osutama teisele isendile, esimene isend jääb sootuks tähelepanuta ning ta koristatakse mälust. Teise isendi väljad on endiselt 1 ja 1. Kui nüüd kirjutatakse b.x=7, siis pannakse teise isendi x-väljale 7. Kuna ka a viitab nüüd teisele isendile, siis kirjutatakse välja 7 ja 1. Et saaks omistamisega andmeid kopeerida nii nagu lihttüüpide korral, selleks tuleb luua isendist koopia, s.t. sama klassi isend samade muutuja väärtustega. Objektid programmeerimisel "Arvutimaailm lendab lõhki! Ja suurimaks probleemiks pole mitte viirused. Pole ka häkkerid ja kräkkerid. Suurimaks probleemideks programmides on vead!" Suurelt jaolt vigade leidmise tarvis hakatigi otsima programmeerimisse uusi vahendeid. Objektorienteeritud programmeerimine on viimase paarikümne aasta jooksul aidanud vigu leida ning tal on ka muid kasulikke omadusi. Suuremate projektide korral aitab objektideks jagamine tõsta abstraktsiooni taset ning valmistada ja kontrollida iseseisvaid programmi osi eraldi. Samuti nagu alamprogrammide loomine aitab orienteeruda suures käskude jadas, aitab objektide loomine orienteeruda alamprogrammide rägastikus. Valmis programmilõike saab korduvalt kasutada vaid siis, kui on võimalik nad arhiivist üles leida. Ka siin on kasu kõrgemast abstraktsioonitasemest. Mõnikord on lihtsalt mugav reaalset elu jäljendavaid programme panna kokku objektidest, sest ka tegelikus elus on meil tegemist suhteliselt iseseisvate üksustega (auto, inimene ... ). Java programmis peab olema kogu kirjutatav kood meetodi sees. Meetod omakorda klassi ehk objektitüübi sees. Klassid omakorda moodustavad paketi. Lihtsama programmi puhul public class Tervitus{ public static void main(String argumendid[]){ System.out.println("Tere"); } } on koodiks System.out.println("Tere"); meetodiks main ning klassiks Tervitus. Lühikeste programmide korral saab kogu programmi teksti kirjutada main()- meetodi sisse ning objektitüüp tema ümber ei ole millekski kasulik. Kui aga programmis tuleb luua uus tüüp ning sellest tüübist isendiga suhtlema hakata, siis saab programmi teksti muuta tavakeelele lähemaks ning isendeid kergemini eraldi kontrollida. class Punkt4{ int x, y; public Punkt4(){ this(0, 0); } public Punkt4(int uus_x, int uus_y){ x=uus_x; y=uus_y; } public String kirjutaAndmed(){ return "x="+x+" y="+y; } public double kaugusNullist(){ return Math.sqrt(Math.pow(x, 2)+Math.pow(y, 2)); } } public class Punktid4{ public static void main(String argumendid[]){ Punkt4 p=new Punkt4(3, 4); System.out.println(p.kirjutaAndmed()); System.out.println("Kaugus koordinaatide alguspunktist= "+ p.kaugusNullist()); } } Kas või selle näite koostamisel oli klassideks jagamine abiks. Kirjutasin programmi valmis ning panin käima. Selgus, et käivitamisel pakkus ta x ja y väärtuste 3 ja 4 juures kauguse nullist 2.2 juures. Kuna peaprogrammis on vaid palve saata oma kaugus, siis seal polnud arvutusviga teha võimalik. Seetõttu nägin, et punkt "käitub imelikult" ning teadsin kohe tema juurest viga otsima minna. Selgus, et olin ruutjuure lihtsalt kaks korda võtnud. Pärimine Kui soovida klassile Punkt4 omadusi lisada, siis pole selleks vaja terve klassi koodi uuesti ümber kopeerida. Saab luua laiendatud võimalustega klassi, mis pärib klassist Punkt4 tema omadused ning lisaks saab talle meetodeid juurde kirjutada. class Punkt4Laiend extends Punkt4{ public void liiguParemale(){ x++; } } class Punktid4a{ public static void main(String argumendid[]){ Punkt4Laiend p=new Punkt4Laiend(); System.out.println(p.kirjutaAndmed()); p.liiguParemale(); System.out.println(p.kirjutaAndmed()); } } Lisaks ümberkirjutamise vaeva vähenemisele näitab selline toimimine ka paremini välja, millise klassi isend mida oskab ning millised oskused tal võrreldes eelnevaga on juurde tulnud. Kuna sama koodi on vaid kord, siis muutmise vajaduse puhul tuleb seda muuta samuti vaid ühes kohas. Kui soovin muuta väljatrükki ilmekamaks, siis piisab, kui muudan klassis Punkt4 meetodi kirjutaAndmed(). Uut koodi kasutatakse siis ka klassi Punkt4Laiend isendi andmete välja kirjutamisel. Samuti piisab ka vea leidmisel ühes kohas parandamisest. Kuna isend klassist Punkt4Laiend oskab teha kõike, mida klassi Punkt4 isend, siis teda saab kasutada kõikjal, kus Punkt4-gi. Analoogia tavaelust võiks olla, et näiteks lendur suudab kõike, mis tavaline inimenegi (süüa, juua, magada, kellaaega öelda jne.), kuid lisaks sellele suudab ta veel lennukit juhtida. Kellaaega võime küsida ükskõik millise inimese käest, kuid lennukijuhtimist saame paluda vaid lendurilt. Seetõttu on omistamine inimene=lendur ehk Punkt4=Punkt4Laiend täiesti lubatud ja võime levinumat tegevust (nt. kirjutaAndmed()) küsida Punkt4 tüüpi muutuja käest teadmata, et tegemist on tegelikult isendiga tüübist Punkt4Laiend sarnaselt nagu võime kella küsida vastutulevalt inimeselt, teadmata, et vastutulija on ametilt lendur. Kui aga on vaja paluda ülemklassile ainuomast tegevust, siis tuleb enne veenduda, et meil kasutada olev isend ka selleks võimeline on. Samuti nagu lennujuhti vajades teeme kindlaks, et rooliasuja tõepoolest ikka lendur on. Et ülemklassi meetod välja kutsuda, tuleb muutujas peituva isendi tüüp muundada vastavaks. Kui kirjutaksime (Punkt4Laiend)p.liiguParemale(), siis saaks kompilaator aru, et tuleks kõigepealt muutuja p poolt osutatud isendil paluda paremale liikuda ning seejärel saadud tulemus muundada tüübiks Punkt4Laiend. Sel juhul kompilaator vaatab, et Punkt4-l palutakse paremale liikuda, mida ta ei oska ning tekib veateade. Kui aga lisada veel ühed sulud, siis on tulemuseks ((Punkt4Laiend)p).liiguParemale() ehk muutujas p olnud isend Punkt4Laiend'iks muundatuna ning temal juba on võimalik lasta paremale liikuda. Juhul, kui muutujas p siiski aga pole Punkt4Laiendi tüüpi isendit tekib viga. Samuti nagu inimesel, kes pole lendur, pole võimalik lenduritunnistust näidata. Kui tüüp vastab oodatule, on kõik korras. class Punktid4b{ public static void main(String argumendid[]){ Punkt4 p=new Punkt4Laiend(); System.out.println(p.kirjutaAndmed()); ((Punkt4Laiend)p).liiguParemale(); System.out.println(p.kirjutaAndmed()); } } Üksikjuhul paistab säärasest muundamisest vähe kasu olema, kuid sellise omistamisvõimaluse tõttu saab ühest allikast põlvnevate isendite andmeid ühes massiivis koos hoida. Lisaks saab neilt paluda seda, mida nad kõik oskavad ning kui oleme kindlaks teinud, et vastaval isendil rohkem oskusi on, saame temalt ka nende rakendamist küsida. Nii saame inimeste andmed üheskoos hoida, sõltumata sellest, kas mõned neist on lendurid või mitte. Kui oleme aga inimese käest teada saanud, et ta lendur on, siis saame tal paluda lennuki rooli asuda. Operaator instanceof kontrollib, kas kontrollitav isend suudab vastavale klassile seatud ülesandeid täita, s.t. kas ta on sellest klassist või tema alamklassist andes vastuseks true või false. class Punktid4c{ public static void main(String argumendid[]){ int pArv=3; Punkt4 pd[] = new Punkt4[pArv]; pd[0]=new Punkt4(2, 1); pd[1]=new Punkt4Laiend(); pd[2]=new Punkt4(5, 5); for(int nr=0; nrjava 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 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