Helsingin yliopisto / Tietojenkäsittelytieteen laitos
Copyright © 2005 Arto Wikla. Tämän oppimateriaalin käyttö on sallittu vain yksityishenkilöille opiskelutarkoituksissa. Materiaalin käyttö muihin tarkoituksiin, kuten kaupallisilla tai muilla kursseilla, on kielletty.

2.5. Metodeista

(Muutettu viimeksi 26.5.2005)

Nimettyjä alialgoritmeja, ns. aliohjelmia kutsutaan Java-kielessä metodeiksi. Metodilla voi olla ns. parametreja, joilla algoritmille annetaan lähtötietoja. Metodi voi myös laskea ja palauttaa jonkin arvon.

Javassa metodeita käytetään myös mm. luokkien toiminnallisuuden toteuttamiseen - "abstraktin koneen mittareiden ja namiskuukkeleiden" ohjelmointiin. Tästä seuraavassa luvussa.

Metodin muoto

Metodin (aliohjelman, "funktion, proseduurin") määrittely on muotoa:
   määreitä arvotyyppi nimi(parametrit) {
      algoritmi
   }

Nimetty alialgoritmi

Ohjelmia laadittaessa esiintyy usein tilanne, että jokin toiminto - alialgoritmi - täytyy suorittaa ohjelman useassa eri kohdassa. Alialgoritmi voidaan tietenkin kirjoittaa uudelleen ja uudelleen, mutta tällä on joitakin huonoja seurauksia:

Jo varhaisissa ohjelmointikielissä käytettiin aliohjelmia, nimettyjä algoritmeja, jotka ensin määritellään ja nimetään. Tällaista nimettyä apuvälinettä voidaan sitten nimellä kutsua siellä missä tuota algoritmia tarvitaan, esimerkiksi pääohjelmassa tai jossakin aliohjelmassa. Kutsuminen tarkoittaa siis, että aliohjelma käydään suorittamassa ja suorituksen jälkeen automaattisesti palataan kutsua seuraavaan ohjelmakohtaan.

Tämä tapa jäsentää ohjelmointiongelmaa on ollut käytössä kauan, jo ohjelmoinnin "vanhalla ajalla" 1950-1960 -luvuilla. Uusia tapoja on kehitetty ajan myötä: rakenteinen ohjelmointi oli "uuden ajan" alussa, n. 1970 tärkeä keksintö. Samoin 1990-luvun alussa yleistynyt olio-ohjelmointi toi uuden lähestymistavan ohjelmointiongelmien ratkaisemiseen. Menetelmät eivät kuitenkaan ole korvanneet toisiaan vaan täydentäneet kalustoa, jolla noita ongelmia ratkotaan.

Yksinkertainen esimerkki kokonaisesta Java-ohjelmasta, jossa käytetään metodeita (HipHoh.java):

public class HipHoh {

  private static void tulostaHip() {
    System.out.println("Hip heijjaa!");
  }  

  private static void tulostaHoh() {
    System.out.println("Hoh hoijjaa!");
  }

  private static void tulostaSuperHip() {
    System.out.println("------------");
    tulostaHip();
    tulostaHip();
    tulostaHip();
    System.out.println("------------");
  }

  public static void main(String[] args){  // PÄÄOHJELMA
    System.out.println("Nyt se alkaa:");
    tulostaHip();
    tulostaHoh();
    tulostaHip();
    tulostaHip();
    tulostaHoh();
    tulostaSuperHip();
    System.out.println("Tähän se loppui.");
  }
} 
Luokassa HipHoh on ensin määritelty metodit tulostaHip(), tulostaHoh() ja tulostaSuperHip(). Esimerkin määrittelyt kertovat, että metodit ovat käytettävissä vain luokassa HipHoh (private), ne ovat ns luokkametodeita (static), ne eivät palauta arvoa (void). Metodin määrittely sisältää lisäksi metodin nimen ja algoritmin, jota tuon nimen tahdotaan tarkoittavan.

Metodin määrittely ei vielä johda metodin suoritukseen. Metodi käynnistetään kutsumalla sitä. Esimerkissä pääohjelma kutsuu kolmea metodia. Metodi tulostaSuperHip() puolestaan kutsuu metodia tulostaHip(). Ohjelma tulostaa:

Nyt se alkaa:
Hip heijjaa!
Hoh hoijjaa!
Hip heijjaa!
Hip heijjaa!
Hoh hoijjaa!
------------
Hip heijjaa!
Hip heijjaa!
Hip heijjaa!
------------
Tähän se loppui.


Huom: Luokan metodien kirjoitusjärjestys on vapaa. Edellisen ohjelman voi vallan mainiosti kirjoittaa myös muotoon:
public class HipHoh {

  public static void main(String[] args){  // PÄÄOHJELMA
    System.out.println("Nyt se alkaa:");
    tulostaHip();
    tulostaHoh();
    tulostaHip();
    tulostaHip();
    tulostaHoh();
    tulostaSuperHip();
    System.out.println("Tähän se loppui.");
  }

  private static void tulostaHip() {
    System.out.println("Hip heijjaa!");
  }  

  private static void tulostaHoh() {
    System.out.println("Hoh hoijjaa!");
  }

  private static void tulostaSuperHip() {
    System.out.println("------------");
    tulostaHip();
    tulostaHip();
    tulostaHip();
    System.out.println("------------");
  }
} 

Huom: Luokassa voi siis olla useita metodeita (ja pääohjelma itsekin on yksi metodi). Luokassa voi olla myös muuta kalustoa. Tämä osoittautuu tärkeäksi, kun ryhdymme luvussa 2.6 rakentamaan olioita!

Huom: Kun luokassa on pääohjelmametodi:

public static void main(String[] args) {...}
luokka voidaan käynnistää sovelluksena eli tavallisena ohjelmana. Pääohjelmametodi puolestaan sitten kutsuu (ja luo) muuta kalustoa.

Parametrit

Mahdollisuus nimetä ja kutsua alialgoritmeja on jo sinänsä arvokasta. Näin voidaan välttyä "koodin kertaamiselta" ja samalla selkeyttää ohjelmaa. Edellä nähdyt metodit ovat kuitenkin sikäli rajoittuneita, että ne tekevät joka kutsukerralla täsmälleen saman asian - kutsujalla ei ole mitään mahdollisuutta vaikuttaa noiden metodien toimintaan.

Metodille voidaan antaa lähtotietoja ns. parametrien avulla. Parametrit ovat metodin määrittelyssä - nimen jälkeen sulkeissa - määriteltyjä muuttujia. Ne saavat alkuarvokseen metodin kutsussa annettavat arvot, ns. todelliset parametrit, ennen metodin algoritmin suorituksen aloittamista. Metodin määrittelyssä olevia parametreja kutsutaan muodollisiksi parametreiksi, koska ne metodissa tavallaan edustavat milloin mitäkin todellisia parametreja.

Laaditaan edelliseen esimerkkiin yleiskäyttöisempi metodi huudahdusten tulostamiseen:

  private static void tulostaHuudahdus(String viesti, int kertaa) {
    for (int i=0; i<kertaa; ++i)
      System.out.println(viesti);
  } 
Edellisen esimerkin erityiset hip- ja hoh-tulostukset voidaan molemmat aikaansaada tällä yleiskäyttöisemmällä metodilla, esimerkiksi kutsu:
    tulostaHuudahdus("Hip heijjaa!", 2);
tulostaa:
Hip heijjaa!
Hip heijjaa!

Kun metodia kutsutaan tällä tavalla, tapahtuu seuraavaa:

Uusi metodi on aiempia parempi sikäli, että sillä voidaan tulostaa muutakin kuin ennaltamäärättyjä rivejä. Kun kutsutaan vaikka:

    int nro=22, kpl=5;
    tulostaHuudahdus("System "+nro+" crashing ...", kpl+2);
saadaan tulostus:
System 22 crashing ...
System 22 crashing ...
System 22 crashing ...
System 22 crashing ...
System 22 crashing ...
System 22 crashing ...
System 22 crashing ...
Tuossa kutsussa
  1. Lasketaan ensin ensimmäisen todellisen parametrin arvo ja sijoitetaan:
               viesti = "System "+nro+" crashing ...";
    /*  ELI:   viesti = "System 22 crashing ...";        */
    
  2. Sitten lasketaan toisen todellisen parametrin arvo ja sijoitetaan:
               kertaa = kpl+2;
    /*  ELI:   kertaa = 7;     */
    
  3. Seuraavaksi käynnistetään tulostaHuudahdus-metodin algoritmi.

  4. Kun algoritmi on suoritettu, metodin muuttujat hävitetään ja palataan kutsua seuraavaan lauseeseen.

Kun metodia kutsutaan:

    tulostaHuudahdus("*****", 5);
saadaan:
*****
*****
*****
*****
*****

Edellinen sovellus voitaisiin ohjelmoida tällä kehittyneemmällä välineellä seuraavasti (HipHoh2.java):
public class HipHoh2 {

  private static void tulostaHuudahdus(String viesti, int kertaa) {
    for (int i=0; i<kertaa; ++i)
      System.out.println(viesti);
  }

  private static void tulostaSuperHip() {
    System.out.println("------------");
    tulostaHuudahdus("Hip heijjaa!", 3);
    System.out.println("------------");
  }

  public static void main(String[] args){  // PÄÄOHJELMA
    System.out.println("Nyt se alkaa:");
    tulostaHuudahdus("Hip heijjaa!", 1);
    tulostaHuudahdus("Hoh hoijjaa!", 1);
    tulostaHuudahdus("Hip heijjaa!", 2);
    tulostaHuudahdus("Hoh hoijjaa!", 1);
    tulostaSuperHip();
    System.out.println("Tähän se loppui.");
  }
} 


Laaditaan metodi kahden double-luvun keskiarvon tulostamiseen. Toteutetaan myös pääohjelma metodin testaukseen. Koko sovellus (KahdenKarvo.java):
public class KahdenKarvo {

  private static void tulosta2Karvo(double luku1, double luku2) {
    double summa = luku1 + luku2;
    double karvo = summa/2;
    System.out.println("Lukujen "+luku1+" ja "+luku2+
                       " keskiarvo on "+karvo+".");
  }

  public static void main(String[] args){  // PÄÄOHJELMA
    tulosta2Karvo(1, 2);
    tulosta2Karvo(-30.2, 1.2);
    tulosta2Karvo(-1, 1);
    tulosta2Karvo(3.14, 123.1);
  }
}
Ohjelma tulostaa:
Lukujen 1.0 ja 2.0 keskiarvo on 1.5.
Lukujen -30.2 ja 1.2 keskiarvo on -14.5.
Lukujen -1.0 ja 1.0 keskiarvo on 0.0.
Lukujen 3.14 ja 123.1 keskiarvo on 63.12.


Huom: Jokaisen parametrin tyyppi ilmaistaan erikseen. Seuraava on siis väärin:

  private static void tulosta2Karvo(double luku1, luku2)
                                    ^^^^^^^^^^^^^^^^^^^

Huom: Javassa kaikki parametrit ovat ns. arvoparametreja. Se tarkoittaa, että parametrin arvon muuttaminen metodissa ei muuta todellisen parametrin arvoa! Mutta kun olio välitetään parametrina, niin asia muuttuu..., luku 2.6 paljastaa mitä miten!

Arvon palauttavat metodit

Tähän mennessä ohjelmoidut metodit ovat olleet lauseen kaltaisia; if-, for- , while- ja do-lauseiden tapaan ne ovat "tehneet jotakin". Tällaisia kutsutaan joskus "proseduureiksi".

Javassa - kuten monissa muissakin ohjelmointikielissä - on mahdollista ohjelmoida myös metodeita, joita käytetään lausekkeen tapaan. Lausekkeitahan olivat mm. aritmeettiset ja loogiset laskukaavat sekä esimerkiksi merkkijonokatenaatiot. Lausekkeen arvo siis lasketaan ja laskettu arvo voidaan esimerkiksi sijoittaa jollekin muuttujalle tai vaikkapa tulostaa. Arvon palauttava metodikin voi "tehdä jotakin", mutta lisäksi se palauttaa jonkin arvon.

Arvon palauttavia metodeita kutsutaan joissakin ohjelmointikielissä "funktioiksi", mikä on sikäli perusteltua, että tällaisia metodeita käytetään lausekkeissa "funktion tapaan". Jos vaikkapa f ja g on määritelty numeerisen arvon palauttaviksi metodeiksi, voidaan kirjoittaa:

    double k = f(x) + g(y-3.14) - 1;

Huom: Luvussa 2.3 opitut syöttötietojen lukemisvälineet on toteutettu arvon palauttavina metodeina, esim:

   private static Scanner lukija = new Scanner(System.in);
   ...

   int luku  = lukija.nextInt();
   int luku2 = lukija.nextInt() + lukija.nextInt(); // kahden s-luvun summa
   ...
Javasta poiketen monissa ohjelmointikielissä syöttötietoja luetaan void-metodien tyyliin:
   // Tämä siis ei ole Javaa!
   int luku, luku2, apu1, apu2;

   read(luku);
   read(apu1);
   read(apu2);
   luku2 = apu1 + apu2;
   ...


Metodin palauttaman arvon tyyppi ilmaistaan metodin alussa olevana määreenä. Toistaiseksi on opittu vasta tyypit int, double, boolean ja String. Lisää opitaan myöhemmin.

Arvon palauttava metodi sisältää aina vähintään yhden return-lauseen, joka ilmaisee metodin palauttaman arvon. Metodi voi sisältää useampiakin return-lauseita.

Huom: Arvon palauttavan metodin algoritmin kaikkien suoritustapojen on johdettava return-lauseen suoritukseen.

Huom: Kun return-lause suoritetaan, metodin suoritus päättyy.

Laaditaan kahden luvun keskiarvon laskeva metodi. Kirjoitetaan myös testipääohjelma (KahdenKarvoFun.java):

public class KahdenKarvoFun {

  private static double laske2Karvo(double luku1, double luku2) {
    double summa = luku1 + luku2;
    double karvo = summa/2;
    return karvo;
  }

  public static void main(String[] args){  // PÄÄOHJELMA

    double a = laske2Karvo(1, 2);
    System.out.println(a);

    a = laske2Karvo(-30.2, 1.2);
    System.out.println(a);

    System.out.println(laske2Karvo(-1, 1));
    System.out.println(laske2Karvo(3.14, 123.1));

    System.out.println(laske2Karvo(2, laske2Karvo(3,4)));
  }
}
Ohjelma tulostaa:
1.5
-14.5
0.0
63.12
2.75

Esimerkin metodi voidaan kirjoittaa lyhyemminkin:
  private static double laske2Karvo(double luku1, double luku2) {
    return (luku1 + luku2)/2;
  }


Laaditaan sitten metodi, joka palauttaa arvonaan kokonaisluvun itseisarvon (Itse.java) (tähän löytyy kyllä valmiskin väline, siitä myöhemmin):
public class Itse {

  private static int itseisArvo(int luku) {
    if (luku < 0)
      return -luku;
    else
      return luku;
  }

  public static void main(String[] args){  // PÄÄOHJELMA

    int a = itseisArvo(3);
    System.out.println(a);

    a = itseisArvo(-34);
    System.out.println(a);

    System.out.println(itseisArvo(-2987));
  }
} 


Metodin palauttama arvo voi olla tyypiltään myös String. Seuraavan esimerkin metodi saa parametrina String-olion ja palauttaa arvonaan toisen String-olion (Korostuksia.java):
public class Korostuksia {

  private static String osoitteleJono(String teksti) {
    return "------> "+teksti+" <------";
  }

  public static void main(String[] args){  // PÄÄOHJELMA

    String kissa = "Missu";
    String s;

    s = osoitteleJono(kissa);
    System.out.println(s);

    System.out.println(osoitteleJono("hiiohei"));

    System.out.println(osoitteleJono("A")+osoitteleJono("B"));

    System.out.println(osoitteleJono(osoitteleJono("AW")));
  }
}
Ohjelma tulostaa:
------> Missu <------
------> hiiohei <------
------> A <------------> B <------
------> ------> AW <------ <------

Huomaa miten metodia käytetään lausekkeen osana. Huomaa myös miten metodin palauttama String-olio annetaan edelleen parametrina samalle metodille.

Totuusarvoinen metodi on usein käyttökelpoinen monimutkaisten tarkistusten toteuttamiseen: varsinaisen algoritmin peruslogiikkaa ei näin jouduta sotkemaan tarkistusten yksityiskohdilla.

Jos vaikkapa syöttöluvun pitää olla suurempi kuin nolla ja parillinen, mutta ei kuitenkaan 274 tai 666, voidaan laatia metodi oikeinko(int):

import java.util.Scanner;

public class Tarkistus {
  private static Scanner lukija = new Scanner(System.in);

  private static boolean oikeinko(int i) {
    return ( i>0 && i%2==0 && i!=274 && i!=666 );
  }

  public static void main(String[] args){  // PÄÄOHJELMA

    int sluku;

    do {
      System.out.println("Syötä kunnon luku!");
      sluku = lukija.nextInt();
    } while (!oikeinko(sluku));

    System.out.println("Vihdoin kunnon luku: "+sluku);
  }
}
Käyttö näyttää seuraavalta:
Syötä kunnon luku!
3
Syötä kunnon luku!
-44
Syötä kunnon luku!
274
Syötä kunnon luku!
275
Syötä kunnon luku!
666
Syötä kunnon luku!
123
Syötä kunnon luku!
1234
Vihdoin kunnon luku: 1234


Kuormittaminen: samannimisiä metodeita eri parametrein

Metodin kutsussa ei voi olla eri lukumäärää parametreja kuin metodin määrittelyssä on lueteltu. Todelliseksi parametriksi ei saa kirjoittaa vääräntyyppistä lauseketta. Esimerkiksi
    private static void meto(int i, int j) { 
       /* metodin algoritmi ... */
    }
    ...
    ...
       meto(2,5);     // oikein
       meto(3);       // lkm VÄÄRIN
       meto(1, 3, 8); // lkm VÄÄRIN
       meto();        // lkm VÄÄRIN 

       meto(3.14, 3); // tyyppi VÄÄRIN
       meto("AB", 1); // tyyppi VÄÄRIN

       meto(true);    // tyyppi ja lkm VÄÄRIN
    ...

Joskus kuitenkin on tilanteita, joissa "sama asia" halutaan tehdä erilaisille parametreille. On luontevaa voida tehdä tuo "sama asia" erilaisille parametrimäärille ja -tyypeille samannimisellä metodilla. Noiden metodien algoritmit ovat tietenkin erilaiset, koska eri lähtötiedot joudutaan käsittelemään eri tavoin.

Javassa on mahdollista laatia samaan luokkaan (tarkemmin samaan ns. näkyvyysalueeseen) metodeita, joilla on sama nimi, mutta eri lukumäärä parametreja ja/tai eri tyyppisiä parametreja. Tätä kutsutaan kuormittamiseksi (overloading). Java-kääntäjä tunnistaa todellisista parametreista, mitä metodia kutsussa kulloinkin tarkoitetaan. Samannimisten metodien on erottava tosistaan nimenomaan parametreiltaan, erot määreissä ja tyypissä eivät riitä!

Esimerkki: Jostakin (käsittämättömästä!) syystä ohjelmoija tarvitsee välinettä tulostaNelio(...), jolla voi tulostaa

Laaditaan ohjelma (
Nelio.java):
public class Nelio {

  private static void tulostaNelio() {
    System.out.println("neliö");
  }

  private static void tulostaNelio(int luku) {
    System.out.println(luku*luku);
  }

  private static void tulostaNelio(double luku) {
    System.out.println(luku*luku);
  }

  private static void tulostaNelio(int i, int j) {
    for (int a=0; a<i; ++a) {
      for (int b=0; b<j; ++b)
        System.out.print("*"); 
      System.out.println();
    }
  }

 public static void main(String[] args){  // PÄÄOHJELMA

   tulostaNelio();
   tulostaNelio(12);
   tulostaNelio(3.14);
   tulostaNelio(4, 6);
 }
}
Ohjelma tulostaa:
neliö
144
9.8596
******
******
******
******

Esimerkissä siis määritellään samassa luokassa useita samannimisiä ja samantyyppisiä metodeita, joiden parametrit ovat erityyppisiä:
  private static void tulostaNelio(...)
Kun metodia tulostaNelio(...) kutsutaan siten, että todellinen parametri puuttuu, samannimisistä metodeista valitaan se, jonka määrittelystä parametri puuttuu, kun kutsussa on yksi int-tyyppinen parametri, valitaan määrittelyistä int-parametrinen metodi, jne.

Huomautuksia metodin muuttujista ja parametreista


Takaisin luvun 2 sisällysluetteloon.