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);
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.
"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.
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; 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());
}
}
}
Kuna kõik klassid on ühtlasi
klassi Objekt 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();
}
}
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 ta 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 olla 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");
}
}
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 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
final piiritleja korral jääb see muutuja kuni oma eluea lõpuni osutama samale
isendile. Kui meetod on final, siis teda ei saa ü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 harilikke muutujaid ehk välju luuakse iga isendi jaoks uued (n.t.
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
keskmist pikkust).
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.
Staatilisi meetodeid saab nagunii kõikjalt kasutada, selleks pole isegi teise
klassi isendit vaja luua. 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.
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 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");
}
});
}
}
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 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.
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.
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<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.