Helsingin yliopisto / Tietojenkäsittelytieteen laitos / 581258-1 Johdatus ohjelmointiin
Copyright © 2005 Arto Wikla. Tämän oppimateriaalin käyttö on sallittu vain yksityishenkilöille opiskelutarkoituksissa. Materiaalin käyttö muihin tarkoituksiin, kuten kaupallisilla tai muilla kursseilla, on kielletty.

3.3 Lausekkeita ja operaatioita

(Muutettu viimeksi 2.6.2005)

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.

Tässä luvussa käsitellään vain alkeistyyppisiä lausekkeita. Viittaustyyppisten arvojen, olioiden, käsittelemistä opitaan muissa luvuissa. Javassa on myös bittijonoa muokkaavia operaatioita, mutta niitä ei kurssilla käsitellä eikä käytetä!

Javalla on mahdollista laatia selkeitä ja ymmärrettäviä lausekkeita, mutta kovasti yrittämällä voi kirjoittaa myös hyvin vaikeaselkoisia. Arvatkaapa kumpia arvostetaan enemmän kokeessa, elämässä, ...?

Tyypit ja tyyppimuunnokset

Java on ns. vahvasti tyypitetty kieli: lausekkeen tyyppi tiedetään (lähes aina) jo käännösaikana, ns. automaattisia tyyppimuunnoksia (implicit conversion) ei tehdä kuin turvallisissa tapauksissa. Tämä tarkoittaa sitä, ettei kääntäjä salli esimerkiksi double-arvon sijoittamista int-muuttujalle, eikä edes double-vakion "0.0" sijoittamista float-muuttujalle vaikka arvo sinne mahtuisikin! (Tarkempia tietoja löytyy Javan spesifikaatiosta: The Java Language Specification: 5.2 Assignment Conversion)

Sääntöjä:

Esimerkki (nimetään muuttujat vähän erikoisesti alkamaan merkillä "_"):

   short  _short  = -23;
   int    _int    = 123;
   long   _long   = 121234;
   char   _char   = 'q';
   float  _float  = 3.14f;
   double _double = 121.2311;

// sallittuja             kiellettyjä
   _int    = _short;     _short = _int;
   _long   = _int;       _int   = _long;
   _double = _short;     _short = _double;
   _double = _float;     _float = _double;

   _int = _char;         _char  = _int;
                         _short = _char;
                         _char  = _short;
   _double = _char;
                         _short = 8-0;
                                 // kokonaislukulaskutoimitukset tehdään
                                 // aina vähintään int-tyyppisinä!!
Kääntäjä antaa seuraavantapaisia ilmoituksia (4. virheellinen tapaus):
Ohjelma.java:12: possible loss of precision
found   : double
required: float
   _float = _double;
            ^
1 error

Eksplisiittinen tyyppimuunnos (cast) ilmaistaan kirjoittamalla arvon saajan tyyppinimi sulkeisiin lausekkeen eteen:

   short  _short  = -23;
   int    _int    = 123;
   long   _long   = 121234;
   char   _char   = 'q';
   float  _float  = 3.14f;
   double _double = 121.2311;

   _short = (short)_int;
   _int   = (int)_long;
   _short = (short)_double;
   _float = (float)_double;
   _char  = (char)_int;
   _short = (short)_char;
   _char  = (char)_short;
   _short = (short)(8-0);

   _short = (short)(3.14 * 1000.3 - 0.00002);
                   // huom. sulkeet lausekkeen ympärillä!!

Järkevillä tapauksilla on järkevä tulkinta: liukuluku katkaistaan kokonaisluvuksi. Kun double-arvosta tehdään float, esitystarkkuus voi pienetä; sopivankokoinen kokonaisluku voidaan muuttaa char-arvoksi, ... (Tarkemmin asiaa esitellään spesifikaatiossa: 5.1.3 Narrowing Primitive Conversions)

Järjettömissä tapauksissa sattuu järjettömyyksiä, joista ohjelmoija kantaa vastuun!

Huom: boolean-tyyppistä lauseketta ei voi muuntaa numeeriseksi edes eksplisiittisellä tyyppimuunnoksella!

Aritmeettiset operaatiot

Javassa kokonaislukulaskutoimitusten ns. yli- ja alivuotoja ei havaita! Kokonaisluvut toimivat 'modulolaskureiden' tapaan: suurimman arvon seuraaja on pienin arvo! Liukulukutyyppejä on laajennettu arvoilla "positiivinen ääretön", "negatiivinen ääretön" ja "ei luku", ... Myös erimerkkiset nollat ovat esitettävissä. Mutta kuten jo aiemmin todettiin, numeerisen laskennan hienouksia ei tällä kurssilla käsitellä.

Kaikille numeerisille tyypeille on käytössä operaatiot:

   +  -  *  /  %
Jos molemmat operandit (so. laskettavat) ovat kokonaislukutyyppisiä, myös laskutoimituksen tulos on on kokonaisluku (aina vain joko int tai long - operandien tyypistä riippuen; jos arvo sijoitetaan byte- tai short-muuttujaan, on tehtävä eksplisiittinen tyyppimuunnos!).

Operaatio "/" tarkoittaa kokonaisjakoa, jos sekä osoittaja että nimittäjä ovat jotain kokonaislukutyyppiä. Aina muulloin kyseessä on liukulukujakolasku.

Operaatio "%" on jäännösjako. Tuloksen etumerkki on sama kuin osoittajan etumerkki, nimittäjän etumerkillä ei ole vaikutusta tuloksen etumerkkiin. Jäännösjaon soveltaminen liukuluvuille on sallittua, mutta ei järkevää! (Ks. spesifikaatio 15.17.3 Remainder Operator %.)

Huom: Jos kahden kokonaislukuarvon jakamisen tuloksena halutaan liukuluku, on se erikseen ilmaistava. Esimerkiksi ohjelma:

public class koe {
   public static void main(String[] args) {
      int a = 5, b = 2;

      System.out.println( a/b );
      System.out.println( a/b * 1.0 );
      System.out.println( 1.0 * a/b );
      System.out.println( (float)a/b );
      System.out.println( (float)(a/b) );
      System.out.println( (a/(float)b) );
   }
}
tulostaa
2
2.0
2.5
2.5
2.0
2.5

Huom: Operaatio "+" tarkoittaa merkkijonojen katenointia (l. yhteenliittämistä) jos edes toinen 'yhteenlaskettavista' on String-olio (laskenta etenee vasemmalta oikealle). Esimerkki:

   int i = 2, j = 5;

   System.out.println( "Summa on " + i+j );
   // tulostaa: Summa on 25

   System.out.println( "Summa on " + (i+j) );
   // tulostaa: Summa on 7

   System.out.println( i+j + " on summa" );
   // tulostaa: 7 on summa

   System.out.println( i+ (j + " on summa") );
   // tulostaa: 25 on summa


Matemaattisia perusfunktioita

Luokassa Math on määritelty joukko numeeristen lausekkeiden laskennassa käytettäviä perusfunktioita ja pari vakiota (luokka Math kuuluu pakkaukseen java.lang, joka on ohjelmoijan käytettävissä ilman eri määrittelyitä).

Vakioita:

Math.E   // luku e   (2.718281828459045)
Math.PI  // luku pii (3.141592653589793)
Funktioita (monia muitakin on!):
Math.abs      // itseisarvo

Math.sin
Math.cos
Math.tan  

Math.exp  
Math.log      // luonnollinen logaritmi

Math.sqrt
Math.pow      // 1. double potenssiin 2. double

Math.round

Math.random() // double-pseudosatunnaisluku väliltä [0.0, 1.0)

Math.max
Math.min

Monet Math-luokan metodit on kuormitettu eri tyyppisille parametreille. API-kuvauksessa on täydellinen luettelo Math-luokan metodeista.

Esimerkki:

  for (double d=0.0; d < 2*Math.PI; d+=0.1)
    System.out.println(Math.sin(d));


Kasvatus- ja vähennysoperaatiot

   ++muuttuja  muuttuja++  --muuttuja   muuttuja--
Operaatio kasvattaa (vähentää) muuttujan arvoa yhdellä.

On siis mahdollista ohjelmoida:

    int i=0;
    while (++i < 10)
      System.out.print(i+" ");
     System.out.println();

    i=0;
    while (i++ < 10)
      System.out.print(i+" ");
    System.out.println();
Lauseet tulostavat:
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9 10

Huom: Kasvatus- ja vähennysoperaatioiden käyttö lausekkeina tai lausekkeiden osina saattaa johtaa hyvin vaikeaselkoiseen ohjelmaan: Mitä esimerkiksi seuraava tulostaa:
     int i=0;
     i = ++i + (++i - i++) + ++i + i++ + ++i;
     System.out.println(i); 
Tuollainen ohjelmointityyli on hyvin huonoa! Sitä ei sallita tällä kurssilla eikä sellaista arvosteta myöskään työelämässä!

Huom: Kasvatus- ja vähennysoperaatiota voi soveltaa vain muuttujiin ei arvoihin! Kaikki seuraavat ovat virheellisiä:

   3++       ++4

   ++(a+b)  (c-d)++

   ++i++    (++i)++    ++(i++)


Sijoitusoperaatioita

Sijoitusoperaatiolla muuttujalle asetetaan arvo. Javassa sijoitusoperaatiot ovat teknisesti lausekkeita - niillä on siis itselläänkin arvo, sijoitettavan lausekkeen arvo. Sijoitusoperaatiota kannattaa kuitenkin yleensä käyttää lauseen tapaan.

Tavallisen sijoituksen lisäksi käytössä on joukko muuttujan vanhaa arvoa muuttavia operaatioita, mm. seuraavat:

   =   +=   -=   *=   /=   %=   
Esimerkiksi
     muuttuja *= lauseke
tarkoittaa
     muuttuja = muuttuja * lauseke
paitsi, että muuttuja selvitetään vain kerran.

Asialla on merkitystä, jos muuttujan selvittämisellä on sivuvaikutuksia.
Esimerkki tällaisesta tilanteesta: Muuttuja on indeksoitu taulukko, jonka indeksilausekkeessa on sivuvaikutus. Vertaa seuraavia lauseita:

     taulu[i++] += 7;
     taulu[i++] = taulu[i++] + 7; 
Tällainen ohjelmointityyli on hyvin epäselvää ja ja siksi virhealtista!

Monista ohjelmointikielistä poiketen Javassa arvon sijoittaminen muuttujaan on siis lauseke eli sillä itselläänkin on arvo (sijoitettavan lausekkeen arvo). On siis luvallista kirjoittaa:

    int a=1,b=2,c=3,d=4;
    a += (b=c+d)+(d=7)-(c*=9);
Mutta ei tuossa mitään järkeä ole! (En suosittele kokeessa kokeilemaan kepillä jäätä ;-)

Järkevää sitävastoin voisi olla vaikkapa yhteisen uuden alkuarvon antaminen:

    int a=1,b=2,c=3,d=4;
  //...
    a = b = c = d = 77;
Tällainen ketjutettu sijoitus lasketaan oikealta vasemmalle! So. d=77, sen arvo on 77, joka sijoitetaan c:lle, ...

Vertailuoperaatioita

   >   >=   <   <=   ==   !=   
Kaikki vertailut tuottavat totuusarvon! On syytä pitää mielessa sijoitusoperaation = ja vertailuoperaation == ero. Molemmat kun voivat esiintyä lausekkeen osina ... Vahvan tyypityksen ansiosta Javalla ei tee virheitä tässä asiassa niin helposti kuin vaikkapa C:llä, mutta tarkkana on silti syytä olla!

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

Loogisia operaatioita

   &&    ehdollinen "ja", "and"
   ||    ehdollinen "tai", "or"
   &     "ja", "and"
   |     "tai", "or"
   ^     poissulkeva "tai", "xor" (myös !=)
   !     negaatio, "not"
Näiden operaatioiden operandit voivat olla vain totuusarvoisia lausekkeita: vertailuja tai totuusarvoisia lausekkeita. ("&", "|" ja "^" ovat myös bittijono-operaatioita.)

Ehdollinen lauseke

Javassa on näppärä väline valita kahdesta lausekkeesta jomman kumman arvo:
   (totuusarvo ? lauseke1 : lauseke2)
Totuusarvoinen lauseke lasketaan. Jos sen arvo on true, ehdollisen lausekkeen arvo on lauseke1, muuten lauseke2. (Sulkeet eivät ole välttämättömät, mutta suositeltavat.)

Joko lauseke1:n täytyy olla tyyppiä, joka ilman eksplisiittistä tyyppimuunnosta on sijoitettavissa lauseke2:n tyyppisen muuttujan arvoksi tai päinvastoin. Ehdollisen lausekkeen tyyppi on valittavien lausekkeiden tyypeistä laajempi.

Esimerkiksi ("a:lle ei-pienempi arvoista b ja c") ohjelmoidaan if-lauseella:

   if (b < c)
     a = c;
   else
     a = b;
ja ehdollisena lausekkeena:
   a = (b < c  ?  c  :  b);
On makuasia, kumpi tapa on selkeämpi, mutta ei ole makuasia, että valintaperusteena on oltava selkeys!

Laskentajärjestys

Javassa lausekkeiden laskentajärjestys on täsmällisesti määritelty: monista muista ohjelmointikielistä poiketen ohjelmoija voi siis olettaa, että missä tahansa ohjelmaa suoritetaan, lausekkeet lasketaan aina samassa järjestyksessä. Tällä on merkitystä, kun lausekkeen osien laskenta aiheuttaa sivuvaikutuksia. Hyvään ohjelmointityyliin ei kuitenkaan kuulu lausekkeiden rasittaminen monilla sivuvaikutuksilla! Ilo tästä Javan lupauksesta on, että yhdessä toteutuksessa testatun ohjelman pitäisi toimia täsmälleen samoin muissa toteutuksissa.

Pääsääntö laskentajärjestyksestä on, että samantasoiset (yhtä sitovat, kts. jäljempänä) operaatiot lasketaan vasemmalta oikealle:
Lausekkeessa x + y + z lasketaan ensin x:n arvo (mikä x sitten onkaan, vaikkapa tulostavan metodin kutsu tai y:n arvoa muuttava sijoitus!), sitten lasketaan y:n arvo. Seuraavaksi lasketaan x:n ja y:n summa, sitten z ja lopuksi lasketaan lausekkeen jälkimmäinen yhteenlasku. Esimerkiksi:

   // Tästä esimerkistä ei pidä ottaa mallia!!

   int i=2, j=5;
   System.out.println( (j+=7)  +  ++i  +  (i+j) );
   //                    x         y        z
   //                   12
   //                              3
   //                         15
   //                                      15
   //                                 30
  
   // tulostuu siis 30!

Kuten edellä jo todettiin, poikkeus laskentajärjestykseen on ketjutettu sijoitus: se lasketaan oikealta vasemmalle!

Javassa lausekkeen kaikki osat lasketaan, jopa silloin kun jossakin osassa sattuu vaikkapa nollalla jako. Ainoat poikkeukset ovat &&, || ja ? (ehdolliset and ja or sekä ehdollinen lauseke).

Operaatioiden sitovuus

Operaatioiden sitovuus (operator precedence) on sopimus siitä, mitkä operaatiot lasketaan ensin, mitkä myöhemmin, ts. mitkä sitovat tiukemmin, mitkä vähemmän tiukasti.

Tätä harjoiteltiin jo alakoulussa. Kun kysyttiin, mitä on

     3 + 4 x 5
kiitosta sai vastauksesta "23", moitteita vastauksesta "35". Näin opeteltiin aritmetiikan kielipeliä.

Operaatioiden sitovuus (tiukimmasta löysimpään) (luettelo on täydellinen; mukana on myös operaatioita joita kurssilla ei käsitellä):


 [] . () e++ e--       indeksointi, komponenttiin viittaus,
                       sulkulausekkeet, parametrit,
                       kasvatus/vähennys jälkeenpäin

 ++e --e +e -e !       kasvatus/vähennys ennen, etumerkit, 
                       boolean not

 new  (tyyppi)e        olion luonti, ekspl. tyyppimuunnos

 * / %                 kertolaskut

 + -                   yhteenlaskut

 << >> >>>             bittisiirrot

 < > >= <= instanceof  vertailut

 == !=                 yhtäsuuruusvertailut

 &                     and (bittijonolle)

 ^                     xor (bittijonolle)

 |                     or  (bittijonolle)

 &&                    ehdollinen and

 ||                    ehdollinen or

 ?                     ehdon soveltaminen

 = += -= *= /= %=      sijoitusoperaatiot
 <<=  >>= >>>= 
 &= ^= |= 

Huom: Jos ei muista operaatioiden sitovuutta, sulkeilla voi itse määrätä laskentajärjestyksen! Usein niillä voi myös parantaa ohjelman luettavuutta ja selkeyttä.


Takaisin luvun 3 sisällysluetteloon.