Ohjelmoinnin jatkokurssi

Matti Paksula, Arto Vihavainen, Matti Luukkainen

Alkeistyyppi ja viittaustyyppi

Javassa on kaksi erilaista muuttujatyyppiä, alkeistyyppiset muuttujat ja viittaustyyppiset muuttujat. Alkeistyyppisillä muuttujilla tallennetaan tietoa yksittäisiin lokeroihin, kun taas viittaustyyppiset muuttujat sisältävät viitteen, joka osoittaa tietoon.

Alkeistyyppi

Alkeistyyppiset muuttujat tallentavat arvonsa omaan lokeroon. Uutta alkeistyyppistä muuttujaa alustettaessa luodaan aina uusi lokero. Alkeistyyppiset muuttujat alustetaan sijoitusoperaatiolla =. Katsotaan esimerkkiä.

int vitonen = 5;
int kutonen = 6;

Esimerkki luo kaksi alkeistyyppistä muuttujaa, nimiltään vitonen ja kutonen. Muuttujan vitonen lokeroon asetetaan arvo 5, ja muuttujan kutonen lokeroon arvo 6. Kaikki alkeistyyppiset muuttujat, kuten javan kaikki muutkin muuttujat, ovat tietyn tyyppisiä. Muuttujat vitonen ja kutonen ovat kumpikin int-tyyppisiä, eli kokonaislukuja. Kuvana alkeistyyppiset muuttujat kannattaa ajatella laatikkoina jonka sisällä muuttujan arvo on talletettuna:

	
          -----                                   
 viitonen | 5 |                                     
          -----       

          -----                                   
 kuutonen | 6 |                                     
          -----  		  

Tarkastellaan vielä alkeistyyppisten muuttujien asettamista toisen muuttujan avulla.

int vitonen = 5;
int kutonen = 6;

vitonen = kutonen; // muuttuja vitonen sisältää nyt arvon 6, eli arvon joka oli muuttujassa kutonen
kutonen = 42; // muuttuja kutonen sisältää nyt arvon 42

// muuttuja vitonen sisältää vieläkin arvon 6

Esimerkissä alustetaan ensiksi muuttujat vitonen ja kutonen. Tämän jälkeen muuttujan vitonen lokeroon asetetaan muuttujan kutonen lokeron sisältämä arvo. Tässä vaiheessa siis muuttujan vitonen lokeroon tallentuu muuttujan kutonen sisältämä arvo. Jos muuttujan kutonen arvoa muutetaan tämän jälkeen, ei muuttujan vitonen sisältämä arvo muutu. Lopputilanne kuvana

	
          ------                                   
 viitonen |  6 |                                     
          ------       

          ------                                   
 kuutonen | 42 |                                     
          ------  		  

Alkeistyyppinen muuttuja metodin parametrina ja paluuarvona

Kun alkeistyyppinen muuttuja annetaan metodille parametrina, saa metodin parametrimuuttuja kopion annetun muuttujan arvosta. Katsotaan seuraavaa metodia lisaaLukuun(int luku, int paljonko).

public int lisaaLukuun(int luku, int paljonko) {
  return (luku + paljonko);  
}

Metodi lisaaLukuun() saa kaksi parametria, kokonaisluvut luku ja paljonko. Metodi palauttaa uuden luvun, joka on annettujen parametrien summa. Tutkitaan vielä metodin kutsumista.

int omaLuku = 10;
omaLuku = lisaaLukuun(omaLuku, 15);
// muuttuja omaLuku sisältää nyt arvon 25

Esimerkissä kutsutaan lisaaLukuun()-metodia muuttujalla omaLuku ja arvolla 15. Metodin muuttujiin luku ja paljonko kopioituvat siis arvot 10, eli muuttujan omaLuku sisältö, ja 15. Metodi palauttaa muuttujien luku ja paljonko summan, eli 10 + 15 = 25.

Minimi- ja maksimiarvot

Eri tietotyypeillä on omat minimi- ja maksimiarvonsa, eli arvot joita pienempiä tai suurempia ne eivät voi olla. Tämä johtuu Javan (ja muidenkin useimpien ohjelmointikielten) sisäisestä tiedon esitysmuodosta, jossa tietotyyppien koot on ennalta määrätty.

Alla vielä muutama Javan alkeistyyppi ja niiden minimi- ja maksimiarvot

MuuttujatyyppiSelitysMinimiarvoMaksimiarvo
intKokonaisluku-2 147 483 648 (Integer.MIN_VALUE)2 147 483 647 (Integer.MAX_VALUE)
longIso kokonaisluku-9 223 372 036 854 775 808 (Long.MIN_VALUE)9 223 372 036 854 775 807 (Long.MAX_VALUE)
booleanTotuusarvotrue tai false
doubleLiukulukuDouble.MIN_VALUEDouble.MAX_VALUE

Liukulukuja käyttäessä kannattaa muistaa että liukuluvun arvo on aina arvio oikeasta arvosta. Koska liukuluvun, kuten kaikkien muidenkin alkeistyyppien sisältämä tietomäärä on rajoitettu, voidaan huomata yllättäviäkin pyöristysvirheitä. Esimerkiksi seuraava tilanne.

double eka = 0.39;
double toka = 0.35;
System.out.println(eka - toka);

Esimerkki tulostaa arvon 0.040000000000000036. Pyöristysvirheisiin varaudutaan usein muunmuassa vertaamalla arvon kuulumista tiettyyn arvoväliin. Ohjelmointikielet tarjoavat usein työkalut vastaavien tilanteiden välttämiseen, esimerkiksi Javassa on olemassa luokka BigDecimal liukulukujen tarkempaa laskemista varten.

Viittaustyyppi

Viittaustyyppiset muuttujat tallentavat niihin liittyvän tiedon viitteen taakse, ja itse muuttuja toimii vain viitteenä tiedon sisältävään paikkaan. Toisin kuin alkeistyyppisillä muuttujilla, viittaustyyppisillä muuttujilla ei ole rajoitettua arvoaluetta, koska niiden oikea arvo tai tieto on viitteen takana.

Viittaustyyppisistä muuttujista puhutaan olioina, ja ne luodaan new-kutsulla. Muuttujan arvo asetetaan vieläkin sijoitusoperaattorilla =, mutta komento new luo olion ja palauttaa viitteen olioon. Tämä viite asetetaan muuttujan arvoksi. Katsotaan kahden viittaustyyppisen muuttujan luontia. Käytetään ohjelmoinnin perusteet -kurssilta tuttua Laskuri-luokkaa esimerkeissä.

Laskuri matinLaskuri = new Laskuri(5);
Laskuri artonLaskuri = new Laskuri(3);

Esimerkissä luodaan ensiksi viittaustyyppinen muuttuja matinLaskuri. Komentoa new kutsuessa viitteen taakse varataan tila muuttujan tiedolle, luodaan Laskuri-tyyppinen olio, ja palautetaan viite siihen. Palautettu viite asetetaan sijoitusoperaattorilla = muuttujaan matinLaskuri. Sama tapahtuu muuttujalle nimeltä artonLaskuri. Kuvana viittaustyyppi kannattaa ajatella siten, että muuttuja sisältää "langan" tai "nuolen", jonka päässä on olio itse. Muuttuja siis ei säilytä olioa vaan tiedon eli viitteen sinne missä olio on.

             -----             --olio----
matinLaskuri | --|---------->  | arvo 5 |
             -----             ----------
	
             -----             --olio----
artonLaskuri | --|---------->  | arvo 3 |
             -----             ----------

Katsotaan seuraavaksi viittaustyyppisen muuttujan asettamista toisen muuttujan avulla.

Laskuri matinLaskuri = new Laskuri(5);
Laskuri artonLaskuri = new Laskuri(3);

matinLaskuri = artonLaskuri; // muuttuja matinLaskuri sisältää nyt muuttujan artonLaskuri sisältämän viitteen, 
                             // eli viitteen Laskuri-tyyppiseen olioon joka on saanut konstruktorissaan arvon 3
artonLaskuri = new Laskuri(10); // muuttujaan artonLaskuri asetetaan uusi viite, joka osoittaa 
                                // new Laskuri(10) - kutsulla luotuun Laskuri-olioon

// muuttuja matinLaskuri sisältää vieläkin viitteen Laskuri-olioon, joka sai konstruktorissaan arvon 3

Esimerkissä tehdään käytännössä samat operaatiot kuin alkeistyyppi-kappaleessa olevassa asetusesimerkissä. Äskeisessä esimerkissä asetimme viittaustyyppisten muuttujien viitteitä, kun taas alkeistyyppi-esimerkissä asetimme alkeistyyppien arvoja. Lopussa kukaan ei viittaa Laskuriolioon, joka sai arvokseen konstruktorissa 5. Javan roskienkeruu huolehtii tälläisistä turhista oliosta. Lopputilanne uvana:

             -----             --olio----
matinLaskuri | --|--           | arvo 5 |   tämä olio on muuttunut roskaksi, jonka Java pian hävittää
             -----  --         ----------
                      ---  
                         --    --olio----
             -----         --> | arvo 3 |
artonLaskuri | --|--           ----------
             -----  --
                      ---
                         --    --olio-----
                           --> | arvo 10 |
                               -----------

Tarkastellaan vielä kolmatta esimerkkiä, joka näyttää viite- ja alkeistyyppisten muuttujien konkreettisen eron.

Laskuri matinLaskuri = new Laskuri(5);
Laskuri artonLaskuri = new Laskuri(3);

matinLaskuri = artonLaskuri; // muuttuja matinLaskuri sisältää nyt muuttujan artonLaskuri sisältämän viitteen, 
                             // eli viitteen Laskuri-tyyppiseen olioon joka on saanut konstruktorissaan arvon 3
artonLaskuri.kasvataArvoa(); // kasvatetaan artonLaskuri-viitteen takana olevan olion arvoa yhdellä

System.out.println(matinLaskuri.annaArvo()); // koska matinLaskuri-muuttujan viite osoittaa samaan olioon, kuin 
                                             // artonLaskuri-muuttuja, on matinLaskuri.annaArvo() - kutsun palauttama arvo 4

Viittaustyyppiset muuttujat siis viittaavat aina toisaalla oleviin olioihin. Useat viittaustyyppiset muuttujat voivat sisältää saman viitteen, jolloin kaikki muuttujat osoittavat samaan olioon. Seuraavassa esimerkissä näemme kolme Laskuri-tyyppistä viitemuuttujaa, mutta vain yhden Laskuri-olion.

Laskuri mattiV = new Laskuri(5);
Laskuri mattiL = mattiV;
Laskuri mattiP = mattiV;

Esimerkissä luodaan vain yksi Laskuri-olio, mutta kaikki Laskuri-tyyppiset viitemuuttujat osoittavat lopulta siihen. Tällöin kaikki metodikutsut viitteelle mattiL, mattiP ja mattiV muokkaavat samaa viitteen takana olevaa oliota. Viittaustyyppisiä muuttujia asetettaessa toisten muuttujien avulla viitteet siis kopioituvat viitemuuttujan lokeroon. Kuvana:

            -----             
     mattiV | --|---           
            -----   ---         
                       ----       
            -----          ---->  --olio----
     mattiL | --|-------------->  | arvo 5 |
            -----          ---->  ----------
                       ----
            -----   ---           
     MattiP | --|---           
            ----- 
   

Katsotaan kopioitumista vielä esimerkillä.

Laskuri mattiV = new Laskuri(5);
Laskuri mattiP = mattiV; // muuttuja mattiP saa arvokseen mattiV-muuttujan sisältämän viitteen
Laskuri mattiL = mattiP; // muuttuja mattiL saa arvokseen mattiP-muuttujan sisältämän viitteen

mattiP = new Laskuri(3); // muuttuja mattiP saa arvokseen uuden viitteen, joka palautuu new Laskuri(3) - kutsusta

Esimerkissä muuttujan mattiL sisältö ei muutu muuttujan mattiP saadessa uuden viitteen, sillä muuttujaan mattiL on luotu asetuksessa mattiL = mattiP kopio muuttujan mattiP sisällöstä, eli viitteestä. Kun muuttujan mattiP sisältö, eli viite muuttuu, se ei vaikuta muuttujan mattiL sisältämään viitteeseen koska se on asetettu jo aiemmin. Kuvana:

            -----             
     mattiV | --|---           
            -----   ---         
                       ----       
            -----          ---->  --olio----
     mattiL | --|-------------->  | arvo 5 |
            -----                 ----------
                     
            -----                 --olio----
     MattiP | --|-------------->  | arvo 3 |
            -----                 ---------- 

Viittaustyyppinen muuttuja metodin parametrina

Kun viittaustyyppinen muuttuja annetaan metodille parametrina, saa metodin parametrimuuttuja kopion annetun muuttujan viitteestä. Katsotaan seuraavaa metodia lisaaLaskuriin(Laskuri laskuri, int paljonko).

public void lisaaLaskuriin(Laskuri laskuri, int paljonko) {
  for (int i = 0; i < paljonko; i++) {
    laskuri.kasvataArvoa();
  }
}

Metodi lisaaLaskuriin() saa kaksi parametria, viittaustyyppisen parametrin laskuri ja alkeistyyppisen (kokonaisluvun) paljonko. Metodi kutsuu Laskuri-tyyppisen parametrin metodia kasvataArvoa() paljonko-muuttujan sisältämän arvon verran. Tutkitaan vielä metodin kutsumista.

int x = 10;
Laskuri mattiL = new Laskuri(10);
lisaaLaskuriin(mattiL, x);
// muuttujan mattiL sisäinen arvo on nyt 20

Esimerkissä kutsutaan lisaaLaskuriin()-metodia muuttujalla mattiL ja muuttujalla x jonka arvo on 19. Metodin muuttujiin laskuri ja paljonko kopioituvat siis viittaustyyppisen muuttujan mattiL viite, ja arvo 10. Metodi suorittaa viitteelle laskuri paljonko muuttujan määrittelemän määrän kasvataArvoa()-metodikutsuja. Kuvana:

    main:                                            metodissa:
  
            -----            --olio----                -----
     mattiL | --|--------->  | arvo 5 |  <-------------|-- | laskuri
            -----            ----------                -----
			
            ------                                     ------
          x | 10 |                                     | 10 | paljonko
            ------                                     ------

Metodi siis näkee saman laskurin johon mattiL viittaa, eli metodin tekemä muutos vaikuttaa suoraan parametrina olevaan olioon. Alkeistyyppien suhteen tilanne on toinen, eli metodille tulee ainoastaan kopio x:n arvosta.

Viittaustyyppinen muuttuja metodin paluuarvona

Kun metodi palauttaa viittaustyyppisen muuttujan, palauttaa se viitteen muualla sijaitsevaan olioon. Metodin palauttaman viittaustyyppisen muuttujan voi asettaa muuttujalle samalla tavalla kuin normaalikin asetus tapahtuu, eli yhtäsuuruusmerkin avulla. Katsotaan metodia luoLaskuri(), joka luo uuden viittaustyyppisen muuttujan.

public Laskuri luoLaskuri(int alkuarvo) {
  Laskuri uusiLaskuri = new Laskuri(alkuarvo);
  return uusiLaskuri;
}

Metodi luoLaskuri palauttaa siis metodissa luotuun olioon viittaavan viitteen uusiLaskuri. Uusi olio luodaan aina metodia kutsuttaessa, seuraavassa esimerkissä luomme kaksi erillistä Laskuri-tyyppistä oliota.

Laskuri mattiV = luoLaskuri(10);
Laskuri mattiP = luoLaskuri(10);

Metodi luoLaskuri luo aina uuden Laskuri-tyyppisen olion. Ensimmäisessä kutsussa, eli kutsussa Laskuri mattiV = luoLaskuri(10); asetetaan metodin palauttama viite viittaustyyppiseen muuttujaan mattiV. Toisessa metodikutsussa luodaan uusi viite, joka asetetaan muuttujaan mattiP. Muuttujat mattiV ja mattiP eivät sisällä samaa viitettä, sillä metodi luo aina uuden olion ja palauttaa viitteen juuri luotuun olioon.

Static ja ei-static

Staattisilla ja ei-staattisilla metodeilla ja muuttujilla erotetaan se, mihin muuttuja tai metodi liittyy. Staattiset metodit ja muuttujat liittyvät luokkaan, kun taas ei-staattiset metodit ja muuttujat ovat oliokohtaisia.

Static

Static-määreen saavat muuttujat eivät liity olioihin vaan luokkiin. Esimerkiksi Integer.MAX_VALUE, Long.MIN_VALUE ja Double.MAX_VALUE ovat kaikki staattisia muuttujia. Staattisia muuttujia ja metodeja käytetään luokan nimen kautta, esimerkiksi LuokanNimi.muuttuja tai LuokanNimi.metodi(), tietysti riippuen metodien ja muuttujien näkyvyydestä. Vain public-näkyvyydellä määritetyt muuttujat ja metodit ovat suoraan käytettävissä.

Luokkakirjasto

Luokkakirjastoksi kutsutaan luokkaa, jossa on yleiskäyttöisiä metodeja ja muuttujia. Esimerkiksi Javan Math-luokka on sellainen. Math-luokkahan tarjoaa muunmuassa Math.PI-muuttujan, jossa on piin likiarvo, sekä Math.random()-metodin, joka palauttaa satunnaisen liukuluvun väliltä 0 ja 1. Omien luokkakirjastojen toteuttaminen on usein hyödyllistä. Esimerkiksi Helsingin Seudun Liikenne (HSL) voisi pitää lippujensa hintoja luokkakirjastossa, josta ne löytyisi aina tarvittaessa.

public class HslHinnasto {
  public static double KERTALIPPU_AIKUINEN = 2.50;
  public static double RAITIOVAUNULIPPU_AIKUINEN = 2.50; 
}    

Tällöin kaikki ohjelmat, jotka käyttävät kerta- tai raitiovaunulipun hintaa voivat käyttää niitä HslHinnasto-luokan kautta. Seuraavassa esimerkissä esitellään yksinkertainen Ihminen-luokka, jolla on metodi onkoRahaaKertalippuun(), joka käyttää HslHinnasto-luokasta löytyvää lipun hintaa.

public class Ihminen {
  private double rahat; // rahat
  ...
  
  public boolean onkoRahaaKertalippuun() {
    if(rahat >= HslHinnasto.KERTALIPPU_AIKUINEN) {
      return true;
    }
    
    return false;
  }
  ...
}    

Metodi onkoRahaaKertalippuun() siis vertaa luokan Ihminen ei-staattista muuttujaa rahat HslHinnasto-luokan staattiseen muuttujaan KERTALIPPU_AIKUINEN. Metodia onkoRahaaKertalippuun() voi kutsua vain olion yhteydessä, koska se ei ole staattinen.

Huomaa nimeämiskäytäntö! Kaikki staattiset alkeistyyppiset muuttujat kirjoitetaan ISOLLA_JA_ALAVIIVOILLA. Staattiset metodit toimivat vastaavasti. Esimerkiksi Luokka HslHinnasto saattaisi kapseloida muuttujat ja antaa vain aksessorit niihin. Aksessoriksi kutsutaan metodia, jolla voi joko lukea muuttujan arvon tai sijoittaa muuttujalle uuden arvon.

public class HslHinnasto {
  private static double KERTALIPPU_AIKUINEN = 2.50;
  private static double RAITIOVAUNULIPPU_AIKUINEN = 2.50;
  
  public static double annaKertalipunHinta() {   // Aksessori
    return KERTALIPPU_AIKUINEN;
  }
  
  public static double annaRaitiovaunulipunHinta() {   // Aksessori
    return RAITIOVAUNULIPPU_AIKUINEN;
  }
}    

Tällöin Ihminen-luokan toteutuksessa täytyisikin kutsua metodiaannaKertalipunHinta() sen sijaan että kutsuttaisiin muuttujaa suoraan.

public class Ihminen {
  private double rahat; // rahat
  ...
  
  public boolean onkoRahaaKertalippuun() {  
    if(rahat >= HslHinnasto.annaKertalipunHinta()) {
      return true;
    }
    
    return false;
  }
  ...
}    

Ei-static

Ei-staattiset metodit ja muuttujat liittyvät olioihin. Oliomuuttujat, eli attribuutit, määritellään luokan alussa. Kun oliota luodaan new-kutsulla, kaikki attribuutit saavat arvon olion liittyvän viitteen päässä, jolloin niihin pääsee käsiksi oliokohtaisesti. Esimerkiksi taas yksinkertainen luokka Ihminen, jolla on kaksi attribuuttia: nimi ja rahat.

public class Ihminen {
  private String nimi; // olioattribuutteja, jokaiselle näistä on oliokohtainen arvo
  private double rahat;
  
  ...
}    

Kun luokasta Ihminen luodaan uusi ilmentymä, alustetaan myös siihen liittyvät muuttujat. Jos viittaustyyppistä muuttujaa nimi ei alusteta, saa se arvokseen null-viitteen. Lisätään luokan Ihminen toteutukseen vielä konstruktori ja muutama metodi.

public class Ihminen {
  private String nimi; // olioattribuutteja, jokaiselle näistä on oliokohtainen arvo
  private double rahat;
  
  // konstruktori
  public Ihminen(String nimi, double rahat) {
    this.nimi = nimi;
    this.rahat = rahat;
  }
  
  // annaNimi
  public String annaNimi() {
    return this.nimi;
  }
  
  // annaRahat
  public double annaRahat() {
    return this.rahat;
  }
  
  // lisaaRahaa, lisätään vain jos yritetään lisätä positiivinen määrä
  public void lisaaRahaa(double summa) {
    if(summa > 0) {
      this.rahat += summa;    
    }
  } 
  ...
}    

Konstruktori Ihminen(String nimi, double rahat) luo uuden ihmisolion ja palauttaa viitteen siihen. Aksessori annaNimi() palauttaa viitteen nimi-olioon, ja annaRahat()-metodi palauttaa alkeistyyppisen muuttujan rahat. Metodi lisaaRahaa(double summa) lisaa oliomuuttujaan rahat parametrina annetun summan jos parametrin arvo on suurempi kuin 0.

Oliometodeja kutsutaan olion viitteen kautta. Seuraava koodiesimerkki luo uuden Ihmis-olion, lisää sille rahaa, ja lopuksi tulostaa sen nimen. Huomaa että metodikutsut ovat muotoa olionNimi.metodinNimi()

Ihminen mattiV = new Ihminen("Matti V", 3.0);
mattiV.lisaaRahaa(5); // palkka, jes!
System.out.println(mattiV.annaNimi());

Esimerkki tulostaa "Matti V".

Metodit luokan sisällä

Luokan sisäisiä ei-staattisia metodeja voi tietysti kutsua myös ilman olio-etuliitettä. Esimerkiksi seuraava toString()-metodi Ihminen luokalle, joka kutsuu metodia annaNimi(). Metodi toString():han mahdollistaa olion tilan tulostamisen vain olion nimeä parametrina käyttäen.

public class Ihminen {
  private String nimi; // olioattribuutteja, jokaiselle näistä on oliokohtainen arvo
  ...
  
  public String annaNimi() {
    return this.nimi;
  }
  
  ...
  
  public String toString() {
    return annaNimi();
  }
}

Metodi toString() kutsuu siis luokan sisäistä, tähän olioon liittyvää annaNimi()-metodia. Metodikutsuun voi lisätä etuliitteen this jos haluaa korostaa kutsun liittyvän juuri tähän ilmentymään.

public class Ihminen {
  private String nimi; // olioattribuutteja, jokaiselle näistä on oliokohtainen arvo
  ...
  
  public String annaNimi() {
    return this.nimi;
  }
  
  ...
  
  public String toString() {
    return this.annaNimi();
  }
}

Ei-staattiset metodit voivat kutsua myös staattisia, eli luokkakohtaisia metodeja. Toisaalta, luokkakohtaiset metodit eivät voi kutsua oliokohtaisia metodeja ilman viitettä itse olioon, sillä ilman viitettä ei ole tietoa oliosta.

Muuttujat metodien sisällä

Poikkeuksena "Ei staattiset metodit ja muuttujat liittyvät olioihin" -sääntöön on metodien sisällä määriteltävät muuttujat. Metodien sisällä määriteltävät muuttujat eivät saa static-määrettä (eivätkä muitakaan määreitä tyyppinsä lisäksi). Metodien sisällä määriteltävät muuttujat ovat metodien suorituksessa käytettäviä apumuuttujia, eikä niitä tule sekoittaa oliomuuttujiin, eli attribuutteihin. Alla esimerkki metodista, jossa luodaan metodimuuttuja. Muuttuja indeksi on olemassa ja käytössä vain metodin suorituksen ajan.

public class ... {
  ...
  
  public static void tulostaTaulukko(String[] taulukko) {
    int indeksi = 0;
    
    while(indeksi < taulukko.length) {
      System.out.println(taulukko[indeksi]);
      indeksi++;
    }    
  }
  
  ...
}

Metodissa tulostaTaulukko() luodaan siis metodin sisäinen apumuuttuja indeksi, jota käytetään taulukon läpikäynnissä avuksi. Muuttuja indeksi on käytössä vain metodin suorituksen ajan.

Static ja ei-static yhteistyössä

Katsotaan esimerkkiä missä oliota luotaessa käytetään staattista muuttujaa järjestysnumeron antamiseen.

public class Jonotuspaikka {
  private static int VUORONUMERO = 0;
  
  private int vuoronumero;
  
  public Jonotuspaikka() {
    this.vuoronumero = VUORONUMERO;
    VUORONUMERO++;
  }
  
  public int annaVuoronumero() {
    return this.vuoronumero;
  }
}

Yllä olevassa luokassa käytetään staattista muuttujaa VUORONUMERO yleisen vuoronumeron ylläpitämiseen. Luokan Jonotuspaikka konstruktorissa uusi jonotuspaikka-olio saa vuoronumeron VUORONUMERO-muuttujalta, jonka jälkeen VUORONUMERO-muuttujan arvoa kasvatetaan yhdellä. Tällöin seuraavan jonotuspaikan vuoronumero olisi yhtä suurempi. Katsotaan vielä samaa toimintaa lähdekoodin avulla.

Jonotuspaikka mattiV = new Jonotuspaikka();
Jonotuspaikka mattiP = new Jonotuspaikka();
Jonotuspaikka mattiL = new Jonotuspaikka();

System.out.println("Matti V:n vuoronumero on: " + mattiV.annaVuoronumero());
System.out.println("Matti P:n vuoronumero on: " + mattiP.annaVuoronumero());
System.out.println("Matti L:n vuoronumero on: " + mattiL.annaVuoronumero());

Ohjelmapätkä tulostaisi seuraavaa:

Matti V:n vuoronumero on: 0
Matti P:n vuoronumero on: 1
Matti L:n vuoronumero on: 2

Yhteenveto

Alla vielä pieni yhteenveto staattisten ja ei-staattisten muuttujien ja metodien eroista.

 staticei-static
muuttujaLuokkaan liittyvä muuttuja, esimerkiksi numerolaskuri tai luokkakirjastotyyppinen vakioolioon liittyvä muuttuja, esimerkiksi nimi, tai metodin sisällä määritelty muuttuja
metodiLuokkaan liittyvä metodi, voi muokata vain staattisia muuttujia. Parametrina annetut muuttujat ovat tietysti aina muokattavissa. Käytössä esimerkiksi luokkakirjastoissa. Eivät voi kutsua oliometodeja ilman viitettä itse olioon.Olihin liittyvät, juuri sen (this) olion tilaa muokkaavat metodit. Voivat kutsua staattisia luokkametodeja.

Kapselointi

Kapselointi on yksi olio-ohjelmoinnin peruskäsitteistä. Muihin, eli periytymiseen, polymorfismiin ja abstrahointiin, palataan vielä myöhemmin tällä kurssilla. Kapseloinnilla tarkoitetaan toteutuksen, eli esimerkiksi oliomuuttujien, piilottamista asettamalla ne private-tyyppisiksi. Kun muuttuja on private-tyyppinen, siihen ei pääse käsiksi luokan ulkopuolelta. Kapselointia voi ajatella olion tilan suojaamisena, jolloin olion tilaa ei pääse muokkaamaan mistä tahansa. Ohjelmalla on siis pääsy olion tilaan vain tarkoin suunniteltujen metodien avulla.

Tutkitaan aiemmin luotua Jonotuspaikka-luokkaa.

public class Jonotuspaikka {
  private static int VUORONUMERO = 0;
  
  private int vuoronumero;
  
  public Jonotuspaikka() {
    this.vuoronumero = VUORONUMERO;
    VUORONUMERO++;
  }
  
  public int annaVuoronumero() {
    return this.vuoronumero;
  }
}

Luokan Jonotuspaikka muuttuja vuoronumero on toteutettu siten, että siihen pääsee käsiksi vain annaVuoronumero()-aksessorimetodin avulla. Jos luokkaa käytetään vain sen metodien kautta, voidaan sen sisäistä toteutusta muuttaa ilman että se näkyy ulkopuolelle, jolloin ohjelmia jotka käyttävät luokkaa Jonotuspaikka ei tarvitse muuttaa.

Ajatellaan tilannetta, missä arkistointisyistä halutaan jonotuspaikan numerot alkamaan kymmenestä miljoonasta. Tällöin voimme muuttaa metodia annaVuoronumero() siten, että se palauttaa vuoronumeron summattuna kymmeneen miljoonaan. Luokan Jonotuspaikka metodi annaVuoronumero() olisi silloin seuraavanlainen.

  public int annaVuoronumero() {
    return (this.vuoronumero + 10000000);
  }

Vastaavasti voisimme päätyä tilanteeseen missä joudumme muuttamaan Jonotuspaikka luokkaa siten, että vuoronumero tuleekin kolmannen osapuolen toteuttamalta komponentilta UlkopuolinenKomponentti. Ulkopuolinen komponentti pitää kirjaa annetuista vuoronumeroista, ja saattaa esimerkiksi mahdollistaa jonon ohittamisen korkeamman prioriteetin ihmisille. Tämä mahdollistaa useiden Jonotuspaikka-ohjelmien käyttämisen (esimerkiksi useat päätteet joista voi ottaa vuoronumeroita), kun vuoronumeroiden luominen on keskitettyä. Luokan Jonotuspaikka toteutus voisi olla silloin seuraavanlainen.

public class Jonotuspaikka {
  private JonotusNumero jonotusNumero;
  
  public Jonotuspaikka() {
    this.jonotusNumero = UlkopuolinenKomponentti.luoJonotusNumero();
  }
  
  public int annaVuoronumero() {
    return this.jonotusNumero.annaArvo(); // palauttaa kokonaisluvun
  }
}

Vaikka luokan sisäinen toteutus onkin muuttunut täysin alkuperäisestä, on ulkopuolelle näkyvät toiminnot täysin samanlaiset. Tällöin Jonotuspaikka-luokkaa käyttävät ohjelmat eivät tarvitse minkäänlaisia muutoksia.

Ajatellaan vielä Jonotuspaikka-luokan toteutusta siten, että vuoronumero-oliomuuttuja olisikin ollut kaikille näkyvissa. Tällöin Jonotuspaikka-luokkaa käyttävä ohjelmoija olisi toteuttanut ohjelmansa esimerkiksi seuraavasti.

Jonotuspaikka paikka = new Jonotuspaikka();
System.out.println("Vuoronumero: " + paikka.vuoronumero); // ei näin!

Ohjelma toimisi alkuperäisen Jonotuspaikka-luokan kanssa jotenkuten (vaikka ei noudatakaan hyvää ohjelmointityyliä!). Kun toteutusta muutetaan siten, että vuoronumero alkaa miljoonasta, täytyisi yllä olevaa ohjelmakoodia muokata. Samoin ulkopuolisen komponentin lisäämisessä. Toisaalta, jos toteutus on käyttänyt aksessoreita ja Jonotuspaikka-luokka on toteutettu kapseloinnin periaatteita seuraten, ei mikään ylläolevista muutoksista olisi vaatinut muutoksia. Seuraava esimerkki toimisi jokaisessa kolmesta tapauksesta.

Jonotuspaikka paikka = new Jonotuspaikka();
System.out.println("Vuoronumero: " + paikka.annaVuoronumero()); // jes!

Taulukot

Taulukot ovat viittaustyyppisiä muuttujia, eli taulukko-muuttuja viittaa muualle missä itse taulukon alkiot ovat. Taulukot, kuten kaikki muutkin viittaustyyppiset muuttujat, luodaan komennon new-avulla. Taulukolle määritellään aina sen koko ja tyyppi, eli kuinka monta alkiota siihen mahtuu ja minkätyyppisiä alkiot ovat. Taulukon alkiot voivat olla alkeis- tai viittaustyyppisiä. Seuraavassa esimerkissä luodaan 100 alkion kokoinen kokonaislukutaulukko, ja asetetaan sen alkoihin luvut yhdestä sataan.

int[] kokonaislukutaulukko = new int[100];
for(int i = 0; i < kokonaislukutaulukko.length; i++) {
  kokonaislukutaulukko[i] = i + 1;
}

Taulukot voivat olla sisäkkäisiä. Sisäkkäisiä taulukoita kutsutaan joskus myös moniulotteisiksi taulukoiksi. Sisäkkäisissä taulukoissa aina ulommassa taulukossa on viite sisempään taulukkoon. Seuraavassa esimerkissä luodaan kaksiulotteinen String-tyyppinen taulukko, ja täytetään kaikki alkiot merkkijonolla "Moi". Huomaa että sisemmät taulukot luodaan esimerkissä vasta toiston aikana, tällöin voimme määritellä sisemmille taulukoille erilaisia kokoja. Lopuksi vielä taulukon sisältö tulostetaan.

String[][] moikka = new String[8][];  // luodaan 8 paikkainen taulukko, jonka alkiot merkkijonotaulukkoja

// luodaan nyt taulukon sisälle tulevat 8 merkkijonotaulukkoa
for(int i = 0; i < moikka.length; i++) { 
  moikka[i] = new String[i+1];   // kaikki ovat eripituisia
  
  for(int j = 0; j < moikka[i].length; j++) {
    moikka[i][j] = "Moi";
  }
}

for(String[] taul: moikka) {
  for(String moi: taul) {
    System.out.print(moi + " ");
  }
  
  System.out.println();
}

Yllä oleva taulukon koodinpätkä tuottaa seuraavanlaisen tulostuksen:

Moi 
Moi Moi 
Moi Moi Moi 
Moi Moi Moi Moi 
Moi Moi Moi Moi Moi 
Moi Moi Moi Moi Moi Moi 
Moi Moi Moi Moi Moi Moi Moi 
Moi Moi Moi Moi Moi Moi Moi Moi

Taulukoista löytyy lisää ohjelmoinnin perusteiden kurssimateriaalista!

API

Eräs tärkeistä taidoista ohjelmoinnissa on tiedon hakeminen ja soveltaminen. Java tarjoaa kattavan Luokkakirjaston, joka sisältää hyvin paljon käyttökelposia luokkia. Javan API (eli ohjelmointirajapinta) löytyy osoitteesta http://java.sun.com/javase/6/docs/api/. Tällä kurssilla ehditään tutustutaan vain muutamaan luokkaan, tärkeämpää on API:n lukutaito.

Etsitään String-luokan kuvaus. Vasemman alakulman laatikossa on listattu kaikki Javan API:n tarjoamat luokat. String löytyy luokkalistauksesta, suora osoite String-luokan kuvaukseen on http://java.sun.com/javase/6/docs/api/java/lang/String.html.

Integer.parseInt()

Integer-luokka tarjoaa metodin merkkijonon kokonaisluvuksi muuttamiseksi. Seuraavassa esimerkissä luodaan aluksi merkkijonon "12345" sisältävä String-olio, jonka jälkeen kutsutaan Integer-luokan staattista parseInt()-metodia. Metodi parseInt() saa parametrikseen merkkijonon, ja palauttaa kokonaisluvun.

String merkkijono = "12345";
int luku = Integer.parseInt(merkkijono);
System.out.println(luku);

Esimerkki tulostaa luvun 12345. Huomaa että Integer-luokan parseInt()-metodi olettaa merkkijonon olevan luku. Jos merkkijono ei esitä lukua, saamme poikkeuksen. Poikkeuksista lisää kurssin loppupuolella.

Math.random()

Javan Math-luokka tarjoaa paljon hyödyllisiä apuvälineitä. Yksi paljon käytetyistä apuvälineistä on staattinen metodi random(), joka palauttaa liukuluvun nollan ja yhden väliltä. Metodia random() voi käyttää myös isompien arvovälien arpomiseen. Seuraava esimerkki arpoo luvun 1 ja 39 väliltä ja tulostaa sen.

int luku = (int) (Math.random() * 39) + 1;
System.out.println(luku);

ArrayList

Yksi eniten käytetyistä Javan valmiista Luokista on ArrayList. ArrayList on kapseloitu taulukko, jota kasvatetaan aina tarpeen tullen. ArrayList-luokan API-kuvaus löytyy osoitteesta http://java.sun.com/javase/6/docs/api/java/util/ArrayList.html. ArrayList saa tyyppiparametrikseen viittaustyyppisen muuttujan, mikä kertoo minkälaisia arvoja se sisältää. Esimerkiksi merkkijonoja sisältävän ArrayList-olion voi luoda seuraavasti.

ArrayList<String> mjonot = new ArrayList<String>();

Huomaa tyyppiparametrin välitys <String>. Tämä kertoo ArrayList-oliolle että kaikki sen sisältämät arvot ovat tyyppiä String. Koska ArrayList-luokka voi ottaa vain viittaustyyppisiä parametreja arvoikseen, on alkeistyypeille olemassa luokat jotka kapseloivat ne. Integer-luokka kapseloi int-tyyppisen muuttujan, Double-luokka kapseloi double-tyyppisen muuttujan ym. Katsotaan vielä esimerkkiä missä luodaan ArrayList-olio kokonaisluvuille, lisätään sinne 5 lukua ja tulostetaan ne lopuksi.

ArrayList<Integer> kluvut = new ArrayList<Integer>();
kluvut.add(3);
kluvut.add(5);
kluvut.add(7);

for(int luku: kluvut) {
  System.out.println(luku);
}

Esimerkin tuloste on seuraavanlainen:

3
5
7

Huomaa, että Integer-tyyppisestä muuttujasta int-tyyppiseen muuttujaan tapahtuu automaattinen muunnos toistossa.