|
6. Olio-ohjelmointi
Java on olio-ohjelmointikieli. Olio-ohjelmointi eroaa perinteisestä lausekielisestä ohjelmoinnista siten,
että pelkän peräkkäisten toimintaohjeiden luettelemisen sijaan ohjelmalla pyritään mallintamaan
reaalimaailman olioita tai joitakin abstrakteja olioita. Ero lausekieliseen ohjelmointiin on lähinnä
ajattelutavassa.
6.1. Johdatus oliomaiseen ajatteluun
Esimerkkinä voisimme tehdä oliomallin vaikkapa reaalimaailman virvoitusjuoma-automaatista. Mallinnettavasta
kokonaisuudesta pyritään erottamaan jokseenkin itsenäisiä osia, joita tässä tapauksessa kutsutaan
olioiksi. Oliossa pitää säilyä ohjelman kannalta kaikki oleellinen tieto, ja siksi olioille on
määritettävä tarvittava määrä ominaisuuksia ja toimintoja. Yksi mahdollinen
virvoitusjuoma-automaatti koostuu alla luetelluista olioista:
Rahanvastaanottolaitteisto (huolehtii rahan vastaanotosta ja yhteistyöstä
rahansäilytyslaitteiston
kanssa)
Rahanpalautuslaitteisto (palauttaa ylimääräiset vaihtorahat, jos rahaa annettiin liikaa)
Merkkivalot (ilmoittaa, millaisia juomia on tarjolla)
Valintapainikkeet (aktiivinen painike mahdollistaa juomien valinnan; valintapainike aktivoituu
kun rahaa
on syötetty juoman ostamiseen vaadittava määrä)
Juomien jakaja (antaa juoman asiakkaalle)
Rahansäilytyslaitteisto (huolehtii syötettyjen rahojen laskennasta ja tallennuksesta)
Juomien säilytystila (säilyttää juomat ja tarjoaa niitä juomien jakajalle)
Jokainen näistä olioista pitää kirjaa omasta tilastaan. Esimerkiksi rahansäilytyslaitteiston
on tiedettävä, kuinka paljon rahaa asiakas on tähän mennessä syöttänyt. Niinpä sille on määritettävä
ainakin seuraava ominaisuus:
syötetty_rahamäärä
Olion toiminnallisia osia kutsutaan metodeiksi. Metodit vastaavat perinteisten ohjelmointikielten
aliohjelmia, mutta ne on kiinnitetty johonkin tiettyyn olioon. Esimerkiksi syötetyn rahasumman laskevaa
laske_rahat-metodia käytetään ainoastaan rahansäilytyslaitteisto-olion kanssa. Kyseiselle
oliolle voidaan määritellä seuraavat toiminnot eli metodit:
laske_rahat (laskee syötetyn rahasumman)
laske_vaihtorahat (laskee vaihtorahojen lukumäärän)
palauta_vaihtorahat (palauttaa vaihtorahat)
tulosta_rahasumma (tulostaa syötetyn rahasumman)
Olioiden on jollain tavalla toimittava yhteistyössä toisten olioiden kanssa. Tämä tapahtuu siten, että
oliot välittävät keskenään tietoa eli lähettävät viestejä metodien sekä mahdollisesti niille
välitettävien parametrien avulla. Esimerkiksi kun rahansäilytyslaitteisto-olio saa
rahanvastaanottolaitteisto-oliolta tiedon syötetystä rahasta, se puolestaan päivittää tietojäsenensä
arvon ja tultuaan kylläiseksi lähettää merkkivalot- ja valintapainikkeet-olioille
aktivoitumiskäskyn, jne.
6.2. Oliot, luokat, tietojäsenet ja metodit
Olio-ohjelmoinnin peruskäsite olio (object) sisältää sekä
tiedon että tietoon
kohdistuvat toiminnot eli metodit (methods). Olio kapseloi (encapsulate)
tiedon, jotta siihen pääsee suoraan käsiksi ainoastaan metodien avulla.
Metodi (toiminto) on yleisnimitys koodille, joka käsittelee olion
sisältämää tietoa. Olio voi
suorittaa ainoastaan sille määritellyt toiminnot. Metodille pitää aina määritellä nimi ja ohjelmoida
toiminta. Metodeille voidaan määritellä myös parametreja, joissa
olion metodille välitetään
tietoa olion ulkopuolelta.
Olion sisältämä tieto tallennetaan olion ominaisuuksiin (attribute),
jotka ovat nimettyjä ja
tavallisesti ulkopuolisilta olioilta suojattuja tietokenttiä. Näitä kutsutaan myös olion
tietojäseniksi. Olion ominaisuuksia käsitellään yleensä metodien avulla.
Olion tila muodostuu
kaikkien olion ominaisuuksien yhdistelmästä.
Ennen olion määrittelyä pitää luoda luokka (kuten Henkilö), josta voidaan luoda yksittäisiä
olioita (esim. antti, juuso tai ville). Yhdestä luokasta voi luoda niin monta oliota
kuin haluaa. Kun olio luodaan, sen tiedot tallentuvat tietokoneen muistiin; kun olio tuhotaan, se
käytännössä tuhotaan tietokoneen muistista.
Luokka (class) on siis ryhmä samankaltaisia olioita. Luokat
määrittelevät miltä oliot
näyttävät määrittelemällä mitä tietoa ja toimintoja kaikki luokan oliot sisältävät. Oliot ovat luokan
ilmentymiä (instances), jotka toteuttavat luokan käytännössä.
Olion ja luokan välistä suhdetta kuvaavat seuraavat määritelmät:
Olio on luokan ilmentymä (käytännössä toteutus).
Olion ominaisuus on luokan ominaisuuden ilmentymä.
Olio käyttää ominaisuuksiaan luokan tarjoamilla metodeilla.
Luokat määritellään Java-kielessä seuraavasti:
class Luokan_nimi
{
// Luokan toteutus:
// a) Tietojäsenet eli ominaisuudet.
// b) Metodit eli toiminnot.
}
Luokan ominaisuudet (tietojäsenet) on tapana esitellä keskitetysti heti luokan alussa. Esimerkiksi
Opiskelija-luokka voi sisältää seuraavaa tietoa opiskelijasta:
class Opiskelija
{
// Alussa kootusti luokan tietojäsenet.
String nimi;
int id;
// Luokan metodit alkavat tästä eteenpäin.
}
Perussyntaksi luokan metodien toteutuksessa on seuraava:
palautustyyppi metodin_nimi (parametrit)
{
// Metodin toteutus.
// Lopuksi: return (palautustyyppinen_muuttuja);
}
6.3. Luokkahierarkiat
Olio-ohjelmoinnissa, aivan kuten reaalimaailmassakin, esiintyy luokkien välisiä hierarkkisia rakenteita.
Oletetaan vaikka, että meillä on opiskelija nimeltään Olavi, joka on Opiskelija-luokkaan
kuuluva
olio. Toisaalta kaikki opiskelijat ovat ihmisiä. Biologisesti ihminen on nisäkäs, ja nisäkkäät vastaavasti
kuuluvat eläinten luokkaan. Luonnon hierarkioita seuraamalla oppii ajattelun hierarkkisesta rakenteesta,
jossa aliluokka perii kaikki yliluokkansa ominaisuudet.
Olio-ohjelmoinnin tärkein tuntomerkki on perintä (inheritance).
Perimällä jokin luokka
saadaan luokan yliluokkaan (superclass) liitetyt ominaisuudet ja
toiminnot käyttöön ilman
erillistä määrittelyä. Vastaavasti perittyä luokkaa kutsutaan perittävän luokan aliluokaksi
(subclass). Perinnän keskeisimmät tavoitteet ovat koodin käyttö muissa ohjelmistoissa sekä
käsitteiden mallintaminen.
Javassa kaikki luokat ovat automaattisesti Object-luokan aliluokkia, eikä ohjelmoijan tarvitse
erikseen määritellä luokkaa tehdessään että se periytyy tästä perusluokasta. Muutoin periminen määritellään
extends-avainsanalla.
class Opiskelija extends Ihminen
{
// Luokan toteutus
}
Näin määriteltynä Opiskelija-luokka siis perii Ihminen-luokan ominaisuudet ja metodit.
6.4. Konstruktorit
Konstruktoria (constructor) tarvitaan luokan alustamiseen sitä luotaessa. Konstruktori eli alustaja
ei ole metodi, eikä palauta mitään arvoa sitä kutsuttaessa. Konstruktorilla on oltava sama
nimi kuin luokalla, jonka alustamiseen sitä käytetään. Konstruktorille voidaan antaa nolla
tai useampia parametreja kutsuttaessa, jolloin luokan tietojäsenet voidaan alustaa järkevään
alkutilaan (esimerkiksi taulukot kannattaa alustaa halutun kokoisiksi.)
class Opiskelija
{
// Alussa kootusti luokan tietojäsenet.
String nimi;
int id;
// Konstruktori luokalle Opiskelija
Opiskelija() {
nimi = ""; // tyhjä merkkijono
id = 0; // alustetaan nollaksi
}
// Luokan metodit alkavat tästä eteenpäin.
}
Konstruktoreita voi kuitenkin olla useampia; tällöin luokasta voidaan luoda uusia ilmentymiä
eri tavoin alustettuina. Mikäli käytetään useampia konstruktoreita, on niiden erottava toisistaan
parametrien määrän ja tyypin suhteen. Useampien konstruktorien käyttöä kutsutaan ylikuormittamiseksi
(overloading).
Ylikuormittamalla konstruktoreista (tai metodeista) saadaan aikaan monipuolisempia;
niitä voidaan kutsua erilaisilla parametreilla tilanteen mukaan.
class Opiskelija
{
// Alussa kootusti luokan tietojäsenet.
String nimi;
int id;
// Konstruktori luokalle Opiskelija
Opiskelija() {
nimi = ""; // tyhjä merkkijono
id = 0; // alustetaan nollaksi
}
// Konstruktori yhden parametrin kanssa
Opiskelija(String n) {
nimi = n; // nimi saa arvokseen merkkijonon n sisällön
id = 0; // alustetaan nollaksi
}
// Konstruktori parametrien kanssa
Opiskelija(String n, int i) {
nimi = n; // nimi saa arvokseen merkkijonon n sisällön
id = i; // id saa arvokseen kokonaisluvun i
}
// Luokan metodit alkavat tästä eteenpäin.
}
Java-kieli tekee automaattisesti oletuskonstruktorin, mikäli luokkaan ei ole kirjoitettu
yhtään muuta konstruktoria. Oletuskonstruktori ei alusta luokan tietojäseniä mihinkään erityiseen
arvoon, vaan toimii ainoastaan apuvälineenä uusien ilmentymien luomisessa.
Luotaessa luokasta uusia ilmentymiä konstruktorien avulla, on käytettävä avainsanaa new.
New -sanalla luodaan luokasta uusi olio:
class Ainejärjesto
{
// Ainejärjestön puheenjohtajan tyyppi on Opiskelija
Opiskelija ainejarjeston_PJ = new Opiskelija();
String ainejarjeston_nimi;
int ainejarjesto_ID;
// Luokan konstruktorit, aksessorit ja muut metodit
}
Mikäli ainejärjestön puheenjohtajan nimi on tiedossa alustettaessa luokan Ainejarjesto
tietojäseniä, voidaan konstruktoria kutsua myös nimi -parametrin kanssa:
class Ainejärjesto
{
// Ainejärjestön puheenjohtajan tyyppi on Opiskelija
Opiskelija ainejarjeston_PJ = new Opiskelija("Matti Mainio");
String ainejarjeston_nimi;
int ainejarjesto_ID;
// Luokan konstruktorit, aksessorit ja muut metodit
}
6.5. Aksessorit
Luokan sisältämiin tietojäseniin tulisi päästä käsiksi myös luokan ulkopuolelta, mutta
niiden suora muuttaminen vaikuttaisi myös tulevaisuudessa luokasta tehtäviin ilmentymiin;
tämän takia tarvitaan aksessorimetodeja (accessor methods)
.
Aksessorimetodien avulla luokan sisältämiä attribuutteja voidaan muuttaa siten, että
ainoastaan luokasta tehdyn yksittäisen ilmentymän arvot muuttuvat. Aksessorimetodit, jotka
palauttavat tietyn tietojäsenen arvon muuttamatta sitä nimetään yleisesti
get_Tietojäsenen_nimi. Aksessorimetodit, jotka muuttavat tietyn tietojäsenen arvoa
nimetään yleisesti set_Tietojäsenen_nimi.
class Opiskelija
{
// Alussa kootusti luokan tietojäsenet.
String nimi;
int id;
// Konstruktori luokalle Opiskelija
Opiskelija() {
nimi = ""; // tyhjä merkkijono
id = 0; // alustetaan nollaksi
}
// Aksessorimetodi, joka palauttaa merkkijonon nimi arvon
String getNimi() {
return nimi;
}
// Aksessorimetodi, joka muuttaa merkkijonon nimi arvon
// parametrin n sisällön mukaiseksi, mutta ei palauta mitään arvoa (void)
void setNimi(String n) {
nimi = n;
}
}
Jos haluaisimme lisätä Ainejarjesto -luokkaan puheenjohtajan tietojäsentä nimi muuttavan
aksessorimetodin, meidän tarvitsisi ainoastaan lisätä luokan toteutukseen seuraavanlainen
metodi:
// Aksessorimetodi, joka muuttaa merkkijonon nimi arvon
// parametrin uusi_nimi sisällön mukaiseksi
void vaihdaPJ_nimi(String uusi_nimi) {
ainejarjeston_PJ.setNimi(uusi_nimi);
}
Nyt voimme tarkastella Ainejarjesto -luokan avulla aiemmin esitellyn Opiskelija-
luokan aksessorimetodien käyttöä. Metodin vaihdaPJ_nimi sisällä kutsutaan luokan
Opiskelija metodia setNimi liittämällä kutsu varsinaiseen tietojäseneen käyttämällä
pisteoperaattoria tietojäsenen ja kutsuttavan metodin välissä:
class Ainejärjesto
{
Opiskelija ainejarjeston_PJ = new Opiskelija("Matti Mainio");
String ainejarjeston_nimi;
int ainejarjesto_ID;
// Luokan konstruktori(t)
// ...
// esimerkkimetodi, jolla olion ainejarjeston_PJ tietojäsen nimi
// muutetaan merkkijonon uusi_nimi sisällön mukaiseksi
void vaihdaPJ_nimi(String uusi_nimi) {
ainejarjeston_PJ.setNimi(uusi_nimi);
}
}
6.6. Näkyvyys
Java-kielessä kaikki luokan sisällä olevat tietojäsenet ja metodit ovat luokan itsensä
käytettävissä; muiden luokkien pääsyä näihin tulee pystyä kontrolloimaan. Näkyvyyttä
(visibility) voidaan säädellä asettamalla tietojäsenten ja
metodien eteen näkyvyyden ilmaiseva määrite (control access modifier).
Näkyvyyden ilmaisevia määritteitä ovat (heikoimmasta vahvimpaan):
public : ominaisuudet ovat saatavilla mistä tahansa muusta luokasta.
Ominaisuudet periytyvät aliluokille.
protected : Ominaisuudet ovat saatavilla samaan package
-luokkaan kuuluvista luokista ja myös muihin package -luokkiin
kuuluvista aliluokista.
package : oletusnäkyvyys, mikäli muuta ei ole ilmaistu.
Ominaisuudet ovat saatavilla ainoastaan samaan package-luokkaan
kuuluvista luokista.
private : ominaisuudet ovat saatavilla ainoastaan luokalle itselleen.
On suositeltavaa asettaa luokan tietojäsenet yksityisiksi, jolloin
niiden tahaton muokkaaminen muista luokista estyy; tietojäsenten muuttamiseen tulee
käyttää aksessorimetodeja.
public class Opiskelija
{
// luokan tietojäsenet, näkyvyys vain luokan itsensä sisällä
private String nimi;
private int id;
// julkinen konstruktori luokalle Opiskelija
public Opiskelija() {
nimi = ""; // tyhjä merkkijono
id = 0; // alustetaan nollaksi
}
// Julkinen aksessorimetodi, joka muuttaa tietojäsenen id
// arvon parametrin uusi_id sisällön mukaiseksi
public void setId(int uusi_id) {
id = uusi_id;
}
}
Näkyvyyden säätelyllä voidaan estää vääräntyyppisen tiedon tallentaminen tietojäsenen
arvoksi. Olio-ohjelmoinnin periaatteiden mukaisesti luokan käyttäjälle pitää tarjota
vain ja ainoastaan se toiminnallisuus, joka riittää luokan metodien käyttämiseen; muu
toiminnallisuus tulee pitää käyttäjän ulottumattomissa.
|