Luvussa 4.1 tutustuttiin ideatasolla luokan ja olion käyttöön mallinnuksessa ja abstraktin tietotyypin toteuttamisessa. Tämä luku kertaa vanhoja ja esittelee uusia luokan käyttötapoja. Pääpaino on uusissa tavoissa.
Ja:
Olioajattelu voidaan nähdä mallintamisena, jossa luokka
vastaa jotakin yleiskäsitettä...
Luokan ilmentymä on jokin erityinen olio, joka kuuluu
tuohon luokkaan.
Jos tehtävänä olisi vaikkapa ratkoa luentokurssin jotakin tietojenkäsittelytehtävää, ongelmakentästä voitaisiin yrittää löytää olioita seuraavasti:
Kirjoitin luvussa 4.1:
... tietorakenteen toteutus ja käyttö eristetään toisistaan.
Tietorakenteen käyttäjä tuntee vain operaatioita, joilla
(abstraktin) tietorakenteen tilaa muutetaan ja kysytään.
Tietorakenteen toteuttaja puolestaan ei ota kantaa siihen,
mihin tietorakennetta käytetään. Käyttäjää ja toteuttajaa yhdistää
pelkästään sopimus siitä, mitä operaatiot (abstraktissa)
tietorakenteessa saavat aikaan. Usein tietorakenteen toteutuksen
piilottamista kutsutaan kapseloinniksi.
Abstraktin tietotyypin voi rakentaa olio-ohjelmoinnin välinein seuraavasti:
Tietueen idea on, että muuttujalla on ns. kenttiä: alimuuttujia, joiden tyypit voivat olla keskenään erilaisia (vrt. taulukkotyyppiin, joka kokoaa yhteen muuttujaan keskenään samantyyppisiä alimuuttujia).
Tietueen ja sen kenttien käyttöön liittyy usein erilaisia operaatioita, jotka on luontevaa rakentaa itse luokkaan. Tällöin päädytään "tavalliseen" luokkaan. Luokan käyttö tietueena saattaa olla merkki huonosta ohjelmansuunnittelusta!
Esimerkki: Laaditaan henkilötietue, jossa on kenttä henkilön nimelle, pituudelle ja iälle.
public class Henkilo { public String nimi; public double pituus; public int ika; } ... Henkilo pomo = new Henkilo(); Henkilo sihteeri = new Henkilo(); pomo.nimi = "Maija"; pomo.pituus = 163; pomo.ika = 24; sihteeri.nimi = "Pekka"; sihteeri.pituus = 184; sihteeri.ika = 48;Huom: Koska tietueet ovat olioita, sijoitus on viitteen kopiointi:
Henkilo paasihteeri = sihteeri; ++paasihteeri.ika; // sihteerikin vanhentui!Huom: Tämä ohjelmointityyli ei mitenkään varmista kenttien oikeaa käyttöä; mikään ei estä asettamasta pituudeksi lukua -3.14 eikä iäksi lukua -123456. Kapselointia käyttäen tätä ongelmaa ei tule, kunhan vain aksessorit vain ohjelmoidaan viisaasti!
Esimerkki: Lukupari (esim. tason piste, kompleksiluku, yms.) voidaan toteuttaa tietueena vaikkapa seuraavasti:
public class Pari { public double x, y; public Pari(double x, double y) { this.x = x; this.y = y; } } ... Pari a = new Pari(3.14, -7); Pari b = new Pari(0.23, 10.1); Pari c = new Pari(0,0); c.x = a.x + b.y; c.y = a.y + b.x; // Mitä tekee a = b; ?Koska kaikki double-arvot ovat sallittuja x- ja y-kenttiin, erillisten "settereiden" ja "gettereiden" ohjelmointi ei tässä tapauksessa mitenkään lisäisi kenttien tiedon pysymistä kelvollisena.
Koska Javassa luokilla on ns. oletuskonstruktori (ks. seur. luku), lukuparin voi toteuttaa vieläkin yksinkertaisemminkin:
public class Pari { public double x, y; } ... Pari a = new Pari(); Pari b = new Pari(); Pari c = new Pari(); a.x = 3.14; a.y = -7; b.x = 0.23; b.y = 10.1; c.x = a.x + b.y; c.y = a.y + b.x;Oman toString()-metodin tekeminen lukuparille voisi olla perusteltua. Jos jatketaan edellistä esimerkkiä ja tulostetaan:
System.out.println(a + " " + b + " " + c);saadaan esimerkiksi:
Pari@80ca74b Pari@80ca74c Pari@80ca74dOhjelmoidaan tyypille Pari oma tulostusmetodi:
public class Pari { public double x, y; public String toString() { return "("+x+","+y+")"; } }Nyt samalla tulostusoperaatiolla saadaan kauniisti:
(3.14,-7.0) (0.23,10.1) (13.24,-6.77)
Ns. linkitetty lista voidaan toteuttaa seuraavasti:
public class Lista { int tieto; Lista linkki; } ... Lista p = new Lista(); p.tieto = 7; p.linkki = new Lista(); p.linkki.tieto = 3; p.linkki.linkki = new Lista(); p.linkki.linkki.tieto = 19; for(Lista q=p; q!=null; q=q.linkki) System.out.println(q.tieto);Huom: Myös tässä tapauksessa olio-ohjelmointityyli ja kapselointi voisi olla parempi ratkaisu. Lista voitaisiin toteuttaa esimerkiksi seuraavaan tapaan (parempiakin on!):
public class Lista { public int tieto; private Lista linkki; public void lisaaAlkuun(int tieto) { ... } public void lisaaLoppuun(int tieto) { ... } public Lista annaSeuraava() { ... } // jne., jne, ... } // käyttöä: ... Lista p = new Lista(); p.lisaaAlkuun(7); p.lisaaLoppuun(7); p.lisaaLoppuun(19); ...
Kentät ja metodit määritellään luokkakohtaisina: static. Käyttäjälle tarjotut ovat public, apuvälineet private.
Tarvittaessa kenttiä voi alustaa staattisella alustuslohkolla.
Kirjastoluokka on syytä määritellä final-määreellä. Tällöin luokkaa ei voi periä (kts. kappale 4.4). Kun luokkaan lisäksi määritellään yksityinen (private) konstruktori, luokasta ei voi luoda myöskään ilmentymiä!
Esimerkki:
public final class EsimKirjasto { private EsimKirjasto() { } // ilmentymien esto!! // vakioita: public static final double VAKIO = 123.456; // julkinen vakio private static int SALAVAKIO = 13; // yksityinen vakio // kirjastometodeja: public static int randomInt(int max) { // arvotaan kokonaisluku väliltä [1..max] return (int)(Math.random()*max) + 1; } public static char randomChar() { // koodit 34-126 ('"'-'~') return (char)(randomInt(93)+33); // käytetään omaa randomInt-metodia } public static void onnittele(String nimi, int montakoKertaa) { if (montakoKertaa == SALAVAKIO) System.out.println("Ähäkutti " + nimi + "!"); else for (int i=0; i<montakoKertaa; ++i) System.out.println("Onnea " + nimi + "!"); } } // kirjaston käyttöä: for (int i=10; i<20; ++i) System.out.println(EsimKirjasto.randomChar()+" "+EsimKirjasto.randomInt(i)); EsimKirjasto.onnittele("Maija", 7); EsimKirjasto.onnittele("Matti",13);
public class Ohjelma{ // "globaalit vakiot ja muuttujat": static final int KOKO = 10; static int luku, indeksi; static double[] taulu = new double[KOKO]; // "proseduurit" tai "void-funktiot": static void arvoluvut(int skaalaus) { for (int i=0; i<KOKO; ++i) taulu[i] = Math.random()*skaalaus; } static void tervetuloa() { System.out.println(" Tervetuloa\n" +" =========="); } // "funktiot" tai "arvon palauttavat funktiot": static double siniToiseen(double argu) { return Math.sin(argu)*Math.sin(argu); } // "pääohjelma": public static void main(String[] args) { tervetuloa(); arvoluvut(14); for (int i=0; i<KOKO; ++i) System.out.println(siniToiseen(taulu[i])); } }