Kuten edellisen luvun alussa todettiin, lauseke (expression) on ohjelmointikielen ilmaus, jolla on jokin arvo ja tuolla arvolla on jokin tyyppi. Lause (statement) puolestaan ilmaisee jonkin algoritmisen toiminnon, "tehdään jotakin"... Javassa on lisäksi lausekelauseita (expression statements), joita voi käyttää sekä lausekkeen, että lauseen tapaan. Laskettua arvoa ei jälkimmäisessä tapauksessa käytetä mihinkään. Laskennalla voi kuitenkin olla haluttuja (sivu-)vaikutuksia.
Ns. rakenteisten lauseiden tehtävä on valita suoritettavia lauseita, toistaa lauseita, ... Ne ovat rakenteisia, juuri siksi, että niiden rakenneosina on lauseita.
Yksinkertaiset lauseet päättyvät aina puolipisteeseen. Rakenteisilta lauseilta tätä ei vaadita. Sallittua se silti on, koska Javassa on ns. tyhjä lause.
Tämä luku esittelee tiiviisti useimmat Javan lauseet.
{ int a = 1, b, c = 2*a; b = a * 2; int d = a + b; // d on käytössä vasta tästä alkaen!! { int e = d + b; // e on käytössä tästä alkaen d = a + e; } // e:n käyttöalue loppuu tähän c = d + b; }Huom: Tavallisesti on toki selkeintä määritellä lohkon muuttujat lohkon alussa! Toisaalta periaate "määrittele mahdollisimman syvällä, mahdollisimman myöhään" on myös perusteltavissa. Miksi?
Huomattavaa:
{ //... { int i; //... } { int i; //... } int i; //... }Metodin muuttujalla ei missään ohjelmakohdassa saa olla kahta merkitystä.
Esimerkkejä muuttujien määrittelyistä (Esimerkit havainnollistavat määrittelyiden monia mahdollisuuksia, älkööt ne kuitenkaan ketään innostako vaikeaselkoisuuteen!):
long i; int j = 1, k = 2, m, n = 0; int eka=1, toka=eka+1, kolm=eka+2, nel=kolm+1; double dd = 6.0, ee = dd*3.14; float ff = 3.21F; // Liukulukuvakiot ovat // ----^! oletusarvoisesti double-tyyppisiä! int ii = (int)ee, jj = '2'; char cc = '#', bb = (char)dd; String gg ="Miau!", hh = "tulos on "+ (ff+dd); String nnn = 2 + 3 + "böö"; //nnn:n arvoksi tulee "5böö" String mmm = 2 + (3 + "böö"); //mmm:n arvoksi tulee "23böö" Pikkuvarasto rr, pp = null, qq = new Pikkuvarasto(10.0, "Mehu");Metodin muuttujilla ei ole mitään oletusalkuarvoja (luokassa määritellyillä ilmentymä- ja luokkamuuttujilla sellaiset sitävastoin on, tähän palataan luvussa 4.2).
Kääntäjä tarkistaa, ettei metodissa käytetä arvon antajana - esimerkiksi sijoitusoperaation oikena puolena tai tulostettavana - sellaista muuttujaa, jolle ei ole asetettu alkuarvoa. Kääntäjä tekee ns. tietovirta-analyysin (data flow analysis) hyvin epäluuloisena: Jos tuollaista muuttujaa jossain tilanteessa edes saatettaisiin käyttää, kääntäjä ei anna armoa!
Huom: Koska määrittely on suoritettava lause, alkuarvon antava lauseke lasketaan kaikkine sivuvaikutuksineen joka suorituskerralla:
int a = 1; for (int i=1; i<=3; ++i) { Pikkuvarasto c = new Pikkuvarasto(i*10.0, "Mehu"); int b = ++a; System.out.println(b); // ... }Joka toistokerralla luodaan uusi Pikkuvarasto-olio ja ohjelmanpätkä tulostaa luvut 2, 3 ja 4.
Vain seuraavia lausekkeita voi käyttää lauseina:
Huom: Muita lausekkeita ei siis voi käyttää lauseina!
Esimerkkejä:
i = 1; j = i + 4; ++i; // Kun 1:llä kasvatus- tai vähennyslauseketta j++; // käytetään lauseena, järjestyksellä ei ole väliä! mehua.otaVarastosta(15); //vaikka: // public double otaVarastosta(double) // ^^^^^^ new Pikkuvarasto(); // konstruktorin suoritus voi joskus // olla ainoa, mitä halutaan
if (ehto) lause if (ehto) lause1 else lause2
if (a<b) {if (c<d) e = f; } else g=h;Ilman tuota lohkoa else-osa liittyisi sisempään if-lauseeseen. (Sisentelystä riippumatta.)
switch (lauseke) { case vakio1: lause1; break; case vakio2: case vakio3: lause2; lause3; break; default: lause4; break; }
//lähtölaskenta (korkeintaan 3:sta) int lkm; // ... switch (lkm) { case 3: System.out.print("kolme, "); case 2: System.out.print("kaksi, "); case 1: System.out.print("yksi, "); case 0: System.out.print("nolla: "); } System.out.println("PUM!");Jos muuttujan lkm arvo on 3, ohjelmanpätkä tulostaa:
kolme, kaksi, yksi, nolla: PUM!
Esimerkki: Klassinen tapa toteuttaa tietokoneen käyttöliittymä on ns. komentotulkki (Komentotulkki.java):
import java.util.Scanner; public class Komentotulkki { private static Scanner lukija = new Scanner(System.in); private static void teeA() { System.out.println("Toteutan operaation a"); // ... tehdään työt ... } private static void teeB() { System.out.println("Toteutan operaation b"); // ... tehdään työt ... } private static void teeC() { System.out.println("Toteutan operaation c"); // ... tehdään työt ... } private static void teeD() { System.out.println("Toteutan operaation d"); // ... tehdään työt ... } public static void main(String[] args) { char komento; do { // kirjoitetaan kehoitemerkki System.out.print("kone> "); // eristetään komento // Huom: Jos komentoon String rivi = lukija.nextLine(); // liittyisi parametreja, if ( rivi.length() > 0 ) // nekin löytyisivät komento = rivi.charAt(0); // muuttujasta rivi! else komento = 'ö'; // mikä vain virheellinen merkki! // valitaan operaatio switch (komento) { case 'a': teeA(); break; case 'b': teeB(); break; case 'c': teeC(); break; case 'd': teeD(); break; case 'l': break; // LOPETUSKOMENTO ON 'l' default: System.out.println("Virheellinen komento!"); break; } } while (komento!='l'); } }
... public static void main(String[] args) { char komento; while (true) { // "ikuinen" toisto keskeytetään break-lauseella ... // valitaan operaatio if (komento=='a') teeA(); else if (komento=='b') teeB(); else if (komento=='c') teeC(); else if (komento=='d') teeD(); else if (komento=='l') break; // ************** katkaistaan "ikuinen" toisto ************** else System.out.println("Virheellinen komento!"); } // end of while (true) }
public enum Maa { RISTI, HERTTA, PATA, RUUTU } ... Maa maa = Maa.HERTTA; switch (maa) { case RISTI: case PATA: System.out.println(maa + " on musta."); break; case HERTTA: case RUUTU: System.out.println(maa + " on punainen."); break; }
while (ehto) lause do lause while (ehto);
ehto
ehto-lause-ehto
ehto-lause-ehto-lause-ehto
...
lause-ehto
lause-ehto-lause-ehto
lause-ehto-lause-ehto-lause-ehto
...
for (alustuslauseke; ehto; kasvatuslauseke) lause
For-lauseen mahdolliset laskentajonot, "suoritushistoriat", ovat:
For-lause vastaa rakennetta:
{ alustuslauseke; while (ehto) { lause kasvatuslauseke; } }(Paitsi: continue-lauseella keskeytetty toisto suorittaa kasvatuslausekkeen ennen ehdon uutta testausta; continue-lause käsitellään seuraavana.)
For-lause voidaan nähdä "toiston yleisenä neliparametrisena rakenteena, johon kuuluu alkutoimet, päättöehto, päivitystoimet (kunkin toiston jälkeen) ja toistettava" (Tomi Silander).
for (int i=1, j=20; i<j; i+=2, j-=3) System.out.println(i + " " + j);Ohjelmanpätkä tulostaa:
1 20 3 17 5 14 7 11
Javan versio 1.5 toi mukanaan uuden tavan käydä läpi taulukon kaikki alkiot ilman tarvetta itse indeksoida. For-lauseen uusi syntaksivaihtoehto on
for (alkio : taulukko) käsittele alkio;Lause tarkoittaa sitä, että yksi kerrallaan käydään läpi kaikki taulukon alkiot ja toistettava alialgoritmi saa yksitellen käyttöönsä kaikkien niiden arvot muuttujassa alkio. Toistettavalla alialgoritmilla ei ole mitään tietoa alkion indeksistä, vain arvosta. Alialgoritmi ei myöskään pääse muuttamaan taulukon alkioiden arvoja! Kuten arvata saattaa, tämä muoto for-lauseesta soveltuu juuri tilanteisiin, joissa mitään tietoa alkion indeksistä ei tarvita!
Tällaisella semantiikalla varustettua for-toistoa kutsutaan joissakin ohjelmointikielissä for-each-toistoksi ilmeisestä syystä. Javassa tällainen on käytettävissä talukon kaikkien alkioiden läpikäynnin lisäksi myös ns. kokoelmatyypeille (Collections). Uusi for-lause käy niissäkin yksitellen läpi kaikki tietorakenteen alkiot.
Esimerkki: Ohjelmoidaan metodi, joka laskee int-taulukon alkioiden summan ja palauttaa sen arvonaan. Summaa laskettaessa ei ole kiinnostavaa, missä järjestyksessä lasketaan eikä ole kiinnostavaa, mikä kunkin alkion indeksi on. Tämä on siis varsin oivallinen tilanne kayttää for-each-toistoa! (LaskeSumma.java)
private static int alkiodenSumma(int[] taulukko) { int summa = 0; for (int alkio : taulukko) summa = summa + alkio; return summa; }
Huom: Itse asiassa for-each-lauseella voidaan taulukon alkioiden lisäksi käydä läpi ns. kokoelmien (Collections) alkioita. Myös luetellun tyypin arvoalue voidaan käydä läpi:
public enum Arvo { KAKKONEN, KOLMONEN, NELONEN, VITONEN, KUTONEN, SEISKA, KASI, YSI, KYMPPI, JÄTKÄ, ROUVA, KUNKKU, ÄSSÄ } public enum Maa { RISTI, HERTTA, PATA, RUUTU } ... for (Maa maa : Maa.values()) for (Arvo arvo : Arvo.values()) System.out.println("" + maa + arvo);
while (jotakin) { ... if (keskeytyttää) break; ... }
On kauniimpiakin tapoja tulostaa parittomat välillä 1-10 kuin seuraava:
for (int i=1; i<10; ++i) { if (i%2 == 0) continue; System.out.println(i); }
public static boolean intToBool(int luku) { return luku != 0; }Return-lauseita voi olla metodissa useampiakin. Jokaisessa arvon palauttavassa metodissa on oltava ainakin yksi saavutettavissa oleva return-lause palautusarvoineen. Itse asiassa jokaisen mahdollisen algoritmin suortusreitin on päädyttävä return-lauseeseen!
ulompi: while (jotakin) { int i = 1; ... sisempi: while (jotain muuta) { int a = 1; ... if (meni vähän pieleen) break sisempi; ... if (kaikki meni pieleen) break ulompi; ... } }Keskeytyslauseet voivat joskus olla näppäriä esimerkiksi virhetilanteiden hoitamisessa. Ohjelman normaalin etenemisen ohjaamisessa niitä on syytä käyttää harkiten.
Ns. rinnakkaisohjelmointia ("threadit") ja siihen liittyviä lauseita ei tällä kurssilla käsitellä.