Arto Wikla 2011. Materiaalia saa vapaasti käyttää itseopiskeluun. Muu käyttö vaatii luvan.

1 Algoritmeja, valintaa, toistoa, taulukoita, metodeita

(Muutettu viimeksi 15.9.2011, sivu perustettu 17.8.2011.)

Tässä luvussa tutustutaan algoritmin ideaan ja perustavanlaatuisten algoritmien laadintaan. Algoritmi on toimintaohje jonkin ongelman ratkaisemiseen tai vaikkapa jonkin palvelun toteuttamiseen. Tietokone aina suorittaa jotakin algoritmia, kun se tekeee jotakin. Myös useimmat ohjelmointikielet perustuvat algoritmin kirjoittamiseen – toisin sanoen ohjelmoija ajattelee laativansa ajassa etenevää toimintaohjetta. Tosin on toisenlaisiakin ohjelmointikieliä...

Algoritmi ja algoritmin tila

Algoritmisen ohjelmoinnin peruskäsitteitä ovat muuttuja (variable) ja sijoitusoperaatio (assignment operation).

Muuttuja tarkoittaa matematiikassa jotakin melko syvällistä: esimerkiksi ilmauksessa a+b=b+a väitetään vaikkapa minkä tahansa lukuparin summan olevan riippumaton laskentajärjestyksestä. Tilastotieteessä muuttuja voi tarkoittaa "selittävää tekijää"; esimerkiksi myytyjen jäätelöiden määrä voisi olla muuttujana hukkumiskuolemien määrää selitettäessä.

Algoritmisessa ohjelmoinnissa muuttuja on paljon konkreettisempi asia: muuttuja on arvon säilytyspaikka, "laatikko, jonka kyljessä on nimi". Laatikon sisältöa voi tutkia ja sisältöä voi muuttaa. Uuden sisällön asettamista laatikkoon kutsutaan sijoittamiseksi, sijoitusoperaatioksi tai sijoituslauseeksi.

Sijoitus korvaa muuttujan vanhan sisällön uudella. Uusi arvo voi olla jonkin laskutoimituksen, ns. lausekkeen (expression) arvo. Tuo lauseke voi sisältää vakioita, muuttujien arvoja ja laskutoimituksia.

Sijoitusoperaation ilmauksena algoritmeja kirjoitettaessa joissain ohjelmointikielissä käytetään merkkijonoa ":=". Myös ilmausta "<-" näkee joskus. Valitettavasti eräissä ohjelmointikielissä (mm. Javassa!) sijoitusoperaation merkkinä käytetään yhtäsuuruusmerkkiä "=". Näissä kielissä matematiikasta tuttuun yhtäsuuruuden vertailuun joudutaan siten käyttämään matematiikan kielestä poikkeavaa ilmausta, esimerkiksi kahta yhtäsuuruusmerkkiä peräkkäin "==".

Esimerkki:

eka, toka ja kolm ovat muuttujia
eka = 7
toka = 9
kolm = 2

eka = toka
toka = kolm * 2
kolm = eka + toka

kolm = kolm + 1

Huom: Sijoitusoperaatio kannattaa heti alussa opetella lukemaan "saa arvokseen", ei "on", vaikka ohjelmointikielessä Javan tapaan käytettäisiin yhtäsuuruusmerkkiä sijoitusoperaation ilmauksena! Erityisen hyvin tuo viimeinen esimerkki osoittaa, miksi.

kolm = kolm + 1

Ilmaus "kolm on kolm plus yksi" on looginen epätotuus. Sitävastoin "kolm saa arvokseen kolm plus yksi" ilmaisee, mistä on kysymys: muuttujan vanhaan arvoon lisätään yksi ja saatu summa sijoitetaan muuttujan uudeksi arvoksi.

Algoritmia siis suoritetaan ajassa: "ensin tehdään sitä, sitten tätä". Algoritmin muuttujien arvot kullakin ajan hetkellä muodostavat algoritmin ns. tilan (state). Ja oikeastaan kaikki tietokoneohjelmat ovat lopulta vain algoritmeja, jotka etenevät tilasta toiseen. Jos ohjelmassa näyttäisi olevan jotakin "viisautta", kyse on siitä, että ohjelman laatija on osannut rakentaa algoritmilleen sellaisen tilojen ketjun, joka ohjelman käyttäjästä vaikuttaa viisaalta.

Edellisen esimerkin tilat, kun algoritmi suoritetaan:

eka, toka ja kolm ovat muuttujia
eka = 7
toka = 9
kolm = 2

tila:     |__7____|  |__9____|  |__2____| 
            eka        toka       kolm

eka = toka

tila:     |__9____|  |__9____|  |__2____| 
            eka        toka       kolm

toka = kolm * 2

tila:     |__9____|  |__4____|  |__2____| 
            eka        toka       kolm

kolm = eka + toka

tila:     |__9____|  |__4____|  |__13___| 
            eka        toka       kolm

kolm = kolm + 1

tila:     |__9____|  |__4____|  |__14___| 
            eka        toka       kolm

Algoritmi liitetään ympäristöönsä ns. syöttö- ja tulostusoperaatioin. Syöttöoperaation eli lukemisen seurauksena jokin muuttuja saa arvon ohjelman käyttäjältä tai vaikkapa jostakin tiedostosta. Lukeminen siis muuttaa ohjelman tilaa. Tulostusoperaatio, kirjoittaminen puolestaan laskee jonkin arvon ja siirtää tuloksen jollekin tulostuslaitteelle. Tällöin algoritmin tila ei muutu.

Syöttötietojen ajatellaan olevan syöttöjonossa, josta ne yksi kerrallaan luetaan. Tulostietojen ajatellaan puolestaan olevan tulosjonossa, johon ne yksi kerrallaan kirjoitetaan. Nämä jonot voivat ihan konkreettisestikin olla "jonoja" jossakin tiedostossa, mutta esimerkiksi vuorovaikutteisen ohjelman syötteiden ja tulosteiden "jonot" ovat pikemminkin jonoja ajassa.

Esimerkki:

eka, toka ja kolm ovat muuttujia
kirjoita viesti: "Anna kaksi lukua"
eka  = lue syöttöluku
toka = lue syöttöluku
kolm = eka + toka
kirjoita viesti: "Niiden summa on"
kirjoita viesti: kolm

Jos tämän ohjelman syöttöjonossa on luvut 3 ja 4, muuttujaan eka luetaan arvo 3, muuttujaan toka arvo 4. Sitten muuttujaan kolm sijoitetaan summa eka+toka eli arvo 7. Lopuksi ohjelma kirjoittaa tulosjonoon arvon 7. Näin "jonot" ovat siis ohjelman käyttäjän näkökulmasta toimintoja näytön ja näppäimistön äärellä.

Peräkkäin kirjoitetut operaatiot siis suoritetaan peräkkäin - "vasemmalta oikealle, ylhäältä alaspäin". Alkeisoperaatioiden (sijoitus, lukeminen, kirjoittaminen) suorittamista ohjataan ns. rakenteisin operaatioin. Rakenteiset operaatiot ovat "rakenteisia" siksi, että ne sisältävät muita operaatioita.

Monet rakenteiset operaatiot perustuvat siihen, että väitetään jotakin algoritmin tilasta, ja jos väite on tosi, "tehdään jotakin", jos epätosi, "tehdään jotakin muuta". Esimerkkejä tällaisista ovat valinta ja toisto:

Esimerkki valinnasta:

luku on muuttuja
kirjoita viesti "Anna luku. Tutkin, onko se positiivinen"
luku = lue syöttöluku

jos (luku>0)             // valinta
  kirjoita viesti "Luku on positiivinen."
muuten
  kirjoita viesti "Luku ei ole positiivinen.")

Esimerkki toistosta:

määrä ja monesko ovat muuttujia
kirjoita viesti "Moneenko kertaan haluat saada onnittelut?"
määrä = lue syöttöluku
monesko = 0
niin pitkään kun (monesko < määrä) toista seuraavia operaatoita 
  kirjoita viesti "Onnea!"
  monesko = monesko + 1

Mitä seuraava ohjelmointivirheen sisältävä ohjelma tekee kun sille syötetään positiivinen luku? Entä kun sille syötetään negatiivinen luku? Simuloi ja opi! Mitä opit?

määrä ja monesko ovat muuttujia
kirjoita viesti "Moneenko kertaan haluat saada onnittelut?"
määrä = lue syöttöluku
monesko = 0
niin pitkään kun (monesko > määrä) toista seuraavia operaatoita 
  kirjoita viesti "Onnea!"
  monesko = monesko + 1

Huom: Rakenteiset operaatiot ovat siis "rakenteisia" siksi, että niiden rakenneosina on muita lauseita. Noita rakenneosia voidaan kutsua alialgoritmeiksi: "Toistolause toistaa alialgoritmia..."

Lady Ada Lovelace, lordi Byronin tytär, keksi valinnan ja toiston 1800-luvun lopulla. Hän laati ohjelmia Charles Babbagen suunnittelemaan mekaaniseen tietokoneeseen, jossa ohjelmat suoritettiin suoraan reikäkorttipakalta. Lady Ada keksi täydentää konekieltä operaatioilla, jotka jostakin ehdosta riippuen hyppäävät joidenkin korttien ohi tai jotka siirtävät jo ohitetun korttipakan osan uudelleen luettavaksi!

Ehdollisuus - algoritmin tilasta riippuva toiminnan ohjaus - on keskeistä algoritmeja laadittaessa. Ilman ehdollisuutta algoritmi toimisi samoin kaikilla syöttötiedoilla! Alialgoritmien nimeäminen ja nimettyjen alialgoritmien kutsuminen on myös tärkeä väline: Kerran ohjelmoitua alialgoritmia ei tarvitse kirjoittaa uudelleen ja uudelleen.

Ohjelmoinnin aloittaminen

Ensimmäinen ohjelma

Aloitetaan ohjelmointi kansainväliseen tapaan. Kirjoita seuraava Java-ohjelma tekstitiedostoon HelloOhjelma.java.

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

Java on tyyliltään huomattavan monisanaista. 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ää.

Oleellinen toiminnallisuus tässä esimerkissä on kohta:

   println("Hello world!");

Tämä lause luetaan "print line". Se tulostaa kuvaruudulle – ohjelman käynnistysikkunaan tms. – yhden rivin, jonka sisältö on sulkeiden sisällä olevan merkkijonon Hello world!

Ohjelman "loitsut" lyhyesti sanottuna tarkoittavat seuraavaa:

Toisinaan tekstitiedostoon kirjoitettua lähdekielistä ohjelmaa kutsutaan koodiksi. Nimitys on kotoisin 1950-luvulta, jolloin konekielikäskyjä "koodattiin" symbolisilla nimillä.

Ohjelman suorittaminen

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 antamalla komento:
    java HelloOhjelma
    
    Huomaa, että välikielisen ohjelmatiedoston nimen loppuosaa .class ei mainita tulkin käynnistyskomennossa.

Muokkaa alkuperäistä tiedostoasi: muuta tulostettavaa tekstiä ja lisää uusia tulostuslauseita. Talleta tiedosto ja suorita taas. Osaat siis jo tehdä ohjelmia, jotka tulostavat, mitä ikinä maailmassa voidaankaan pyytää tulostamaan.

Algoritmi tietokoneen suoritettavaksi

Tietokone ei kerta kaikkiaan "osaa" suorittaa mitään muuta algoritmia kuin sellaista, joka on kirjoitettu koneen sisäisellä omalla konekielellä. Tuo kieli kuitenkin on useimpiin ohjelmointitehtäviin auttamattoman kömpelöä ja koneenläheistä.

Lähdekielinen ohjelma

Ohjelma kirjoitetaan useimmiten jollakin konekieltä korkeamman tason kielellä, lausekielellä. Toisinaan näin kirjoitettua ohjelmaa kutsutaan lähdekoodiksi. Lähdekoodi talletetaan tekstimuodossa ja suoritetaan jollakin tavalla.

Kääntäjä ja tulkki

Tietokone ei siis osaa suorittaa mitään muita kuin konekielellä ilmaistuja algoritmeja. Siksi tarvitaan kääntäjä tai tulkki, joka muokkaa lähdekielisen ohjelman tavalla tai toisella konekielioperaatoiden jonoksi.

---> Lisätietoa kääntäjistä ja tulkeista

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: NetBeans-aloittelijan ohje

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

Algoritmisessa ohjelmoinnissa muuttuja on keskeinen käsite. Muuttuja on nimetty "laatikko", jossa säilytetään jotakin arvoa. Ja kuten nimi "muuttuja" antaa ymmärtää, tuota arvoa voi muuttaa. Algoritmin tila tarkoittaa algoritmin muuttujien arvoja "tällä hetkellä".

Tietokoneessa kaikki tiedot, muuttujien arvot, esitetään bittijonoina ja koneen kannalta bittijono on vain bittijono. Mutta ohjelmoijan kannalta jotkin bittijonot esittävät kokonaislukuja, jotkin desimaalilukuja, jotkin merkkejä, jotkin totuusarvoja, jotkin merkkijonoja, jotkin vaikkapa kokonaislukumatriiseja, jne... Ohjelmoijan kannalta siis muuttujien arvoilla ja samalla itse muuttujilla on jokin tyyppi (type). Vanhanaikaisilla ohjelmointikielillä ohjelmoidessa tyypillinen virhe oli vahingossa käsitellä vaikkapa desimaaliluvun bittiesitystä kokonaisluvun tavoin, kokonaislukua totuusarvon tavoin jne.

Tuollaisia virheitä oli vaikea välttää, mutta sitten keksittiin hyvä ajatus: Miksei panna tietokonetta tarkistamaan, että arvoja käytetään ohjelmoijan tarkoittamalla tavalla. Juuri tuollainen mekaaninen tarkistustyöhän sopii vallan erinomaisesti juuri koneelle.

Edellytys oli, että ohjelmoija ilmoittaa ennalta, mihin aikoo mitäkin muuttujaa käyttää. Kieliin otettiin mukaan muuttujien määrittelyt: esitellään ennalta, millaiseen tarkoitukseen kutakin muuttujaa käytetään. Sitten ohjelmointijärjestelmä (kääntäjä ja käännetty ohjelma) pyrkii tarkastamaan ja varmistamaan, ettei ohjelmoija yritä sijoittaa muuttujalle vääräntyyppistä arvoa. Tätä nimitetään vahvaksi tyypitykseksi.

Tavanomaisia tyyppejä ovat esimerkiksi merkkijono, kokonaisluku, "liukuluku" eli desimaaliluku ja totuusarvo.

Esimerkkejä muuttujien määrittelystä ja arvojen muuttamisesta

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

    String teksti = "kissantassu";  // String on merkkijonotyyppi
    int kokonaisluku = 1234;        // int on kokonaislukutyyppi
    double liukuluku = 3.14;        // double on liukulukutyyppi
    boolean onkoTotta = true;       // boolean on totuusarvotyyppi

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

    teksti = "ketunhäntä";
    kokonaisluku = -666;
    liukuluku = 1.2;
    onkoTotta = false;

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

Ohjelma tulostaa komentotulkin ikkunaan:

kissantassu
1234
3.14
true
ketunhäntä
-666
1.2
false

Kuten esimerkissä nähdään, muuttujan arvoa siis voi muuttaa. Mikä on algoritmin tila ennen ensimmäisiä tulostuksia? Entä ennen toisia?

Tunnukset eli muuttujien yms. nimet

Ohjelmia laadittaessa monenlaisille itse määritellyille asioille annetaan itse päätettäviä nimiä eli tunnuksia (identifier). Esimerkiksi juuri muuttujien nimet ovat näitä tunnuksia. Javassa tunnukset alkavat aina kirjaimella ja voivat sisältää kirjaimia ja numeromerkkejä. (Myös alaviiva "_ lasketaan "kirjaimeksi".)

Vaikka skandinaaviset "ääkkösemmekin" kelpaavat tunnuksissa käytettäviksi, niitä on paras välttää, koska maailmalla on käytössä pari hyvin yleistä toisistaan poikkeavaa tapaa koodata nuo rakkaat kirjaimemme, ks. Wikipedia-artikkeli merkkikoodauksesta.

Nimessä ei siis saa olla erikoismerkkejä, kuten vaikkapa huutomerkkejä (!). Välilyönti ei ole sallittu, sillä se erottaa kielen rakenteita toisistaan. Välilyönti kannattaa korvata alaviivalla tai camelCase tyylillä, jolloin nimi muistuttaaKamelia.

Muuttujan nimi ei myöskään saa olla jo entuudestaan käytössä. Tälläisiä nimiä ovat mm. ns. varatut sanat, joilla ilmaistaan kielen rakenteita.

Muuttuja kannattaa nimetä siten, että sen käyttötarkoitus on selvää ilman selityksiä tai miettimistä. Itse asiassa tällä kurssilla muuttujat pitää nimetä kuvaavasti!

Kunnollisia muuttujien nimiä:

Huonompia muuttujien nimiä:

Virheellisiä muuttujien nimiä:

Kurssilla suositaan (Javan tapaan) Camel Case -nimiä: kuukaudenEnsimmainenPaiva, jne... Ja säästetään isolla kirjaimella alkavat tunnukset Javan luokkanimille.

Selitteet eli kommentit

Ohjelmatekstiin on mahdollista, hyödyllistä ja välttämätöntä liittää selittäviä tekstejä eli kommentteja. Miksi?

Javassa on käytössä kaksi kommentointitapaa, monirivinen kommentti ja loppurivikommentti:

/* Tämä ohjelma esittelee muuttujan määrittelyä,
   arvon tulostamista ja arvon muuttamista.      */

int kokonaisluku = 1234;  // määritellään ja annetaan alkuarvo

System.out.println(kokonaisluku);    // tulostetaan

/*
   ja sitten muutetaan muuttujan arvoa: 
*/
kokonaisluku = -666;

System.out.println(kokonaisluku);   // muuttuiko?

Tulostaminen

Tulostukseen on käytettävissä jo nähdyn println-operaation lisäksi operaatio print. Ensin mainittu vaihtaa riviä tulostettuaan parametrinsa, jälkimmäinen ei vaihda.

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.
  }
}

Ohjelman tulostus:

kissakävelee
kissa
kävelee
Muuttujan i arvo on 4321.

Merkkijonoista ja niiden katenoinnista

Tuttu yhteenlaskuoperaatio + sovellettuna kahdelle merkkijonolle synnyttää uuden merkkijonon, jossa "yhteenlaskettavat" merkkijonot liitetään toisiinsa. Hienolla nimellä tätä kutsutaan merkkijonojen katenoinniksi.

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

Eipä tästä vielä paljon iloa ole, mutta kun kuulee ilosanoman, että merkkijonoja ja lukuarvoja voi katenoida, alkaa tulostusoperaatoiden kirjoittaminen helpottua:

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ää.");
  }
}

Ohjelman tulostus:

Muuttujan i arvo on 4321.
Yhtä suurempi arvo on 4322.
Yhtä suurempi arvo on 43211.
ahaa 43217
ahaa 4328
4328 ahaa
Merkkijonon sisäänkin saa lainausmerkin: ". Mukavaa.
Sarkain eli tabulointikin onnistuu: 	 Kas näin.
Rivinvaihtojakin voi kirjoittaa: 
 Tässä yksi. 

Ja pari lisää.

Huomaa siis Javan knoppologia:

int i=68;
int j=2;

System.out.println("arvo on " + i + j + 3);   // arvo on 6823
System.out.println(i + j + 3 + " on arvo");   // 73 on arvo

System.out.println("arvo on " + (i + j + 3)); // arvo on 73
System.out.println(i + j + (3 + " on arvo")); // 703 on arvo

Merkkijonokatenointi on toki käytettävissä myös merkkijonomuuttujien käsittelyssä:

String kissa = "topi";
String hanta = "katti";
String koko  = kissa + hanta;
System.out.println(koko);  // tulostaa topikatti

Kokonaisluvut

Ohjelmoinnissa – ja tietojenkäsittelytieteessä yleensäkin – laskennaksi kutsutaan melkeinpä mitä tahansa toimintaa, jossa arvoista tavalla tai toisella muokataan uusia arvoja. Nyt tarkastellaan numeerista laskentaa eli aritmetiikkaa.

Aritmeettiset operaatiot ovat tuttuja: +, -, * ja /. Erikoisempi operaatio on %, joka on jäännösjako, jakolaskun jakojäännös eli modulo. Myös laskentajärjestys on tuttu: kertolaskut ennen yhteenlaskuja, suluissa olevat ennen muita, ...

int eka = 2;
int toka = 4;
int kolm = 6;
int arvo = eka + toka * kolm;
System.out.println(arvo);  // tulostaa 26
int sulut = (1+1) + 3 * (2+5)   // 23
int suluitta = 1+1 + 3 * 2+5    // 13

Laskutoimituksia kirjoitetaan usein myös tulostuslauseeseen:

System.out.println((1+1) + 3 * (2+5)); // tulostaa 23

System.out.println(5 / 2)  // tulostaa 2
System.out.println(19 / 5) // tulostaa 3

System.out.println(5 % 2)  // tulostaa 1
System.out.println(19 % 5) // tulostaa 4

Liukuluvut

Desimaalilukujen (tai reaalilukujen) esittämiseen tietokoneissa käytetään ns. liukulukuja (floating point number). Yksinkertaistaen sanottuna ne esittävät reaaliluvun likiarvon kahtena kokonaislukuna: "merkitsevät numerot" ja "pisteen paikka". Tärkeää on muistaa, että tietokoneen liukuluku on aina likiarvo, ei "tarkka arvo". Asian voisi selittää vaikkapa sillä, että "merkiteseviä numeroita" voi tietokoneessa olla vain äärellinen määrä.

Javan liukuluvun tyyppi on double:

int x = 0;       // x on kokonaislukutyyppinen
double y = 0.0;  // y on liukuluku
System.out.println(x + "  " + y) // tulostaa 0  0.0

x = 123;
y = 1.23;
println(x + "  " + y) // tulostaa 123  1.23

// x = y; // VIRHE: double-arvoa ei voi sijoittaa int-muuttujaan
// mutta
y = x;
System.out.println(x + "  " + y) // tulostaa 123  123.0

Jakolaskuoperaation "/" merkitys – onko kyse kokonaisjakolaskusta vai liukulukujakolaskusta – määräytyy siten, että jos jaettava, jakaja tai molemmat ovat liukulukuja, kyseessä on liukulukujakolasku. Kokonaisjako on kyseessä vain, jos sekä jakaja että jaettava ovat kokonaislukuja:

System.out.println(5 / 2)     // 2
System.out.println(5.0 / 2)   // 2.5
System.out.println(5 / 2.0)   // 2.5
System.out.println(5.0 / 2.0) // 2.5

Joskus ohjelmoija haluaa kahden kokonaisluvun jakolaskun tulokseksi liukuluvun. Yksinkertainen tapa on ennen jakolaskua muuttaa jaettava liukuluvuksi kertomalla se luvulla 1.0:

int summa = 55;
int lukumaara = 20;

System.out.println("keskiarvo on " + summa/lukumaara);        // 2
System.out.println("keskiarvo on " + 1.0*summa/lukumaara);    // 2.75

// mutta
System.out.println("keskiarvo on " + summa/lukumaara*1.0);    // 2.0
// tosin toki
System.out.println("keskiarvo on " + summa/(lukumaara*1.0));  // 2.0

Ohjelman tulostus:

keskiarvo on 2
keskiarvo on 2.75
keskiarvo on 2.0
keskiarvo on 2.75

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

Erikoistettuja sijoitusoperaatioita

Usein muuttujan uusi arvo lasketaan sen vanhasta vanhasta arvosta:

int pituus = 98;
pituus = pituus + 2;
pituus = pituus - 50;
pituus = pituus * 2;
pituus = pituus / 2;
System.out.println(pituus); // 50

Tällaisessa melko tavanomaisessa tilanteessa voi käyttää erityisiä sijoitusoperaatioita +=, -=, *=, /=. Näiden avulla yllä näkyvät sijoitukset voi ohjelmoida hieman lyhyemmin:

pituus += 2;
pituus -= 50;
pituus *= 2;
pituus /= 2;
System.out.println(pituus); // 50

Useimmiten on kuitenkin selkeämpää kirjoittaa sijoitusoperaatiot tavanomaisessa muodossa!

Kokonaislukumuuttujan kasvattaminen tai vähentäminen yhdellä on algoritmeissa niin tuiki tavanomaista, että tähän tarkoitukseen Javassa ja sen edeltäjissä on käytössä erityiset ilmaukset i++, ++i, i-- ja --i:

int i = 1;

i++;  // seuraus sama kuin sijoituksella i = i + 1;
++i;  // seuraus sama kuin sijoituksella i = i + 1;
i--;  // seuraus sama kuin sijoituksella i = i - 1;
--i;  // seuraus sama kuin sijoituksella i = i - 1;

Näitä käytetään usein.

Keskusteleva ohjelma: ponnahdusikkunoita

Nykyään useimmat tavalliselle tietokoneen käyttäjälle tarkoitetut ohjelmat perustuvat graafiseen käyttöliittymään (GUI) ja tapahtumaohjattuun ohjelmointiin. Tällaisen käyttöliittymän ohjelmoinnin ideaan tutustutaan jatkokurssilla.

Hyvin perinteinen ja "vanhanaikainen" käyttäjävuorovaikutus perustuu siihen, että sekä ohjelman tulosteet ja käyttäjän antamat syötteet toteutetaan käyttöjärjestelmän komentotulkissa, sellaisessa ikkunassa, jossa mm. kurssin alun ohjelmaesimerkki käännettiin ja suoritettiin. Myös tutuksi tullut System.out.println() tulostaa juuri tuohon ikkunaan. Javassa on toki välineitä myös lukea syötteet tästä komentotulkki-ikkunasta. Näiden käytössä on omat pikku kömpelyytensä, mutta tällaiseen "perinneohjelmointiinkin" tutustutaan kurssilla aikanaan.

Mutta aluksi käytämme ohjelman käyttäjän kanssa keskusteluun ponnahdusikkunoita (pop up windows). Olen toteuttanut Javan kirjastoluokkana joukon helppokäyttöisiä operaatioita ikkunoiden ponnahdutteluun: Pop.java. Tällainen välinekirjasto on siis eräänlainen työkalupakki.

Saadaksesi ponnahdusikkunatyökalut käyttöösi riittää sijoittaa tuo lähdekielinen Java-luokka samaan hakemistoon kuin luokka, jossa välineitä haluat käyttää. Kääntäjä osaa kääntääkin tuon työkaluluokan tarvittaessa automaattisesti. Muitakin – tyylikkäämpiä? – tapoja ottaa nämä välineet käyttöön on toki olemassa. NetBeansin käyttäjät saavat harjoitusten ohjaajilta neuvot, miten asia tuossa ympäristössä hoidellaan.

Seuraavassa luetellaan ensin lyhyesti "poppikoneen" osaamat operaatiot. Myöhemmin opetellaan operaatioiden monipuolisempaa käyttöä. (Operaatioilla on myös englantilaiset nimet, jos jotakuta suomi ei miellytä.)

ilmoita(viesti) [tell(message)]

Ohjelman käyttäjälle voi kirjoittaa erilaisia ilmoituksia operaatiolla ilmoita(viesti). Komennolle annetaan ns. parametrina (eräänlaisena lähtötietona) haluttu ilmoitus merkkijonona eli String-arvona aivan samaan tapaan kuin jo on opittu kirjoittamaan komentotulkin ikkunaan System.out.println-operaatiolla.

Pop.ilmoita("Ilmoitus on tällainen." + "\n" +
            "Se on kuitattava ok-nappulalla.");

Lauseen suoritus ponnauttaa näkyville ikkunan:

kuva

Rivejä voi olla enemmänkin:

Pop.ilmoita("Ilmoituksessa voi olla useitakin rivejä" + "\n" +
            "Rivinvaidot pitää itse ilmaista \\n-merkillä."  + "\n" +
            "Pitkiäkin rivejä voi kirjoittaa. " +
            "Pitkiäkin rivejä voi kirjoittaa. " +
            "Pitkiäkin rivejä voi kirjoittaa. " + "\n" +
            "Tuohon tyyliin.."
           );

kuva

kysyString(kysymys) [askString(question)]

Merkkijonoa pyydetään operaatiolla kysyString(kysymys). Esimerkiksi:

String vastaus = Pop.kysyString("Anna jokin merkkijono!");
Pop.ilmoita("Se oli: " + vastaus);

kuva

Kun käyttäjä on kirjoittanut merkkijonon, hän voi painaa OK-nappulaa tai näppäimistön enter-näppäintä, jolloin ohjelma saa annetun merkkijonon käyttöönsä. (Cancel-nappulasta myöhemmin.)

kuva

kysyInt(kysymys) [askInt(question)]

Kokonaislukujen lukeminen:

int luku = Pop.kysyInt("Anna kokonaisluku.");
Pop.ilmoita("Luku oli " + luku);

kuva kuva

kysyDouble(kysymys) [askDouble(question)]

Liukulukujen lukeminen:

double liuku = Pop.kysyDouble("Anna desimaaliluku.");
Pop.ilmoita("Luku oli " + liuku);

kuva kuva

Laaditaan valaisevaksi esimerkiksi pieni tekoälyohjelma:

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

    String nimi;
    int    ika;
    double pituus;

    nimi   = Pop.kysyString("Mikä on nimesi?");
    ika    = Pop.kysyInt("Ja ikäsi: ");
    pituus = Pop.kysyDouble("Entä pituutesi? ");

    Pop.ilmoita("Moi " + nimi + "!\n" +
                "Tiedän että olet " + ika + "-vuotias \n" +
                "ja että olet " + pituus + " senttiä pitkä.\n" +
                "Enkö olekin viisas!");
  }
}

Käyttöesimerkki:

kuva kuva kuva kuva

valitseNappula(kysymys, valinnat) [selectButton(question, selections)]

Vaihtoehtoja voi kysellä operaatiolla valitseNappula(kysymys, valinnat). Ensimmäinen parametri on kysymys. Seuraavat parametrit määräävät ponnahdusikkunaan tulevat nappulat. Näitä on yksi tai enemmän.

Tuttu "kyllä vai ei" -kysymys voidaan ohjelmoida näin:

int vast = Pop.valitseNappula("Kyllä vai ei?", "kyllä", "ei");
Pop.ilmoita("Vastauksen indeksi on " + vast);

Operaatio palauttaa nappulan ns. indeksin, tiedon siitä, monettako nappulaa painettiin. Huomaa että nappulat on numeroitu nollasta alkaen.

kuva kuva

Esimerkki kolmen nappulan käytöstä:

int lemmikki = Pop.valitseNappula("Mikä on lemmikkisi?",
                                  "kissa", "hiiri", "koira");
Pop.ilmoita("Valintasi indeksi on " + lemmikki);

Valitaan koira:

kuva kuva

Valintavaihtoehtoja voi olla hyvinkin paljon:

int maa = Pop.valitseNappula("Mikä maa?",
   "Suomi", "Ruotsi", "Venäjä", "Norja", "Tanska", "Islanti",
   "Saksa", "Ranska", "Hollanti", "Belgia", "Puola", "Itävalta",
   "Sveitsi", "Italia", "Espanja");
Pop.ilmoita("Valintasi indeksi on " + maa);

Valitaan vaikkapa Islanti:

kuva kuva

Tällaisessa tapauksessa on kuitenkin ehkä järkevämpää käyttää seuraavaksi esiteltävää ponnahdusikkunan vaihtoehtoa.

valitseString(kysymys, valinnat) [selectString(question, selections)]

Vaihtoehtoja voi kysellä myös alasvetovalikon tarjoavalla ponnahdusikkunalla. Operaatio on valitseString(kysymys, valinnat). Kuten nappulavaihtoehdossa myös tässä ensimmäinen parametri on kysymys ja seuraavat parametrit määräävät valintavaihtoehdot.

String lempi = Pop.valitseString("Mikä on lemmikkisi?",
                                 "kissa", "hiiri", "koira");
Pop.ilmoita("Valintasi on " + lempi);

Operaatio palauttaa arvonaan nappulavaihtoehdosta poiketen valitun vaihtoehdon merkkijonona eli String-arvona.

Valitaan taas koira:

kuva kuva

Kun vaihtoehtoja on paljon, alasvetovalikko on luonnollinen ratkaisu:

String v = Pop.valitseString("Mikä maa?",
   "Suomi", "Ruotsi", "Venäjä", "Norja", "Tanska", "Islanti",
   "Saksa", "Ranska", "Hollanti", "Belgia", "Puola", "Itävalta",
   "Sveitsi", "Italia", "Espanja");
Pop.ilmoita("Valintasi on " + v);

kuva kuva

Valintalause if

Tähän mennessä nähdyt ohjelmat ovat olleet vähän tylsiä: ne on suoritettu aina samalla tavoin ensimmäisestä lauseesta viimeiseen. Valintalauseen avulla algoritmin toiminta saadaan toimimaan vaihtoehtoisilla tavoilla riippuen vaikkapa syötteiden arvosta.

Useimmissa ohjelmointikielissä valintalause on muodoltaan if-lause:

int luku = Pop.kysyInt("Anna kokonaisluku!");
if (luku > 0)
  Pop.ilmoita("Luku on positiivinen.");

If-lauseen ehto (luku > 0) on väittämä algoritmin tilasta. Kun algoritmi etenee tuon ehdon laskentaan, tutkitaan juuri sillä hetkellä, sattuuko muuttujan luku arvo olemaan positiivinen. Jos sattuu, suoritetaan if-lauseen alilause Pop.ilmoita("Luku on positiivinen."). Tällöin ehdon totuusarvo on true. Jos ehto ei ole tosi eli jos väittämä ei pidä paikkaansa (false), alilausetta ei suoriteta.

If-lauseeseen voi liittää myös else-osan, jos haluaa jotakin tehtäväksi myös tilanteessa, jossa ehto on epätosi:

int luku = Pop.kysyInt("Anna kokonaisluku!");
if (luku > 0)
  Pop.ilmoita("Luku on positiivinen.");
else
  Pop.ilmoita("Luku ei ole positiivinen.");

Usein if-lauseita ketjutetaan:

int luku = Pop.kysyInt("Anna kokonaisluku!");
if (luku > 0)
  Pop.ilmoita("Luku on positiivinen.");
else if (luku < 0)
  Pop.ilmoita("Luku on negatiivinen.");
else
  Pop.ilmoita("Ahaa, luku on nolla."); // ei ollut positiivinen eikä negatiivinen...

Loogisia lausekkeita: vertailuoperaatiot

Loogisilla lausekkeilla esitetään väitteitä ohjelman tilasta eli ohjelman muuttujien arvoista. Väitteiden totuusarvoa - totta tai ei totta - käytetään ohjaamaan algoritmin suoritusta. Loogisia lausekkeita (eli totuusarvoisia lausekkeita) muodostetaan tavallisimmin erilaisista vertailuista, joita mahdollisesti liitetään toisiinsa loogisilla operaatioilla.

Tavallisimpia vertailuoperaatioita:

Kaikki vertailut tuottavat totuusarvon true tai false.

On syytä pitää mielessa sijoitusoperaation "=" ja vertailuoperaation "==" ero:

i = 1;  // "i saa arvokseen yksi"
i == 1  // "onko i:n arvo yksi"

Suuremmuutta tai pienemmyyttä tutkivilla operaatioilla voi vertailla vain numeerisia lausekkeita, yhtäsuuruus ja erisuuruus ovat käytettävissä monien muidenkin arvojen vertailussa.

Esimerkki: Olkoon ohjelmassa vaikkapa seuraavat määrittelyt:

int i = 7;
int d = 3.14;
boolean b = false;

Tässä tilanteessa looginen lauseke

(i < 10)     on arvoltaan true
(d > 10.0)   on arvoltaan false
(8 == (i+1)) on arvoltaan true
(b != true)  on arvoltaan true  // boolean muuttujan arvo voi olla vain true tai false

Huom: Liukulukujen (so. desimaalilukujen) vertailussa ei ole syytä käyttää yhtäsuuruusvertailuja (== ja !=), koska ns. pyöristysvirheiden takia yhtäsuuruusvertailut voivat antaa odottamattomia tuloksia. Tällaisessa tilanteessa on parempi tutkia lukujen erotuksen itseisarvon pienuutta. Sen voi tehdä vaikkapa seuraavaan tyyliin:

double a, b;
// .. muuttujat saavat arvot, ei tiedetä kumpi on suurempi
if (Math.abs(a-b) < 0.001)  // Math.abs laskee itseisarvon
  // tilanne että arvot ovat riittävän samat
else
  // tilanne että arvot eivät riittävän samat

Huom: String-arvojen eli merkkijonojen vertailu ei Javalla onnistu yhtä vaivattomasti kuin numeeristen arvojen vertailu. Yritetään:

String s = "hupsista";
...
if (s != "hupsista")  ... sallittu, mutta voi olla true tai false
if (s == "hupsista")  ... sallittu, mutta voi olla true tai false

Näin saa kyllä kirjoittaa – se on "oikein" – mutta tarkoittaa luultavasti jotain muuta kuin ohjelmoija tahtoi tarkoittaa: 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 luultavasi siis tarkoittaa jotakin muuta kuin ohjelmoija tahtoi tarkoittaa.

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

if (s.equals("hupsista")) ... on arvoltaan true  // NÄIN VERTAILLAAN
if (s.equals("muumi")) ...    on arvoltaan false // MERKKIJONOJEN SAMUUTTA!

Olioista ja niiden vertailuista lisää myöhemmin. Toistaiseksi riittää vain muistaa operaatio equals.

Loogisia lausekkeita: loogiset operaatiot

Usein on tarpeen liittää algoritmin tilaa koskevia väittämiä yhteen monimutkaisemmiksi väittämiksi. Käytettävissä ovat mm. seuraavat loogiset operaatiot:

Näiden operaatioiden operandit eli laskettavat voivat olla vain totuusarvoisia lausekkeita, tavallisimmin ne ovat vertailuja. Operaatioiden ehdollisuus tarkoittaa sitä, että operaation jälkimmäinen laskettava lasketaan vain, jos totuusarvo ei selviä jo ensimmäisestä!

int luku = Pop.kysyInt("Anna luku väliltä 5-10.");

if ( 5 <= luku && luku <= 10 )  // HUOM: ILMAUS (5 <= luku <= 10) EI OLE LUVALLINEN!
  Pop.ilmoita("Oikein!");
else
  Pop.ilmoita("Luku ei kelpaa.");
int vastaus = Pop.kysyInt("Valitse joko 1, 50 tai 100.");

if ( vastaus == 1 || vastaus == 50 || vastaus == 100 )
  Pop.ilmoita("Valitsit ohjeen mukaisesti.");
else
  Pop.ilmoita("Valitsit väärin.");

Loogisien operaatioiden totuustaulukko (a ja b voivat itse olla mitä tahansa loogisia lausekkeita, esimerkiksi vertailuja):

 a    |   b   ||  a && b  |  a || b  |   !a 
--------------------------------------------------------
true  | true  ||  true    |  true    |  false
true  | false ||  false   |  true    |  false
false | true  ||  false   |  true    |  true
false | false ||  false   |  false   |  true

Javassa on myös "jompikumpi mutta eivät molemmat" -operaatio, ns. "xor" eli "poissulkeva tai". Sen merkki on "^". Tuo merkki ei siis Javassa tarkoita potenssiin korottamista.

Lohko

Hyvin usein if-lauseen alilauseeksi if-osaan tai else-osaan halutaan useampi kuin yksi lause. Tällöin nuo lauseet kootaan yhdeksi lauseeksi, lohkoksi, aaltosulkeiden "{" ja "}" väliin:

int luku = Pop.kysyInt("Anna kokonaisluku!");

if (luku > 0) {
  Pop.ilmoita("Luku on positiivinen.");
  Pop.ilmoita("Kaksinkertaisena se on " + 2*luku);
}
else {
  Pop.ilmoita("Luku ei ole positiivinen.");
  Pop.ilmoita("Mutta itsellään kerrottuna se ei ainakaan ole negatiivinen: " + luku*luku);
}

Huomaa että nuo aaltosulkeet ovat välttämättömät! Pelkkä sisentäminen ei riitä. Seuraava on siis väärin:

int luku = Pop.kysyInt("Anna kokonaisluku!");

if (luku > 0)                           // TÄMÄ ON VIRHEELLINEN!
  Pop.ilmoita("Luku on positiivinen."); // AALTOSULUT PUUTTUVAT!
  Pop.ilmoita("Kaksinkertaisena se on " + 2*luku);
else 
  Pop.ilmoita("Luku ei ole positiivinen.");
  Pop.ilmoita("Mutta itsellään kerrottuna se ei ainakaan ole negatiivinen: " + luku*luku);

Jotkut suosivat seuraava ulkoasua:

int luku = Pop.kysyInt("Anna kokonaisluku!");

if (luku > 0) 
{
  Pop.ilmoita("Luku on positiivinen.");
  Pop.ilmoita("Kaksinkertaisena se on " + 2*luku);
}
else 
{
  Pop.ilmoita("Luku ei ole positiivinen.");
  Pop.ilmoita("Mutta itsellään kerrottuna se ei ainakaan ole negatiivinen: " + luku*luku);
}

Myös seuraavaa else-layout on mahdollinen:

if (luku > 0) {
  ...
} else {
  ...
}

Useimmissa ohjelmointikielissä ohjelman ulkoasulla ei ole kääntäjälle viestiä. Niinpä kääntäjälle kelpaa jopa seuraava sotku — sinänsä täysin toimiva ohjelma:

int luku = Pop.kysyInt("Anna kokonaisluku!"); if (luku > 
0) {Pop.ilmoita("Luku on positiivinen."); Pop.ilmoita("Kaksinkertaisena se on " + 
2*luku); } else {Pop.ilmoita("Luku ei ole positiivinen.");
Pop.ilmoita("Mutta itsellään kerrottuna se ei ainakaan ole negatiivinen: "
+ luku*luku); }

Mutta ohjelmoijalle ohjelman selkeä ja johdonmukainen ulkoasu on välttämätön! Miksi?

Alkuehtoinen toisto – while-lause

Samaan tapaan kuin väittämät algorimin tilasta ohjaavat valintaa if-lauseissa, väittämiä käytetään myös ohjaamaan algoritmin jonkin osan toistamista.

Ehdollinen toistolause, while-lause, toistaa alilausettaan yhä uudelleen ja uudelleen kunnes toistoehto ei enää ole totta. Toki on mahdollista, että ehto alkujaankin on epätosi, jolloin toistoja ei ole ainuttakaan:

int lukumaara = Pop.kysyInt("Moneenko kertaan haluat onnentoivotuksen?");

while (lukumaara > 0) {
  Pop.ilmoita("Onnea!");
  lukumaara = lukumaara - 1;
}

Toistolauseen alussa tutkitaan, onko väittämä (lukumaara > 0) totta. Jos se on, koko toistettava alialgoritmi suoritetaan. Sen jälkeen taas tutkitaan, onko ehto voimassa, jne... Huomaa että toistettavan alialgoritmin on syytä vaikuttaa jotenkin toistoehdon arvoon! Miksi?

Toinen esimerkki: Vaaditaan että käyttäjä syöttää parittoman luvun. Luku on pariton, jos se ei ole parillinen. Luku on parillinen, jos jakojäännös kahdella jaettaessa on nolla (vaihteeksi kokonainen ohjelma):

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

    int luku = Pop.kysyInt("Anna pariton luku.");

    while (luku % 2 == 0)
      luku = Pop.kysyInt("Eihän luku " + luku + " ole pariton!\n" +
                         "Yritä uudelleen");
    Pop.ilmoita("Kyllä! Luku " + luku + " todellakin on pariton.");
  }
}

Mikään ei estä käyttämästä tulostukseen myös System.out.println-operaatiota. Ehkäpä onnentoivotusohjelma tällaisena olisi luontevampi?

int lukumaara = Pop.kysyInt("Moneenko kertaan haluat onnentoivotuksen?");

while (lukumaara > 0) {
  System.out.println("Onnea!");
  lukumaara = lukumaara - 1;
}

Kun ohjelmalle syöttää vaikkapa luvun neljä, ohjelman käynnistysikkunaan tulostuu seuraavaa:

Onnea!
Onnea!
Onnea!
Onnea!

Laaditaan vielä hyötyohjelma, jolla voi tupalata (kertoa kahdella) minkä tahansa luvun paitsi nollan. Nolla toimii ns. lopetusmerkkinä, jolla ohjelmalle ilmaistaan halu lopettaa lukujen tuplaaminen:

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

    int luku = Pop.kysyInt("Lukujen tuplausohjelma:\n" +
                           "Anna luku! Nolla lopettaa.");
    while (luku != 0) {
      Pop.ilmoita("Luku " + luku + " tuplana on " + luku*2);
      luku = Pop.kysyInt("Anna luku! Nolla lopettaa.");
     }
    Pop.ilmoita("Moi!");
  }
}

Loppuehtoinen toisto – do-while-lause

Alkuehtoinen toisto tarkoittaa siis sitä, että toistamista vahtiva ehto lasketaan aina ennen jokaista toistokertaa. Usein on luontevaa toimia toisin: suorittaa aina yksi toisto ja sen jälkeen tutkia, tarvitaanko uusi toisto. Tähän tarkoituksiin useimmissa ohjelmointkielissä on käytössä ns. loppuehtoinen toisto. Javassa se on nimeltään do-while-lause.

Loppuehtoinen toisto on erityisen luonteva syöttötietojen tarkastamisessa. Edellä nähty ohjelma, joka vaati paritonta syöttölukua sujuu loppuehtoisella toistolla seuraavaan tapaan:

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

    int luku;
    do {
      luku = Pop.kysyInt("Anna pariton luku.");
      if (luku % 2 == 0)
        luku = Pop.kysyInt("Eihän luku " + luku + " ole pariton!\n" +
                           "Yritä uudelleen");
    } while (luku % 2 == 0);

    Pop.ilmoita("Kyllä! Luku " + luku + " todellakin on pariton.");
  }
}

Tässä ohjelmassa moni ohjelmoija näkee pienen vian: ehto (luku % 2 == 0) lasketaan joka toistokerralla kahteen kertaan. Tietokonehan todella tekee töitä myös ehtoja laskiessaan.

Tässä esimerkissä onkin oivallinen tilaisuus käyttää totuusarvoista boolean-muuttujaa:

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

    int luku;
    boolean parillinen;
    do {
      luku = Pop.kysyInt("Anna pariton luku.");
      parillinen = (luku % 2 == 0);
      if (parillinen)
        luku = Pop.kysyInt("Eihän luku " + luku + " ole pariton!\n" +
                           "Yritä uudelleen");
    } while (parillinen);

    Pop.ilmoita("Kyllä! Luku " + luku + " todellakin on pariton.");
  }
}

Ohjelmointitekniikkaa: 1.5-kertainen toisto

Alkuehtoinen ja loppuehtoinen toisto löytyvät miltei kaikista ohjelmointikielistä. Ne molemmat perustuvat toistettavan alialgoritmin suorittamiseen aina kokonaan.

Melko usein algoritmeissa kuitenkin esiintyy tilanne, jossa toistettava alialgoritmi on luontevinta lopettaa "puolessa välissä" eli viimeistä toistokertaa ei suoritekaan loppuun asti vaan keskeytetään heti, kun havaitaan toiston tarve päättyneeksi. Tällaista toistorakennetta kutsutaan joskus "1.5-kertaiseksi toistoksi."

Harvoissa ohjelmointikielissä on erityistä omaa lausetyyppiään tähän tarkoitukseen, mutta esimerkiksi Javassa vastaava toiminnallisuus voidaan ohjelmoida käyttäen näennäisesti "ikuista" toistoa while (true) ja erityistä keskeytyslausetta break.

Tarkastellaan esimerkkinä edellä nähtyjä parittomuustarkistuksia alku- ja loppuehtoisella toistolla ohjelmoituna.

Alkuehtoinen toisto:

    int luku = Pop.kysyInt("Anna pariton luku.");
    while (luku % 2 == 0)
      luku = Pop.kysyInt("Eihän luku " + luku + " ole pariton!\n" +
                         "Yritä uudelleen");
    Pop.ilmoita("Kyllä! Luku " + luku + " todellakin on pariton.");

Loppuehtoinen toisto:

    int luku;
    do {
      luku = Pop.kysyInt("Anna pariton luku.");
      if (luku % 2 == 0)
        luku = Pop.kysyInt("Eihän luku " + luku + " ole pariton!\n" +
                           "Yritä uudelleen");
    } while (luku % 2 == 0);
    Pop.ilmoita("Kyllä! Luku " + luku + " todellakin on pariton.");

"Puolitoistakertaisella" toistolla sama voidaan ohjelmoida tähän tyyliin:

    int luku;
    while (true) {    ///// toisto keskeytetään break-lauseella
      luku = Pop.kysyInt("Anna pariton luku.");
      if (luku % 2 != 0) break;  /////////// toiston keskeytys /////////////
      Pop.ilmoita("Eihän luku " + luku + " ole pariton!\n" +
                  "Yritä uudelleen");
    }
    Pop.ilmoita("Kyllä! Luku " + luku + " todellakin on pariton.");

Ohjelman toiminnan hahmottamisen helpottamiseksi on syytä kommentein kororostaa keskytyskohta!

Ponnahdusikkunoiden paluuarvojen tutkiminen

Nyt kun ehtojen avulla on opittu ohjaamaan algoritmin toimintaa, on aika alkaa tutkia ponnahdusikkunoista saatavia syötteitä tarkemmin.

Edellä nähdyt ponnahdusikkunaoperaatiot ovat ns. metodeita, nimettyjä algoritmeja. Tällaisia opitaan pian tekemään itsekin, mutta jo nyt on syytä tarkastella näiden metodeiden ns. otsakkeita, joista näkee, millä tavoin metodeita kutsutaan eli miten ne käynnistetään.

public void ilmoita(...);

public String kysyString(String kysymys);

public int kysyInt(String kysymys);
public double kysyDouble(String kysymys);

public int valitseNappula(String kysymys, String... valinnat);
public String valitseString(String kysymys, String... valinnat);

Metodin ns. paluuarvon tyyppi näkyy ennen metodin nimeä: String, int, double. Sana void tarkoittaa, että metodilla ei ole paluuarvoa, se on ikäänkuin pelkkä käsky tai komento tehdä jotakin; "näytä ilmoitus ponnahdusikkunassa", tms. Niinpä metodista ilmoita ei tässä yhteydessä sen enempää.

kysyString

Koska metodi kysyString palauttaa String-arvon eli merkkijonon, metodin palauttamaa arvoa voidaan tutkia seuraavaan tapaan:

String vastaus = Pop.kysyString("Mikä oli Jaakobin poikien isän nimi?");
if (vastaus.equals("Jaakob"))
  Pop.ilmoita("Vastauksesi " + vastaus + " on oikein!");
else
  Pop.ilmoita("Vastauksesi " + vastaus + " on väärin!");

Käyttöesimerkki:

kuva kuva

Metodi kysyString sallii myös tyhjän merkkijonon syöttämisen. Painetaan siis OK tai enter kirjoittamatta mitään tekstiikenttään. Silloinkin ohjelma tulostaa:

kuva

Jos tyhjää merkkijonoa ei haluta kelpuuttaa vastaukseksi, asia voidaan hoidella seuraavaan tyyliin:

String vastaus = Pop.kysyString("Anna merkkijono");
if (vastaus.equals(""))
  // käsittele virhetilanne
else
  // merkkijono ei ollut tyhjä.

Vaihtoehtoisesti voidaan tutkia merkkijonon pituutta erityisellä length-metodilla:

String vastaus = Pop.kysyString("Anna merkkijono");
if (vastaus.length() == 0)
  // käsittele virhetilanne
else
  // merkkijono ei ollut tyhjä.

Jaakob-esimerkin tapauksessa voitaisiin ohjelmoida vaikkapa seuraavaan tapaan:

String vastaus = Pop.kysyString("Mikä oli Jaakobin poikien isän nimi?");
if (vastaus.equals(""))
  Pop.ilmoita("Tyhjästä on paha nyhjästä!");
else if (vastaus.equals("Jaakob"))
  Pop.ilmoita("Vastauksesi " + vastaus + " on oikein!");
else
  Pop.ilmoita("Vastauksesi " + vastaus + " on väärin!");

kuva

Kuten kaikissa kunnon ikkunoissa, myös kysyString-ikkunassa on yläpalkissa keskeytysrasti. Jos sitä tai Cancel-nappia painetaan, metodi ei palauta edes tyhjää String-arvoa vaan erikoisarvon null, joka tarkoittaa, ettei mitään varsinaista arvoa saatu. Koska null "ei ole mitään", siihen ei voi soveltaa esimerkiksi String-luokan metodia equals eikä mitään muutakaan metodia. Jos yrittää, huonosti käy: Jaakob-ohjelman käynnistysikkunaan ilmestyy jokin seuraavanlainen virheilmoitus ja ohjelman suoritus päättyy siihen paikkaan:

Exception in thread "main" java.lang.NullPointerException
	at koe.main(koe.java:8)

Ei ole kovin mukavaa, kun ohjelma ns. "kaatuu"! Jos kaatumisen tässä tilanteessa tahtoo estää, se käy vaikkapa tähän tapaan:

String vastaus = Pop.kysyString("Anna merkkijono");
if (vastaus == null)
  // käsittele tilanne, jossa ei saatu mitään merkkijonoa
else
  // saatiin String, jolle voi soveltaa kaikkia String-operaatioita

Jaakob-esimerkki voisi näyttää vaikka tältä:

String vastaus = Pop.kysyString("Mikä oli Jaakobin poikien isän nimi?");
if (vastaus == null)
  Pop.ilmoita("Keskeytys!"); // Riippuu tilanteesta, mitä sitten tehdään!
else if (vastaus.equals(""))
  Pop.ilmoita("Tyhjästä on paha nyhjästä!");
else if (vastaus.equals("Jaakob"))
  Pop.ilmoita("Vastauksesi " + vastaus + " on oikein!");
else
  Pop.ilmoita("Vastauksesi " + vastaus + " on väärin!");

Klikataan Cancel-nappulaa tai keskeytysrastia:

kuva

kysyInt ja kysyDouble

Numeerisia syötteitä kyselevät operaatiot kysyInt ja kysyDouble ovat sitkeitä; ne eivät anna periksi ennen kunnon luvun syöttämistä. Edes Cancel tai yläpalkin keskeytysrasti eivät lopeta niiden intoa saada oikeanlainen numeerinen syöte. Kokeile!

Kuten jo edellä nähtiin, if-lauseella on (tietenkin) paljon käyttöä lukujen vertailussa:

int luku = Pop.kysyInt("Anna kokonaisluku!");
if (luku > 0)
  Pop.ilmoita("Luku on positiivinen.");
else if (luku < 0)
  Pop.ilmoita("Luku on negatiivinen.");
else
  Pop.ilmoita("Ahaa, luku on nolla."); // ei ollut positiivinen eikä negatiivinen...

valitseNappula

Metodi valitseNappula on tyypiltään int. Idea on että ponnahdusikkunassa näkyvät nappulat on numeroitu nollasta alkaen vasemmalta oikealle. Metodi palauttaa arvonaan painetun nappulan järjestysnumeron eli indeksin.

Kuten edellä jo nähtiin valinnan "koira" indeksi oli 2:

kuva kuva

Indeksiä voi tietenkin tutkia ja ohjata ohjelman toimintaa sen mukaan:

int lemmikki = Pop.valitseNappula("Mikä on lemmikkisi?",
                                  "kissa", "hiiri", "koira");
if (lemmikki == 0)
  Pop.ilmoita("Miau");
else if (lemmikki == 2)
  Pop.ilmoita("Hau");      // hiiri on hiljaa

Kun nyt valitaan "koira", saadaan ilmoitus:

kuva

Jos käyttäjä keskeyttää ikkunan käytön keskeytysrastilla, metodi palauttaa arvonaan luvun -1. Sitäkin voi tietysti tutkia:

int lemmikki = Pop.valitseNappula("Mikä on lemmikkisi?",
                                  "kissa", "hiiri", "koira");
if (lemmikki == -1)
  Pop.ilmoita("Okei, okei, et siis valitse mitään!");
if (lemmikki == 0)
  Pop.ilmoita("Miau");
else if (lemmikki == 2)
  Pop.ilmoita("Hau");

Keskeytetään:

kuva

valitseString

Metodi valitseString puolestaan on tyyppiä String. Edellisestä metodista poiketen se palauttaa arvonaan valitun merkkijonon:

String maa = Pop.valitseString("Mikä maa on Suomen naapuri?",
   "Suomi", "Ruotsi", "Venäjä", "Norja", "Tanska", "Islanti",
   "Saksa", "Ranska", "Hollanti", "Belgia", "Puola", "Itävalta",
   "Sveitsi", "Italia", "Espanja");

if (maa.equals("Ruotsi") || maa.equals("Venäjä") || maa.equals("Norja") )
  Pop.ilmoita(maa + " on Suomen naapuri.");
else
  Pop.ilmoita(maa + " ei ole Suomen naapuri.");

Käyttöesimerkki:

Valitaan Ruotsi:

kuva kuva

Valitaan Sveitsi:

kuva kuva

Jos painetaan Cancel-nappulaa tai keskeytysrastia, operaatio valitseString palauttaa arvonaan jo aiemmin nähdyn erikoisarvon null, joka on siis ilmaus siitä, että mitään ei valittu. Kun tuolle arvolle yritetään soveltaa equals-metodia, ohjelman suoritus päättyy virheeseen:

Exception in thread "main" java.lang.NullPointerException
	at koe.main(koe.java:12)

Virhetilanteeseen joutuminen voidaan välttää vaikkapa seuraavaan tapaan:

String maa = Pop.valitseString("Mikä maa on Suomen naapuri?",
   "Suomi", "Ruotsi", "Venäjä", "Norja", "Tanska", "Islanti",
   "Saksa", "Ranska", "Hollanti", "Belgia", "Puola", "Itävalta",
   "Sveitsi", "Italia", "Espanja");

if (maa == null)  // Riippuu tilanteesta, mitä sitten ihan oikeasti tehdään!
  Pop.ilmoita("Ai ettei kiinnosta vastata vai!?");
else if (maa.equals("Ruotsi") || maa.equals("Venäjä") || maa.equals("Norja") )
  Pop.ilmoita(maa + " on Suomen naapuri.");
else
  Pop.ilmoita(maa + " ei ole Suomen naapuri.");

Askeltava toisto – for-lause

Useimmissa ohjelmointikielissä on erityinen lause, joka soveltuu erityisen hyvin ns. taulukon alkioiden läpikäymiseen. Taulukoista kohta lisää. Monissa kielissä tuo lause on nimeltään for-lause. Niin myös Javassa:

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

Selityksiä:

Kolme tyypillistä tapaa lukea vaihteleva määrä syötteitä

Vaihtelevaan määrään syöttötietoja voidaan varautua eri tavoin. Tarkastellaan esimerkkinä ohjelmaa, joka laskee syötteinä saamiensa kokonaislukujen summan. Tehdään ohjelmasta kolme erilaista versiota:

Taulukko

Tähän mennessä tavatut muuttujat ovat olleet yksittäisiä "laatikoita, joihin mahtuu vain yksi arvo kerrallaan". Tällaisilla saa toki ohjelmoitua kaikki mahdolliset algoritmit. Mutta työlääksi ja tylsäksi se voi käydä...

Esimerkki: Laadi ohjelma, joka lukee viisi lukua ja tulostaa ne käänteisessä järjestyksessä:

int eka    = Pop.kysyInt("Anna 1. luku: ");
int toka   = Pop.kysyInt("Anna 2. luku: ");
int kolm   = Pop.kysyInt("Anna 3. luku: ");
int nelj = Pop.kysyInt("Anna 4. luku: ");
int viid = Pop.kysyInt("Anna 5. luku: ");

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

System.out.println(viid);
System.out.println(nelj);
System.out.println(kolm);
System.out.println(toka);
System.out.println(eka);

Kun ohjelmalle syötetään vaikkapa luvut 2, 4, 6, 8, 9, se tulostaa:

Luvut käänteisessä järjestyksessä:
9
8
6
4
2

Kyllähän homma siis onnistuu, mutta vaikkapa jo sadan luvun tapauksessa ohjelmoija joutuisi näkemään melkoisesti vaivaa. Puhumattakaan kymmennetuhannen tai miljoonan luvun tapauksesta. Ja aivan turhaan!

Taulukko (array) on muuttuja, joka sisältää monta "lokeroa", joihin jokaiseen voidaan tallettaa yksi arvo. Taulukko on siis ikään kuin monen muuttujan "lokerikko".

Ja nyt tulee koko homman idis: Taulukkomuuttujan yksittäiseen lokeroon voidaan viitata lokeron järjestysnumerolla! Ja tuo järjestysnumero voi olla muuttujan arvona. Muista, että muuttujan arvoa voi muuttaa!

Javassa ja monessa muussa ohjelmointikielessä taulukkomuuttujan lokerot on numeroitu nollasta alkaen. Lokeroiden numeroita on tapana kutsua indekseiksi ja lokeroita taulukon alkioiksi.

Äskeisen esimerkin voi taulukkoa käyttäen ohjelmoida vaikkapa seuraavasti:

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

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

    int lokeronNumero = 0;

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

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

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

Yllä ilmaus int[] luvut = new int[5]; on Java-kielen tapa määritellä taulukkomuuttuja, jonka alkiot ovat tyypiltään kokonaislukuja ja jonka koko eli alkioiden lukumäärä on viisi.

Ilmaus luvut[lokeronNumero] on taulukon indeksointi; se tarkoittaa taulukkomuuttujan alkiota, jonka järjestysnumero on lokeronNumero. Jos muuttujan arvoa vastaavaa alkiota ei taulukossa ole, ohjelman suoritus keskeytyy virheeseen. Kelvollinen indeksi on siis ohjelmoijan vastuulla.

Taulukon pituuden voi tarkistaa ilmauksella luvut.length:

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

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

Taulukot ja for-lause

Kuten edellä mainittiin, askeltava for-lause soveltuu erityisen hyvin taulukon alkioiden läpikäymiseen.

Esimerkkinä viimeistellään ja yleistetään yllä nähty esimerkki EkaVikaksiJne.java, jossa syöttöluvut tulostetaan käänteisessä järjestyksessä. Tässä kehittyneemmässa versiossa lukujen lukumäärä kysytään käyttäjältä:

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

    int koko = Pop.kysyInt("Montako lukua haluat tulostaa käännetyssä järjestyksessä?");

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

    for (int lokeronNumero=0;  lokeronNumero < luvut.length; ++lokeronNumero)
      luvut[lokeronNumero] = Pop.kysyInt("Anna " + (lokeronNumero+1) + ". luku: ");

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

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

Kokoelman alkioiden läpikäynti – "for-each"

Edellä opittu for-lauseen muoto taulukoiden 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}; // näinkin voi luoda taulukon!

    int summa = 0;

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

    Pop.ilmoita("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.

Merkkijonotaulukot

Jo aiemmin nähtiin esimerkki merkkijonomuuttujasta:

String teksti = "kissantassu";
System.out.println(teksti);
teksti = "ketunhäntä";
System.out.println(teksti);

Merkkijonot kelpaavat myös taulukon alkioiksi. Tällöin alkiotyypin nimi on String.:

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ä merkkijonoja vertaillaam equals-operaatiolla! Yhtäsuuruusmerkki == tarkoittaa jotakin muuta...

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

Taulukon alkuarvojen luetteleminen

Taulukon voi määritellä myös luettelemalla alkioiden alkuarvot seuraavaan tapaan:

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

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

    String kuka = Pop.kysyString("Kenen henkilön iän haluat tietää?");

    // 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 )
      Pop.ilmoita("Henkilöä " + kuka + " ei löytynyt.");
    else {
      int haetunIka = ika[indeksi];
      Pop.ilmoita("Henkilön " + kuka + " ikä on " + haetunIka + ".");
    }
  }
}

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

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.

Mahdollisuus nimetä ja kutsua alialgoritmeja on jo sinänsä arvokasta. Näin voidaan välttyä "koodin kertaamiselta" ja samalla selkeyttää ohjelmaa. Tärkeä lisäominaisuus on mahdollisuus antaa aliohjelmille lähtötietoja, ns. parametreja, jotka ohjaavat alialgoritmin suoritusta.

Nimetyt alialgoritmit voivat olla "komentoja tehdä jotakin" samaan tapaan kuin vaikkapa println osaa kirjoittaa parametrinsa ohjelman käyttäjän kuvaruudulle. Samoin Pop.ilmoita(...) vain "komentaa" ohjelman näyttämään tiedotuksen ponnahdusikkunassa.

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. Myös kurssilla käytettävät tietoja lukevat ponnahdusikkunat palauttavat arvon ja ovat sikäli funktion kaltaisia: esimerkiksi Pop.kysyInt(...) palauttaa arvonaan käyttäjän syöttämän kokonaisluvun.

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

Javassa arvoa palauttamattomia metodeita kutsutaan void-metodeiksi. Arvon palauttavat metodit saavat nimensä palautettavan arvon tyypin mukaan: int-metodi, double-metodi, String-metodi, jne.

Metodin määrittely ja kutsu

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

Ensin esimerkki esimerkki "komennon kaltaisesta" void-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(); 
  }
}

Ohjelman tulostus:

Kutsutaanpa tervehdystä:
Moi!
Kutsutaanpa kolmasti:
Moi!
Moi!
Moi!

Metodin määrittely ei vielä johda sen algoritmin suorittamiseen. Vasta kutsu – nimi mainiten – johtaa algoritmin suorittamiseen.

Arvon palauttava aliohjelma määritellään samaan tapaan, mutta määrittelyssä metodille annetaan paluuarvon tyyppi ilmauksen void sijasta:

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()));
  }
}

Ohjelman tulostus:

Onnenluku = 14
Ja kolmeen kertaan: 42

Huom: Näissä kahdessa esimerkissä metodeilla ei ole parametreja. Silti sekä määrittelyssä että kutsussa niiden nimen yhteyteen on kirjoitettava "tyhjät" kaarisulut.

Ja sitten muutamia huomioita Javan tyylistä

Parametrit

Metodille siis voi antaa lähtötietoja ns. parametrien avulla. Idea on seuraava:

Yleistetty tervehtimisalgoritmi ja sitä esittelevä ohjelma kokonaisuudessaan:

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!
  }
}

Ohjelman tulostus:

Kutsutaanpa tervehdystä:
Moi Aku!
Kutsutaanpa kolmasti:
Moi Iines!
Moi Minni!
Moi Mikki!
Moi Jaska Jokunen, perijä numero 9!

Tietenkin myös arvon palauttavalle aliohjelmalle voi antaa parametreja:

public class Tuplaus {

    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))));
  }
}

Ohjelman tulostus:

Kolme tuplana = 6
36
8

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);
  }
}

Ohjelman tulostus:

Onnea Liisa!
Onnea Liisa!
Onnea Liisa!
Onnea Pekka!
Onnea Pekka!

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));
  }
}

Ohjelman tulostus:

4
25

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 metodin 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];     // int-taulukon alkioden alkuarvo on 0
    System.out.println(t[7]);  // 0
    asetaJokaAlkiolle(t, 100);
    System.out.println(t[7]);  // 100

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

Ohjelman tulostus:

0
100
9876

Esimerkki peräkkäishausta: metodi etsii taulukosta kysyttyä arvoa ja palauttaa vastaavan indeksin ensimmäisestä löytyneestä. Jos päästään taulukon 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");
  }
}

Ohjelman tulostus:

Luvun 8 indeksi on 3
Luvun 7 indeksi on -1
8 löytyy
7 ei löydy

Taulukko metodin palauttamana arvona eli metodin tyyppinä

Metodin tyyppinä eli metodin palauttamana arvona voi olla myös taulukko, tarkemmin sanottuna viite taulukko-olioon.

Laaditaan pikku työkalu, jolla voi pyytää käyttäjältä taulukon alkioille arvot. Metodille annetaan parametrina taulukon koko. Metodi luo taulukon ja kyselee sen alkoille arvot. Lopuksi metodi palauttaa valmiin taulukon kutsujalleen.

public class TaulukkometodiA {

  private static int[] kyseleTaulukko(int koko) { // huomaa tyyppi!

      int[] tulos = new int[koko]; // luodaan taulukko

      Pop.ilmoita("Anna " + koko + " lukua.");
      for (int i=0; i < tulos.length; ++i)
        tulos[i] = Pop.kysyInt("Anna " + (i+1) + ". luku.");

      return tulos; // palautetaan valmis taulukko
  }

  // pääohjelma
  public static void main(String[] args) {

     int[] b = kyseleTaulukko(7);  // 1. käyttöesimerkki

     for (int i=0; i < b.length; ++i)
       System.out.print(b[i]+" ");
     System.out.println();

     b = kyseleTaulukko(3);        // 2. käyttöesimerkki

     for (int i=0; i < b.length; ++i)
       System.out.print(b[i]+" ");
     System.out.println();
  }
}

Kun tälle ohjelmalle syötetään vaikkapa ensin luvut 8, 7, 6, 5, 4, 3, 2 ja sitten 5, 3, 1, se odotetusti tulostaa:

8 7 6 5 4 3 2 
5 3 1 

Toinen vaihtoehto tällaisen palvelun toteuttamiseen on void-metodi, joka saa taulukon parametrina:

public class TaulukkometodiB {

  private static void kyseleTaulukko(int[] tulos) {

      Pop.ilmoita("Anna " + tulos.length + " lukua.");
      for (int i=0; i < tulos.length; ++i)
        tulos[i] = Pop.kysyInt("Anna " + (i+1) + ". luku.");
  }

  // pääohjelma
  public static void main(String[] args) {

     int[] b = new int[7];  // nyt taulukko luodaan pääohjelmassa!
     kyseleTaulukko(b);     // 1. käyttöesimerkki

     for (int i=0; i < b.length; ++i)
       System.out.print(b[i]+" ");
     System.out.println();

     b = new int[3];
     kyseleTaulukko(b);     // 2. käyttöesimerkki

     for (int i=0; i < b.length; ++i)
       System.out.print(b[i]+" ");
     System.out.println();
  }
}

Huomaa että kun taulukko välitetään parametrina, todellisuudessa parametrina välitetään viite taulukko-olioon. Siksi kutsutun metodin toimenpiteet taulukolle kohdistuvat pääohjelman luomaan taulukkoon!