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; 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.

 

Ülekate

 

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();   

  }

}

 

Liidesed

 

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.

 

Piiritlejad

 

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).

 

Sisemine klass

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");

      }

    });

  }

}

 

Kokkuvõte.

 

                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.

 

Kokkuvõte

 

                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.

 

 

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 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.

 

Kokkuvõte

 

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.