Sisäkkäiset silmukat


Toistolauseen sisällä oleva käsky voi olla toinen toistolause.


Tällöin sisempi toistolause on kuin mikä tahansa käsky: yhdellä ulomman silmukan suorituskerralla sisempi toistokäsky suoritetaan kokonaan kaikkine toistoineen.


Vasta sitten, kun sisemmän toistolauseen suoritus päättyy, siirrytään ulomman toistolauseen seuraavalle kierrokselle.


Esimerkiksi


int i, j;

System.out.println("i j");

for (i = 0; i < 3; i++)

for (j = 0; j < 2; j++)

System.out.println(i + " " + j);



tulostaa


i j

0 0

0 1

1 0

1 1

2 0

2 1


Metodit


"Oikeat" ohjelmat ovat usein tuhansia tai kymmeniä tuhansia rivejä pitkiä. Jos koko ohjelma olisi kirjoitettu yhdeksi pääohjelmaksi, ohjelman ylläpitäminen olisi liian hankalaa.


Lisäksi usein ohjelmassa halutaan tehdä suunnilleen

sama asia useaan kertaan. Jos jokaista kertaa varten joudutaan kirjoittamaan oma koodinpätkä, on se


Tietyn asian suorittavasta ohjelmanpalasta tehdään metodi (aliohjelma), jota voidaan kutsua muualta ohjelmasta. Metodia kutsuttaessa siirrytään suorittamaan metodia. Kun metodi on suoritettu, palataan takaisin siihen kohtaan ohjelmaa, josta kutsu tapahtui.


Esimerkeissä on jo esiintynyt metodien kutsuja, esimerkiksi


System.out.println("Tulos on " + tulos);


x1 = (-b + Math.sqrt(disk)) / (2.0 * a);


Näillä metodeilla on se ero, että System.out.println ei palauta mitään arvoa, kun taas Math.sqrt palauttaa liukuluvun, jota voidaan käyttää kutsuvassa ohjelmassa.


Nämä metodit löytyvät Javan APIn pakkauksista. Seuraavassa opetellaan kirjoittamaan omia metodeja.

Ensimmäinen esimerkki


Kirjoitetaan kaksi metodia: toinen tulostaa rivin tähtejä, toinen huudahduksen. Mukana on myös pääohjelma, josta metodeja kutsutaan.


public class Huudahduksia{
  
  private static void tulostaHuudahdus() {
    System.out.println("ELÄKÖÖN! ELÄKÖÖN!");
  }


  private static void tulostaTahtia() {
    System.out.println("************************************");
  }


  public static void main(String[] args) {
    tulostaTahtia();
    tulostaHuudahdus();
    tulostaTahtia();
    System.out.println(); // kaksi tyhjaa rivia 
    System.out.println();
    tulostaTahtia();
    tulostaTahtia();
    tulostaHuudahdus();
    tulostaTahtia();
    tulostaTahtia();
  }
}

Metodin muoto


Metodin määrittely on muotoa:


määreitä arvotyyppi nimi(parametrit) {

metodin koodi

}


Esimerkki parametrin käytöstä


Metodit tulostaHuudahdus ja tulostaTahtia toimivat joka kerran samalla tavalla. Usein halutaan metodille antaa kuitenkin lähtötietoja - esimerkiksi, minkä luvun neliöjuuri pitää laskea tai mikä merkkijono halutaan tulostaa.


Tietoa voidaan välittää parametrien avulla: Metodin sisällä parametreja käytetään kuin tavallisia muuttujia. Metodin kutsussa parametreille annetaan alkuarvot.


Seuraava metodi tulostaa sille parametrina annettuna merkkijonon halutun monta kertaa:


private static void

tulostaViesti(String viesti, int kertaa) {

int i;

for (i = 0; i < kertaa; i++)

System.out.println(viesti);

}



Metodia voidaan pääohjelmassa kutsua esimerkiksi seuraavasti:


...

tulostaViesti("VAROITUS: kone sammuu", 3);

...


Jolloin kuvaruudulle tulostuu

VAROITUS: kone sammuu

VAROITUS: kone sammuu

VAROITUS: kone sammuu

Metodin määrittelyssä käytettäviä parametreja kutsutaan muodollisiksi parametreiksi ja kutsussa olevia parametreja todellisiksi parametreiksi.


Todellisten parametrien ei tarvitse olla vakioita, vaan kutsussa voidaan käyttää myös muuttujia ja lausekkeita:


....

public static void main(String[] args) {

String tiedotus;

int lkm;


tiedotus = "Käyttökatko on jo ohi";

lkm = 5;

tulostaViesti(tiedotus, lkm-1);

....

}


tulostaa

Käyttökatko on jo ohi

Käyttökatko on jo ohi

Käyttökatko on jo ohi

Käyttökatko on jo ohi


Kun metodia kutsutaan, lasketaan ensin todellisten parametrien arvot. Nämä arvot sijoitetaan metodin parametrien alkuarvoiksi. Sitten suoritetaan metodin koodi.

Arvon palauttavat metodit


Usein metodin laskema arvo halutaan kutsuvan ohjelman käyttöön. Esimerkiksi neliöjuuren laskevasta metodista ei ole paljon iloa, jos laskettua neliöjuurta ei voi mitenkään käyttää kutsuvassa ohjelmassa.


Metodin paikalliset muuttujat ovat käytettävissä vain metodin suorituksen ajan. Niihin ei pääse käsiksi metodin suorituksen loputtua.


Myöskään parametreille metodissa tehdyt muutokset eivät näy kutsuvassa ohjelmassa, jos parametrit ovat yksinkertaista tyyppiä (char, int, double, boolean). Oliot toimivat tässä suhteessa toisella tavalla.


Metodi voi kuitenkin palauttaa arvon, jota voi käyttää kutsuvassa ohjelmassa.


Palautettavan arvon tyyppi kerrotaan metodin otsikossa. Aliohjelma voi palauttaa arvon return-lauseella:


return luku;


palauttaa kutsuvalle ohjelmalle muuttujan luku arvon.


Return-lauseen suoritus lopettaa metodin suorituksen.


Huomaa, että arvon tulostaminen kuvaruudulle ei ole sama kuin arvon palauttaminen.

Esimerkki: kuution laskeminen


Ohjelma korottaa metodin avulla käyttäjän antamia lukuja kolmanteen potenssiin.



public class KuutioEsim {
  
  private static double kolmanteen(double luku) {
    double kuutio;
    kuutio = luku * luku * luku;
    return kuutio;
  }


  public static void main(String[] args) {
    double luku1, kuutio1;

    System.out.println("Anna luku:");
    luku1 = Lue.dluku();
    kuutio1 = kolmanteen(luku1);
    System.out.println("kolmanteen: " + kuutio1);
    System.out.println("Anna toinen luku:");
    luku1 = Lue.dluku();
    kuutio1 = kolmanteen(luku1);
    System.out.println("kolmanteen: " + kuutio1);
  }
} 

Esimerkki: kahden luvun minimi


Metodi palauttaa pienemmän sille parametrina annetusta kahdesta luvusta. (Jos luvut ovat yhtäsuuret, ei ole väliä, kumpi palautetaan).



public class Minimi {

  private static double min(double eka, double toka) {
    
    if (eka < toka)
      return eka;
    else 
      return toka;
  }


  public static void main(String[] args) {

    double luku1, luku2, minimi;

    System.out.println("lukujen 3.0 ja 7.0 minimi on "+
		       min(3.0, 7.0));
    System.out.println("Anna kaksi lukua:");
    luku1 = Lue.dluku();
    luku2 = Lue.dluku();
    minimi = min(luku1, luku2);
    System.out.println("lukujen minimi on " + minimi);
  }
}

Esimerkki: kolmen luvun keskiarvo


Metodi palauttaa sille parametrina annettujen lukujen keskiarvon


private static double keskiArvo( int eka,

int toka,

int kolmas){

double karvo;


karvo = (eka + toka + kolmas) / 3.0;

return karvo;

}


Muuttujaa karvo ei välttämättä tarvita, yhtä hyvin voidaan kirjoittaa


private static double keskiArvo( int eka,

int toka,

int kolmas){

return (eka + toka + kolmas) / 3.0;

}


Metodia voidaan kutsua pääohjelmasta

tulos = keskiArvo(14, 18, 19);


tai

tulos = keskiArvo(luku1, luku2, luku3);


tai

System.out.println(keskiArvo( luku1, luku2, luku3));


jos muuttujille luku1, luku2 ja luku3 on annettu arvot.

Esimerkki: kahden pisteen välinen etäisyys


Ohjelma pyytää käyttäjältä kaksi (x,y)-koordinaatiston pistettä ja laskee niiden etäisyyden. Etäisyyden laskemiseen käytetään Pythagoraan lausetta.


Koska etäisyyden laskemisessa tarvitaan kahdesti neliöön korotusta, kirjoitetaan sitä varten oma metodi.




public class EtaisyysEsim {

  private static double etaisyys(int x1, int y1, int x2, int y2) {
    double eta;

    eta = Math.sqrt(nelio(x1-x2) + nelio(y1-y2));
    return eta;
  }


  private static double nelio(int luku) {
    return luku * luku;
  }


  public static void main(String[] args) {
    int ekaX, ekaY, tokaX, tokaY;
    double valimatka;

    System.out.println("Anna ensimmäisen pisteen koordinaatit");
    ekaX = Lue.kluku();
    ekaY = Lue.kluku();
    System.out.println("Anna toiseen pisteen koordinaatit");
    tokaX = Lue.kluku();
    tokaY = Lue.kluku();
    valimatka = etaisyys(ekaX, ekaY, tokaX, tokaY);
    System.out.println("Pisteiden etaisyys on " + valimatka);
  }
}

Siistimmin pisteitä voidaan käsitellä olioiden avulla.

Esimerkki: potenssiin korotus



public class Potenssi3{

  private static double pot(double kanta, int exp) {
    int i;
    double tulos;

    tulos = 1.0;
    for (i=1; i<=exp; i++)
      tulos = tulos * kanta;
    return tulos;
  }

  
  public static void main(String[] args) {
    int ekspo;
    double kantaluku;

    System.out.println("Anna kantaluku ja eksponentti");
    kantaluku = Lue.dluku();
    ekspo = Lue.kluku();
    if (ekspo <= 0)
      System.out.println("Liian pieni eksponentti");
    else
      System.out.println(kantaluku + " potenssiin " + ekspo +
			 " on " + pot(kantaluku, ekspo));
  }
}

Esimerkki: tehokkaampi potenssiin korotus



public class Potenssi4{

  private static double pot2(double kanta, int exp) {
 
    double tulos;

    tulos = 1.0;
    while (exp > 0)
      if (exp % 2 == 0) {
	kanta = kanta * kanta;
	exp = exp / 2;
      }
      else {
	tulos = tulos * kanta;
	exp--;
      }
    return tulos;
  }

  
  public static void main(String[] args) {
    int ekspo;
    double kantaluku;

    System.out.println("Anna kantaluku ja eksponentti");
    kantaluku = Lue.dluku();
    ekspo = Lue.kluku();
    if (ekspo <= 0)
      System.out.println("Liian pieni eksponentti");
    else
      System.out.println(kantaluku + " potenssiin " + ekspo +
			 " on " + pot2(kantaluku, ekspo));
  }
}

Jos metodin parametrit eivät ole olioita, niin niihin metodissa tehdyt muutokset eivät näy kutsuvassa ohjelmassa:



class Vaihto {

  private static void vaihda(int eka, int toka) {
    int apu;

    System.out.println("Muuttujat metodissa ennen vaihtoa: "+eka+" "+toka);
    apu = eka;  // vaihda ekan ja tokan arvot
    eka = toka;
    toka = apu;
    System.out.println("Muuttujat metodissa vaihdon jälkeen: "+eka+" "+toka);
  }


  public static void main(String[] args) {
    int luku1, luku2;

    luku1 = 5;
    luku2 = 10;
    System.out.println("Muuttujat pääohjelmassa aluksi: "+luku1+" "+luku2);
    vaihda(luku1, luku2);
    System.out.println("Muuttujat pääohjelmassa lopuksi: "+luku1+" "+luku2);
  }
}

Esimerkki: opintolainan kertyminen


Ohjelma pyytää käyttäjältä erikseen eri vuosina nostettavan lainaerän ja korkoprosentin.


Oletetaan, että laina nostetaan yhtenä eränä vuoden alussa. Korko liitetään pääomaan vuoden lopussa.


Korkoprosentti voi vaihdella vuosittain, mutta se pysyy samana koko vuoden.


Ohjelma laskee kertyneen lainapääoman opiskeluajan lopussa.


Laskeminen tehdään silmukassa, jossa lasketaan uusi pääoma kunkin vuoden lopussa.


Uusi pääoma = vanha pääoma + vuoden alussa nostettu erä + vuoden aikana kertynyt korko


Laskeminen lopetetaan, kun käyttäjä antaa negatiivisen koron.



public class Opintolaina {

  public static void main(String[] args) {

    double nostoera, paaoma = 0, korko;
    int i;

    System.out.println("Opintolainan laskuautomaatti. Lopeta antamalla "
		       + "negatiivinen korko.");
    System.out.println("Anna 1. vuonna nostettava erä");
    nostoera = Lue.dluku();
    System.out.println("Anna 1. vuoden korko");
    korko = Lue.dluku();
    i = 1;
    while (korko >= 0) {
      paaoma = laskePaaoma(paaoma, nostoera, korko);
      i++;
      System.out.println("Anna " + i + ". vuonna nostettava erä");
      nostoera = Lue.dluku();
      System.out.println("Anna " + i + ". vuoden korko");
      korko = Lue.dluku();
    }
    System.out.println("Kertynyt lainasumma opintojen lopussa " +
		       paaoma + " mk");
  }


  private static double laskePaaoma(double lainasumma, 
				    double era, 
				    double korkopros) {
    lainasumma = lainasumma + era + (lainasumma + era) * korkopros/100;
    return lainasumma;
  }
}

Asuntolainan kustannukset


Tehdään ohjelma, joka laskee asuntolainan kokonaiskustannukset koko laina-ajalta. Ohjelma antaa kustannukset kahdelle vaihtoehtoiselle maksutavalle:


Molemmissa vaihtoehdoissa lainaa lyhennetään joka kuukausi. Korkoprosentin oletetaan pysyvän samana koko laina-ajan.


Annuiteettilainan kuukausierä saadaan kaavalla era = (r**n * p * A) / (1200 * (r**n - 1))




missä


Tasalyhenteisessä lainassa korkoerä lasketaan joka kuukausi erikseen


public class Asuntolaina {
  
  private static double annuiteettiKustannus(double paaoma,
					    double korkopros,
					    int lainavuodet) {
    double rPotn, kustannus, kkEra;
    int lainakk;

    lainakk = 12 * lainavuodet;
    rPotn = Math.pow(1 + korkopros/1200, lainakk);
    kkEra = rPotn * korkopros * paaoma / (1200 * (rPotn -1));
    kustannus = lainakk * kkEra;
    return kustannus;
  }


  private static double tasalyhKustannus(double paaoma2,
					 double korkopros2,
					 int lainavuodet2) {
    double kustannus = 0.0, kkLyhennys, loppuera, korkoera;
    int lainakk, i;

    lainakk = 12*lainavuodet2;
    kkLyhennys = paaoma2 / lainakk;
    loppuera = paaoma2;
    for (i = 1; i <= lainakk; i++) {
      korkoera = loppuera * korkopros2 / 1200;
      kustannus = kustannus + kkLyhennys + korkoera;
      loppuera = loppuera - kkLyhennys;
    }
    return kustannus;
  }

  
  public static void main(String[] args) {
    double lainasumma, korko, kustannus1, kustannus2;
    int lainaAika;

    System.out.println("Anna lainan maara, korkoprosentti ja "+
		       "takaisinmaksuaika");
    lainasumma = Lue.dluku();
    korko = Lue.dluku();
    lainaAika = Lue.kluku();
    kustannus1 = annuiteettiKustannus(lainasumma, korko, lainaAika);
    kustannus2 = tasalyhKustannus(lainasumma, korko, lainaAika);
    System.out.println("Annuiteettilainan kokonaiskustannus on " +
		       kustannus1 + "mk ja tasalyhenteisen lainan " +
		       kustannus2 + "mk koko laina-ajalta");
  }
}

Kuormittaminen


Metodin kutsussa on oltava sama lukumäärä parametreja kuin metodin määrittelyssä. Parametrien on myös oltava oikeaa tyyppiä.


Usein samantapaisia asioita halutaan tehdä erityyppisille arvoille, esimerkiksi sekä merkkijonoille että kokonaisluvuille.


Java-ohjelmissa voidaan määritellä useita metodeja, joilla on sama nimi, mutta erityyppisiä (ja mahdollisesti eri lukumäärä) parametreja. Tätä kutsutaan metodin kuormittamiseksi.


Metodia kutsuttaessa kääntäjä päättelee todellisten parametrien tyypeistä ja lukumäärästä, mitä samannimisistä metodeista käytetään.


Esimerkki: kirjoitetaan metodi tuplaa, jota voi käyttää sekä kokonaislukujen, liukulukujen että merkkijonojen tuplaamiseen.


Jos metodi saa parametrikseen luvun, se kertoo sen arvon kahdella. Jos parametrina on merkkijono, palautetaan sama merkkijono kaksi kertaa peräkkäin. Metodia voi kutsua myös ilman parametreja: silloin se tulostaa "Tuplaa tuplasti!".


public class Tuplaus {
  
  private static void tuplaa() {
    System.out.println("Tuplaa tuplasti!");
  }

  private static int tuplaa(int kluku) {
    return 2 * kluku;
  }

  private static double tuplaa(double dluku) {
    return 2 * dluku;
  }

  private static String tuplaa(String mjono) {
    return mjono + mjono;
  }

  public static void main(String[] args) {
    
    tuplaa();
    System.out.println("3.5 tuplattuna: " + tuplaa(3.5));
    System.out.println("10 tuplattuna: " + tuplaa(10));
    System.out.println("\"Ohjelma\" tuplattuna: " + tuplaa("Ohjelma"));
  }
}

Tyyliasioita


Ohjelman aiheuttamista kustannuksista itse ohjelmointi on vain pieni osa. Usein valtaosa kustannuksista aiheutuu ohjelman ylläpitämisestä.


Usein vanhojen ohjelmien osia käytetään apuna uusia kirjoitettaessa.


Jotta ylläpito ja uudelleenkäyttö olisi helppoa, ohjelman on oltava selkeä.


Tärkeintä on, että ohjelma on jaettu järkevästi luokkiin ja metodeihin. Mutta myös hyvä ohjelmointityyli on tärkeää.


Ohjelmointityyliin kuuluu esimerkiksi kommentit, käytetyt tunnukset (nimet) ja ohjelman ulkoasu.



Kommenteista


Jokaisen ohjelmatiedoston alkuun on syytä kirjoittaa yleiskommentti:

Jokaisen luokan alkuun on syytä kirjoittaa yleiskommentti luokan tarkoituksesta.


Jokaisesta metodista kannattaa kirjoittaa kommentti metodin tarkoituksesta.


Metodin sisällä kommentoi suurempia kokonaisuuksia ja erikoisia ratkaisuja, älä itsestäänselvyyksiä.


Ei: i++; // kasvatetaan i:n arvoa yhdellä


Älä kommentoi liikaa: jos joka toisella rivillä on kommentti, ei olennaisia kommentteja erota epäolennaisista.


Kommentoi jo silloin, kun kirjoitat ohjelmaa, älä vasta sitten, kun ohjelma on valmis.

Tunnuksista (nimistä)


Valitse mahdollisimman kuvaavat nimet.


Yksikirjaimisten nimien käyttö on järkevää vain erikoistapauksissa (silmukkalaskuri, sama tunnus kuin vastaavassa matemaattisessa kaavassa).


Noudata Javan vakiintuneita käytäntöjä:


Rivinvaihdoista ja sisennyksistä


Jaa eri käskyt omille riveilleen ja käytä tyhjiä rivejä selkeyttämään ohjelmaa


ei

luku1 = 5; luku2 = 6; luku3 = luku1 + luku2;


vaan

luku1 = 5;

luku2 = 6;

luku3 = luku1 + luku2;


Sisennä eri rakenteet niin, että ne erottuvat selvästi omiksi kokonaisuuksikseen.


public class Kertoma2 {
  public static void main(String[] args) {
    int i, luku, kertoma;

    System.out.println("Anna luku: ");
    luku = Lue.kluku();
    if (luku < 0 || luku > 12)
      System.out.println("Ohjelma ei voi laskea kertomaa");
    else {
      kertoma = 1;
      for (i = 1; i <= luku; i++)
	kertoma = kertoma * i;
      System.out.println("Kertoma on " + kertoma);
    }
  }
}

Toinen vaihtoehto on kirjoittaa aukeava aaltosulku omalle rivilleen


public class Kertoma2 
{
  public static void main(String[] args) 
  {
    int i, luku, kertoma;

    System.out.println("Anna luku: ");
    luku = Lue.kluku();
    if (luku < 0 || luku > 12)
      System.out.println("Ohjelma ei voi laskea kertomaa");
    else 
    {
      kertoma = 1;
      for (i = 1; i <= luku; i++)
	kertoma = kertoma * i;
      System.out.println("Kertoma on " + kertoma);
    }
  }
}

On makuasia, kumpaa tapaa käyttää, mutta on syytä käyttää systemaattisesti samaa tapaa.