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)
lause
tai
if (ehto)
lause1
else
lause2
Ensimmä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.
Takaisin luvun 2 sisällysluetteloon.