"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());
}
}
D:\Kasutajad\jaagup\java>java
Punktid4
x=3 y=4
Kaugus koordinaatide alguspunktist= 5.0
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.
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());
}
}
D:\Kasutajad\jaagup\java>java Punktid4a
x=0 y=0
x=1 y=0
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 muutus sisse viia
samuti vaid ühes kohas. Kui soovin muuta väljatrükki ilmekamaks, siis piisab,
kui muudan klassi Punkt4 meetodi kirjutaAndmed(). Uut koodi kasutatakse
siis ka klassi Punkt4Laiend isendi andmete välja
kirjutamisel. Samuti piisab ka vea leidmisel vaid ü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());
}
}
D:\Kasutajad\jaagup\java>java
Punktid4b
x=0 y=0
x=1 y=0
Ü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 võime 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; nr<pArv; nr++){
if(pd[nr] instanceof Punkt4Laiend)
((Punkt4Laiend)pd[nr]).liiguParemale();
}
for(int nr=0; nr<pArv; nr++){
System.out.println(pd[nr].kirjutaAndmed());
}
}
}
D:\Kasutajad\jaagup\java>java
Punktid4c
x=2 y=1
x=1 y=0
x=5 y=5
Kuna
kõik klassid on ühtlasi klassi Object alamklassid (et see on üldine, siis võib kirjutamata
jätta), siis saab kõiki struktuurtüüpide andmeid panna kokku objektimassiivi.
Pärimisel
saab luua meetodeid juurde. Samuti on võimalik panna alamklassis ülemklassi meetod teisiti toimima. Selleks
tuleb lihtsalt kirjutada samanimeline ning samade parameetritega meetod.
class
Inimene{
int vanus;
public void ytleVanus(){
System.out.println("Mu vanus on
"+vanus);
}
}
class
Daam extends Inimene{
public void ytleVanus(){
System.out.println("Sain just
"+(vanus-5)+" aastat vanaks.");
}
}
public
class Tutvustus{
public static void main(String
argumendid[]){
Inimene naine1=new Inimene();
Inimene naine2=new Daam();
naine1.vanus=40;
naine2.vanus=40;
naine1.ytleVanus();
naine2.ytleVanus();
}
}
D:\Kasutajad\jaagup\java>java
Tutvustus
Mu vanus on 40
Sain just 35 aastat vanaks.
Iga
klassi isend suudab käituda nii selle klassi kui ka tema ülemklasside jaoks
loodud olukordades. Nagu siin näiteks on iga Daam samal ajal ka Inimene, sest
Daam pärineb inimesest. Ning samal ajal on nii Inimene kui Daam ka Object, sest
Java hierarhias on juhul, kui ülemklassi pole määratud, ülemklassiks vaikimisi
Object. Kui aga soovime, et meie loodava klassi isend sobiks peale oma
ülemklasside ka muudesse kategooriatesse, tuleb kasutada liideseid.
interface
Viisakas{
public void tervita(String partner);
}
Kui
loodav klass realiseerib liidest Viisakas, peab temas olema meetod tervita, mis saab parameetriks
sõne. Liidese realiseerimine annab klassile kohustuse osata liideses ette antud
tegevusi (ehk meetodeid). Samas annab aga selle klassi isendile asuda igal
pool, kus on lubatud olla vastavat liidest realiseerival klassil.
class
Laps extends Inimene implements Viisakas{
public void tervita(String tuttav){
System.out.println("Tere
"+tuttav);
}
}
class
Koer implements Viisakas{
public void tervita(String nimi){
System.out.println("Auh!");
}
}
public
class Tutvustus2{
public static void main(String
argumendid[]){
Laps juku=new Laps();
juku.vanus=6;
juku.ytleVanus();
Viisakas kylaline1=juku;
Viisakas kylaline2=new Koer();
kylaline1.tervita("vanaema");
kylaline2.tervita("Juku
vanaema");
}
}
D:\Kasutajad\jaagup\java>java
Tutvustus2
Mu vanus on 6
Tere vanaema
Auh!
Mõnes
programmeerimiskeeles (näiteks C++) eraldi liideseid ei ole. Seal lubatakse
pärida korraga mitmest ülemklassist ning loodud klassi isend on samuti
võimeline paiknema nii ühe kui teise ülemklassi jaoks eraldatud kohas. Kuna aga
võib juhtuda, et mõlemal ülemklassil on sama nimega meetod, siis sellest võib
tekkida probleeme. Selleks on javas loodud liidesed, mida realiseerides tekib
vaid kohustus liideses kirjeldatud tegevusi osata. Oskusi eneseid ehk
programmikoodi saab aga pärida vaid ülemklassidest. Kui on vaja mõnele klassile
lisada teise klassi oskusi, siis on võimalik klassi üheks andmeväljaks võtta
teise klassi isend, kellel paluda soovitud tegevusi sooritada. Ülemklasse saab
olla igal klassil vaid üks, liideseid aga võib realiseerida iga klass
piiramatul hulgal.
Objektorienteeritud
programmides püütakse objekti kasutaja eest talle mittevajalikud tunnused ära
peita, et need teda ei häiriks ning et ta ka ei saaks neid muuta ning sellega
objekti toimimist häirida. Vaikimisi on võimalik nii klasse, meetodeid kui
muutujaid kasutada vaid sama paketi (kataloogi) seest. Piiritleja public tähendab, et ligi pääseb
ka väljastpoolt, private lubab kasutada vaid sama
klassi piires ning protected lubab lisaks oma
klassile ka alamklassides. Seda ka juhul, kui alamklassid juhtuvad teises
paketis olema; final tähendab, et enam ei
lubata muuta. Kui muutujal on ees piiritleja final, siis tema väärtus
omistamisjärgselt enam muutuda ei saa, s.t. on konstant. Struktuurtüübi muutuja
jääb final piiritleja korral kuni
oma eluea lõpuni osutama samale isendile. Kui meetod on final, siis ei saa teda üle
katta. Kui klass on final, siis ei saa talle luua
alamklasse. Võtmesõna static meetodi või muutuja ees
tähendab, et meetod või muutuja kuulub klassile, mitte isendile. Tema
kasutamiseks pole vaja luua eraldi isendit. Staatilisi meetodeid ei saa üle
katta. Kui harilikud muutujaid ehk väljad luuakse iga isendi jaoks uued (nt.
kolmel isendil tüübist Inimene on igaühel oma vanus), siis staatilist
muutujat on vaid üks (näiteks muutuja klassi Inimene juures, mis näitab inimeste
arvu). Sellisel muutujal on väärtus siis, kui ühtki inimest pole loodud.
Sellisel juhul on vastava muutuja väärtuseks 0. Samuti on tal üks ja konkreetne
väärtus juhul kui inimesi on rohkem.
Java keeles tohib olla igal klassil vaid üks
ülemklass. Kui tahta oma loodud klassis kasutada muude klasside oskusi, siis on
mõistlik oma klassi luua muutuja, mille abil pöörduda vastava klassi isendi
poole. Näiteks kui soovin, et rakendi pinnale tekiks tekstiväli, siis lasen
rakendil lihtsalt luua isendi klassist TextField ning paigutada viimase oma
pinnale. Märkimisväärne hulk olukordi õnnestub niimoodi lahendada, kus
puuduvate võimaluste hankimiseks kasutatakse muude klasside isendeid.
Loodud isendile saab ligi ja tunnuseid pärida,
sest meie loodud klassis paiknev muutuja osutab sinna. Nii on näiteks kerge
rakendist tekstivälja sisu muuta või pärida. Samas on raske panna tekstivälja
muutuse peale rakendil midagi toimuma, sest loodud tekstiväljal me rakendi
kohta andmed puuduvad. Et saaks tekstiväljalt rakendile teateid saata, selleks
peab esimesel teisele ligipääsuks osuti olema. Kui kirjutame rakendis tf.addActionListener(this), sel juhul annamegi loodud tekstiväljale
võimaluse rakendile ligi pääseda ning tema meetodeid käivitada. Muul juhul
peaks rakend pidevalt iga natukese aja tagant tekstivälja kontrollima, et kas
seal midagi muutunud on ning siis seepeale muutuse avastama ning reageerima.
Lisaks saab kõikjalt kasutada teiste klasside poolt välja pakutud staatilisi
meetodeid, selleks pole isegi vastava klassi isendit vaja luua.
Sisemised klassid võimaldavad ka
teisiti klassi võimalusi laiendada. Sisemisel klassil on kasutada lähteklassi
eksemplari kõik muutujad ja meetodid, lisaks sellele veel need, mis sisemises
klassis eneses juurde kirjeldatud on. Kuna sisemised klassid võivad olla
samaaegselt ka mõne muu klassi alamklassid, siis nõnda saab ühendada kahe
klassi omadused. Järgnevas näites kuulub rakendiklassi SisemiseKlassigaRakend sisse klass SisemineKlass. Hiirevajutustele
reageerimiseks luuakse init-meetodis sisemisest
klassist isend ning pannakse ta rakendile saabuvaid hiire teateid kuulama. Kuna
sisemine klass pääseb ligi rakendi meetoditele ja muutujatele, saab ta ka
ekraanile joonistada. Joonistusvahendi küsimiseks tuleb siiski kirjutada SisemiseKlassigaRakend.this.getGraphics(), sest lihtsalt this tähendaks sisemise
klassi seest kutsutuna hoopis sisemise klassi eksemplari, kelle valduses aga
ekraani pole, millele joonistada.
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
public class SisemiseKlassigaRakend extends Applet{
class SisemineKlass extends
MouseAdapter{
public void
mousePressed(MouseEvent e){
SisemiseKlassigaRakend.this.getGraphics().
drawRect(e.getX(), e.getY(),
10, 10);
}
}
public void init(){
addMouseListener(new
SisemineKlass());
}
}
Sisemise klassi saab luua ka isendi loomise ajal.
import java.applet.Applet;
import java.awt.event.*;
import java.awt.*;
public class AnonyymneKlass extends Applet{
final Button b=new
Button("Algus");
ActionListener anonyymneKuular=new
ActionListener(){
public void
actionPerformed(ActionEvent e){
b.setLabel("Ots");
}
};
public void init(){
add(b);
b.addActionListener(anonyymneKuular);
}
}
Veel
lühem võimalus on toodud allpool. Siin luuakse sündmusekuularit realiseeriv
isend otse addActionListeneri sulgude sees.
import java.applet.Applet;
import java.awt.event.*;
import java.awt.*;
public class AnonyymneKlass2 extends Applet{
Button b=new Button("Algus");
public void init(){
add(b);
b.addActionListener(new
ActionListener(){
public void
actionPerformed(ActionEvent e){
b.setLabel("Ots");
}
});
}
}
Arvutusvõimsuste ja programmimahtude kasvades on programmide arengut
pidurdama jäänud programmeerija tööaeg ning programmides leiduvad vead.
Loodeti, et tupikust päästab objektitehnika, kuid ka see ei osutunud
imevahendiks. Nii nagu protseduurid aitasid liigendada jadaprogramme, aitasid
objektid süstematiseerida protseduure. Kui aga klasse saab palju, on nendega
samuti raske toime tulla. Javas on loodud lisatasemeks paketid, kuhu
koondatakse sarnaste omadustega klassid. See aitab sobivat klassi leida. Samuti
saavad ühes paketis paiknevad klassid lubada vastastikku üksteise omadusi
kasutada.
Järgmiseks lahenduseks on pakutud
komponenttehnoloogia. Selle järgi pannakse programm kokku kasutamisvalmis
tükkidest. Näiteks võivad tükkideks olla kalender, liikuv auto, draiverite
komplekt. Programmeerijal tuleb nad sobivalt ühendada ning loota, et tulemusena
valmib kasutajasõbralik programm. Ühte komponenti võivad kuuluvad klassid eri
pakettidest, samuti võib võrguprogrammide puhul üks komponent asetseda laiali
mitmes masinas. Komponente saab luua mitmetes programmeerimiskeeltes. Nii on
Java komponentideks oad (Bean), Microsoftil ActiveX komponendid. Et nad
omavahel suhelda saaksid, on kirjeldatud sillad, millisel kujul komponendid
andmeid vahetada saavad.
Mõningaid Java abil loodud
komponente nimetatakse ubadeks (bean). Et neid võiks julgelt kõikjal kasutada,
on neil samasugused turvapiirangud nagu rakenditelegi. Vabalt saab omale
laadida BDK, mille abil saab hiirega komponente kokku lohistada ning rakendiks
muuta. Seal saab kasutada ka neid komponente, mille loomisel pole BDK võimalusi
silmas peetud, kuid järgides komponentidevahelise suhtlemise reegleid on
võimalik oma komponenti teistega paremini suhtlema panna.
Objektorienteeritud
programmeerimine loodi vigade paremaks avastamiseks, suurte programmide
lihtsamaks kirjutamiseks, programmikoodi korduvkasutuseks ning reaalse maailma
objektide paremaks kirjeldamiseks.
Enne isendi loomist peavad olema
tema omadused ja oskused kirjeldatud klassis muutujate ja meetodite abil.
Klassi isendi oskusi saab suurendada, luues klassile alamklassi. Alamklassis
saab meetodeid juurde kirjeldada, samuti kasutada ülemklassi meetodeid. Kui
soovida ülemklassi meetodi tööd muuta, tuleb alamklassis kirjeldada sama nime
ja parameetritega, kuid alamklassi jaoks vajaliku koodiga meetod. Alamklasside
isendit saab kasutada kõikjal, kuhu sobib ülemklassi isend, sest alamklasside
omad mõistavad kõiki neid tegevusi sooritada, mis ülemklassis kirjeldatud on.
Nad kas pärivad need oskused muutumatuna, või muudavad nende sisu meetodi
ülekatmise abil. Kui soovida, et
loodava klassi isend saaks tegutseda ka mujal kui oma ülemklassile ettenähtud
kohas, tuleb kasutada liidest. Liidese realiseerimiseks peavad loodavad klassis
olema kirjeldatud kõik oskused, mis liideses kirjas on.
Piiritlejate public, protected ja private abil saab määrata
ligipääsetavust. Piiritleja static näitab, et temaga märgitu kuulub klassi, mitte isendi
juurde.
Lihtandmetüüpideks Java-s on byte, short, int, long, float, double, char ja boolean. Nendest saab kombineerida
struktuurtüüpe. Lihttüübi muutujas on väärtus, struktuurtüübi omas osuti
isendile. Struktuurtüübist muutuja tühivääruseks on null. Nii liht- kui struktuurtüüpi
andmeid saab paigutada
massiivi.
Objektid
· Loo klass Inimene väljadega eesnimi ja synniaasta
· Koosta klassist kaks eksemplari, anna väljadele väärtused
· Koosta inimeste andmetest massiiv. Trüki välja inimesed, kes on sündinud varem kui 1980.
· Koosta Inimese alamklass Kodanik. Lisa väli "perekonnanimi"
· Koosta massiiv, kus on nii Inimesed kui Kodanikud
· Trüki massiivist välja kõik eesnimed, kodanikel ka perekonnanimed.
· Loo Kodanikule konstruktor andmete sisestamiseks.
· Lisa kodanikule ka sünniaastata konstruktor. Sel juhul
· tuleb sünniaasta väärtuseks -1.
· Lisa ka Inimesele konstruktor andmete sisestamiseks.
· Kutsu Kodaniku Konstruktorist välja Inimese konstruktor.
Klassid ja isendid
· Loo klass Loom väljaga "nimi" ning meetodid "tutvusta" ning "nimepikkus".
· Tee loomale alamklassid "Koer" ja "Lehm", kus tuleb katta üle meetod "tutvusta" nii, et selles väljastataks ka looma liik ning looma nime pikkus. Koosta kestprogramm loodud klasside katsetamiseks.
· Lisa klassile Loom staatiline väli loomadeArv, mille väärtus suureneb iga looma (ka koera ja lehma) lisamisel (st klassi Loom konstruktori käivitamisel). Muuda klassi Loom meetodit "tutvusta" nii, et see väljastaks loomade arvu. Kutsu klassi "Koer" meetodist "tutvusta" välja klassi "Loom" meetod "tutvusta".
·
Muuda konstruktoreid nii, et isendi loomisel saaks
talle koheselt ka nime omistada. Loo
staatiline meetod loomade arvu väljastamiseks ning käivita see ilma isendit kasutamata.
Punktid
· Loo klass Punkt väljadega x ja y.
· Loo sellest klassist kaks isendit ja anna nende väljadele väärtused.
· Trüki mõlema punkti väljade väärtused ekraanile.
· Kopeeri ühe punkti andmed teise punkti andmeteks.
· Paiguta loodud kaks punkti massiivi.
· Loo massiiv saja punkti ning juhuslike andmetega.
· Määra viiekümnenda punkti x-koordinaadi väärtuseks 243.
·
Kirjuta välja punkti andmed, mille x-koordinaadi
väärtus on vähim.
Iga vähegi suurema programmi juures tekib
mittestandardseid 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
täiesti kasutatav. 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. Samuti võib
juhtuda, et veale on kasulik reageerida selle tekkimise kohast koodis küllalt
kaugel. 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.
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 teateid 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. Näide.
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 arvuks. 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 kümnendsüsteemis arvuks 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.
Järgneva näite
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.
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 viietähelisest
sõnast püütakse leida seitsmendat tähte, siis
tekib IndexOutOfBoundsException. Need mõlemad on RuntimeExceptioni alamklassid. Kui muud eriolukorra tekkimise võimalused
tuleb alati katsendibloki abil kinni püüda või kirjutada meetodi päisesse
teade, et meetodist võib väljuda vastav erind, siis klassi RuntimeException või ükskõik millist tema
alamklassi erindit 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 mõlemad esimesed 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");
Erindi 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 erindi tekkevõimalustele tähelepanu pöörama.
Niimoodi kompilaator ühtlasi 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<hulk; i++)System.out.print("-");
}
Sel juhul tuleb loodud erindiga tegelda siinset
meetodit välja kutsuvas meetodis.
Soovi või
vajaduse korral saab ka ise luua uusi erinditüüpe, s.t. klassi Exception alamklasse.
Erindid aitavad probleemiteavet transportida
tekkekohast töötlemiskohta. Erindid püüab kokku try{} katsendiblokk ning
töödelda saab neid catch-püünistes. Töötlema ei pea vaid RuntimeExceptioni ning tema alamklasside
erindeid.