11. Olio-ohjelmointi

Olio

Olio muodostuu kahdesta asiasta:

Tähän mennessä olemme jo käyttäneet kahdenlaisia olioita:

String-oliot

String-olion tietosisältönä ovat merkkijonossa olevat merkit. Olion metodeja ovat mm. length, joka palauttaa merkkien määrän, sekä charAt, joka palauttaa tietyssä kohdassa olevan merkin.

ArrayList-oliot

ArrayList-olion tietosisältönä ovat listalla olevat arvot. Olion metodeja ovat mm. add, joka lisää uuden arvon listalle, sekä size, joka palauttaa listalla olevien arvojen määrän.

Luokka

Luokka kertoo, miten sitä vastaavat oliot rakentuvat. Tarkastellaan esimerkiksi seuraavia merkkijonoja:

Merkkijonot ovat erilaisia, mutta niissä on yhteistä se, että kaikissa on joukko merkkejä peräkkäin. String-luokka määrittelee tämän asian: jokainen String-olio sisältää joukon merkkejä peräkkäin.

Muuttujien tyypit

Javassa muuttujan tyyppi on joko alkeistyyppi tai viittaustyyppi.

Alkeistyyppi tarkoittaa, että muuttujaan tallennetaan suoraan sen arvo. Esimerkiksi int ja double ovat alkeistyyppejä.

Viittaustyyppi tarkoittaa, että muuttujaan tallennetaan viittaus olioon. Luokkaa vastaava muuttuja on viittaustyyppinen.

Tarkastellaan seuraavaa koodia:

String nimi = "Aapeli";
int ika = 31;

Tässä String on viittaustyyppi, kun taas int on alkeistyyppi, minkä vuoksi tilanne muistissa näyttää seuraavalta:

Arvo vs. viittaus

Seuraava koodi havainnollistaa arvon ja viittauksen eroa:

int luku1 = 5;
int luku2 = 12;

ArrayList<Integer> lista1 = new ArrayList<Integer>();
lista1.add(2);
lista1.add(7);
ArrayList<Integer> lista2 = new ArrayList<Integer>();
lista2.add(4);

System.out.println("luku1 = " + luku1 + ", luku2 = " + luku2);
System.out.println("lista1 = " + lista1 + ", lista2 = " + lista2);

luku1 = luku2;
lista1 = lista2;

System.out.println();
System.out.println("luku1 = " + luku1 + ", luku2 = " + luku2);
System.out.println("lista1 = " + lista1 + ", lista2 = " + lista2);

luku1 = 8;
lista2.add(5);

System.out.println();
System.out.println("luku1 = " + luku1 + ", luku2 = " + luku2);
System.out.println("lista1 = " + lista1 + ", lista2 = " + lista2);

Koodin tulostus on seuraava:

luku1 = 5, luku2 = 12
lista1 = [2, 7], lista2 = [4]

luku1 = 12, luku2 = 12
lista1 = [4], lista2 = [4]

luku1 = 8, luku2 = 12
lista1 = [4, 5], lista2 = [4, 5]

Seuraava kuva vastaa ensimmäistä tilannetta:

Toisessa tilanteessa tapahtuu seuraavaa:

Nyt muuttujilla luku1 ja luku2 on sama arvo, kun taas muuttujat lista1 ja lista2 alkavat viitata samaan olioon.

Kolmas tilanne on mielenkiintoisin:

Muuttujat lista1 ja lista2 viittaavat samaan olioon, joten kun olio muuttuu, muutos heijastuu kumpaankin muuttujaan.

Roskienkerääjä

Yllä olevissa kuvissa kannattaa myös huomata, että vaikka mikään muuttuja ei viittaa enää listaan [2, 7], se on silti edelleen muistissa.

Roskienkerääjä on Javan suoritusympäristön osa, jonka tehtävänä on poistaa muistista tarpeettomia olioita. Se ei välttämättä käy toimeen heti, kun viimeinen viittaus olioon katoaa, mutta ennemmin tai myöhemmin olion varaama tila vapautuu uuteen käyttöön.

Metodin parametrit

Arvon ja viittauksen ero näkyy myös metodin parametreissa. Jos parametri on alkeistyyppinen, metodin muutokset siihen eivät välity kutsukohtaan. Jos taas parametri viittaa olioon ja metodi muuttaa oliota, muutos jää voimaan metodin suorituksen jälkeen.

Asiaa havainnollistaa seuraava metodi:

public static void testi(int luku, ArrayList<Integer> lista) {
    luku = 5;
    lista.add(8);
}

Käytetään metodia seuraavasti:

int luku = 3;
ArrayList<Integer> lista = new ArrayList<Integer>();
lista.add(4);
lista.add(7);
System.out.println("luku = " + luku + ", lista = " + lista);
testi(luku, lista);
System.out.println("luku = " + luku + ", lista = " + lista);

Koodin tulostus on seuraava:

luku = 3, lista = [4, 7]
luku = 3, lista = [4, 7, 8]

Vaikka metodi asetti parametrille luku uuden arvon 5, kutsukohdassa arvo 3 säilyy ennallaan. Sen sijaan luku 8, jonka metodi lisää parametrin lista viittaamaan listaan, ilmestyy myös kutsukohtaan.

Tyhjä viittaus

Javassa on myös mahdollista ilmaista, että viittaustyyppinen muuttuja ei viittaa mihinkään olioon. Tällöin muuttujan arvona on null.

Oma olio

Olio-ohjelmointi kuuluu pääasiassa Ohjelmoinnin jatkokurssin asioihin. Jo tässä vaiheessa on kuitenkin paikallaan vilkaista, miltä itse tehty olio näyttää. Teemme luokan Opiskelija, jota vastaavat oliot pitävät sisällään yhden opiskelijan tiedot.

Esimerkin testaamiseksi voit luoda uuden NetBeans-projektin, jonka nimeksi tulee OpiskelijaTesti. Saat projektiin uuden luokan valitsemalla File -> New File ja kirjoittamalla kohtaan Class Name luokan nimeksi Opiskelija.

Luokan Opiskelija koodi on seuraava:

public class Opiskelija {
    private String nimi;
    private int opintopisteet;

    public Opiskelija(String nimi, int opintopisteet) {
	this.nimi = nimi;
	this.opintopisteet = opintopisteet;
    }

    public void opiskele() {
	opintopisteet++;
    }

    public String toString() {
	return nimi + " (" + opintopisteet + " op)";
    }    
}

Seuraava ohjelma esittelee luokan käyttöä:

public class OpiskelijaTesti {
    public static void main(String[] args) {
	Opiskelija aapeli = new Opiskelija("Aapeli", 115);
	Opiskelija maija = new Opiskelija("Maija", 209);

	System.out.println("Opiskelijat:");
	System.out.println(aapeli);
	System.out.println(maija);

	aapeli.opiskele();
	aapeli.opiskele();
	maija.opiskele();

	System.out.println("Opiskelijat:");
	System.out.println(aapeli);
	System.out.println(maija);
    }
}

Ohjelman tulostus on seuraava:

Opiskelijat:
Aapeli (115 op)
Maija (209 op)
Opiskelijat:
Aapeli (117 op)
Maija (210 op)

Tarkastellaan osissa luokan Opiskelija rakennetta:

Tässä vaiheessa riittää, että saat suoritettua esimerkin ja ymmärrät suurin piirtein, mitä siinä tapahtuu. Palaamme asiaan tarkemmin Ohjelmoinnin jatkokurssilla.