Alla pseudokoodina luokan määrittely. Hakasuluilla merkityt ovat ehdollisia.
[Muuntimet] class LuokanNimi [extends YliluokanNimi] [implements RajapinnanNimi] {
//Luokan runko. Täällä määritellään mm. attribuutit ja metodit
}
Muuntimet:
Tähän mennessä olemme käyttäneet muuttujia metodeissa ja pääohjelmassa, jolloin ne ovat käytettävissä vain metodin suorituksen ajan. Olioilla on yhden metodin suoritusta pidempi elinkaari. Attribuutit kuvaavat olion rakenteen ja attribuuttien arvot yhdessä muodostavat olion tilan. Attribuutit ovat olion muuttujia, joissa voi säilyttää kyseiseen olioon liittyviä tietoja. Attribuuttien määrittely koostuu seuraavista osioista.
[Näkyvyysmääre] [static] [final] [transient] [volatile] tyyppi attribuutinNimi;
Näkyvyysmääreet toteuttavat tiedon suojausta eli olio piilottaa sisäisen toteutuksensa muilta. Olio voi itse kontrolloida, miten attribuutteja käytetään. Olio voi tarjota esimerkiksi painon asettamiseen metodin, jossa tarkistetaan aina, ettei uusi paino ole negatiivinen. Attribuutit piilotetaan yleensä muiden luokkien olioilta, määritellään yleensä saantimetodit, joilla attribuuttien arvon voi kysyä ja asettaa.
Jos näkyvyysmäärettä ei anneta, käytetään oletusta eli attribuutti on käytettävissä saman pakkauksen luokissa. Pakkaukset ovat Javan tapa koota yhteenkuuluvia luokkia kokonaisuuksiksi. Oletusnäkyvyydellä varustettu attribuutti näkyy jälkeläisluokissa siinä tapauksessa, että jälkeläisluokat ovat samassa pakkauksessa.
Hyvä tapa on koota attribuuttien määrittelyt luokan määrittelyn alkuun. Vakiot on tapana määritellä ennen muita attribuutteja. Esimerkki:
import java.awt.Color;
/**
* Esimerkki attribuuttien määrittelystä.
*/
public class VarillinenYmpyra {
/**
* Vakio OLETUSSÄDE, jonka arvoksi asetetaan 10. Oletussädettä ei voi
* muuttaa, koska muuttuja on lopullinen.
*/
public static final int OLETUSSÄDE = 10;
/** Keskipisteen x- ja y-koordinaatit. */
private int x, y;
/** Säde, jonka arvoksi sijoitetaan oletussäteen arvo. */
private int säde = OLETUSSÄDE;
/**
* Kuvion väri - viite Color-luokan olioon. Alustetaan viittamaan samaan
* olioon, johon Color-luokassa määritelty luokkakohtainen vakioarvoinen
* muuttuja RED viittaa.
*/
private Color väri = Color.RED;
}
Koodin monistaminen lisää koodirivien määrää, tekee ohjelmasta vaikeaselkoisen, altistaa virheille ja todennäköisesti monistaa virheitä. Usein tarvittavista algoritmeista tehdään nimettyjä algoritmeja eli metodeja. Metodille annetaan nimi ja määritellään algoritmin toiminta. Tämän jälkeen metodia voidaan kutsua sen nimellä aina kun algoritmia tarvitaan. Kutsumisella tarkoitetaan siis sitä, että metodi käydään suorittamassa ja suorituksen jälkeen palataan automaattisesti kutsua seuraavaan kohtaan.
Oliolähestymistavassa metodit määrittävät olion toiminnot. Ne määräävät, mitä toimintoja olio osaa suorittaa tai kuinka olion tilaa voi muuttaa. Javassa kaikki ohjelman toiminta on luokissa määritellyissä metodeissa. Metodit piilottavat toimintojen yksityiskohtia. Tiedämme, mitä metodi tekee, mutta emme tiedä, miten metodi sen tekee. Toinen hyvä sääntö on, että metodi tekee vain yhden tehtävän, mutta hyvin.
[näkyvyysmääre] [abstract] [static] [final] [synchronized] paulautusarvonTyyppi metodinNimi ([parametrit]) [throws Poikkeuksentyyppi] {
//Metodin suoritus
}
Metodin palautusarvon tyyppi kertoo kutsujalle, minkä tyyppisen arvon metodi palauttaa. Void sanalla ilmaistaan, että metodi ei palauta mitään. Joissain ohjelmointikielissä palautusarvollisia metodeja kutsutaan funktioiksi ja palauttamattomia metodeja proseduureiksi.
Metodin parametrit ovat metodin muuttujia, jotka saavat arvon metodia kutsuttaessa. Kutsussa annettavat arvot ovat todellisia parametreja (actual argument expression) kun taas metodin määrittelyssä esiintyvät parametrit ovat muodollisia parametreja (formal parameter)
public class Laskuri {
/** Laskurin lukema. */
private int lukema = 0;
/** Kasvattaa laskurin arvoa yhdellä. */
public void kasvata() {
lukema++;
}
/** Palauttaa laskurin lukeman arvon. */
public int getLukema() {
return lukema;
}
/** Asettaa laskurin lukeman arvon. */
public void setLukema(int uusiLukema) {
lukema = uusiLukema;
}
/** Lisää laskurin lukeman arvoa parametrina annetun lisäyksen verran. */
public void lisää(int lisäys) {
lukema = lukema + lisäys;
}
/**
* Lisää laskurin lukemaan tulon kertoja * kerrottava. Palauttaa lisätyn
* tulon.
* @param kertoja
* @param kerrottava
* @return lisätyn arvon
*/
public int lisääTulo(int kertoja, int kerrottava) {
int tulo = kertoja * kerrottava;
lukema = lukema + tulo;
return tulo;
}
}
Metodin kutsumisessa on seuraavat vaiheet:
Aluksi lasketaan todellisten parametrien arvot, jos metodilla on parametreja. Parametriksi annetaan yleensä muuttujia, mutta yhtä hyvin parametrina voi olla vakioarvo tai lauseke, jonka tuottama arvo tulee parametrin arvoksi.
laskin.laskeTulo(3,10);
laskin.laskeTulo(42*54+(34*3),luku+10);
Todellisten parametrien arvot sijoitetaan metodin parametrien arvoiksi. Metodin parametrit ja muuttujat luodaan siis vasta metodia kutsuttaessa.
public int laskeTulo(int a, int b) {return a*b};
// laskeTulo(3,10); kutsussa metodin suorituksen alussa parametri a saa arvoksi 3 ja b 10
// laskeTulo(10*10,luku);kutsussa metodin suorituksen alussa parametri a saa arvoksi 100 ja b luku-muuttujan sisältämän arvon
Suoritetaan metodi
Metodista palattaessa metodin paikalliset muuttujat ja parametrit hävitetään, eli niiden varaama muisti vapautetaan. Jos metodi palauttaa arvon, palautettu arvo annetaan kutsujalle.
Javassa parametrin välitys toimii aina “call-by-value”-periaatteella. Metodille välitetään parametrina annettu arvo ja se kopioidaan metodin parametrin arvoksi. Metodin parametrin arvon muuttaminen metodin suorituksen aikana ei siis vaikuta mitenkään metodin kutsujan parametreiksi antaman muuttujan arvoon.
Luokassa määriteltyjen metodien pitää erota toisistaan kutsumuodoltaan (signature). Kutsumuotoon kuuluvat metodin nimi sekä parametrien lukumäärä ja tyypit. Luokan metodeilla voi siis olla sama nimi, jos niiden parametrit eroavat toisistaan joko lukumäärän, vastinparametrien tyyppien tai molempien osalta. Metodin nimi voidaan siis kuormittaa (overload) useilla merkityksillä, jotka erotetaan toisistaan parametrien avulla.
public class Laskuri {
/** Laskurin lukema. */
private int lukema = 0;
/** Palauttaa laskurin lukeman arvon. */
public int getLukema() {
return lukema;
}
/** Lisää laskurin lukeman arvoa parametrina annetun lisäyksen verran. */
public void lisää(int lisäys) {
lukema = lukema + lisäys;
}
/**
* Lisää laskurin lukemaan tulon kertoja * kerrottava.
* @param kertoja
* @param kerrottava
*/
public void lisääTulo(int kertoja, int kerrottava) {
lukema = lukema + kertoja * kerrottava;
}
}
Fiksummin toteutettuna tulon lisäävä metodi hyödyntää yhden parametrin lisää-metodia. Nyt yhden parametrin lisää metodiin voidaan kirjoittaa tarkistus, että lisättävä luku on aina positiivinen. Ylikuormitetuissa metodeissa ei tarvitse enää vastaavaa tarkistusta tehdä.
public class Laskuri {
/** Laskurin lukema. */
private int lukema = 0;
/** Palauttaa laskurin lukeman arvon. */
public int getLukema() {
return lukema;
}
/** Lisää laskurin lukeman arvoa parametrina annetun lisäyksen verran. */
public void lisää(int lisäys) {
if (lisäys >= 0) lukema = lukema + lisäys;
}
/**
* Lisää laskurin lukemaan tulon kertoja * kerrottava.
* @param kertoja
* @param kerrottava
*/
public void lisääTulo(int kertoja, int kerrottava) {
lisää(kertoja * kerrottava); //tässä metodissa ei tarvitse huolehtia, onko lisättävä tulos positiivinen
}
}
Kutsuttavan kuormitetun metodin valinta tapahtuu käännösaikana. Parametrien käännösaikainen tyyppi määrää siis kutsuttavan metodin. Metodin kuormittamista tulee käyttää harkiten. Usein metodien käyttö on selkeämpää hyvällä nimeämisellä.
//vertaa
public void lisää (int luku)...
public void lisää (double luku)...
//vastaan
public void lisääInt (int luku)...
public void lisääDouble (double luku)...
Oliota luotaessa pitää olla ensin luokka, jossa olio määritellään. Olion luonti on yleisesti muotoa:
new LuokanNimi ([parametrit])
Seuraavassa luodaan String- ja Laskuri-luokan oliot:
String jono = new String ("Helou");
Laskuri lask = new Laskuri();
Lauseissa on kolme osaa. Alussa määritellään viitemuuttuja, jonka tyyppi on vastaava kuin luotavan olion tyyppi. Olion luonti tapahtuu luontioperaatiolla new. Tämä luo uuden olion eli varaa muistista olion attribuuttien vaatiman tilan, alustaa olion attribuutit oletusarvoihinsa ja palauttaa viitteen luotuun olioon. New operaation perässä on kutsu rakentajaan (käytetään tämän jälkeen termiä konstruktori, koska se lienee vakiintuneempi). Konstruktori on erityisasemassa oleva metodi, jonka vastuulla on olion tilan alustaminen. Viite luotuun olioon sijoitetaan viitemuuttujan arvoksi.
Konstruktori on aina saman niminen kuin luokan nimi, eikä sille määritellä palautustyyppiä. Luokalla on aina oletuskonstruktori, mutta sen voi ohjelmoija myös määritellä itse.
Konstruktoria voi myös kuormittaa. Silloin kutsutaan luokan toista konstruktoria käyttämällä “metodin nimenä” this-avainsanaa. Toisen konstruktorin kutsu pitää olla sen ensimmäinen lause. Määritellään luokkaan Laskuri konstruktori, jolle voidaan antaa laskurin alkuarvo. Nyt joudumme kirjoittamaan uusiksi myös parametrittoman konstruktorin.
public class Laskuri {
private int lukema = 0;
public Laskuri() {
this(0); //kutsutaan jälkimmäistä konstruktoria
}
public Laskuri (int alkuarvo) {
lukema = alkuarvo;
}
}
Olion luontioperaatio palauttaa viitteen luotuun olioon. Viitä pitää ottaa talteen viitemuuttujaan, jotta olioon päästään käsiksi.
Laskuri lask;
Ensin määriteltiin pelkkä viitemuuttuja.
lask = null;
Muuttujan arvoksi asetettiin tyhjä viite. Nyt muuttujalla lask on määritelty arvo, mutta se ei viittaa mihinkään olioon eli sen arvo on null
Seuraavaksi luodaan Laskuri-luokan olio new-operaattorilla ja sijoitetaan sen palauttama viite edellä määritellyn lask-muuttujan arvoksi.
lask = new Laskuri();
Nyt muuttujan lask arvona on siis viite luotuun olioon, EI LUOTU OLIO. Luodaan nyt toinen Laskuri-luokan olio, joka saa konstruktorissa lukemaksi arvon 2.
Laskuri toinen = new Laskuri(2);
HUOM: koska lukema on määritelty privateksi, niin ota huomioon:
lask.kasvata(); // Toimii
toinen.lisää(10); // Toimii
lask.lukema = lask.lukema + 10; // EI toimi, koska kutsujalla ei ole pääsyä suoraan attribuuttiin.
Vielä kertauksena, mitä tapahtuu kun asetetaan viite toiseen viitemuuttujaan
toinen = lask;
lask.kasvata(); //kasvattaa laskuria 1:llä
toinen.kasvata(); //Kasvattaa SAMAA laskuria 1:llä
Kun olioon ei ole enää viitettä, siihen ei päästä käsiksi. Silloin olio muuttuu roskaksi, joka poistetaan automaattisesti eli vapautetaan olion varaama muisti. Ohjelmoija ei voi vaikuttaa siihen, milloin ja missä järjestyksessä roskat kerätään. Päätöksen tekee virtuaalikone. Viitemuuttujan viittaus voidaan puolestaan tyhjentää sijoittamalla muuttujan arvoksi null (toinen = null;). Nyt jos yritetään kasvattaa laskurin arvoa (toinen.kasvata()) niin ohjelma kaatuu virheeseen NullPointerException.
Arvokeskeisellä luokalla tarkoitetaan sitä, että luokan attribuutit määritellään vakioiksi. Tällöin ajatellaan, että attribuuttien arvo on niin ominainen oliolle, ettei niitä voida muuttaa, vaan muuttavassa metodissa luodaan kokonaan uusi olio.