Tässä luvussa opetellaan ehtolausekkeiden kirjoittamista ja niiden käyttöä algoritmin toiminnan ohjaamiseen.
Tähän mennessä opitut ohjelmat ovat vähän tylsiä: ne suoritetaan aina samalla tavoin ensimmäisestä lauseesta viimeiseen. Valintalauseella ja ehdollisella toistolla algoritmin toiminta saadaan riippumaan syöttötiedoista. Toistolauseet ovat tavallaan varsinainen syy siihen, että tietokoneita kannattaa käyttää algoritmien suorittamiseen: vaikkei tietokoneista mihinkään 'viisaaseen' olisikaan, ne ovat hyvin nopeita mekaanisesti toistamaan alialgoritmeja. Ja ehkä viisas ohjelmoija voi saada koneenkin vaikuttamaan viisaalta...
boolean oikein; oikein = false;Loogisia lausekkeita (eli totuusarvoisia lausekkeita) rakennetaan vertailuista, loogisista operaatioista ja toisista loogisista lausekkeista.
> suurempi kuin >= suurempi tai yhtäsuuri kuin < pienempi kuin <= pienempi tai yhtäsuuri kuin == yhtäsuuri kuin != erisuuri kuinKaikki 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.
Esimerkkejä loogisista lausekkeista - laskettu totuusarvo sijoitetaan boolean-muuttujan arvoksi (tavallisempaa kuitenkin on loogisten lausekkeiden käyttäminen ehdollisissa lauseissa, valinnassa ja toistossa):
int i = 7; double d = 3.14; boolean b, bb, bbb; b = (i < 10); // true bb = (d > 10.0); // false bbb = (8 == (i+1)); // true bb = (b == bbb); // true bb = (b != bbb); // falseHuom: Liukulukujen vertailussa on syytä käyttää vain erisuuruusvertailuja, koska ns. pyöristysvirheiden takia yhtäsuuruusvertailut voivat antaa odottamattomia tuloksia.
Huom: String-arvojen vertailussa "==" ja "!=" eivät tarkoita sisältöjen vertailua vaan merkkijononojen sijaintipaikkojen vertailua, koska String-arvot ovat olioita. String-vertailuun käytetään erikoisvälineitä. Asia opitaan myöhemmin.
&& ehdollinen "ja", "and" || ehdollinen "tai", "or" ^ poissulkeva tai, "xor" (myös !=) ! negaatio, "ei" "not"Näiden operaatioiden ns. operandit eli laskettavat voivat olla vain totuusarvoisia lausekkeita, tavallisesti vertailuja.
Operaation ehdollisuus tarkoittaa sitä, että operaation jälkimmäinen laskettava lasketaan vain, jos totuusarvo ei selviä jo ensimmäisestä! (Ehdottomat and ja or ovat "&" ja "|". Näitä käytettäessä molemmat operandit lasketaan aina.)
Esimerkkejä:
int i = 7; double d = 3.14; boolean b, bb, bbb; b = (i == 7) && (d > 10.0); // false bb = (i == 7) && (d < 10.0); // true b = (i == 7) || (d > 10.0); // true bb = (i != 7) || (d > 10.0); // false bbb = !b || !!bb; // false // huom: !(i==7) ja (i!=7) tarkoittavat samaa: // "i ei ole seitsemän"
Ehdollinen lause eli if-lause on muotoa:
if (ehto) lausetai
if (ehto) lause1 else lause2Ensimmäinen tarkoittaa, että jos ehto on true, lause suoritetaan. Jälkimmäinen, että jos ehto on true, lause1 suoritetaan, muuten suoritetaan lause2.
Esimerkki (käytetään lukemiseen luokan Lue välineitä):
public class KumpiSuurempi1 { /* ilmoittaa kumpi kahdesta syöttöluvusta on suurempi */ public static void main(String[] args) { double luku1, luku2; System.out.println("Anna kaksi lukua!"); luku1 = Lue.dluku(); luku2 = Lue.dluku(); if (luku1 > luku2) System.out.println("Ensimmäinen luku on suurempi."); else System.out.println("Ensimmäinen ei ole suurempi."); } }Huom: Javassa yksinkertainen lause päättyy aina puolipisteeseen! Puolipiste on siis lauseen lopetin.
Jos valintavaihtoehto - valittava alialgoritmi - muodostuu useammasta lauseesta, nuo lauseet on koottava yhteen ns. lohkoksi (block) eli "kootuksi lauseeksi". Lohko on merkkien "{" ja "}" välissä oleva lauseiden jono. Noita merkkejä sanotaan joskus käskysulkeiksi. (Huomaa miten näitä samoja merkkejä käytetään luokan ja pääohjelman rajaamiseen! Lohkon loppusulkeen jälkeen puolipistettä ei käytetä.)
Esimerkki:
public class KumpiSuurempi2 { /* ilmoittaa kumpi kahdesta syöttöluvusta on suurempi ja tulostaa luvuista suuremman */ public static void main(String[] args) { double luku1, luku2; double suurempi; System.out.println("Anna kaksi lukua!"); luku1 = Lue.dluku(); luku2 = Lue.dluku(); if (luku1 > luku2) { System.out.println("Ensimmäinen luku on suurempi."); suurempi = luku1; } else { System.out.println("Ensimmäinen ei ole suurempi."); suurempi = luku2; } System.out.println("Ei-pienemmän arvo on "+suurempi); } }Rakenteisen lauseen alilause voi olla itsekin rakenteinen lause, jonka alilause voi olla rakenteinen lause, ... Näistä nähdään lukuisia esimerkkejä myöhemmin.
Koska if-lauseita on kahdenlaisia - niihin joko liittyy tai ei liity else-osa - syntyy seuraavanlainen moniselitteisyysongelma:
Tarkoittaako
if (a<b) if (c<d) e = f; else g=h;toimintaa:
if (a<b) if (a<b) if (c<d) if (c<d) e = f; VAI e = f; else else g=h; g=h;Toisin sanoen mihin if-lauseeseen esimerkin else-osa liittyy? (Kääntäjälle tekstin sisennyksellä ei ole viestiä! Ohjelmoijalle se on välttämätöntä!)
Ongelma on Javassa ratkaistu säännöllä: else-osa liittyy lähimpään edeltävään if-lauseeseen, johon ei ole vielä liittynyt else-osaa. Esimerkkitapauksessa siis jälkimmäinen tulkinta pätee. Ensimmäinen tulkinta saadaan kun suljetaan else-osaton if-lause omaksi lohkokseen:
if (a<b) { if (c<d) e = f; } else g=h;Melko usein algoritmeja laadittaessa ehdollisia lauseita joudutaan ketjuttamaan.
Esimerkki: Halutaan double-muuttujan a arvosta riippuen suorittaa eri lause seuraavissa tilanteissa:
if ( a < 0 ) lause1; else if ( a >=1 && a < 50 ) lause2; else if ( a > 61 && a < 103 ) lause3; else if ( a >= 203 && a <= 429 ) lause4; else if ( a >= 929 && a < 1021 ) lause5; else if ( a >= 1621 && a < 5000 ) lause6; else lause7;Tässä tilanteessa ohjelmoija kuitenkin mieltää arvovälit ennemminkin rinnakkaisiksi vaihtoehdoiksi, kuin valintojen alivalinnoiksi. Näin seuraava ulkoasu vastaa paremmin ohjelmoijan ajattelua:
if ( a < 0 ) lause1; else if ( a >=1 && a < 50 ) lause2; else if ( a > 61 && a < 103 ) lause3; else if ( a >= 203 && a <= 429 ) lause4; else if ( a >= 929 && a < 1021 ) lause5; else if ( a >= 1621 && a < 5000 ) lause6; else lause7;Huom: Ohjelman ulkoasu, 'lay-out', on syytä aina laatia sellaiseksi, että ohjelman toimintalogiikka on helppo hahmottaa. Alilauseet sisennetään, välilyöntejä ja tyhjiä rivejä on syytä käyttää erottamaan rakenteita toisistaan, ...
Mitä seuraava kääntäjän kannalta ihan kelvollinen ohjelmanpätkä tekee?
if (a<0) lause1; else if (a>=0&&a<51) lause2;else if(a >=51&&a<103) lause3;else if (a>=103&&a<429) lause4;else if (a>=429&&a<1021) lause5;else if (a>=1021&&a<5000) lause6;else lause7;
Java-kielen for-toistolauseen muoto on
for (alkuasetus; jatkamisehto; eteneminen) toistettava lause
Hyvin tavallinen ja luonteva käyttötapa for-lauseelle on arvoalueen läpikäynti:
int i; for (i=0; i<6; ++i) System.out.println(i);Lauseet tulostavat:
0 1 2 3 4 5Huom: Tämän esimerkin toisto on tyylikkäämpää ohjelmoida toisin. (Mitä "tyylikkyys" tarkoittaa ohjelmoinnissa ja miksi sitä kannattaa tavoitella?):
for (int i=0; i<6; ++i) System.out.println(i);For-lauseen alkuasetuksessa voidaan siis määritellä muuttuja! Tällainen muuttuja on käytettävissä vain tuon for-lauseen sisällä.
For-lauseen toistama alialgoritmi voi toki sisältää for-toiston:
for (int i=0; i<3; ++i) for (int j=0; j<2; ++j) System.out.println(i+" "+j);Tulostus:
0 0 0 1 1 0 1 1 2 0 2 1For-lauseessa voidaan käyttää int-tyyppisen "askelmuuttujan" sijaan myös vaikkapa double-muuttujaa. Ja eteneminen voi olla muutakin kuin ykkösellä kasvattamista tai vähentämistä:
//Tulostetaan piin 20 pienemmät monikerrat: for (double piit = 3.14; piit < 20; piit+=3.14) System.out.println(piit);Tulostus:
3.14 6.28 9.42 12.56 15.700000000000001 18.84(Yllä nähdään esimerkki ns. pyöristysvirheestä: kun lasketaan 12.56+3.14 saadaan luku, joka tietokoneen esitystavalla onkin hieman suurempi kuin 15.70!)
For-lauseen osat (alkuasetus, jatkamisehto, eteneminen, toistettava lause) voivat olla monimutkaisempiakin. Toistaiseksi tyydymme käyttämään for-lausetta arvoalueiden läpikäyntiin.
Tämä toistotapa soveltuu tilanteeseen, jossa toistokertoja tarvitaan nolla tai enemmän, alialgoritmia ei siis välttämättä toisteta kertaakaan.
While-toisto on muotoa
while (jatkamisehto) lause
Edellä nähty for-esimerkki:
int i; for (i=0; i<6; ++i) System.out.println(i);voidaan ohjelmoida while-lauseella:
int i=0; while (i<6) { System.out.println(i); ++i; }Laaditaan esimerkkiohjelma Tuplatw.java, joka tulostaa syöttölukuja kaksinkertaisina. Tuplattavien lukujen lukumäärää ei tiedetä etukäteen. Kun käyttäjä syöttää nollan, ohjelman suoritus päättyy (käytetään lukujen lukemiseen luokan Lue välineitä):
public class Tuplatw { public static void main(String[] args){ System.out.println("Lukujen tuplausohjelma, nolla lopettaa"); int luku = Lue.kluku(); while (luku != 0) { System.out.println("Tuplana: "+luku*2); luku = Lue.kluku(); } System.out.println("Siinä ne olivat."); } }Ohjelman käyttö voi näyttää vaikkapa seuraavalta:
Lukujen tuplausohjelma, nolla lopettaa 3 Tuplana: 6 -8 Tuplana: -16 0 Siinä ne olivat.[Sama ohjelma voidaan toteuttaa for-lauseella (Tuplatf1.java):
public class Tuplatf1 { public static void main(String[] args){ System.out.println("Lukujen tuplausohjelma, nolla lopettaa"); for (int luku=Lue.kluku(); // alustus luku != 0; // jatkamisehto luku=Lue.kluku()){ // eteneminen System.out.println("Tuplana: "+luku*2); } System.out.println("Siinä ne olivat."); } }Tämän ratkaisun tyylikkyys on makuasia. (Vielä eksoottisempi ratkaisu on Tuplatf2.java.) ]
Tämä toistotapa soveltuu tilanteeseen, jossa toistokertoja tarvitaan yksi tai enemmän, toistettava alialgoritmi siis suoritetaan ainakin kerran.
Do-while -toisto on muotoa
do lause while (jatkamisehto)
Edellä nähdyt for- ja while-esimerkit
int i; for (i=0; i<6; ++i) System.out.println(i);ja
int i=0; while (i<6) { System.out.println(i); ++i; }voidaan ohjelmoida do-while -toistolla:
int i=0; do { System.out.println(i); ++i; } while (i<6);Yksi tyypillinen käyttö loppuehtoiselle toistolle on syöttötietojen tarkistaminen. Laaditaan sovellus Pariton.java, joka välttämättä haluaa parittoman luvun:
public class Pariton { public static void main(String[] args){ int luku; boolean parillinen; do { System.out.println("Anna pariton luku!"); luku = Lue.kluku(); parillinen = (luku%2 == 0); if (parillinen) System.out.println("Eihän "+luku+" ole pariton ..."); } while (parillinen); System.out.println("Hienoa, "+luku+" on pariton!"); } }Ohjelman käyttö näyttää vaikkapa seuraavalta:
Anna pariton luku! 468 Eihän 468 ole pariton ... Anna pariton luku! -32 Eihän -32 ole pariton ... Anna pariton luku! 9 Hienoa, 9 on pariton!
Esimmäinen ohjelmaversio (Karvo1.java) kysyy lukujen lukumäärän käyttäjältä.
public class Karvo1 { /* Sovellus keskiarvojen laskentaan, AW-97*/ public static void main(String[] args) { int lukujenLkm = 0; boolean lkmOk; double luku, lukujenSumma = 0, lukujenKarvo; System.out.println("\n**** Keskiarvon laskenta ****\n"); // Monenko luvun keskiarvo lasketaan // (tarkistetaan, että määrä ei ole negatiivinen): do { System.out.print("Monenko luvun keskiarvo lasketaan? "); lukujenLkm = Lue.kluku(); lkmOk = (lukujenLkm >= 0); // nollakin kelpaa! if (!lkmOk) System.out.println("Virhe: negatiivinen ei kelpaa!"); } while (!lkmOk); // Lukujen summan laskenta: for (int monesko = 1; monesko <= lukujenLkm; ++monesko) { System.out.print("Anna "+monesko+". luku: "); luku = Lue.dluku(); lukujenSumma += luku; } // Keskiarvon tulostus (estetään 0:lla jakaminen): if (lukujenLkm == 0) System.out.println("\nEi lukuja, ei keskiarvoa.\n"); else { lukujenKarvo = lukujenSumma/lukujenLkm; System.out.println("\nKeskiarvo on "+lukujenKarvo+".\n"); } } }Ohjelman suoritus voi näyttää seuraavanlaiselta:
**** Keskiarvon laskenta **** Monenko luvun keskiarvo lasketaan? -23 Virhe: negatiivinen ei kelpaa! Monenko luvun keskiarvo lasketaan? 3 Anna 1. luku: 40.1 Anna 2. luku: -3 Anna 3. luku: 9.1 Keskiarvo on 15.4.Toinen ohjelmaversio (Karvo2.java) osaa laskea vain ei-negatiivisten lukujen keskiarvoja, mutta se on edellistä versiota joustavampi sikäli, että lukujen lukumäärää ei tarvitse tietää ennakolta. Ensimmäinen vastaantuleva negatiivinen luku on ohjelmalle ns. loppumerkki, jolla ohjelmalle ilmoitetaan, että syöttölukuja ei enää anneta lisää. Loppumerkki itse ei enää kuulu lukuihin, joiden keskiarvo lasketaan.
public class Karvo2 { /* Sovellus ei-negatiivisten lukujen keskiarvojen laskentaan, AW-97*/ public static void main(String[] args) { int lukujenLkm = 0; double luku, lukujenSumma = 0, lukujenKarvo; System.out.println("\n**** Keskiarvon laskenta ****\n"); // Käyttöohjeen tulostus: System.out.println("Syötä luvut, joiden keskiarvon haluat laskea!\n"+ "Negatiivinen luku ilmoittaa lukujen loppuvan.\n"); // Lukujen summan laskenta: luku = Lue.dluku(); // 1. luku (voi olla jo loppumerkki!) while (luku >= 0) { lukujenSumma += luku; ++lukujenLkm; luku = Lue.dluku(); // seuraava luku tai loppumerkki } // Keskiarvon tulostus (estetään 0:lla jakaminen): if (lukujenLkm == 0) System.out.println("\nEi lukuja, ei keskiarvoa.\n"); else { lukujenKarvo = lukujenSumma/lukujenLkm; System.out.println("\nKeskiarvo on "+lukujenKarvo+".\n"); } } }Ohjelman käyttö näyttää vaikkapa seuraavanlaiselta:
**** Keskiarvon laskenta **** Syötä luvut, joiden keskiarvon haluat laskea! Negatiivinen luku ilmoittaa lukujen loppuvan. 21.2 0.02 33 2.5 -1 Keskiarvo on 14.18.Kolmas ohjelmaversio kysyy ennen jokaista syöttölukua, halutaanko lukuja vielä syöttää. Kysymykseen vastaaminen on tässä esimerkissä vielä kömpelöä, koska merkkijonojen vertailua ei vielä ole opittu.
public class Karvo3 { /* Sovellus keskiarvojen laskentaan, kysellään "onko vielä lukuja" */ public static void main(String[] args) { int lukujenLkm = 0, jatkuu; double luku, lukujenSumma = 0, lukujenKarvo; System.out.println("\n**** Keskiarvon laskenta ****\n"); // Lukujen summan laskenta: do { System.out.println("Onko vielä lukuja? (1:on, muu luku: ei)"); jatkuu = Lue.kluku(); if (jatkuu == 1) { System.out.println("Anna " + (lukujenLkm + 1) + ". luku"); luku = Lue.dluku(); lukujenSumma += luku; ++lukujenLkm; } } while (jatkuu == 1); // Keskiarvon tulostus (estetään 0:lla jakaminen): if (lukujenLkm == 0) System.out.println("\nEi lukuja, ei keskiarvoa.\n"); else { lukujenKarvo = lukujenSumma/lukujenLkm; System.out.println("\nKeskiarvo on "+lukujenKarvo+".\n"); } } }Ohjelman suoritus voi näyttää vaikkapa seuraavalta:
**** Keskiarvon laskenta **** Onko vielä lukuja? (1:on, muu luku: ei) 1 Anna 1. luku 76 Onko vielä lukuja? (1:on, muu luku: ei) 1 Anna 2. luku -4 Onko vielä lukuja? (1:on, muu luku: ei) 0 Keskiarvo on 36.0.Luvussa 2.7 opitaan, miten vastaus voitaisiin toteuttaa String-arvona:
... String jatkuu; ... do { System.out.println("Onko vielä lukuja? (kyllä/ei)"); jatkuu = Lue.rivi(); if (jatkuu.equals("kyllä")) { System.out.println("Anna " + (lukujenLkm + 1) + ". luku"); luku = Lue.dluku(); lukujenSumma += luku; ++lukujenLkm; } } while (jatkuu.equals("kyllä")); ...Suoritusesimerkki:
Onko vielä lukuja? (kyllä/ei) kyllä Anna 1. luku 314 Onko vielä lukuja? (kyllä/ei) kyllä Anna 2. luku 51 Onko vielä lukuja? (kyllä/ei) kyllä Anna 3. luku -23 Onko vielä lukuja? (kyllä/ei) ei Keskiarvo on 114.0.