Oppimateriaali perustuu kevään 2010 kurssiversion materiaaliin ja Arto Wiklan ohjelmointisivustoon. Materiaalin copyright © (aakkosjärjestyksessä): Matti Luukkainen, Matti Paksula, Arto Vihavainen, Arto Wikla. Materiaalia saa vapaasti käyttää itseopiskeluun. Muu käyttö vaatii luvan.

II Algoritmeja Javalla, ohjelmointiympäristö

Huom: Tämä luku voi vielä muuttua.

(Muutettu viimeksi 23.9.2010, sivu perustettu 3.9.2010. Arto Wikla)

Tässä luvussa siirrytään Scalan alkeista Java-kieleen. Java on kirjoitustyyliltään huomattavasti Scalaa monisanaisempaa. Javassa yhtenä ajatuksena on, että ohjelmoijan on kerrottava hyvin tarkasti, mitä hän tahtoo.

Tällä on hyvät puolensa, mutta toisaalta seurauksena on, että aloittelija joutuu kirjoittamaan paljon sellaista, mikä toistaiseksi vaikuttaa pelkältä loitsulta. Jos yhtään lohduttaa, niin jatkokurssin loppupuoleen mennessä noiden "loitsujen" merkityskin selviää.

Nyt ryhdytään myös käyttämään ohjelmankehitysympäristöä eli "IDEä" nimeltään NetBeans. Se osaltaan helpottaa myös noiden "loitsujen" lausumista.

Tässä luvussa käydään tiiviisti läpi luvun I Algoritmeja (Scala) tärkeimmät esimerkit Java-versioina. Samalla tiiviisti kerrotaan kielten eroista.

Ohjelmoinnin aloittaminen

Ohjelman kirjoittaminen

Scalalla maailmaa tervehdittiin vaivatta:

println("Hello world!");

Java-ohjelmoija joutuu kirjoittelemaan aika lailla noita mainittuja "loitsuja":

public class HelloOhjelma {
  public static void main(String[] args) {
    System.out.println("Hello world!");
  }
}

Kuten näkyy, Scala-versio sisältyy Java-versioon. Mutta loitsuissa löytyy...

Tässä loitsut lyhyesti sanottuna tarkoittavat seuraavaa:

Ohjelman suorittaminen perinteiseen tapaan

Java-ohjelma voidaan suorittaa ilman erityistä ohjelmankehitysympäristöä seuraavaan tapaan käyttöjärjestelmän komentotulkissa ("terminal"):

  1. Kirjoita ohjelma jollakin tekstieditorilla tiedostoksi HelloOhjelma.java. Ohjelman nimen alkuosan pitää olla sama kuin luokan nimi, esimerkissä siis HelloOhjelma. Muista että luokkanimet on Javassa tapana kirjoittaa suurella alkukirjaimella.

  2. Käännä alkukielinen Java-ohjelma Javan välikielelle (Bytecode) komennolla:
    javac HelloOhjelma.java
    
    Jos ohjelmassa on muotovirheitä, kääntäjä antaa niistä enemmän tai vähemmän osuvia virheilmoituksia. Korjaa virheet tiedostossa HelloOhjelma.java ja yritä kääntää uudelleen. Sitten kun ohjelma kelpaa kääntäjälle, kääntäjä tuottaa ohjelman välikielivastineen tiedostoksi HelloOhjelma.class.

  3. Suorita välikielinen ohjelma Java-tulkilla. Anna komento:
    java HelloOhjelma
    
    Huomaa, että välikielisen ohjelmatiedoston nimen loppuosaa .class ei mainita tulkin käynnistyskomennossa.

Ohjelmointiympäristö NetBeans

Ohjelmointiympäristö eli IDE sisältää kääntäjän, editorin, yksinkertaisen virhetarkastuksen ja monia muita hyödyllisiä välineitä ohjelmoinnin helpottamiseen. Ja koska kaikki nuo ovat samassa ohjelmassa, kyseessä todellakin on "integroitu" ympäristö.

Kurssilla käytetään NetBeans-ohjelmointiympäristöä, joka löytyy valmiiksi asennettuna laitoksen koneista, Linuxissa Applications/Programming-alasvetovalikosta.

Ohjelman kirjoittaminen ja suorittaminen

  1. Käynnistä NetBeans. Valitse:
    File -> New Project -> Java -> Java Application -> Next
    Kirjoita kenttään Project Name (toistaiseksi) ohjelman luokan nimi HelloOhjelma. Kirjoita sama nimi myös kenttään Create Main Class. Paina nappulaa Finish. KUVA! (Jos käynnityksen jälkeen yläpalkin alla näkyy Start Page, sulje se klikkamalla rastia, ks. kuva.)

  2. Nyt ohjelman runko on valmiina näkyvissä. Älä ainakaan toistaiseksi välitä kummallisista kommenttialueista. Kirjoita pääohjelman algoritmi paikalleen. Nyt siis vain tuo System.out.println("Hello world!");

  3. Suorita ohjelma painamalla yläpalkissa näkyvää oikealle osoittavaa nuolenpäätä tai funktionäppäintä F6.

  4. Jos ohjelmassa ei ollut muotovirheitä, alalaidan tulostusalueella pitäisi näkyä teksti Hello world!. (Siellä on myös jotain ohjaustietoa, älä siitä välitä.)

  5. Muokkaa sitten ohjelmasi tulostus tekstiksi "Goodbye texteditor!" ja suorita ohjelma uudelleen. KUVA!

  6. Kun käynnistät NetBeansin myöhemmin uudelleen, se tarjoaa viimeksi käsitellyn projektin. Voit ylhäältä vasemmalta valita muitakin projekteja käsittelyyn.

Ohjelman suorittaminen askeleittain – virheenjäljitys

Niin yllättävältä kuin se saattaakin kuulostaa, ohjelmat eivät ihan aina toimi ihan oikein, vaikka ne kääntäjälle kelpaisivatkin... ;-)

Yksi tapa yrittää löytää syitä ohjelman virheelliseen toimintaan on ohjelman suorituksen pysäyttäminen ennalta määrättyihin kohtiin ja muuttujien arvojen tutkiminen tuolla hetkellä.

NetBeansilla tällaista analyysia voi tehdä seuraavaan tapaan:

  1. Lisää ohjelmaan pysäytyspisteitä (breakpoint) klikkaamalla editointi-ikkunan asemmassa laidassa näkyviä rivinumeroita. Näihin kohtiin ohjelman suoritus pysähtyy, kun ohjelma suoritetaan virheenjäljitystilassa.

  2. Käynnistä ohjelman suoritys virheenjäljitystilassa painamalla ctrl-F5 tai play-nuolen oikealta puolelta Debug Main Project-nappulaa. Ohjelma etenee ensimmäiseen pysäytyspisteeseen. NetBeansin oikeassa alalaidassa voit tarkastella muuttujien arvoja (eli ohjelman tilaa!) pysäytyshetkellä.

  3. Joko pelkällä F5:llä tai ylälaidan pikku nuolella voit edetä seuraavaan pysäytyspisteeseen ja jälleen tarkastella muuttujien arvoja.

  4. KUVA!

NetBeans-ohjelman kääntäminen ja suorittaminen komentoriviltä

NetBeansilla laaditusta Java-ohjelmasta on olemassa myös ihan tavallinen tekstitiedostoversio, jota voi perinteseen tapaan kääntää ja suorittaa käyttöjärjestelmän komentotulkissa.

Linuxissa NetBeansilla laaditut ohjelmat yms. oletusarvoisesti löytyvät kotihakemiston alihakemistosta NetBeansProjects.

Esimerkkitapauksessamme hakemistopolku on

NetBeansProjects/HelloOhjelma2/src/

Tuossa hakemistossa ohjelma on omalla nimellään tekstiedostona HelloOhjelma.java.

Muuttuja ja sijoitus, tietotyyppi

Scalan esimerkki

var teksti = "juttuhomma";
var kokonaisluku = 1234;
var liukuluku = 3.14;
var onkoTotta = true;

println(teksti);
println(kokonaisluku);
println(liukuluku);
println(onkoTotta);

teksti = "uusjuttu";
kokonaisluku = -666;
liukuluku = 1.2;
onkoTotta = false;

println(teksti);
println(kokonaisluku);
println(liukuluku);
println(onkoTotta);

Javalla:

public class Muuttujia {
  public static void main(String[] args) {
    String teksti = "juttuhomma";
    int kokonaisluku = 1234;
    double liukuluku = 3.14;
    boolean onkoTotta = true;

    System.out.println(teksti);
    System.out.println(kokonaisluku);
    System.out.println(liukuluku);
    System.out.println(onkoTotta);

    teksti = "uusjuttu";
    kokonaisluku = -666;
    liukuluku = 1.2;
    onkoTotta = false;

    System.out.println(teksti);
    System.out.println(kokonaisluku);
    System.out.println(liukuluku);
    System.out.println(onkoTotta);
  }
}

Huomaa: Muuttujat määritellään hieman toisin kuin Scalassa: ensin tyyppininmi ja sitten muuttujan nimi. Javan tyyppejä ovat mm. String, int, double ja boolean.

Tunnukset eli muuttujien yms. nimet

Käytäntö on Javassa sama kuin Scalassa.

Ja kurssilla siis suositaan "javamaisia" CamelCase-nimiä: kuukaudenEnsimmainenPaiva, jne... Ja säästetään isolla kirjaimella alkavat tunnukset Javan luokkanimille.

Selitteet eli kommentit

Javassa on sama kommenttikäytäntö kuin Scalassa: monirivinen ja rivin loppu -kommentti:

/*
   monirivinen
   sepustus.
*/

// rivin loppu on kommenttia

Tulostaminen

Tulostukseen on käytettävissä Scalan tapaan println-operaation lisäksi operaatio print. Ensin mainittu vaihtaa riviä tulostettuaan, jälkimmäinen ei vaihda. Tarvitaan vain se System.out.-lisäys:

public class Kissoja {
  public static void main(String[] args) {

    System.out.print("kissa");
    System.out.println("kävelee");  // tulostaa: kissakävelee

    System.out.println("kissa");
    System.out.println("kävelee");  // tulostaa: kissa
                                    //           kävelee

    int i = 4321;
    System.out.print("Muuttujan i arvo on ");
    System.out.print(i);
    System.out.println(".");    // tulostaa Muuttujan i arvo on 4321.
  }
}

Merkkijonoista ja niiden katenoinnista

Merkkijonokatenaatiokin toimii tuttuun tapaan:

public class Katenointeja {
  public static void main(String[] args) {

    int i = 4321;
    System.out.println("Muuttujan i arvo on " + i + ".");        // tulostaa Muuttujan i arvo on 4321.

    System.out.println("Yhtä suurempi arvo on " + (i+1) + ".");  // tulostaa Yhtä suurempi arvo on 4322.
                  //  HUOM: SULUT I:N KASVATUKSEN YMPÄRILLÄ VÄLTTÄMÄTTÖMÄT, SILLÄ
     System.out.println("Yhtä suurempi arvo on " + i+1 + ".");    // tulostaa Yhtä suurempi arvo on 43211.

     System.out.println("ahaa " + i + 7);   // ahaa 43217
     System.out.println("ahaa " + (i + 7)); // ahaa 4328
     System.out.println(i + 7 + " ahaa");   // 4328 ahaa

     System.out.println("Merkkijonon sisäänkin saa lainausmerkin: \". Mukavaa.");
     System.out.println("Sarkain eli tabulointikin onnistuu: \t Kas näin.");
     System.out.println("Rivinvaihtojakin voi kirjoittaa: \n Tässä yksi. \n\nJa pari lisää.");
  }
}

Kokonaisluvut, liukukuvut ja aritmetiikkaa

Nämä käyttäytyvät hyvin samaan tapaan kuin jo Scala-osiossa opittiin.

Esimerkki:

public class Lukuja {
  public static void main(String[] args) {
    int lkm=1, kpl=100;
    double pituus=1.1, leveys=2.2, korkeus=3.3;

    lkm = kpl- 10 * (kpl-8);
    pituus = leveys * korkeus + pituus * lkm;

    lkm = 5;
    korkeus = 5;
    kpl = lkm/2;        // arvo on 2
    leveys = korkeus/2; // arvo on 2.5
    
    lkm = 17%3;  // arvo on 2
  }
}

---> Lisätietoa kokonaisluvuista, liukuluvuista ja tyyppimuunnoksista

Syöttötietojen lukeminen

Javalla syöttötietoja voi lukea käyttämällä ns. Scanner-oliota

import java.util.Scanner;   // Scanner-luokka tuodaan käännösyksikköön

public class Viisas {
  private static Scanner lukija = new Scanner(System.in); // luodaan Scanner-olio
  public static void main(String[] args) {

    String nimi;
    int    ika;
    double pituus;

    System.out.println("Mikä on nimesi?");
    nimi = lukija.nextLine();
    System.out.print("Ja ikäsi: ");
    ika = lukija.nextInt();
    System.out.print("Entä pituutesi? ");
    pituus = lukija.nextDouble();   // Syötteessä käytettävä desimaalipilkkua!!

    System.out.println("Moi " + nimi +"!");
    System.out.print("Tiedän että olet " + ika + "-vuotias ");
    System.out.println("ja että olet " + pituus + " senttiä pitkä.");
    System.out.println("Enkö olekin viisas!");
  }
}

Syöttö tapahtuu muuten kuin Scala-versiossa, mutta laitoksen koneissa desimaaliluku on syötettävä desimaalipilkullisena vaikka ohjelma itse käyttääkin desimaalipistettä. Syynä on "oletuslokalisaatio". Se voi omassa koneessasi olla eri...

Mikä on nimesi?
Violetta
Ja ikäsi: 20
Entä pituutesi? 167,8
Moi Violetta!
Tiedän että olet 20-vuotias ja että olet 167.8 senttiä pitkä.
Enko olekin viisas?

Jos ohjelmalle syöttää väärän tyyppistä tietoa, ohjelman suoritus päättyy virheeseen. Myös desimaalipisteen käyttö siis kaataa ohjelman.

---> Lisätietoa desimaalipisteen sallimisesta

If-lause, vertailut, loogiset operaatiot ja lohkot

Javan if-lause, loogisten ehtojen kirjoittaminen ja lohkot ovat samanlaisia kuin jo Scala-osiossa opittiin.

import java.util.Scanner;

public class PosNegNol {
  private static Scanner lukija = new Scanner(System.in);
  public static void main(String[] args) {

  System.out.println("Anna kokonaisluku!");
  int luku = lukija.nextInt();

  if (luku > 0)
    System.out.println("Luku on positiivinen.");
  else if (luku < 0)
    System.out.println("Luku on negatiivinen.");
  else
    System.out.println("Ahaa, luku on nolla."); // ei ollut positiivinen eikä negatiivinen...
  }
}

Yksi kiinnostava ero kuitenkin löytyy: Scalan tapaan merkkijonon vertaaminen toiseen merkkijonoon ei onnistu vaivattomasti tyyliin:

if (s != "hupsista")  ...
if (s == "hupsista")  ...

Näin saa kyllä kirjoittaa – se on "oikein" – mutta tarkoittaa luultavasti jotain muuta kuin ohjelmoija tarkoitti: Javassa kaikkien olioarvojen yhtäsuuruus- ja erisuuruusvertailut tarkoittavat vastausta kysymykseen "onko kyseessä yksi ja sama olio". Ja on täysin mahdollista, että muuttuja s viittaa eri merkkijono-olioon, jonka sisältö sattuu olemaan myös "hupsista"! Tässä tilanteessa ehto siis luultavasi siis tarkoittaa jotakin muuta kuin ohjelmoija tarkoittaa.

Java-ohjelmassa merkkijonojen samansisältöisyyttä tutkitaan String-luokan aksessorimetodilla equals:

if (!s.equals("hupsista"))  ...
if (s.equals("hupsista"))  ...

Olioista ja niiden vertailuista lisää myöhemmin.

Ehdolliset toistot – while ja do-while

Javan alkuehtoinen toisto, while-lause, on hyvin samanlainen kuin Scalassa:

import java.util.Scanner;

public class Pariton {
  private static Scanner lukija = new Scanner(System.in);
  public static void main(String[] args) {

    System.out.println("Anna pariton luku.");
    int luku = lukija.nextInt();

    while (luku % 2 == 0) {
      System.out.println("Eihän luku " + luku + " ole pariton!");
      System.out.println("Yritä uudelleen");
      luku = lukija.nextInt();
    }

    System.out.println("Kyllä! " + luku + " todella on pariton.");
  }
}

Loppuehtoinen toisto eli do-while-lause on muotoa:

 do {
   lause1;
   lause2;
   ...
 } while (jatkamisehto)

Esimerkki:

import java.util.Scanner;

public class Pariton2 {
  private static Scanner lukija = new Scanner(System.in);
  public static void main(String[] args) {
    int luku;
    boolean parillinen;

    do {
      System.out.println("Anna pariton luku!");
      luku = lukija.nextInt();
      parillinen = (luku%2 == 0);
      if (parillinen) {
        System.out.println("Eihän "+luku+" ole pariton ...");
        System.out.println("Yritä uudelleen");
      }
    } while (parillinen);
    System.out.println("Hienoa, "+luku+" on pariton!");
  }
}

Tässä esimerkissä on muutakin opittavaa: Totuusarvoista muuttujaa parillinen käytetään muistamaan oliko viimeksi luettu luku parillinen. Jos oli, luku ei siis ollut vaadittu pariton, ja voidaan antaa virheilmoitus ja toistoehto aikaansaa uuden kyselyn.

Taulukko

Javassa taulukoiden määrittelyn ja käytön merknnät poikkeavat jonkin verran kurssilla jo opitusta, mutta idea on aivan sama:

import java.util.Scanner;

public class EkaVikaksiJne {
  private static Scanner lukija = new Scanner(System.in);
  public static void main(String[] args) {

    int[] luvut = new int[5];  // tämä selitetään kohta...

    int lokeronNumero = 0;

    while (lokeronNumero < 5) {
      System.out.print("Anna " + (lokeronNumero+1) + ". luku: ");  // pyydetään 1., 2., ..., siksi tuo +1
      luvut[lokeronNumero] = lukija.nextInt();
      lokeronNumero = lokeronNumero +1;
    }

    System.out.println("Luvut käänteisessä järjestyksessä:");

    lokeronNumero = 4;
    while (lokeronNumero >= 0) {
      System.out.println(luvut[lokeronNumero]);
      lokeronNumero = lokeronNumero - 1;
    }
  }
}

Javassa taulukkomuuttujan määrittely ja taulukko-olion luonti ovat siis muotoa:

    int[] luvut = new int[5];

Muuttuja luvut on tyypiltään kokonaislukutaulukko. Sen arvoksi luodaan viisialkioinen kokonaislukutaulukko-olio. Indeksointi Scalan tapaan alkaa nollasta. Koska muuttuja on "muuttuja", sen arvoksi voidaan tarvittaessa myöhemmin sijoittaa jokin toinen kokonaislukutaulukko, myös toisen mittainen. Mutta vain kokonaislukutaulukko.

Taulukkoa indeksoidaan Javassa useimpien muiden ohjelmointikielten tapaan hakasulkein:

      luvut[lokeronNumero] = lukija.nextInt();

Myös Javassa taulukon koon eli alkioiden lukumäärän ilmaisee .length:

public class Kokoja {
  public static void main(String[] args) {

    int[]    t1 = new int[5];
    double[] t2 = new double[500];
    int[]    t3 = new int[1000000];

    System.out.println(t1.length); // 5
    System.out.println(t2.length); // 500
    System.out.println(t3.length); // 1000000
  }
}

Askeltava toisto – for-lause indeksoiden

Edellä opittiin Scalan for-lauseesta esimerkki:

for (i <- 5 to 10)
  print(i + " ");  // 5 6 7 8 9 10
println(); // rivinvaihto
for (i <- (5 to 10).reverse)
  print(i + " ");  // 10 9 8 7 6 5
println(); // rivinvaihto

Saman voi tehdä Javalla seuraavaan tapaan:

for (int i = 5; i < 11; i = i+1)
  System.out.print(i + " ");  // 5 6 7 8 9 10
System.out.println(); // rivinvaihto
for (int i = 10; i > 4; i = i-1)
  System.out.print(i + " ");  // 10 9 8 7 6 5
System.out.println(); // rivinvaihto

Tässä kannattaa kiinnittää huomio muutamaan yksityiskohtaan:

Tuttu Scala-esimerkki "javannettuna": Kysytty määrä syöttölukuja tulostetaan käänteisessä järjestyksessä:

import java.util.Scanner;

public class EkaVikaksiJnePlus {
  private static Scanner lukija = new Scanner(System.in);
  public static void main(String[] args) {

    System.out.println("Montako lukua haluat tulostaa käännetyssä järjestyksessä?");
    int koko = lukija.nextInt();

    int[]luvut = new int[koko];  // HUOM: taulukon koko saa olla siis myös muuttuja!

    for (int lokeronNumero=0;  lokeronNumero < luvut.length; ++lokeronNumero) {
      System.out.print("Anna " + (lokeronNumero+1) + ". luku: ");
      luvut[lokeronNumero] = lukija.nextInt();
    }

    System.out.println("Luvut käänteisessä järjestyksessä:");

    for (int lokeronNumero = luvut.length-1; lokeronNumero >= 0; --lokeronNumero)
      System.out.println(luvut[lokeronNumero]);
  }
}

Askeltava toisto – "for-each"

Edellä opittu for-lauseen muoto talukoiden käsittelyssä perustuu indeksointiin, siihen että askelmuuttuja (vaikkapa int i) käy läpi taulukon indeksit ja toistettava alialgoritmi viittaa taulukon alkiohin indeksointi-ilmauksella (vaikkapa taulu[i]). Tällä menettelyllä taulukon alkioiden arvoja voidaan tutkia ja myös muuttaa.

Jos on tarve vain tutkia taulukon alkiot yksi kerrallaan alusta loppuun, käytettävissä on myös ns. "for-each"-toisto, jossa taulukon alkioiden tyyppinen muuttuja saa yksi kerrallaan arvokseen taulukon alkioiden arvot. Lauseen muoto on:

  for (taulukon_alkion_tyyppi alkio: taulukko) {
    // tänne saadaan yksi kerrallaan käyttöön 
    // taulukon alkioiden arvot muuttujassa alkio
  }

Esimerkkinä taulukon alkioiden summan laskenta.

public class PiinAlkua {
  public static void main(String[] args) {

    int[] taulu = {3, 1, 4, 5, 9, 2, 6, 5, 3, 5};

    int summa = 0;

    for (int alkio : taulu)
       summa = summa + alkio;

    System.out.println("Alkioiden summa: " + summa);
  }
}

Huom: Tätä for-toiston muotoa käyttäen alkioiden arvoja ei siis voi muuttaa. Muutostarpeen tullessa indeksointia "omin käsin" ei voi välttää. Alkioiden idekseistä ei myöskään ole mitään tietoa. Jos on tiedettävä, on syytä tällöikin ideksoida "omin käsin"
Siis:

  for (taulukon_alkion_tyyppi alkio: taulukko) {
    // tällä ei ole tietoa alkion indeksistä
    // ja vaikka muutujan alkio arvoa muuttaisi, 
    // taulukossa mikään ei muutu 
  }

Huom: Tämä sama väline on käytettävissä myös Javan monien ns. kokoelmien alkioiden arvojen järjestelmälliseen läpikäyntiin.

Merkkijonotaulukoista

Aiemmin nähtyjä esimerkkejä Javalla:

String teksti = "juttuhomma";
System.out.println(teksti);
teksti = "uusjuttu";
System.out.println(teksti);

String[] nimet = new String[3];

nimet[0] = "Aku";
nimet[1] = "Iines";
nimet[2] = "Hessu";

for (int indeksi=0;  indeksi <  nimet.length; ++indeksi)
  System.out.println(nimet[indeksi]);

Muista että Java-ohjelmoija joutuu vertailemaan merkkijonojen samuutta erityisellä equals-aksessorilla. Yhtäsuuruusmerkki == tarkoittaa jotakin muuta...

if (teksti.equals("kissantassu")) ...

Myös Javassa taulukon voi määritellä myös luettelemalla alkioiden alkuarvot. Syntaksi vain on hieman erilainen:

import java.util.Scanner;

public class koe {
  private static Scanner lukija = new Scanner(System.in);
  public static void main(String[] args) {


    String[] henkilot = {"Matti", "Maija", "Pekka", "Liisa"};
    int[]    ika      = {   29,      22,     19,       27};

    System.out.println("Kenen henkilön iän haluat tietää?");
    String kuka = lukija.nextLine();

    // etsitään indeksi:

    int indeksi = 0;

    while (indeksi < henkilot.length && !henkilot[indeksi].equals(kuka))
      indeksi = indeksi + 1;

    // Nyt joko löytyi tai indeksi kasvoi yli:

    if ( indeksi == henkilot.length )
      System.out.println("Henkilöä " + kuka + " ei löytynyt.");
    else {
      int haetunIka = ika[indeksi];
      System.out.println("Henkilön " + kuka + " ikä on " + haetunIka + ".");
    }
  }
}

Tässä esimerkissä on tälläkin kertaa paljon muutakin opittavaa kuin taulukon määrittely alkuarvot luettelemalla. Mitä? Mieti, tutki ja opi!

Nimetty alialgoritmi – metodi

Java-ohjelmoijat kutsuvat nimettyjä aliohjelmia useimmiten metodeiksi (method). Nimitys tulee metodien yhdestä tärkeästä kayttotavasta: olioita ja niiden tilaa käsitellään metodein. Tästä lisää myöhemmin.

Metodien ohjelmoinnin ja käytön ideat ovat jo tuttuja kurssin alkuvaiheilta. Mutta kuten niin usein tällä alalla, nytkin joudutaan opettelemaan uusia tapoja ilmaista tuttuja ideoita.

Nimetyt alialgoritmit voivat olla "komentoja tehdä jotakin" samaan tapaan kuin vaikkapa println osaa kirjoittaa parametrinsa ohjelman käyttäjän kuvaruudulle. Nimettyjä alialgoritmeja voi kirjoittaa myös sellaisiksi, että ne laskevat jonkin arvon ja palauttavat tuon arvon. Esimerkkejä tällaisista ovat vaikkapa trigonometriset funktiot, jotka useimmista ohjelmointikielistäkin löytyvät.

Javassa ensin mainittuja kutsutaan usein void-metodeiksi eli arvoa palauttamattomiksi metodeiksi. Jälkimmäiset siten luontevasti ovat arvon palauttavia metodeita. Ja koska kaikilla arvoilla Javassa on tyyppi, arvon palauttavia metodeita usein nimitetään paluuarvon tyypin mukaan: int-metodi, double-metodi, String-metodi, jne.

Metodin määrittely ja kutsu

Tässä luvussa laaditaan metodeita vielä klassiseen malliin "pääohjelman pikku apulaisiksi". Olio-ohjelmoinnin puolella sitten opitaan toinen tärkeä käyttötapa metodeille.

Ensin tuttu esimerkki esimerkki "komennon kaltaisesta" metodista:

public class Moi {   

  private static void tervehdi() {   // määrittely
    System.out.println("Moi!");
  }

  public static void main(String[] args) {
    System.out.println("Kutsutaanpa tervehdystä:");
    tervehdi();  // kutsu 

    System.out.println("Kutsutaanpa kolmasti:"); 
    tervehdi(); tervehdi(); tervehdi(); 
  }
}

Tuttu esimerkki arvon palauttavasta metodista:

public class Moi2 {

  private static int onnenluku() {   // määrittely
    return 14;
  }

  public static void main(String[] args) {
    System.out.println("Onnenluku = " + onnenluku());  // kutsu
    System.out.println("Ja kolmeen kertaan: "
                       + (onnenluku() + onnenluku() + onnenluku()));
  }
}

Ja sitten muutamia huomioita Javan tyylistä

Parametrit

Metodin parametrien määrittely ja antaminen muistuttavat jo opittua – Javan tapaan tyyppinimi edeltää muuttujaa:

public class Moi3 {

  private static void tervehdi(String kuka) {   // määrittely
    System.out.println("Moi " + kuka + "!");
  }

  public static void main(String[] args) {
    System.out.println("Kutsutaanpa tervehdystä:");
    tervehdi("Aku");  // kutsu

    System.out.println("Kutsutaanpa kolmasti:");
    tervehdi("Iines"); tervehdi("Minni"); tervehdi("Mikki");

    int monesko = 7;
    String nimi = "Jaska Jokunen";
    tervehdi(nimi + ", perijä numero " + (monesko+2));
              // tulostus: Moi Jaska Jokunen, perijä numero 9!
  }
}

Huom: Muista terminologia: Metodin määrittelyssä esiteltyjä parametreja kutsutaan muodollisiksi parametreiksi. Metodin kutsuun kirjoitettavia arvoja kutsutaan todellisiksi parametreiksi, toisinaan myös argumenteiksi. [Tämän tyylin parametreja kutsutaan arvoparametreiksi. Scalassa ja Javassa parametrit ovat aina tällaisia. Mutta myös muita parametrinvälitystapoja ohjelmointikielistä löytyy.]

Tietenkin myös arvon palauttavalle metodille voi antaa parametreja:

public class Moi4 {

  private static int tuplaa(int luku) {   // määrittely
    return 2 * luku;
  }

  public static void main(String[] args) {
    System.out.println("Kolme tuplana = " + tuplaa(3));  // kutsu

    int i = 8;
    int j = tuplaa(i);

   System.out.println( tuplaa(j) + tuplaa(2) );
   System.out.println(tuplaa(tuplaa(tuplaa(1))));
  }
}

Parametreja voi toki olla useampiakin:

public class Onnea {

  private static void onnittele(String kuka, int kertaa) {
  for (int i = 0; i < kertaa; ++i)
    System.out.println( "Onnea " + kuka + "!");
  }

  public static void main(String[] args) {
    onnittele("Liisa", 3);
    onnittele("Pekka", 2);
  }
}

public class Nelis {

  private static int neliosumma(int a, int b) {
    int summa = a + b;
    int nelio = summa * summa;
    return nelio;
  }

  public static void main(String[] args) {
    System.out.println(neliosumma(1, 1));
    System.out.println(neliosumma(3, 2));

  }
}

Metodin paikalliset muuttujat

Metodin sisällä eli metodin lohkossa määritellyt muuttujat eivät milloinkaan voi näkyä metodin lohkon ulkopuolelle. Tällaisia muuttujia kutsutaan funktion paikallisiksi muuttujiksi. Ne ovat käytettävissä vain metodin omassa algoritmissa.

Metodin paikalliset muuttujat ja muodolliset parametrit itse asiassa syntyvät, kun metodia kutsutaan ja metodi käynnistyy. Paikallisille muuttujille ja muodollisille parametreille varattu tila vapautetaan, kun metodin suoritus päättyy. Koska paikalliset muuttujat eivät säily suorituskerrasta toiseen, niiden avulla myöskään "ei voi muistaa mitään" seuraavalle suorituskerralle.

Metodin muodolliset parametrit ovat muuten samassa asemassa kuin paikalliset muuttujat, mutta niillä on aina alkuarvonaan kulloisessakin kutsussa annettujen todellisten parametrien arvo. Arvoa on lupa myös muuttaa, mutta muutos ei näy metodin ulkopuolelle, koska muodollisten parametrienkin tila vapautetaan metodin suorituksen päättyessä.

Java-kielessä pätee sääntö: Mistään aliohjelmasta ei koskaan voi nähdä minkään toisen aliohjelman paikallista muuttujaa.

Taulukot ja metodit

Myös taulukon voi välittää parametrina, mikä usein on näppärää:

public class ArvotPaikalleen {

  private static void asetaJokaAlkiolle(int[] taulu, int arvo) {
  for (int i = 0; i < taulu.length; ++i)
    taulu[i] = arvo;
  }

  public static void main(String[] args) {
    int[] t = new int[10];
    System.out.println(t[7]);  // 0
    asetaJokaAlkiolle(t, 100);
    System.out.println(t[7]);  // 100

    t = new int[1000000];      // uusi arvo t:lle
    asetaJokaAlkiolle(t, 9876);
    System.out.println(t[765432]);  // 9876
  }
}

Esimerkki peräkkäishausta: metodi etsii taulukosta kysyttyä arvoa ja palauttaa vastaavan indeksin ensimmäisestä löytyneestä. Jos päästään talukon loppuun löytämättä haettua arvoa, palautetaan arvo -1, joka ei kelpaa taulukon indeksiksi. Se viestii, ettei haettua arvoa löytynyt:

public class Perakkaishakuesimerkki {

  private static int mikaOnArvonIndeksi(int etsittava, int[] taulu) {
    for (int i = 0; i < taulu.length; ++i)
      if (taulu[i] == etsittava)
        return i;
    // jos tänne päästään, ei löytynyt
    return -1;   // -1 ei voi olla indeksi
  }

  public static void main(String[] args) {

    int[] t = {2, 4, 6, 8, 10, 12, 14};

    System.out.println("Luvun 8 indeksi on " + mikaOnArvonIndeksi(8, t));
    System.out.println("Luvun 7 indeksi on " + mikaOnArvonIndeksi(7, t));

    if (mikaOnArvonIndeksi(8, t) != -1)
      System.out.println("8 löytyy");
    else
      System.out.println("8 ei löydy");

    if (mikaOnArvonIndeksi(7, t) != -1)
      System.out.println("7 löytyy");
    else
      System.out.println("7 ei löydy");
  }
}