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 saattaakin 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ä tapa ei mitenkään varmista kenttien oikeaa käyttöä; mikään ei estä asettamasta pituudeksi lukua -3.14 eikä iäksi lukua -123456. Abstraktia tietotyyppiä käyttäen tätä ongelmaa ei tule, kun aksessessorit vain ohjelmoidaan viisaasti!
Esimerkki: Lukupari (esim. tason piste, kompleksiluku, ...) voidaan toteuttaa vaikkapa:
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; ?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 voisi olla viisaampi ratkaisu:
public class Lista { public int tieto; private Lista linkki; public void lisaaAlkuun(int tieto) { ... } public void lisaaLoppuun(int tieto) { ... } public Lista annaSeuraava() { ... } // jne., jne, ... } ... 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:
// satunnaissatuja public final class Satuja { private Satuja() { } // ilmentymien esto!! // vakio: public static final double VAKIO = 123.456; // luokan latauskerran (ladataan kun 1. kerran viitataan) // vakio: private static double apuvaki; static { // arvotaan kun luokka ladataan! apuvaki = Math.random(); // so. oma vakio joka käyttökerralla } public static final double SATUVAKIO=apuvaki; // kirjastometodeja: public static int randomInt(int max) { return (int)(Math.random()*max) + 1; } public static char randomChar() { // koodit 34-126 ('"'-'~') return (char)(randomInt(93)+33); // ... } } ... // kirjaston käyttöä: for (int i=1; i<50; ++i) System.out.println(Satuja.randomChar()+" "+Satuja.randomInt(i)); System.out.println("Ihan vakio:"+Satuja.VAKIO+ ", latauskohtainen vakio: "+Satuja.SATUVAKIO);
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])); } }