Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Luokka ja olio

osaamistavoitteet

  • Ymmärrät luokan ja olion suhteen, ja miten luokka toimii mallina olioiden luomisessa.
  • Osaat määritellä oman luokan ja sen jäsenet, eli attribuutit, metodit ja konstruktorit.
  • Ymmärrät olioiden elinkaaren ja osaat käyttää olioita ohjelman osina.
  • Ymmärrät, miten this-viite toimii olion metodeissa.
  • Tiedät, mitä static-määrite merkitsee luokan jäsenten osalta.

Luokka

Ensimmäinen askel olio-ohjelmointiin on luokan määritteleminen class-avainsanaa käyttäen. Luokkaa voi ajatella kaavana tai muottina, jonka pohjalta olioita luodaan. Luokka kertoo, mitä tietoja olio sisältää (attribuutit) ja mitä se voi tehdä (metodit). Luokassa määriteltyjä attribuutteja ja metodeja kutsutaan myös luokan jäseniksi (engl. class member).

Tehdään pieni ajatusharjoitus: mietitään hetki talonrakennusta. Arkkitehdin piirtämän yhden rakennuspiirustuksen pohjalta voidaan rakentaa monta rakennusta. Ne olisivat rakenteeltaan samanlaisia, sillä ne ovat saman kaavan mukaan tehty, mutta jokaisella rakennuksella olisi kuitenkin oma tila; eri omistaja, väri, sisustus, ja niin edelleen. Rakennuspiirustusta voi (ainakin etäisesti) ajatella olio-ohjelmoinnin luokkana, kun taas rakennukset ovat sen pohjalta tehtyjä olioita. Luokan nimi kertoo, mikä olio on, joten jos tekisimme luokan rakennuksille, sen nimeksi sopisi Rakennus.

Huomaa, että Javassa on tapana aloittaa luokkien nimet aina isolla kirjaimella.

Määritellään aluksi tyhjä luokka Rakennus, jota lähdemme täydentämään.

class Rakennus {
    // Luokan sisällä määritellään rakenne, jota luokasta 
    // tehdyt oliot vastaavat.
}

Luokasta luodaan ilmentymiä eli olioita käyttämällä avainsanaa new. Tämä varaa muistista oliolle sopivan tilan, valitsee ja suorittaa sopivan muodostajan, ja palauttaa viitteen juuri luotuun olioon. Sijoitamme tämän viitteen muuttujaan, jotta pääsemme olioon sitä kautta käsiksi. Luokan nimi on muuttujan tyyppi ja siten kertoo kääntäjälle, millainen olio muistisijainnissa täytyy olla.

void main() {
    // Lauseke 'new Rakennus()' luo olion ja palauttaa viitteen siihen. 
    // Sijoitamme tämän viitteen muuttujaan 'rakennus'.
    Rakennus rakennus = new Rakennus();
}

huomautus

Viitemuuttuja ja olio ovat kaksi eri asiaa. Viitemuuttuja on kuin nuoli, joka voi osoittaa olioon. Viitemuuttujan ei kuitenkaan ole pakko viitata mihinkään, jolloin sen arvo on null. Olio on vastaavasti mahdollista luoda ilman siihen viittaavaa muuttujaa, mutta jos olioon osoittavia viitteitä ei ole, siihen ei päästä käsiksi ja se merkitään automaattisesti roskaksi. Useampi viitemuuttuja voi viitata samaan olioon, mutta viitemuuttuja voi osoittaa vain yhteen olioon kerrallaan.

Attribuutit

Attribuutti on luokan sisällä, aliohjelmien ulkopuolella määritelty muuttuja, joka edustaa olion ominaisuutta, piirrettä tai tilaa. Luokasta tehdyt oliot sisältävät aina luokassa määritellyt attribuutit. Siinä missä luokka määrittelee, mitä tietoja olioilla voi olla, attribuutit tallentavat kunkin yksittäisen olion konkreettiset arvot. Kuten muuttujat yleensä, attribuutit voivat olla alkeistietotyyppejä tai viitteitä. Niiden nimeämisessä käytetään myös samoja käytänteitä. Huomaa, että metodien sisällä esitellyt muuttujat eivät ole attribuutteja; ne ovat metodin paikallisia eli lokaaleja muuttujia. Lokaalit muuttujat eivät ole osa olion tilaa.

Lisätään nyt muutama attribuutti Rakennus-luokkaamme.

public class Rakennus {
    // Nämä muuttujat ovat olion attribuutteja. Jokaisella rakennuksella
    // on omistaja ja väri, mutta ne eivät välttämättä ole kaikilla 
    // olioilla arvoiltaan samat.
    private String omistaja;

    // Attribuutille voidaan asettaa oletusarvo. Tässä värin oletusarvo on 
    // sininen, jolloin kaikilla tämän luokan pohjalta tehdyillä olioilla
    // on aluksi tämä arvo, ellei sitä muuteta.
    private String väri = "sininen";
}

Attribuutit poikkeavat paikallisista muuttujista siten, että niiden näkyvyyttä voidaan hallita näkyvyysmääreiden avulla. Paikalliset muuttujat ovat olemassa ja nähtävillä vain aliohjelman sisällä sen suorituksen ajan, mutta attribuutit ovat olemassa koko olion eliniän ajan ja näkyvät kaikille muille jäsenille luokan sisällä. Ne voidaan asettaa näkyväksi myös olion ulkopuolelta, joskin tämä on yleisesti ottaen huono idea. Palaamme näkyvyysmääreisiin myöhemmin tässä osassa.

huomautus

Paikallisella muuttujalla voi olla sama nimi kuin attribuutilla, jolloin se peittää attribuutin. Tätä kutsutaan varjostamiseksi (engl. shadowing). Jos attribuutilla ja paikallisella muuttujalla on sama nimi, käytetään lausekkeissa ensisijaisesti lokaalia muuttujaa. Tässäkin tilanteessa olion metodeista voi yhä päästä käsiksi sen attribuuttiin käyttämällä this-viitettä, johon palaamme hyvin pian.

Attribuutille voidaan luokassa antaa oletusarvo, jolloin luokasta luodut oliot saavat sen myös oman attribuuttinsa alkuarvoksi. Jos attribuutilla ei ole oletusarvoa, sen arvo voidaan määrittää olion luomisen yhteydessä, myöhemmin metodien avulla, tai jopa jättää määrittämättä.

public class Rakennus {
    // Olion attribuutti, jolla on oletusarvo.
    private String väri = "sininen";

    public void tulosta() {
        // Tämä lokaali muuttuja peittää saman nimen omaavan attribuutin 
        // aliohjelman sisällä.
        String väri = "punainen"; 

        // Tulostaa "punainen". Tunniste 'väri' viittaa ensisijaisesti 
        // lokaaliin muuttujaan, jos sellainen on näkyvissä.
        IO.println(väri); 

        // Tulostaa "sininen". Voimme viitata olion metodin sisältä sen 
        // attribuuttiin 'this'-viitteen avulla.
        IO.println(this.väri); 
    }
}

Metodit

Luokassa määriteltyjä aliohjelmia kutsutaan metodeiksi. Siinä missä attribuuttia voisi kuvailla niin, että se muodostaa olion sisäisen tilan, metodia voisi kuvailla olion kyvyksi tehdä jotain. Javassa erikoista on se, että kaikki aliohjelmat ovat itse asiassa aina jonkin luokan sisällä. Ehkä tästä syystä Java-kielessä tupataan kutsumaan kaikkia aliohjelmia metodeiksi.

Metodien määrittely ei syntaksiltaan eroa muista aliohjelmista, ja niiden nimeämisessä käytetään myös samanlaisia käytänteitä. Metodeja voidaan myös kuormittaa. Metodien näkyvyyttä luokan ulkopuolelle voidaan hallita attribuuttien tapaan näkyvyysmääreiden avulla.

Kuten yleensä aliohjelmia tehdessä, metodin tulisi suorittaa tehtävä, jota sen nimi kuvastaa. Liian suuret tehtävät on hyvä jakaa pienempiin osiin eli useammaksi metodiksi.

Voidaksemme kutsua olion metodia, meillä täytyy olla olio ja siihen viite. Lisätään nyt Rakennus-luokkaamme pari yksinkertaista metodia olion tilan käsittelyyn ja kutsutaan näitä. Tällaisia metodeja kutsutaan usein saantimetodeiksi.

Rakennus.java
public class Rakennus {
    private String omistaja;
    private String väri;

    // Olion metodi, joka ottaa vastaan merkkijonon ja sijoittaa 
    // sen 'väri'-attribuuttiin.
    public void setVäri(String väri) {
        // Parametrilla ja attribuutilla on sama nimi, joten käytämme 
        // this-viitettä.
        this.väri = väri;
    }

    // Olion metodi, joka palauttaa 'väri'-attribuutin arvon kutsujalle.
    public String getVäri() {
        return this.väri;
    }
}

Voisimme lisätä myös vastaavat metodit luokan toista attribuuttia varten. Keskustelemme myöhemmin tässä osassa siitä, miten olion tilaa voidaan käsitellä hieman järkevämmin, mutta tässä vaiheessa tällaiset yksinkertaiset get- ja set-metodit riittävät.

This-viite

Avainsana this viittaa olioon itseensä. Se toimii viitteenä "tähän olioon", jonka kontekstissa koodia suoritetaan.

Käytimme tämän osan alun esimerkeissä this-viitettä lukeaksemme olion attribuutteja näin:

public class Rakennus {
    private String väri;

    // ...

    public String getVäri() {
        return this.väri;
    }
}

Tämä viite on automaattisesti käytettävissä aina, kun kutsutaan jonkin olion metodia. Metodikutsun yhteydessä this asetetaan osoittamaan metodin suorituksen sisällä siihen olioon, jonka metodia kutsuttiin, eikä sitä voi muuttaa. Viitteen kautta metodi pääsee käsiksi oikean olion tilaan sekä metodeihin. Olion metodien sisällä this-viite on pakko kirjoittaa vain, jos näkyvyysalueella on samanniminen jäsen, kuten lokaali muuttuja. Meidän ei siis tarvitse kirjoittaa aina attribuutin tai metodikutsun eteen this, sillä kääntäjä osaa päätellä sen itse, jos konfliktia ei ole. Joissain ohjelmointikielissä tällaista viitettä kutsutaan myös nimellä self.

Katsotaan muutama esimerkki this-viitteen käytöstä.

Rakennus.java
public class Rakennus {
    private String omistaja;
    private String väri;

    public void setVäri(String väri) {
        // Käytämme tässä this-viitettä, sillä attribuutilla ja parametrilla 
        // on sama nimi. Lokaalia muuttujaa käytettäisiin muuten ensisijaisesti.
        this.väri = väri; 
    }

    public String getVäri() {
        // Tässä this on vapaaehtoinen. Samalla näkyvyysalueella ei ole muita 
        // "väri" nimisiä muuttujia, joten sekaannusta ei tapahdu.
        // this-viitteen käyttö on kuitenkin täysin sallittua.
        return this.väri;
    }
}

Metodien sisällä voimme käyttää this-viitettä kuin muitakin viitemuuttujia. Voimme esimerkiksi välittää sen toiselle aliohjelmalle parametrina. Tämä ei ole tarpeen olion omia metodeja kutsuessa, mutta voimme näin välittää viitteen olioon myös luokan ulkopuolisille aliohjelmille.

Rakennus.java
public class Rakennus {
    private String omistaja;
    private String väri;

    public String getOmistaja() {
        return omistaja;
    }

    public void setOmistaja(String omistaja) {
        this.omistaja = omistaja;
    }

    public String getVäri() {
        return väri;
    }

    public void setVäri(String väri) {
        this.väri = väri;
    }

    // Olion metodi.
    public String kaunista() {
        // Välitetään omistava olio toisen luokan metodille.
        return Kaunistaja.MuotoileKauniisti(this);
    }
}

Muodostaja eli konstruktori

Muodostaja eli konstruktori on luokan erikoismetodi, jota käytetään uuden olion luomisen yhteydessä sen tilan alustamiseen. Muodostajan nimi on aina sama kuin luokan nimi ja se kirjoitetaan isolla alkukirjaimella, mikä poikkeaa muiden metodien nimeämistyylistä. Muodostajalle ei määritetä paluuarvon tyyppiä, vaan muodostaja palauttaa aina viitteen muodostettuun olioon.

Luokassa täytyy olla ainakin yksi muodostaja. Olemme kuitenkin tähän asti luoneet olioita määrittelemättä luokkaan muodostajaa. Tämä onnistuu Javassa, sillä jos luokkaan ei ole tehty yhtään muodostajaa, kääntäjä luo automaattisesti parametrittoman muodostajan. Automaattisesti luotu parametriton muodostaja on toteutukseltaan tyhjä siinä mielessä, että se ei sisällä yhtään lausetta. Parametritonta muodostajaa ei luoda automaattisesti, jos määrittelemme luokkaan yhdenkin muodostajan itse.

Muodostajan nimi on aina sama kuin luokan, joten teemme siis muodostajia lisätessämme metodin kuormitusta erilaisilla parametreilla. Kääntäjä valitsee oikean muodostajan automaattisesti olion luomisen yhteydessä annettujen argumenttien perusteella.

Käytimme aikaisemmassa esimerkissä Rakennus-luokkaa määrittelemättä muodostajaa. Otetaan metodit hetkeksi pois selkeyden vuoksi ja katsotaan, mitä olioita luodessa tapahtuu.

Rakennus.java
public class Rakennus {
    private String omistaja;
    private String väri;
}

Tässä tapauksessa olio muodostetaan automaattista oletusmuodostajaa käyttäen, sillä yhtään muodostajaa ei ole määritelty. Voimme tehdä vastaavan muodostajan itsekin seuraavasti.

Rakennus.java
public class Rakennus {
    private String omistaja;
    private String väri;

    // Parametriton muodostaja, joka vastaa oletusmuodostajaa.
    public Rakennus() {
        // Voisimme täällä alustaa olion tilan jollain tavalla, 
        // asettamalla attribuuteille alkuarvot.
    }
}

Olemme tähän asti muodostaneet olioita ilman kunnollista alustusta. Tämä ei ole hyvä käytäntö, joten korjataan tilanne seuraavaksi.

Rakennukset ovat heti luomisen jälkeen tilassa, jossa niillä ei ole omistajaa tai väriä. Tällä hetkellä vasta luodun olion attribuuttien arvot ovat null. Oliosta ja sen tarkoituksesta riippuen tällaiset arvot voivat olla sallittuja, mutta jos rakennus on olemassa, sillä täytynee olla jokin omistaja ja väri.

Olisi parempi, että olio olisi heti luonnin jälkeen käyttökelpoinen. Voisimme luoda olion suoraan oikeaan tilaan määrittelemällä muodostajan, joka ottaa olion tilan alustamiseen tarvittavat tiedot vastaan parametreina ja alustaa attribuutit oikein.

Lisätään nyt Rakennus-luokalle muodostaja, joka ottaa omistajan ja värin vastaan parametreina ja alustaa näiden avulla olion attribuutit. Näin meidän ei tarvitse asettaa attribuutteja erikseen olion luomisen jälkeen pääohjelmassa. Voimme nyt myös poistaa parametrittoman muodostajan, jotta sitä ei voi enää käyttää luomaan olioita, joilla on virheellinen alkutila.

Rakennus.java
public class Rakennus {
    private String omistaja;
    private String väri;

    public Rakennus(String omistaja, String väri) {
        // Alustetaan olion tila parametreilla. Huomaa tässä this-viitteen 
        // käyttö, sillä parametrien ja attribuuttien nimet ovat samat.
        this.omistaja = omistaja;
        this.väri = väri;
    }
}

Pari huomiota: Emme voi luoda rakennusta yhdellä parametrilla, esimerkiksi Rakennus rakennus2 = new Rakennus("JYU");, sillä määrittelemämme muodostaja tarvitsee kaksi parametria. Emme voi myöskään luoda rakennusta ilman parametreja, esimerkiksi Rakennus rakennus3 = new Rakennus();, koska oletusmuodostajaa ei enää ole.

Lisätään vielä lopuksi hieman erikoisempi muodostaja, joka ottaa vastaan toisen saman luokan olion ja kopioi sen tilan muodostettavalle oliolle. Tällaisesta muodostajasta puhuttaessa käytetään usein nimitystä copy constructor.

Rakennus.java
public class Rakennus {
    private String omistaja;
    private String väri;

    public Rakennus(String omistaja, String väri) {
        this.omistaja = omistaja;
        this.väri = väri;
    }

    // Muodostaja, joka ottaa vastaan toisen saman luokan olion ja 
    // kopioi sen arvot muodostettavalle oliolle.
    public Rakennus(Rakennus kopioitava) {
        this.omistaja = kopioitava.omistaja;
        this.väri = kopioitava.väri;
    }
}

Voimme lisäksi käyttää muodostajassa this-avainsanaa kuin metodia, jos haluamme siirtää muodostamisen toiselle saman luokan muodostajalle. Voimme usein välttää näin turhaa toistoa.

Muutetaan luokkaa nyt niin, että parametriton muodostaja käyttää parametrillista muodostajaa antamaan attribuuteille alkuarvot.

Rakennus.java
public class Rakennus {
    private String omistaja;
    private String väri;

    public Rakennus(String omistaja, String väri) {
        this.omistaja = omistaja;
        this.väri = väri;
    }

    public Rakennus(Rakennus kopioitava) {
        // Siirrämme muodostamisen yllä olevalle muodostajalle sitä 
        // vastaavilla parametreilla
        this(kopioitava.omistaja, kopioitava.väri);
    }
}
Tehtävä 2.1: Kello1 p.

Tee luokka Kello, jolla on attribuutit minuutit ja tunnit kokonaislukuina.

Lisää luokkaan metodit setMinuutit ja setTunnit, jotka ottavat parametrina kelloon minuutit ja tunnit. Minuuttien täytyy olla välillä 0-59, muuten tulostetaan virheilmoitus, ja aika pysyy entisellään. Samoin tuntien täytyy olla välillä 0-23, muuten tulostetaan virheilmoitus.

Tee myös metodi naytaAika, joka tulostaa ajan muodossa "HH:MM".

Voit testata luokan toimintaa valmiin pääohjelman avulla.

Tee tehtävä TIMissä
Tehtävä 2.2: Ajastin1 p.

Tee luokka Ajastin, jolla on attribuutit minuutit ja sekunnit kokonaislukuina.

Lisää luokkaan metodit lisaaMinuutteja ja lisaaSekunteja, jotka ottavat parametrina ajastimeen lisättävät minuutit ja sekunnit. Lisää myös metodi annaMerkkijono, joka antaa ajastimen minuutit ja sekunnit merkkijonona.

Minuutteja voi olla kuinka monta tahansa, mutta sekuntien täytyy olla välillä 0-59. Jos sekunnit ylittävät rajan, muutetaan ne minuuteiksi. Sekunnit voi muuttaa minuuteiksi + sekunneiksi esimerkiksi näin:

int sekunteja = 75; // Esimerkki parametrina tulevasta arvosta

// Tämä antaa this.minuutteja-attribuuttiin _lisättävät_ minuutit
int lisattaviaMinuutteja = (this.sekunteja + sekunteja) / 60; 

// Tämä antaa this.sekunteja-attribuuttiin _sijoitettavat_ sekunnit
int jaljelleJaavatSekunnit = (this.sekunteja + sekunteja) % 60;  

Voit testata luokan toimintaa valmiin pääohjelman avulla.

Tee tehtävä TIMissä
Tehtävä 2.3: Oma luokka1 p.

Valitse jokin tosimaailman esine tai käsite ja tee siitä oma yksinkertainen luokka.

Lisää luokkaan seuraavat:

  • Vähintään kaksi sille sopivaa attribuuttia.
  • Vähintään kaksi metodia, jotka muuttavat tai tarkastelevat olion tilaa jollain tavalla.
  • Vähintään kaksi erilaista muodostajaa, jotka alustavat olion järkevään tilaan. Se mikä on järkevä tila riippuu omasta oliostasi.

Voit käyttää materiaalin esimerkkejä tarvittaessa inspiraationa attribuutteja ja metodeja keksiessä.

Luo olio ja kokeile sen tilan muuttamista pääohjelmassa. Tulosta olion tietoja ennen tilan muuttamista ja sen jälkeen.

Tee tehtävä TIMissä

Static

Luokan jäsenet voidaan määritellä kuuluvaksi olion sijaan luokalle static-määritettä käyttämällä. Tällaisia attribuutteja ja metodeja kutsutaan luokan attribuuteiksi (engl. class attribute) ja luokan metodeiksi (engl. class method).

Sana static voi olla hieman harhaanjohtava; staattisuus ei tässä tarkoita, että nämä luokan jäsenet ovat pysyviä tai muuttumattomia. Käytämme kuitenkin tässä materiaalissa sanaa staattinen kuvaamaan luokan jäseniä.

Siinä missä attribuutit ja metodit liittyvät olioon -- olion attribuutit ja metodit pääsevät olion tilaan käsiksi -- luokan attribuutti ei ole osa minkään olion tilaa ja sillä on vain yksi arvo, joka on jaettu kaikkien luokan olioiden kesken. Jos yksi olio muuttaa oman luokkansa attribuutin arvoa, muutos näkyy kaikissa saman luokan olioissa.

Samalla tavalla voimme ajatella myös luokan metodien olevan jaettu kaikkien luokan olioiden kesken; ne eivät liity mihinkään olioon, eivätkä siten pääse minkään olion tilaan suoraan käsiksi. Oliot voivat kutsua oman luokkansa metodia, siis staattista metodia, mutta tämä kutsu ei mahdollista olion tilan käsittelyä. Koska staattiset metodit eivät liity mihinkään olioon, niiden sisällä ei myöskään voi käyttää this-viitettä.

Voidaksemme kutsua olion metodia, meillä täytyy olla olio ja siihen viite. Staattista luokan metodia sen sijaan voimme kutsua suoraan luokan kautta ilman olion luomista. Staattista metodia on mahdollista kutsua myös olioviitteen kautta, mutta tämäkään ei mahdollista olion tilan tarkastelua metodin sisältä.

Tehdään aluksi luokka, jossa ei ole staattisia jäseniä.

Henkilo.java
public class Henkilo {
    private String etunimi;
    private String sukunimi;

    public Henkilo(String etunimi, String sukunimi) {
        this.etunimi = etunimi;
        this.sukunimi = sukunimi;
    }

    public String annaNimi() {
        return etunimi + " " + sukunimi;
    }
}

Koska annaNimi on olion metodi, meidän täytyy ensin luoda olio. Emme voi kutsua tätä metodia staattisesti luokan kautta, esimerkiksi Henkilo.annaNimi().

Lisätään nyt luokan attribuutti taysiIkaisyydenRaja, joka kuvastaa kaikkien henkilöiden täysi-ikäisyyden rajaa. Lisätään luokkaan myös metodi onkoTaysiIkainen, joka tarkistaa, onko annettu ikä täysi-ikäinen. Huomaa, että kyseinen metodi ei liity mihinkään olioon, vaan se tarkistaa vain annetun iän.

Henkilo.java
public class Henkilo {
    private String etunimi;
    private String sukunimi;
    private static int taysiIkaisyydenRaja = 18;

    public Henkilo(String etunimi, String sukunimi) {
        this.etunimi = etunimi;
        this.sukunimi = sukunimi;
    }

    public String annaNimi() {
        return etunimi + " " + sukunimi;
    }

    public static boolean onkoTaysiIkainen(int ika) {
        return ika >= taysiIkaisyydenRaja;
    }
}

Nyt meillä on luokan metodi onkoTaysiIkainen, jota voimme kutsua ilman oliota. Voisimme kutsua tätä luokan metodia myös olion kautta, mutta se ei ole tarpeen ja kääntäjä varoittaakin siitä.

Staattinen jäsen on hyödyllinen, kun haluamme määritellä jonkin toiminnallisuuden, joka ei liity mihinkään yksittäiseen olioon, mutta liittyy luokkaan yleisesti.

Staattiseen jäseneen pääsee käsiksi myös olion metodin sisältä. Voimme esimerkiksi tarkistaa, onko henkilö täysi-ikäinen.

// Olion metodi voi myös käyttää staattista muuttujaa
public boolean onkoHenkiloItseTaysiIkainen() {
    return this.ika >= taysiIkaisyydenRaja;
}

Selkeyden vuoksi on hyvä käyttää luokan nimeä, kun viitataan staattiseen jäseneen luokan sisälläkin, esimerkiksi Henkilo.taysiIkaisyydenRaja. Tämä auttaa erottamaan staattiset jäsenet olion jäsenistä.

Yllä oleva esimerkki on sikäli varoittava, että täysi-ikäisyyden rajaa pystyy muuttamaan mistä tahansa koodista, joka pääsee käsiksi Henkilo-luokkaan. Tällaisen "globaalin" tilan käyttäminen koodissa on yleensä erittäin huono idea.

Yksi esimerkki staattisesta metodista, jota käytämme usein, on IO.println(). Tämä on IO-luokan staattinen metodi. Sen käyttäminen on helpompaa, kun meidän ei tarvitse joka kerta luoda IO-oliota ja kutsua sen println-metodia.

Tehtävä 2.4: Static1 p.

Muuta esimerkin Henkilo-luokkaa niin, että jokainen luotu henkilö saa automaattisesti oman järjestysnumeron, joka on kokonaisluku. Ensimmäiseksi luodun henkilön numero on 1, seuraava saa numeroksi 2, jne.

Numeroa ei saa antaa henkilölle sen ulkopuolelta esimerkiksi kutsumalla sen metodia.

Toteuta luokkaan myös metodi annaNumero, joka palauttaa tietyn olion numeron kokonaislukuna. Muita metodeja ei tarvitse lisätä.

Vinkki

Tarvitset tässä tehtävässä staattisia luokan jäseniä.

Mieti aluksi seuraavia kysymyksiä:

  • Milloin olio saa numeron?
  • Mistä olio tietää, mikä sen numeron pitäisi olla?
  • Mikä tieto on jaettua olioiden kesken ja mikä on oliokohtaista?
Tee tehtävä TIMissä

Olion elinkaari

Ohjelman ajon aikana luokasta luodaan ilmentymä eli olio. Jotta olioon voidaan päästä käsiksi, luodaan sitä varten viitemuuttuja. Javan kääntäjä tarkistaa käännöksen yhteydessä, että muuttuja on yhteensopiva luodun olion kanssa, ja asettaa viitteen osoittamaan luotuun olioon.

Olion luonnin yhteydessä Java varaa sille sopivan tilan virtuaalikoneensa kekomuistista. Kun olioon ei enää ole yhtään viitettä olemassa, se tuhoutuu. Javassa ohjelmoijan ei tarvitse itse pitää huolta muistin varaamisesta tai vapauttamisesta. Tuhoutuneiden olioiden varaama muisti vapautetaan lopulta Javan automaattisen roskienkeräyksen toimesta.

Käydään vielä olion koko elinkaari läpi esimerkkien avulla. Tarvitsemme olioiden luomista varten ensimmäiseksi luokan. Käytetään esimerkkinä taas Henkilo-luokkaa, mutta lisätään nyt muutama hyvin yksinkertainen metodi olion tilan muuttamiseen.

Henkilo.java
class Henkilo {
    private String etunimi;
    private String sukunimi;

    public Henkilo(String etunimi, String sukunimi) {
        this.etunimi = etunimi;
        this.sukunimi = sukunimi;
    }

    public String annaNimi() {
        return etunimi + " " + sukunimi;
    }

    public void asetaEtunimi(String etunimi) {
        this.etunimi = etunimi;
    }

    public void asetaSukunimi(String sukunimi) {
        this.sukunimi = sukunimi;
    }
}

Pääohjelmassa luodaan nyt olio eri muodostajia käyttäen ja katsotaan samalla viitteiden toimintaa.

Kun olemme luoneet olioita, voimme tarkastella ja muokata niiden tilaa ohjelman suorituksen aikana.

main.java
void main() {
    Henkilo h1 = new Henkilo("Joni", "Mäkinen");
    IO.println(h1.annaNimi());
    h1.asetaSukunimi("Korhonen");
    IO.println(h1.annaNimi());
}

Tarkastellaan lopuksi olioiden elinkaaren loppua, eli niiden tuhoutumista. Kun olioon ei enää ole yhtään viitettä, se merkitään "roskaksi", jonka Javan automaattinen roskienkeräys (engl. garbage collection) voi aikanaan poistaa muistista vapauttaen sitä varten varten varatun tilan.

main.java
void main() {
    // Luomme taas kaksi oliota.
    Henkilo h1 = new Henkilo("Joni", "Mäkinen");
    Henkilo h2 = new Henkilo("Anna", "Korhonen");

    // Muuttuja h1 on tällä hetkellä ainoa viite ensimmäiseen olioon.
    // Jos sijoitamme h1-muuttujaan jonkin muun viitten tai asetamme sen arvoksi
    // null, olio merkitään tuhottavaksi, sillä siihen ei ole enää viitteitä.
    h1 = null;

    // Aliohjelman päättyessä kaikki sen sisällä luodut lokaalit 
    // muuttujat (h1 ja h2) tuhoutuvat. Tässä tapauksessa olioihin ei ole 
    // enää muita viitteitä, joten nekin tuhoutuvat.
}

Emme tällä kurssilla perehdy kovin syvällisesti Javan automaattiseen roskienkeräykseen tai muistin hallintaan. Tämän kurssin kannalta riittää, että tiedämme milloin olio muuttuu roskaksi ja tuhoutuu. Jos haluat tutustua aiheeseen hieman tarkemmin, voit aloittaa lukemalla täältä täältä lisää kekomuistista ja sen varaamisesta sekä täältä Javan roskienkeräyksestä suhteellisen helposti lähestyttävässä muodossa.

Tehtävät

Tehtävä 2.5: Puhelin1 p.

Tee luokka Puhelin, jolla on attribuutit merkki (merkkijono) ja akunVaraus (kokonaisluku, joka kuvaa akun varausta prosentteina väliltä 0-100). Lisää luokkaan seuraavat metodit:

  • lahetaViesti(String henkilo, String viesti): tulostaa viestin muodossa "Lähetetään viesti henkilölle <henkilo>: <viesti>". Viestin lähettäminen vähentää akkua 5 prosenttiyksikköä.
  • soita(String henkilo, int minuutit): tulostaa viestin muodossa "Soitetaan puhelu henkilölle <henkilo>, kesto: <minuutit> minuuttia". Soittaminen vähentää akkua 1 prosenttiyksikköä per minuutti.
  • lataa(int prosentteja): lisää akun varausta annetun määrän, mutta akun varaus ei voi ylittää 100 prosenttia.
  • tulostaTiedot(): tulostaa puhelimen merkin ja akun varauksen muodossa "Puhelimen <merkki> akun varaus on <akku>%".

Korvaa kulmasulkeissa olevat kohdat sopivilla attribuuttien / parametrien arvoilla.

Akun varaus ei voi mennä alle 0%.

Jos akun varaus on 0%, viestiä ei voida lähettää eikä voi soittaa "Akku tyhjä. Viestiä ei voida lähettää / ei voi soittaa.".

Testaa sovellustasi luomalla Puhelin-olio, lähettämällä viesti, soittamalla puhelu, lataamalla akkua ja tulostamalla puhelimen tiedot.

Valinnainen lisätehtävä: Jos akku loppuu kesken

Muokkaa lahetaViesti- ja soita-metodeja siten, että jos akun varaus ei riitä koko viestin lähettämiseen tai puhelun soittamiseen, niin lähetetään/soitetaan niin kauan kuin akku riittää. Jos akku loppuu kesken puhelun / viestin, tulosta kauanko puhelu onnistui / kuinka paljon viestistä saatiin lähetettyä ennen akun loppumista. Esimerkiksi "Akku tyhjä. Sait lähetettyä 60% viestistä." tai "Akku loppui 3 minuutin jälkeen.

Tee tehtävä TIMissä
Tehtävä 2.6: Kirjasto1 p.

Toteuta luokka Kirja, joka pitää kirjaa yksittäisistä kirjoista, mutta myös seuraa kirjaston tilastoja globaalisti. Luo luokalle seuraavat muuttujat:

Oliomuuttujat:

  • String nimi: Kirjan nimi.
  • String kirjoittaja: Kirjan kirjoittaja.
  • boolean onLainassa: Kertoo, onko kyseinen kirja tällä hetkellä lainassa.

Luokkamuuttujat (static):

  • static int kirjojenMaara: Kuinka monta kirjaa on luotu yhteensä.
  • static int lainassaOlevat: Kuinka monta kirjaa on tällä hetkellä lainassa.

Muodostajan tulee ottaa vastaan nimi ja kirjoittaja. Aina kun uusi kirja luodaan, kirjojenMaara-muuttuja kasvaa yhdellä.

Tee oliometodit lainaa() ja palauta: Nämä muuttavat kirjan onLainassa-tilaa ja päivittävät staattisen lainassaOlevat-laskurin.

Tee staattinen metodi tulostaTilastot(), joka tulostaa ruudulle kirjaston tilastot: "Kirjasto sisältää X kirjaa, joista Y on lainassa."

Saat valmiina pääohjelman, jota voit käyttää ohjelmasi testaamiseen

Tee tehtävä TIMissä