1. Java–kielen muuttujista ja aliohjelmista

Turha koodi muuttujitta,

ompi onneton ohjelmaksi.

Parametri kutsuun pistä

aliohjelmalle argumentti.

 

Tarjoappa käyttöön tuota

metodia mielekästä

rutiinia riittävätä

itse tarkoin testattua.

 

 

Mitä tässä luvussa käsitellään?

·      muuttujat

·      malliohjelma jossa tarvitaan välttämättä muuttujia

·      oliomuuttujat eli viitemuuttujat

·      aliohjelmat, eli funktiot (metodit)

·      aliohjelman testaaminen

·      erilaiset aliohjelmien kutsumekanismit

·      parametrin välitys

·      lokaalit muuttujat

·      pöytätesti

 

Syntaksi:

Seuraavassa muut = muuttujan nimi, koostuu kirjaimista,0-9,_, ei ala 0-9

muut.esittely:    tyyppi muut = alkuarvo;            // 0-1 x =alkuarvo

sijoitus:         muut = lauseke;

merkkijonon lukeminen,  ks. Syotto-luokka

aliohj.esittely:  tyyppi aliohj_nimi(tyypi muut, tyyppi muut);  // 0-n x muut

aliohj.kutsu      muut = aliohj_nimi(arvo, arvo);    // 0-1 x muut=, 0-n x arvo

olion luonti      Tyyppi olion_nimi = new Tyyppi(parametrit);

 

Luvun esimerkkikoodit:

http://www.mit.jyu.fi/~vesal/kurssit/ohj2/moniste/esim/java-muut/

1.1 Mittakaavaohjelman suunnittelu

Satunnainen matkaaja ajelee tällä kertaa kotimaassa.  Autoillessa hänellä on käytössä Suomen tiekartan GT –karttalehtiä, joiden mittakaava on 1:200000.  Viivoittimella hän mittaa kartalta milleinä matkan, jonka hän aikoo ajaa.  Ilman matikkapäätä laskut eivät kuitenkaan suju.  Siis hän tarvitsee ohjelman, jolla matkat saadaan muutettua kilometreiksi. 

Millainen ohjelman toiminta voisi olla?  Vaikkapa seuraavanlainen:

C:\OMA\MATKAAJA>matka[RET]

Lasken 1:200000 kartalta millimetreinä mitatun matkan

kilometreinä luonnossa.

Anna matka millimetreinä>35[RET]

Matka on luonnossa 7.0 km.

C:\OMA\MATKAAJA>matka[RET]

Lasken 1:200000 kartalta millimetreinä mitatun matkan

kilometreinä luonnossa.

Anna matka millimetreinä>352[RET]

Matka on luonnossa 70.4 km.

C:\OMA\MATKAAJA>

 

Edellisessä toteutuksessa on vielä runsaasti huonoja puolia.  Mikäli samalla haluttaisiin laskea useita matkoja, niin olisi kätevämpää kysellä matkoja kunnes kyllästytään laskemaan.  Lisäksi olisi ehkä kiva käyttää muitakin mittakaavoja kuin 1:200000.  Muutettava matka voitaisiin tarvittaessa antaa jopa ohjelman kutsussa.  Voimme lisätä nämä asiat ohjelmaan myöhemmin, kunhan kykymme siihen riittävät.  Toteutamme nyt kuitenkin ensin mainitun ohjelman.

1.2 Muuttujat

Ohjelmamme  poikkeaa aikaisemmista esimerkeistä siinä, että nyt ohjelman sisällä tarvitaan muuttuvaa tietoa: matka millimetreinä.  Tällaiset muuttuvat tiedot talletetaan ohjelmointikielissä muuttujiin.  Muuttuja on koneen muistialueesta varattu tarvittavan kokoinen "muistimöhkäle", johon viitataan käytännössä muuttujan nimellä. 

Kone viittaa muistipaikkaan muistipaikan osoitteella.  Kääntäjäohjelman tehtävä on muuttaa muuttujien nimiä muistipaikkojen osoitteiksi.  Kääntäjälle täytyy kuitenkin kertoa aluksi minkä kokoisia 'möhkäleitä' halutaan käyttää.  Esimerkiksi kokonaisluku voidaan tallettaa pienempään tilaan kuin reaaliluku.  Mikäli haluaisimme varata vaikkapa muuttujan, jonka nimi olisi matka_mm kokonaisluvuksi, kirjoittaisimme seuraavan Java-kielisen lauseen (muuttujan esittely): 

int matka_mm; // yksinkertaisen tarkkuuden kokonaisluku

 

Pascal –kielen osaajille huomautettakoon, että Pascalissahan esittely oli päinvastoin:

var matka_mm: integer;

 

Tulos, eli matka kilometreinä voitaisiin laskea muuttujaan matka_km.  Tämän muuttujan on kuitenkin oltava reaalilukutyyppinen (ks. esimerkkiajo), koska tulos voi sisältää myös desimaaliosan: 

double matka_km; // kaksinkertaisen tarkkuuden reaaliluku

 

On olemassa myös yksinkertaisen tarkkuuden reaaliluku float, mutta emme tarvitse sitä tällä kurssilla.  Samoin kokonaisluvusta voidaan tehdä "tosi lyhyt", "lyhyt" tai "kaksi kertaa isompi":  

int   matka_km; 

short sormia;                // max 32767

byte  varpaita;              // max 127

long  valtion_velka_Mmk;     // Tarvitaan ISO arvoalue 

 

Muuttujan määritys voisi olla myös

volatile static long sadasosia;

 

Tulemme kuitenkin aluksi varsin pitkään toimeen pelkästään seuraavilla perustyypeillä:

short    - kokonaisluvut –32 768 –  32 767,  16-bit

int      – kokonaisluvut -2 147 483 648 - 2 147 483 647, 32-bit

double   – reaaliluvut n. 15 desim. –> 1.7e308

char     – kirjaimet 16 bit Unicode

boolean  - true tai false

 

Katso lisää Javan tietotyypeistä linkistä:

http://java.sun.com/docs/books/tutorial/java/nutsandbolts/datatypes.html

 

1.2.1 Matkan laskeminen

Ohjelman  käyttämä mittakaava kannattaa sijoittaa ehkä vakioksi, tällöin ainakin ohjelman muuttaminen on helpompaa.  Samoin vakioksi kannattaa sijoittaa tieto siitä, paljonko yksi km on millimetreinä (1 km = 1000 m, 1 m = 1000 mm).  Ohjelmastamme tulee tällöin esimerkiksi seuraavan näköinen:

java-muut\Matka.java - mittakaavamuunnos 1:200000 kartalta

import java.io.*;

/**

 * Ohjelmalla lasketaan mittakaavamuunnoksia 1:200000 kartalta

 * @author Vesa Lappalainen

 * @version 1.0 / 05.01.2003

 */

class Matka {

  static final double MITTAKAAVA = 200000.0;

  static final double MM_KM      = 1000.0*1000.0;

 

  public static void main(String[] args) {

    int    matka_mm;

    double matka_km;

 

    // Ohjeet

    System.out.println("Lasken 1:" + MITTAKAAVA +

                       " kartalta millimetreinä mitatun matkan");

    System.out.println("kilometreinä luonnossa.");

 

    // Syöttöpyyntö ja vastauksen lukeminen

    System.out.print("Anna matka millimetreinä>");

    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

    String s = "";

    try {

      s = in.readLine();

    } catch (IOException ex) {

    }

    if ( s == null ) return;

    if ( s.equals("") ) return;

    matka_mm = Integer.parseInt(s);

 

    // Datan käsittely

    matka_km = matka_mm*MITTAKAAVA/MM_KM;

 

    // Tulostus

    System.out.println("Matka on luonnossa " + matka_km + " km.");

  }

}

 

Lukija huomatkoon, että muuttujien ja vakioiden nimet on pyritty valitsemaan siten, ettei niitä tarvitse paljoa selitellä.  Tästä huolimatta isommissa ohjelmissa on tapana kommentoida muuttujan esittelyn viereen muuttujan käyttötarkoitus.  Mekin pyrimme tähän myöhemmin.

Valitettavasti Javan suoraan sanoen alkeiskäyttöön kelvottoman IO:n takia ohjelman kohta "Syöttöpyyntö ja vastauksen lukeminen" venähti toivottoman pitkäksi.  No toisaalta näemme kohta sitäkin paremmin yleiskäyttöisten alirutiinien hyödyt.

Tehtävä 8.1    Vakion korvaaminen

Kokeile ottaa vakioiden edestä pois sana static.  Mitä tällöin tapahtuu ja miksi?  Onko final-sanan poistamisella sama vaikutus (palauta ensin static)?

 

1.2.2 Muuttujan nimeäminen

Muuttujien nimissä on sallittuja kaikki kirjaimet (myös skandit, itse asiassa kaikki Unicode-kirjaimet) sekä numerot (0–9) sekä alleviivausviiva (_).  Muuttujan nimi ei kuitenkaan saa alkaa numerolla.  Muuttujia saa esitellä (declare) useita samalla kertaa, kunhan muuttujien nimet erotetaan toisistaan pilkulla.  Yleinen Java-tapa on että muuttujan nimi alkaa pienellä kirjaimella ja sen jälkeen jokainen muuttujan nimessä oleva alkava sana alkaa isolla kirjaimella (parasTulos).

Muuttujan nimi ei myöskään saa olla mikään vakioista (literal): 

true  false  null

 

eikä mikään seuraavista avainsanoista (keyword):

abstract

assert

boolean

break

byte

case

catch

char

class

const *

continue

default

do

double

else

extends

final

finally

float

for

goto *

if

implements

import

instanceof

int

interface

long

native

new

package

private

protected

public

return

short

static

strictfp **

super

switch

synchronized

this

throw

throws

transient

try

while

void

volatile

 

Tähdellä (*) merkityt sanat on varattu myöhempään käyttöön.

Vaikka muuttujan nimi saakin sisältää skandeja, kannattaa niiden käytöstä pidättäytyä toistaiseksi ainakin luokkien nimissä, koska luokan nimi on samalla tiedoston nimi ja skandit tiedostojen nimissä aiheuttavat edelleen ongelmia.

Javan nimeämiskäytännöistä katso lisää linkistä:

http://java.sun.com/docs/codeconv/html/CodeConventions.doc8.html

Tehtävä 8.2    Avainsanat

Merkitse edelliseen taulukkoon kunkin avainsanan viereen se, missä kohti monistetta ko. sana on selitetty.

 

Tehtävä 8.3    Muuttujan nimeäminen

Mitkä seuraavista ovat oikeita muuttujan esittelyjä ja mitkä niistä ovat hyviä:

 

int    o;

int    9_kissaa;

int    _9_kissaa;

double pitkä_matka, pitkaMatka;

int    i, j, kissojen_maara, kissojenMäärä;

int    auto, pyora, juna;

 

1.2.3 Muuttujalle sijoittaminen =

Muuttujalle voidaan antaa ohjelman aikana uusia arvoja käyttäen sijoitusoperaattoria = tai ++,––,+=,–=,*= jne. –operaattoreilla.

Sijoitusmerkin = vasemmalle puolelle tulee muuttujan nimi ja oikealle puolelle mikä tahansa lauseke, joka tuottaa halutun tyyppisen tuloksen (arvon).  Lausekkeessa voidaan käyttää mm. operaattoreita +,–,*,/ ja funktiokutsuja.  Lausekkeen suoritusjärjestykseen voidaan vaikuttaa suluilla ( ja ):

kenganKoko   = 42;

pi           = 3.14159265358979323846;

// usein käytetään Math-luokan PI vakiota

pi           = Math.PI;

pinta_ala    = leveys * pituus;

ympyranAla   = pi*r*r;

hypotenuusa  = vastainen_kateetti/sin(kulma);

matka_km     = matka_mm*MITTAKAAVA/MM_KM;

 

Seuraava sijoitus on tietenkin mieletön:

r*r = 5.0; /* MIELETÖN USEIMMISSA OHJELMOINTIKIELISSA! */              L

 

Eli sijoituksessa tulee vasemmalla olla sen muistipaikan nimi, johon sijoitetaan ja oikealla arvo joka sijoitetaan.

Huom! Java–kielessä = merkki EI ole yhtäsuuruusmerkki, vaan nimenomaan sijoitusoperaattori.  Yhtäsuuruusmerkki on = =.

Tehtävä 8.4    Muuttujien esittely

Esittele edellisissä sijoitus –esimerkeissä tarvittavat muuttujat.

 

1.2.4 Muuttujan esittely ja alkuarvon sijoittaminen

Muuttujan esittelyn (declaration) yhteydessä muuttujalle voidaan antaa myös alkuarvo (alustus, definition).  Muuttujien alustaminen tietyllä arvolla on tärkeää, koska alustamattoman muuttujan arvo saattaa olla hyvinkin satunnainen.  Alustamattoman muuttujan käyttö onkin jälleen eräs tyypillinen ohjelmointivirhe.  Java-kääntäjä tosin ilmoittaa virheenä jos muuttujaa yritetään käyttää ennenkuin  sille on annettu alkuarvo.

int    kengan_koko = 32, takin_koko = 52;

double pi = Math.PI, r = 5.0;

 

1.3 Muuttujan arvon lukeminen päätteeltä

Javassa tosiaan on tehty melkoisen vaikeaksi tietojen lukeminen päätteeltä.  Monissa muissa kielissä esimerkiksi kokonaisluvun lukemista varten on huomattavasti yksinkertaisemmat rakenteet tarjolla:

  scanf("%d",&matka_mm);   /*  C-kieli */

  cin >> matka_mm;         // C++ -kieli

  readln(matka_mm);        // Pascal-kieli 

 

Rehellisyyden nimissä on kyllä sanottava ettei oikeassa elämässä mikään noistakaan ole hyvä käytännön ratkaisu.  Jos käyttäjä syöttää muuta kuin kokonaisluvun, on virheestä toipuminen kaikissa esitetyissä kielissä varsin työlästä.

Usein helpoin ratkaisu onkin lukea tieto ensin merkkijonoon ja sitten "kaivaa" merkkijonosta tarvittava informaatio.  Tästä saadaan lisäetuna samalla se, että voidaan käsitellä myös muita kuin numeerisia arvoja eikä ohjelmasta tarvitse tehdä sellaista että jokin tietty luku tarkoittaa ohjelman lopettamista:

Anna lukuja (-99 lopettaa) >                                           L

 

1.3.1 Lukeminen merkkijonoon

Javan IO-systeemi on varsin monimutkainen.  Sitä ei olekaan suunniteltu aloittelevaa käyttäjää silmällä pitäen, vaan mahdollisimman laajennettavaksi.  Sellaiseksi että samoilla luokilla voitaisiin hoitaa tiedon lukeminen tiedostosta ja verkosta.

    // Syöttöpyyntö ja vastauksen lukeminen

    System.out.print("Anna matka millimetreinä>");

    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

    String s = "";

    try {

      s = in.readLine();

    } catch (IOException ex) {

    }

 

Alkuun tarvitsemme olion, joka pystyy lukemaan kokonaisen rivin ja tunnistaa meidän puolestamme rivin lopun.  Tämä saadaan aikaiseksi yhdistämällä System-luokan olio in lukijaan (InputStreamReader) ja yhdistämällä se puskuroituun lukijaan (BufferedReader):

    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

 

Sama voitaisiin tehdä useammallakin lauseella:

    InputStreamReader instream = new InputStreamReader(System.in);

    BufferedReader in = new BufferedReader(instream);

 

Tässä tapauksessa emme kuitenkaan tarvitse itse käyttää apuluokkaa instream, joten tyydymme yhden rivin versioon.

Saatu uusi olio in pystyy lukemaan päätteeltä tietoa.  Esimerkiksi metodi read­Line lukee kokonaisen rivin.  Eli käyttäjä syöttää merkkejä päätteelle ja painaa Enter.  Jos tulee jokin ongelma syöttövirran kanssa olio heittää poikkeuksen IOException.  Tässä tapauksessa emme välitä poikkeuksista muuta kuin, että se on otettava vastaan (catch).

Nyt lohkon

    String s = "";

    try {

      s = in.readLine();

    } catch (IOException ex) {

    }

  

jälkeen merkkijono-oliossa s on joko päätteeltä luettu arvo tai mikäli jokin meni vikaan, niin tyhjä merkkijono.  Vielä on mahdollista että syöttövirta katkaistiin kesken kaiken. Windows-konsolilla tämä tapahtuu jos painetaan Ctrl-Z ja Unix/Linux-konsolilla Ctrl-C.  Tällöin olioviite s ei viittaa mihinkään (sen arvo on null).

Siksipä tutkimmekin seuraavaksi mistä on kyse ja lopetamme ohjelman ilman sen suurempia mukinoita:

    if ( s == null ) return;

    if ( s.equals("") ) return;

 

Tuon voi kirjoittaa myös yhdelle riville, koska Javan ||-operaattori (tai)  suorittaa totuusarvoista lauseketta vain siihen saakka kunnes totuusarvo selviää:

    if ( ( s == null ) || ( s.equals("") ) ) return;

 

Huomattakoon että myös muoto

    if ( ( s == null ) | ( s.equals("") ) ) return;

 

on syntaktisesti oikein, mutta tarkoittaa hieman eri asiaa.  Looginen lopputulos molemmissa on ehdon lausekkeelle sama.  Mutta | -operaattorilla molemmat lausekkeet suoritetaan aina.  Ja tässä tapauksessa tämä olisi virhe jos s olisi null.

1.3.2 Lukuarvon selvittäminen merkkijonosta

Kaiken edellä mainitun jälkeen meillä on käytössä oliossa s käyttäjän syöttämä merkkijono.  Seuraava ongelma on saada tämä merkkijono muutettua numeroksi, jolla voidaan jopa jotakin laskeakin.  Kokonaisluvun tapauksessa tämä onnistuu käyttämällä luokkaa Integer ja pyytämällä tätä selvittämään luvun arvon:

    matka_mm = Integer.parseInt(s);

 

Mikäli käyttäjä on kiltisti syöttänyt kokonaisluvun, niin kaikki menee hienosti.  Mutta jos käyttäjä antaa merkkijonon, joka on jotakin muuta kuin kokonaisluku, niin silloin parseInt heittää poikkeuksen:

bash-2.05a$ java Matka

Lasken 1:200000.0 kartalta millimetreinä mitatun matkan

kilometreinä luonnossa.

Anna matka millimetreinä>kolme

Exception in thread "main" java.lang.NumberFormatException: kolme

        at java.lang.Integer.parseInt(Integer.java:414)

        at java.lang.Integer.parseInt(Integer.java:463)

        at Matka.main(Matka.java:32)

bash-2.05a$

 

Jos haluamme tästäkin siististi selvitä ja vielä ystävällisesti huomauttaa käyttäjälle, tarvitsee muunnoksen ympärille laittaa myös poikkeuskäsittely ja vielä koko lukeminen silmukkaan.  Kaikkien näiden muutosten jälkeen pelkkä yhden kokonaisluvun lukeminen viekin jo likemmäksi 20 riviä ja "sotkee" muuten yksinkertaisen ohjelmamme rakenteen lähes täysin.

1.3.3 Apumetodit

Tämän takia onkin ilman muuta järkevää eristää lukemiskoodi omaksi metodikseen:

  /**

   * Kysytään kokonaisluku. Jos annetaan ei-luku, kysytään uudelleen.

   * @param kysymys näytölle tulostettava kysymys

   * @param oletus arvo jota käytetään jos painetaanpelkkä Ret

   * @return käyttäjän kirjoittama kokonaisluku

   */

  public static int kysy_int(String kysymys, int oletus)

  {

    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

    while ( true ) {

      System.out.print(kysymys+" >");

      String s = "";

      try {

        s = in.readLine();

      } catch (IOException ex) {

        continue; // jatkaa silmukkaa

      }

      if ( ( s == null ) || ( s.equals("") ) ) return oletus;

      try {

        return Integer.parseInt(s);

      } catch (NumberFormatException ex) {

        System.out.println("Ei numero: " + s);

      }

    }

  }

 

Nyt omassa ohjelmassamme voidaan korvata "koko hirveä sotku" vain yhdellä rivillä:

    matka_mm = kysy_int("Anna matka millimetreinä",0);

 

Lisäsimme aliohjelmaamme vielä kutsuun yhden parametrin:  oletus.  Näin voidaan käyttäjälle antaa mahdollisuus painaa pelkästään Enter ja silti saadaan järkevä vastaus. 

Tehtävä 8.5    Oletuksen tulostaminen

Lisää apumetodiin kysy_int vielä oletusarvon tulostaminen sulkuihin ennen väkäsen tulostamista.  Eli tulostus olisi:

Anna matka millimeterinä (0) >

 

1.3.4 Apuluokat

Seuraava kysymys sitten onkin että mihin tuo apumetodi lue_int kirjoitetaan?  Yksinkertainen vaihtoehto on kirjoittaa se joko ennen tai jälkeen main-metodia.  Tässä ratkaisussa olisi se huono puoli, että tuo metodi voisi olla käyttökelpoinen vaikka missä ohjelmassa.  Siksipä se kannattaa kirjoittaa omaan luokkaansa.  Mutta mihin tämä luokka.  Yleiskäyttöisyyden nimissä tuo luokka kannattaa kirjoittaa omaan tiedostoonsa.

Kirjoitammekin koodin vaikkapa tiedostoon Syotto.java:

java-muut\Syotto.java – kokonaisluvun lukeminen päätteeltä

import java.io.*;

 

/**

 * Aliohjelmia tietojen lukemiseen päätteeltä

 * @author Vesa Lappalainen

 * @version 1.0/08.01.2003

 */

public class Syotto {

  /**

   * Kysytään kokonaisluku. Jos annetaan ei-luku, kysytään uudelleen.

   * @param kysymys näytölle tulostettava kysymys

   * @param oletus arvo jota käytetään jos painetaanpelkkä Ret

   * @return käyttäjän kirjoittama kokonaisluku

   */

  public static int kysy_int(String kysymys, int oletus)

  {

...

  }

 

  public static void main(String[] args) {

    int i;

    i = kysy_int("Anna kokonaisluku",12);

    System.out.println("Luku oli: " + i);

  }

}

 

1.3.5 Luokan testaaminen

Olio-ohjelmoinnin - samoin kun minkä tahansa muun ohjelmoinnin - yksi tavoite on modulaarinen testaus.  Eli jokainen palanen testataan - jos suinkin vain mahdollista - omana kokonaisuutenaan.  Näin lopullinen ohjelma voidaan koostaa toimiviksi todetuista palikoista.

Syotto-luokkaan on myös kirjoitettu pääohjelma ja nyt testaus voidaan tehdä ensin pelkälle Syotto-luokalle ennen sen liittämistä muuhun ohjelmaan.  Komentoriviltä tämä tapahtuisi nyt vaikkapa:

bash-2.05a$ javac Syotto.java

bash-2.05a$ java Syotto

Anna kokonaisluku >

Luku oli: 12

bash-2.05a$ java Syotto

Anna kokonaisluku >392

Luku oli: 392

bash-2.05a$ java Syotto

Anna kokonaisluku >kolme

Ei numero: kolme

Anna kokonaisluku >0

Luku oli: 0

bash-2.05a$

 

Vaikka tässä tapauksessa luokka testattiinkin lukemalla tieto päätteeltä, ei missään tapauksessa pidä tätä yleistää.  Yleensä paras testiohjelma on sellainen, joka automaattisesti kokeilee testattavaa yksikköä (oliota, metodia) niillä arvoilla joilla sitä tulee kuormittaa.  Hyvä testiohjelma sitten kertoo millä arvoilla yksikkö toimi kuten pitikin ja millä ei toiminut.  Ihminen testaajana on kaikista testaajista huonoin, koska ihminen väsyy ja muutoksen jälkeen helposti laiskuuksissaan jättää testaamatta niillä arvoilla, jotka jo ennen muutosta oli testattu.  Kuitenkin muutos saattaa tuottaa virheitä jo testattuun osaan ja siksi testi pitää aina aloittaa aivan alusta jokaisen muutoksen jälkeen.

1.3.6 Luokan käyttäminen

Nyt kun uusi luokka, tai oikeastaan tässä tapauksessa uusi apumetodi, on huolellisesti testattu, se voidaan ottaa käyttöön.  Nyt kun yksinkertaisuuden vuoksi emme vielä käyttäneet paketteja, niin luokka löytyy jos se on samassa hakemistossa kuin sitä käyttävä luokka.  Eli ainoa muutos ohjelmakoodiin on kertoa mistä luokasta metodi kysy_int löytyy.

    matka_mm = Syotto.kysy_int("Anna matka millimetreinä",0);

 

Tehtävä 8.6    Muiden tyyppien lukeminen

Tee vastaavasti luokkaan Syotto metodit kysy_double ja kysyString.  Tuleeko paljon samanlaista koodia?  Kannattaisiko käyttää jotakin hyväksi?  Lisää luokan testiohjelmaan testi uusillekin metodeille.

Tehtävä 8.7    Mittakaavan kysyminen

Muuta matka–ohjelmaa siten, että myös mittakaava kysytään käyttäjältä.  Mikäli mittakaavaan vastataan pelkkä [RET] pitää mittakaavaksi tulla 1:200000.

 

1.4 Viitteet

1.4.1 Miksi viitteet?

C–kielessä osoittimet piti opetella heti ohjelmoinnin alussa, jos halusi tehdä minkäänlaisia järkeviä aliohjelmia.  C++:ssa ongelmaa voidaan kiertää viitemuuttujien (references) avulla.  Javassa on myös vastaava käsite, eli kaikki Javan olio-muuttujat ovat tosiasiassa viitemuuttujia.  Ne ovat kuitenkin tietyssä mielessä  perinteisen C:n osoittimen ja C++:n viitteen välimuoto.  Javan viitemuuttujan voi laittaa osoittamaan toistakin oliota kesken koodin.  C++:n viitemuuttuja osoittaa aina samaan olioon, mihin se luotiin osoittamaan. 

Tutkimme seuraavaksi Javan viitemuuttujien käyttäytymistä.  Tehdään ohjelma, jossa päällisin puolin näyttäisi olevan kaksi samanlaista merkkijonoa ja kaksi samanlaista kokonaislukuoliota.  Merkkijonot ovat Javassa olioita ja merkkijonomuuttujat viitteitä noihin olioihin.

java-muut\Jonotesti.java - merkkijonoviitteet

/**

 * Tutkitaan olioviitteiden käyttäytymistä

 * @author Vesa Lappalainen

 * @version 1.0, 08.01.20003

 */

class Jonotesti {

 

  private static void tulosta(boolean b) {

    if ( b ) System.out.println("Samat ovat");

    else System.out.println("Erilaiset ovat");

  }

 

  public static void main(String[] args) {

    String s1 = "eka";

    String s2 = new String("eka");

 

    tulosta(s1 == s2);       // Erilaiset ovat

    tulosta(s1.equals(s2));  // Samat ovat

 

    int i1 = 11;

    int i2 = 10 + 1;

 

    tulosta(i1 == i2);       // Samat ovat

 

    Integer io1 = new Integer(3);

    Integer io2 = new Integer(3);

 

    tulosta(io1 == io2);       // Erilaiset ovat

    tulosta(io1.equals(io2));  // Samat ovat

    tulosta(io1.intValue()== io2.intValue()); // Samat ovat

 

    s2 = s1;

    tulosta(s1 == s2);         // Samat ovat

  }

}

 

 

Koodiin on rivien viereen kommentoitu mitä mikäkin rivi tulostaisi.

Javassa on kahden tyyppisiä muuttujia, aikaisemmin lueteltuja perustyyppisiä (boolean, char, byte, short, int, long, float, double) muuttujia ja sitten oliomuuttujia.  Oliomuuttujat Javassa ovat aina vain viitteitä todellisiin olioihin.  Edellisessä esimerkissä muuttujat s1,s2,io1,io2 ovat olioviitteitä.  Silti olioviitteistä puhekielessä käytetään helposti nimitystä olio. 

1.4.2 Lokaalit muuttujat

Ohjelman kaikki muuttujat ovat lokaaleja muuttujia.  Eli ne on esitelty lokaalisti main-metodin sisällä eivätkä "näy" näin ollen main-metodin ulkopuolelle.  Tällaisille muuttujille varataan tilaa yleensä kutsupinosta.  Kun kaikki muuttujat on esitelty ja alustettu, pino voisi hieman yksinkertaistettuna olla näiden lokaalien muuttujien kohdalta suurin piirtein seuraavan näköinen:

Kuva 8.1  Olioviitteet

1.4.3 Dynaaminen muisti

Javassa itse olioiden tila varataan muualta dynaamisen muistinhallinnan hoitamalta alueelta.  Usein tätä muistia nimitetään keko- tai kasamuistiksi (heap).  Kun ohjelmoija pyytää new-operaattorilla uuden olion, muistinhallinta etsii sopivan vapaan muistipaikan ja palauttaa viitteen tähän muistipaikkaan.  Todellisuudessa olioviitteet ovat hieman monimutkaisempia.  Asiasta voi lukea lisää sivuilta:

http://java.sun.com/docs/books/vmspec/html/Overview.doc.html

 

Asian ymmärtämiseksi meille kuitenkin riittää yllä piirretty yksinkertaistettu malli.

1.4.4 Viitteiden vertaaminen

Vaikka molemmat viitteet s1 ja s2 osoittavat sisällöltään samanlaiseen olioon, palauttaa vertailu

( s1 == s2 )     // onko s1 sama kuin s2, => true tai false

 

epätoden arvon.  Miksikö?  Koska vertailussa verrataan muuttujien arvoja, ei niitä arvoja, joihin muuttujat viittaavat.  Esimerkissä on kuviteltu että ensimmäinen "eka"-merkkijono olisi sijoittunut muistissa osoitteeseen 8010 ja toinen osoitteeseen 8040.  Siis itse asiassa kysytäänkin:

( 8010 == 8040 )

 

mikä ei ole totta.  Javan primitiivityypit sen sijaan sijoittuvat suoraan arvoina pinomuistiin (tai myöhemmin olioiden attribuuttien tapauksessa oliolle varattuun muistialueeseen).  Siksi vertailu

( i1 == i2 )

 

on totta.  Merkkijonoja vastaavasti myös kokonaislukuoliot io1 ja io2 käyttäytyvät samalla tavalla.  Javassa on kokonaislukuoliot sitä varten, että primitiivityyppejä ei voi tallentaa Javan tietorakenneluokkiin.  Piilottamalla primitiivityyppejä "kääreeseen", voidaan näitä "kääreitä" sitten tallentaa tietorakenteisiin.

1.4.5 Viitteeseen sijoittaminen

Jos sijoitetaan "olio" toiseen "olioon", niin tosiasiassa sijoitetaan viitemuuttujien arvoja, eli sijoituksen s2 = s1 jälkeen molemmat merkkijono-olioviitteet "osoittavat" samaan olioon.

 

Kuva 8.2  Kaksi viitettä samaan olioon

 

Sijoituksen jälkeen kuvassa muistipaikkaan 8040 ei osoita (viittaa) enää kukaan ja tuo muistipaikka muuttuu "roskaksi".  Kun Javan roskienkeruu (garbage-collection, gc) seuraavan kerran käynnistyy, "vapautetaan" tällaiset käyttämättömät muistialueet.  Tätä automaattista roskienkeruuta on pidetty yhtenä syynä Javan menestykseen.  Samalla täytyy kuitenkin varoittaa että muisti on vain yksi resurssi ja Javassa on automatiikka vain muistin hoitamiseksi.  Muut resurssit kuten esimerkiksi tiedostot ja tietokannat pitää edelleen hoitaa samalla huolellisuudella kuin muissakin kielissä.  Jopa C++:aa huolellisemmin, koska Javassa ei ole C++:n tapaan automaattisia olioita.

Javan viitemuuttuja voidaan siis laittaa "osoittamaan" milloin tahansa toista oliota.  Tämä tapahtuu sijoittamalla viitemuuttujaan joko olemassa olevan olion viite

    s2 = s1;    // laitetaan s2 viittaamaan samaan paikkaan kuin s1

 

tai luomalla uusi olio,

    String s2 = new String("eka");  // laitetaan s2 viittaamaan uuteen olioon

 

jolloin new-operaattorin palauttama viite sijoitetaan. Käytännössä Javan viitteet ovat siis oikeastaan osoittimia.  Javan viitteillä ei kuitenkaan voi "edetä" C++:n osoittimien tapaan (esim. s1++).  Tämä osoitinaritmetiikan puute on toinen Javan hyväksi puoleksi usein mainostettu ominaisuus (tosin ääneen tämä sanotaan "Javassa ei ole osoittimia", lisäksi on tosin totta että Javassa ei todellakaan ole viitteitä tai osoittimia primitiivityyppeihin).

1.4.6 null-viite

Viitemuuttujan arvo voi olla myös null.  Tämä tarkoittaa ettei oliomuuttuja viittaa mihinkään todelliseen olioon ja tällaista viitemuuttujaa ei saa käyttää ennen kuin siihen on sijoitettu jonkin todellisen olion viite.  Yksi Java-ohjelmien yleisimmistä virheistä onkin "null pointer reference" kun ohjelmoija ei ole huolellinen viitteiden kanssa.

Hyvin usein pitää siis testata

if ( s1 != null ) { // nyt voi käyttää s1 viitettä huoletta

1.5 Aliohjelmat (metodit, funktiot)

Eräs ohjelmoinnin tärkeimmistä rakenteista on aliohjelma. C–kielessä kaikkia eri tyyppisiä aliohjelmia nimitetään funktioiksi; joissakin muissa kielissä eri tyyppejä erotetaan eri nimille.  Javassa oikeastaan aliohjelmia nimitetään metodeiksi.  Kuitenkin kaikkia tähän asti käytettyjä metodeja voidaan suhteellisen hyvällä omallatunnolla nimittää aliohjelmiksi tai C:n tapaan funktioiksi.  Aikaisempien esimerkkien metodit nimittäin kaikki ovat olleet static-määreellä varustettuja metodeja ja tällaisten metodien virallinen nimi on luokkametodi.  Lisäksi kun esimerkkiemme luokkametodit eivät ole koskeneet mihinkään luokan ominaisuuteen, ei metodeilla ole oikeastaan ollut luokan kanssa muuta tekemistä kuin että ne ovat olleet luokan sisällä.  Tällöin niitä voi aivan hyvin kutsua aliohjelmiksi.  Luokan merkitys on toistaiseksi ollut vain pitää joukkoa metodeja omassa "nimiavaruudessaan".  C++:ssa vastaava rakenne hoidetaankin yleensä käyttäen nimiavaruuksia.

Aliohjelmaa käytetään seuraavissa tapauksissa:

1.  Haluttu tehtävä on valmiiksi jonkun toisen kirjoittamana aliohjelmana esimerkiksi standardikirjastossa  (y=Math.sin(x))

2.  Haluttua tehtävää suoritetaan usein liki samanlaisena joko samassa ohjelmassa tai jossain toisessa ohjelmassa.

3.  Haluttu tehtävä muodostaa selvän kokonaisuuden, jonka toiminta on ilmaistavissa muutamalla sanalla riittävän selkeästi (= aliohjelman nimi).

4.  Haluttua tehtävää ei juuri sillä hetkellä osata tai viitsitä ohjelmoida.  Tällöin määritellään millainen aliohjelma tarvitaan ja kirjoitetaan tarvittavaan kohtaan pelkkä aliohjelman kutsu.  Itse aliohjelma voidaan aluksi toteuttaa varsin yksinkertaisena ja korjata myöhemmin tekemään sen varsinainen tehtävä.

5.  Rakenne saadaan selkeämmän näköiseksi.

1.5.1 Parametriton aliohjelma

Aliohjelma esitellään vastaavasti kuin "pääohjelmakin", eli Javan main-metodi.  Esimerkiksi satunnaisen matkaajan mittakaavaohjelmassa voisimme kirjoittaa käyttöohjeet omaksi aliohjelmakseen:

java-muut\Matka_a1.java - ohjeet aliohjelmaksi

import java.io.*;

/**

 * Ohjelmalla lasketaan mittakaavamuunnoksia 1:200000 kartalta

 * @author Vesa Lappalainen

 * @version 1.0 / 05.01.2003

 */

class Matka_a1 {

  static final double MITTAKAAVA = 200000.0;

  static final double MM_KM      = 1000.0*1000.0;

 

  /**

   * Tulostaa ohjelman käyttöohjeet

   */

  private static void ohjeet() {

    System.out.println("Lasken 1:" + MITTAKAAVA +

                       " kartalta millimetreinä mitatun matkan");

    System.out.println("kilometreinä luonnossa.");

  }

 

 

  public static void main(String[] args) {

    int    matka_mm;

    double matka_km;

 

    ohjeet();

    matka_mm = Syotto.kysy_int("Anna matka millimetreinä",0);

 

    // Datan käsittely

    matka_km = matka_mm*MITTAKAAVA/MM_KM;

 

    // Tulostus

    System.out.println("Matka on luonnossa " + matka_km + " km.");

  }

}

 

Tämän etu on siinä, että saimme pääohjelman selkeämmän näköiseksi.

1.5.2 Funktiot ja parametrit

Voisimme jatkaa pääohjelman selkeyttämistä.  Tavoite voisi olla aluksi vaikkapa kirjoittaa pääohjelma muotoon:

    ohjeet();

    matka_mm = Syotto.kysy_int("Anna matka millimetreinä",0);

    matka_km = mittakaava_muunnos(matka_mm);

    tulosta_matka(matka_km);

 

Tällainen pääohjelma tuskin tarvitsisi paljoakaan kommentteja.

Edellä on käytetty kolmen eri tyypin aliohjelmia (funktioita)

1. ohjeet();– parametriton aliohjelma

2.  mittakaava_muunnos(matka_mm); – funktio, joka palauttaa tuloksen nimessään

3.  tulosta_matka(matka_km); – aliohjelma, jolle vain viedään arvo, mutta mitään arvoa ei palauteta

Valmis ohjelma, jossa myös aliohjelmat on esitelty, näyttäisi seuraavalta (rivien numerointi on myöhemmin esitettävää pöytätestiä varten):

java-muut\Matka_a3.java - erilaisia funktioita


/**

 * Ohjelmalla lasketaan mittakaavamuunnoksia 1:200000 kartalta

 * @author Vesa Lappalainen

 * @version 1.0 / 05.01.2003

 */

public class Matka_a3 {

  static final double MITTAKAAVA = 200000.0;

  static final double MM_KM      = 1000.0*1000.0;

 

  /**

   * Tulostaa ohjelman käyttöohjeet

   */

  private static void ohjeet() {

    System.out.println("Lasken 1:" + MITTAKAAVA +

                       " kartalta millimetreinä mitatun matkan");

    System.out.println("kilometreinä luonnossa.");

  }

 

  /**

   * Muuttaa mm mittakaavan mukaisesti kilometreiksi

   * @param matka_mm muutettavat millit

   * @return mittakavan mukaiset kilometrit

   */

  private static double mittakaava_muunnos(int matka_mm)

  {

    return matka_mm*MITTAKAAVA/MM_KM;

  }

 

  /**

   * Tulostaa matkan kilometreinä

   * @param matka_km tulostettava kilometrimäärä

   */

  private static void tulosta_matka(double matka_km)

  {

    System.out.println("Matka on luonnossa " + matka_km + " km.");

  }

 

  /**

   * Varsinainen pääohjelma matka kysymiseksi ja laskemiseksi

   * @param args ei käyttöä

   */

  public static void main(String[] args) {

    int    matka_mm;

    double matka_km;

 

    ohjeet();

    matka_mm = Syotto.kysy_int("Anna matka millimetreinä",0);

    matka_km = mittakaava_muunnos(matka_mm);

    tulosta_matka(matka_km);

  }

 

}


 

Edellä olevasta huomataan, että aliohjelmat jotka eivät palauta mitään arvoa nimessään, esitellään void–tyyppisiksi.

mittakaava_muunnos on reaaliluvun palauttava funktio, joten se esitellään double –tyyppiseksi.

Seuraavaksi pöytätestaamme ohjelmamme toiminnan: 

 

main

mi..muunnos

tulosta

 

lause

matka_mm

matka_km

matka_mm

tulos

matka_km

tulostus

46 ohjeet()

??

??

 

 

 

 

13-17 System

 

 

 

 

 

Lasken 1:200000

47 matka_mm=

352

 

 

 

 

Anna matka ...

48 matka_km

 

 

352

 

 

 

26 return

 

 

 

70.4

 

 

48 matka_km

 

70.4

 

 

 

 

49 tulosta

 

 

 

 

70.4

 

33-36 System

 

 

 

 

 

Matka on luo..

50 }

 

 

 

 

 

 

 

Emme enää käyneet läpi  sitä, mitä Syotto.kysy_int tekee, koska se oli testattu erikseen ja sen jälkeen aliohjelma voidaan käsittää "valmiina kieleen kuuluvana käskynä".

Mikäli kukin "omatekoinen" aliohjelmakin olisi testattu erikseen, riittäisi meille pelkkä pääohjelman testi: 

 

main

 

lause

matka_mm

matka_km

tulostus

46 ohjeet()

??

??

Lasken 1:200000

47 matka_mm=

352

 

Anna matka ..

48 matka_km

 

70.4

 

49 tulosta

 

 

Matka on luo..

50 }

 

 

 

 

Tämä on testaustapa, johon tulisi pyrkiä.  Isossa ohjelmassa ei ole enää mitään järkeä testata sitä jokainen aliohjelma kerrallaan.  Koodiin liitettyjen aliohjelmien tulee olla testattuja kukin erillisinä ja lopullinen testi on vain viimeisimmän mallin mukainen!

1.5.3 Parametrin nimi kutsussa ja esittelyssä

Huomattakoon, ettei parametrien nimillä aliohjelmien esittelyissä ja kutsuissa ole mitään tekemistä keskenään.  Nimi voi olla joko sama tai eri nimi.  Parametrien idea on nimenomaan siinä, että samaa aliohjelmaa voidaan kutsua eri muuttujien tai mahdollisesti vakioiden tai lausekkeiden arvoilla.  Esimerkiksi nyt kirjoitettua tulosta_matka aliohjelmaa voitaisiin kutsua myös seuraavasti:

java-muut\Matka_a4.java - erilaisia tapoja kutsua funktiota

/**

 * Esimerkkejä kutsua aliohjelmaa eri tavoin

 * @author Vesa Lappalainen

 * @version 1.0 / 05.01.2003

 */

public class Matka_a4 {

  /**

   * Tulostaa matkan kilometreinä

   * @param matka_km tulostettava kilometrimäärä

   */

  private static void tulosta_matka(double matka_km)

  {

    System.out.println("Matka on luonnossa " + matka_km + " km.");

  }

 

  /**

   * Varsinainen pääohjelma matka kysymiseksi ja laskemiseksi

   * @param args ei käyttöä

   */

  public static void main(String[] args) {

    double d = 50.2;

    tulosta_matka(d);         // eri niminen muuttuja

    tulosta_matka(30.7);      // vakio

    tulosta_matka(d+20.8);    // lauseke

    tulosta_matka(2*d-30.0);  // lauseke

  }

 

}

 

Edellä aliohjelman kutsut voidaan tulkita seuraaviksi sijoituksiksi aliohjelman tulosta_matka lokaaliin parametrimuuttujaan matka_km:

matka_km = d;

matka_km = 30.7;

matka_km = d+20.8;

matka_km = 2*d–30.0

 

Aliohjelma jouduttiin edellä vielä kirjoittamaan uudestaan (käytännössä kopioimaan edellisestä ohjelmasta), mutta myöhemmin opimme miten aliohjelmia voidaan kirjastoida standardikirjastojen tapaan (ks. moduuleihin jako), jolloin kerran kirjoitettua aliohjelmaa ei enää koskaan tarvitse kirjoittaa uudestaan (eikä kopioida).

1.5.4 Nimessään arvon palauttavat funktiot

Funktion arvo palautetaan return –lauseessa.  Jokaisessa ei-void –tyyppiseksi esitellyssä funktiossa tulee olla vähintään yksi return –lause.  void–tyyppisessäkin voi olla return–lause.  Tarvittaessa return–lauseita voi olla useampiakin:

public static int suurempi(int a, int b)

{

  if ( a >= b ) return a;

  return b;

}

 

Kun return –lause tulee vastaan, lopetetaan HETI funktion suoritus.  Tällöin myöhemmin olevilla lauseilla ei ole mitään merkitystä.  Näin ollen useat return–lauseet ovat mielekkäitä vain ehdollisissa rakenteissa.  Siis seuraavassa ei olisi mitään mieltä:

public static int hopo(int a)                                          L

{

  int i;

  return 5;  /* Palauttaa aina 5!!! */

  i = 3 + a;

  return i+2;

}

 

return–lausetta ei saa sotkea siihen, että parametrina vietyjä olioita voidaan pyytää muuttamaan sisältöään funktion aikana:

java-muut\FunJaOlio.java - sivuvaikutuksellinen funktio

/**

 * Esimerkki funktiosta joka muuttaa myös parametriään

 * @author Vesa Lappalainen

 * @version 1.0 / 05.01.2003

 */

public class FunJaOlio {

 

  private static int pituus_ja_muuta(StringBuffer s)

  {

    int pit = s.length();

    s.delete(0,pit).append("toka"); // pääohjelman jono muuttuu nyt

    return pit;

  }

 

  public static void main(String[] args) {

    int i; StringBuffer jono = new StringBuffer("eka");

    i = pituus_ja_muuta(jono);

    System.out.println("i=" + i + ", jono="+jono); // tulostaa: i=3, jono=toka

  }

 

}

 

Edellä ei kutsusta näe millään tavalla että kutsun jälkeen jono on muuttunut.  Yhtenä Java-kielen miinuksena voidaankin pitää sitä, että siitä puuttuu C++-kielessä oleva mekanismi suojata oliot muutoksilta aliohjelman suorituksen aikana (const).

Näin paljon jääkin ohjelmoijan vastuulle, eli ohjelmoijan pitää nimetä aliohjelmat siten, että niiden nimi jo paljastaa jos jotakin parametria muutetaan ohjelman suorituksen aikana.  Ja sitten aliohjelmat on tehtävä huolellisesti, etteivät ne todellakaan muuta kutsuparametrejaan jollei se ole aliohjelmien tarkoitus.

Tehtävä 8.8    Funktio ja osoitin

Mitä pääohjelma FunJaOlio tulostaisi jos aliohjelma olisikin ollut:

 

  private static int pituus_ja_muuta(StringBuffer s)

  {

    s.append("toka");

    return s.length();

  }

Tehtävä 8.9    String vs. StringBuffer

Kirjoita edellisestä tehtävästä versio jossa muutat kaikki StringBuffer => String ja korvaat append-metodin concat-metodilla. Mitä tulostuu?

 

1.5.5 Ketjutettu kutsu

Koska funktio–aliohjelma palauttaa valmiiksi arvon, voitaisiin Matka_a3.java:n pääohjelma kirjoittaa myös muodossa:

  public static void main(String[] args) {

    double matka_mm;

    ohjeet();

    matka_mm = Syotto.kysy_int("Anna matka  millimetreinä",0);

    tulosta_matka(mittakaava_muunnos(matka_mm));

  }

 

Funktioita käytetään silloin, kun aliohjelman tehtävänä on palauttaa vain yksi täsmällinen arvo.  Math–luokan funktioita ovat: 

abs, acos, asin, atan, atan2, ceil, cos, exp, floor, IEEEremainder, log, max, min, pow, random, rint, round, sin, sqrt, tan, toDegrees, toRadians 

 

Funktioita käytetään kuten matematiikassa on totuttu:

double alpha = 1.32, a = 4, b=3;

double c = Math.sqrt(a*a+b*b) + Math.asin((Math.sin(alpha)+0.2)/2.0);

 

kysy_matka ja kysy_mittakaava voitaisiin kirjoittaa myös funktioiksi, ja tällöin niitä voitaisiin kutsua esim. seuraavasti:

matka_km = kysy_matka()*kysy_mittakaava()/MM_KM;

 

Vaarana olisi kuitenkin se, ettei voida olla aivan varmoja kumpiko funktiosta kysy_matka vai kysy_mittakaava suoritettaisiin ensin ja tämä saattaisi aiheuttaa joissakin tilanteissa yllätyksiä.

Tämän vuoksi pyrimmekin kirjoittamaan funktioiksi vain sellaiset aliohjelmat, jotka palauttavat täsmälleen yhden arvon ja jotka eivät ota muuta informaatiota ympäristöstä kuin sen mitä niille parametrinä välitetään.  Eli tavoitteena on se, että funktioiden kutsuminen lausekkeen osana olisi turvallista.  Tämä ei valitettavasti ole aina Javassa mahdollista, koska Javan aliohjelmakutsuista puuttuu muissa kielissä oleva muuttujaparametrin välitys (Pascal: var, C: osoitin *, C++ referenssi &).

Muissa kielissä aliohjelmat kirjoitamme siten, että arvot palautetaan osoitteen avulla.  Hyvin yleinen C–tapa on kuitenkin palauttaa tällaisenkin aliohjelman onnistumista kuvaava arvo funktion nimessä (vrt. esim. scanf C-kielessä).

Tehtävä 8.10  Math-luokka

Katso SDK:n dokumenteista kunkin Mathluokan funktion parametrien määrä ja tyyppi sekä se mitä kukin todella tekee.

Tehtävä 8.11  Funktiot

Kirjoita edellä mainitut kysy_matka ja kysy_mittakaava nimessään arvon palauttavina funktioina.

Tehtävä 8.12  Ympyrän ala ja pallon tilavuus

Kirjoita funktiot, jotka palauttavat r–säteisen ympyrän pinta–alan ja r–säteisen pallon tilavuuden.

 

Kirjoita pääohjelma, jossa pinta–ala ja tilavuus –funktiot testataan.

Tehtävä 8.13  Pääohjelma yhtenä funktiokutsuna

Jatka edellä mainittua ketjuttamista siten, että koko pääohjelma on vain yksi lauseke (ohjeet–kutsu saa olla oma rivinsä jos haluat).  Tosin tämä on C-hakkerismia eikä mikään tavoite helposti luettavalta ohjelmalta.  Itse asiassa hyvä kääntäjä tekee automaattisesti tämän kaltaista optimointia (mitä muka voitiin säästää?).

 

1.5.6 Aliohjelmien testaaminen

Kuten aiemmin todettiin, kannattaa aliohjelmien testaamista varten kirjoittaa hyvin lyhyt testi–pääohjelma. 

Esimerkiksi kerhon jäsenrekisterin päämenun tulostamista varten voisimme kirjoittaa aliohjelman nimeltä paamenu.  Tämä päämenu voitaisiin sitten testata vaikkapa seuraavalla testipääohjelmalla:  

 

java-muut\Paamenu.java - päämenun totetutus ja testi

/**

 * Testataan Kerho-ohjelman päämenun tulostamista

 * @author  Vesa Lappalainen

 * @version 1.0, 13.01.2003

 */

public class Paamenu {

 

  private static void tulosta(String s) {

    System.out.println(s); 

  }   

    

  /**

   * Tulostaa Kerho-ohjelman päämenun

   * @param jasenia kerhon jäsenten lukumäärä

   */

  public static void paamenu(int jasenia) {

    tulosta("\n\n\n\n");

    tulosta("Jäsenrekisteri");

    tulosta("==============");

    tulosta("");

    tulosta("Kerhossa on " + jasenia + " jäsentä.");

    tulosta("");

    tulosta("Valitse:");

    tulosta("   ?  = avustus");

    tulosta("   0  = lopetus");

    tulosta("   1  = lisää uusi jäsen");

    tulosta("   2  = etsi jäsenen tiedot");

    tulosta("   3  = tulosteet");

    tulosta("   4  = tietojen korjailu");

    tulosta("   5  = päivitä jäsenmaksuja");

    tulosta("   :");     

  }   

   

  public static void main(String[] args) {

    paamenu(10);   

  }

   

}

 

Huomattakoon, että aliohjelma on saatu kopioiduksi suoraan aikaisemmasta ohjelman suunnitelmasta lisäämällä vain kunkin rivin alkuun tulosta("ja loppuun ");.  Tällaiset toimenpiteet voidaan automatisoida tekstinkäsittelyn avulla.

1.5.7 Yksinkertaisen aliohjelman kutsuminen

Valmiin aliohjelman kutsuminen on helppoa:  etsitään aliohjelman esittely ja kirjoitetaan kutsu, jossa on vastaavan tyyppiset parametrit vastaavissa paikoissa.

Esimerkiksi funktion Math.sin esittely saattaa olla muotoa:

sin

public static double sin(double a)

  Returns the trigonometric sine of an angle. Special cases:

    - If the argument is NaN or an infinity, then the result is NaN.

    - If the argument is zero, then the result is a zero with the same

      sign as the  argument.

  A result must be within 1 ulp of the correctly rounded result.

  Results must be semi-monotonic.

Parameters:

  a - an angle, in radians.

Returns:

  the sine of the argument.

 

 

Funktion tyyppi on double ja sille viedään double tyyppinen parametri.  Funktio ei muuta mitään parametrilistassa esiteltyä parametriaan (mistä tietää?).  Siis funktiota ei ole mitään mieltä kutsua muuten kuin sijoittamalla sen palauttama arvo johonkin muuttujaan tai käyttämällä funktiota osana jotakin lauseketta. x:ää vastaava parametri voi olla mikä tahansa double tyyppisen arvon palauttava lauseke (tietysti mielellään sellainen joka tarkoittaa kulmaa radiaaneissa):

double kulman_sini,a,b,x,y;

...

kulman_sini = Math.sin(x);

...

y = Math.sin(x/2) + Math.cos(a/3);

...

 

Funktiota voitaisiin tietysti kutsua myös muodossa:

double x = 3.1;

Math.sin(x);                                                           L

 

mutta kutsussa olisi yhtä vähän järkeä kuin kutsussa

double x=3.1;

x + 3.0;                                                               L

tai jopa

3.0;                                                                   L

 

Mihin lausekkeiden arvot menisivät?  Eivät minnekään!  Tosin Javassa kääntäjäkään ei päästä lävitse kahta viimeksi mainittua vaihtoehtoa, eli pelkää vakioita tai muuttujia sisältävää lauseketta, jota ei sijoiteta mihinkään.

Usein aloittelijan näkee yrittävän kutsua muodoissa

y = double Math.sin(double a);

y =  Math.sin(double a)

 

mutta näissäkään ei ole järkeä, koska parametrin tyypin esittely kuuluu vain aliohjelman otsikon puoleiseen päähän, ei kutsupäähän.

1.5.8 Aliohjelmat tulostavat harvoin

Yksi yleinen aloittelijan virhe on tehdä paljon aliohjelmia, jotka tulostavat.  Pikemminkin pitää toimia päinvastoin, eli aliohjelmien on tehtävä oma työnsä ja annettava sitten tulokset muille tulostettavaksi.   Näin samoja aliohjelmia voidaan käyttää myös järjestelmässä, jossa varsinaista konsolitulostusta ei voi tehdä.  Tällaisia ovat mm. graafiset käyttöliittymät. 

Jos halutaan että aliohjelma kuitenkin tulostaa, niin useimmiten sille kannattaa siinä tapauksessa viedä parametrina tietovirta johon tulostetaan.  Palaamme tähän esimerkin kanssa seuraavissa luvuissa.  Alla kuitenkin pikainen esimerkki:

java-muut\Tulostustesti.java - tulostus näytölle ja tiedostoon

import java.io.*;

/**

 * Testataan tietovirran viemistä parametrina

 * @author  Vesa Lappalainen

 * @version 1.0, 19.01.2003

 */

public class Tulostustesti {

 

  private static void tulosta(OutputStream os,int h, int m) {

    PrintStream out = new PrintStream(os);

    out.println("" + h + ":" + m);

  }

 

  public static void main(String[] args) throws FileNotFoundException,

                                                IOException {

    int h=12, m=15;

 

    // Tulostaminen näyttöön

    tulosta(System.out,h,m);

 

    // Tulostaminen tiedostoon

    FileOutputStream f = new FileOutputStream("Tulostustesti.txt");

    try {

      tulosta(f,h,m);

    } finally {

      f.close();

    }

 

    // Tulostaminen tavutietovirtaan, joka voidaan muuttaa sitten merkkijonoksi

    ByteArrayOutputStream bs = new ByteArrayOutputStream();

    tulosta(bs,h,m);

    String s = bs.toString();

    System.out.println(s); // Lisätty, jotta nähdään tulos.

  }

 

}

 

1.6 Parametrin välitys

1.6.1 Useita parametrejä

Suuressa osassa  edellisissä esimerkeissämme meillä on ollut vain 0 tai yksi parametria välitettävänä aliohjelmaan.  Käytännössä usein tarvitsemme useampia parametrejä.  Esimerkiksi edellisessä paamenu–aliohjelmassa pitäisi oikeastaan tulostaa myös kerhon nimi. 

Ottakaamme esimerkiksi mittakaava_muunnos –funktio.  Mikäli ohjelma haluttaisiin muuttaa siten, että myös mittakaavaa olisi mahdollista muuttaa, pitäisi myös mittakaava voida välittää muunnos–aliohjelmalle parametrinä.  Kutsussa tämä voisi näyttää esim. tältä:

  matka_km = mittakaava_muunnos(32,10000.0);

 

Vastaavasti funktio–esittelyssä täytyisi olla kaksi parametria:

private static double mittakaava_muunnos(int matka_mm, double mittakaava)

{

  return matka_mm*mittakaava/MM_KM;

}

 

Kun kutsu suoritetaan, välitetään aliohjelmalle parametrit siinä järjestyksessä, missä ne on esitelty.  Voitaisiin siis kuvitella aliohjelmakutsun aiheuttavan sijoitukset aliohjelman parametrimuuttujiin (tosin sijoitusjärjestystä ei taata, eli ei tiedetä kumpi sijoitus suoritetaan ensin):

mittakaava = 10000.0;

matka_mm   = 32;

 

Jos kutsu on muotoa

  matka_km = mittakaava_muunnos(matka_mm, MITTAKAAVA);

 

kuvitellaan sijoitukset:

matka_mm   = matka_mm;   // Pääohjelman muuttuja matka_mm sijoitetaan aliohjelman

                         // vastinpaikassa olevaan muuttujaan

mittakaava = MITTAKAAVA; // Ohjelman vakio toiseeen aliohjelman muuttujaan

 

Siis vaikka kutsussa ja esittelyssä esiintyykin sama nimi, ei nimien samuudella ole muuta tekemistä kuin mahdollisesti se, että turha on väkisinkään keksiä lyhennettyjä huonoja nimiä, jos kerran on hyvä nimi keksitty kuvaamaan jotakin asiaa.

Parametreista osa, ei yhtään tai kaikki voivat olla myös oliota.

Huom! Vaikka kaikilla aliohjelman parametreille olisikin sama tyyppi, täytyy jokaisen parametrin tyyppi mainita silti erikseen:

public static double nelion_ala(double korkeus, double leveys)

 

Tehtävä 8.14  Päämenuun kerhon nimi

Lisää Paamenu.java:n aliohjelmaan paamenu parametriksi myös kerhon nimi.

 

Tehtävä 8.15  Toisen asteen yhtälön juuri

Kirjoita funktio root_1(a,b,c), joka palauttaa jomman kumman toisen asteen yhtälön ax2+bx+c=0 juurista (oletetaan tällä kertaa, että a<>0 ja D = b2–4ac >= 0.  Miksi oletetaan?).

Tehtävä 8.16  Toisen asteen polynomi, root_1

Kirjoita funktio root_1 joka palauttaa toisen asteen polynomin P(x) = ax2+bx+c arvon (muista viedä parametrinä myös a,b ja c).

Tehtävä 8.17  root_1 testaus

Kirjoita pääohjelma, jolla voidaan testata root_1 – aliohjelma (jotenkin myös se, että tulos toteuttaa yhtälön).

 

1.6.2 Parametrin paikka ratkaisee, ei nimi

Aloitteleva ohjelmoija sotkee yleensä aliohjelmakutsua tehdessään kutsuvan ja kutsuttavan parametrien nimiä keskenään.  Parametrien nimillä ei ole Java-kielessä mitään merkitystä.  Aliohjelmakutsussa ratkaisee vain parametrien paikka.  Kunkin kutsussa oleva arvo "sijoitetaan" vastinparametrilleen kun aliohjelmaan mennään.  Seuraava esimerkki havainnollistaa tätä:

java-muut\Parampaikka.java - parametrin paikka kutsussa ratkaisee

/**

 * Esimerkki miten parametrin paikka ratkaisee, ei nimi

 * @author  Vesa Lappalainen

 * @version 1.0, 19.01.2003

 */

public class Parampaikka {

 

  private static void ali(int a, int b, int c) {

    System.out.println("a=" + a + " b=" + b + " c=" + c);

  }

 

  public static void main(String[] args) {

    int a=1,b=2,c=3;

    ali(a,b,c);  // Tulostaa: a=1 b=2 c=3

    ali(b,a,c);  // Tulostaa: a=2 b=1 c=3

    ali(c,a,b);  // Tulostaa: a=3 b=1 c=2

    ali(10,c,c); // Tulostaa: a=10 b=3 c=3

  }

}

 

On olemassa myös kieliä, joissa parametrit ovat nimettyjä.  Tällainen on tarpeen jos parametreja on niin paljon, ettei niitä kaikkia välitetä joka kutsussa.  Esimerkki tällaisesta kielestä on vaikkapa Microsoft Visual Basic for Application (VBA).

1.6.3 Metodin nimen kuormittaminen

Javassa - samoin kuin monessa muussakin nykykielessä - on mahdollista kuormittaa (overload) aliohjelman nimeä.  Eli samassa näkyvyysalueessa saa esiintyä samannimisiä aliohjelmia kunhan niiden parametrit eroavat toisistaan määrältään ja/tai tyypiltään.

  private static double mittakaava_muunnos(int matka_mm, double mittakaava)

  {

    return matka_mm*mittakaava/MM_KM;

  }

 

  private static double mittakaava_muunnos(int matka_mm)

  {

    return matka_mm*MITTAKAAVA/MM_KM;

  }

 

...

  matka_km = mittakaava_muunnos(20);

...

  matka_km = mittakaava_muunnos(32,20000.0); 

 

 

Kääntäjä pystyy kutsussa päättelemään oikean aliohjelman parametrien määrän ja tyypin mukaan.

Tehtävä 8.18  Toisiaan kutsuvat aliohjelmat

Kirjoita yhden parametrin mittakaava_muunnos siten, että se kutsuu kahden parametrin mittakaava_muunnosta.

 

1.6.4 Muuttujien lokaalisuus

Kukin aliohjelma muodostaa oman kokonaisuutensa.  Edellä olleissa esimerkeissä aliohjelmat eivät tiedä ulkomaailmasta mitään muuta, kuin sen, mitä niille tuodaan parametreinä kutsun yhteydessä. 

Vastaavasti ulkomaailma ei tiedä mitään aliohjelman omista muuttujista.  Näitä aliohjelman lokaaleja muuttujia on esim. seuraavassa:

  private static int pituus_ja_muuta(StringBuffer s)

  {

    int pit = s.length();

    s.delete(0,pit).append("toka"); // pääohjelman jono muuttuu nyt

    return pit;

  }

 

s    – aliohjelman parametrimuuttuja (tässä tapauksessa viite merkkijonoon).

pit  – aliohjelman lokaali apumuuttuja pituuden säilyttämiseksi

 

Yleensäkin Java–kielessä lausesulut { ja } muodostavat lohkon, jonka ulkopuolelle mikään lohkon sisällä määritelty muuttuja tai tyyppimääritys ei näy.  Näkyvyysalueesta käytetään englanninkielisessä kirjallisuudessa nimitystä scope.  Lokaaleilla muuttujilla voi olla vastaava nimi, joka on jo aiemmin esiintynyt jossakin toisessa yhteydessä.  Lohkon sisällä käytetään sitä määrittelyä, joka esiintyy lohkossa: 

java-muut\Lokaali.java - lokaalien muuttujien näkyvyys

/**                                                                    L

 * Testataan Javan muuttujien lokaalisuutta

 * @author  Vesa Lappalainen

 * @version 1.0, 13.01.2003

 */

public class Lokaali {

 

  static private char ch='A';

 

  static private void ali() {

    double ch = 4.5;

    System.out.println("Reaaliluku " + ch);

  }                                                                      

 

  public static void main(String[] args) {

    System.out.println("Kirjain " + ch);

    {

      int ch = 5;

      System.out.println("Kokonaisluku " + ch);

      ali();

    }

    System.out.println("Kirjain " + ch);

  }

 

}

 

Ohjelma tulostaa:

Kirjain A

Kokonaisluku 5

Reaaliluku 4.5

Kirjain A

 

Saman tunnuksen käyttäminen eri tarkoituksissa on kuitenkin kaikkea muuta kuin hyvää ohjelmointia.

Tehtävä 8.19  Eri nimet

Korjaa edellinen ohjelma siten, että kullakin erityyppisellä muuttujalla on eri nimi.

 

1.6.5 Parametrinvälitysmekanismi

Ainoa Java–kielen tuntema parametrinvälitysmekanismi on parametrien välittäminen arvoina.  Tämä tarkoittaa sitä, että aliohjelma saa käyttöönsä vain (luku)arvoja, ei muuta.  Olkoon meillä esimerkiksi ongelmana tehdä aliohjelma, jolle viedään parametreinä tunnit ja minuutit sekä niihin lisättävä minuuttimäärä.  Jos ensimmäinen yritys olisi seuraava:

java-muut\Aikalisa.java - yritys lisätä arvoja

/**                                                                    L

 * Yritetään lisätä metodissa parametrien arvoja

 * @author  Vesa Lappalainen

 * @version 1.0, 18.01.2003

 */

public class Aikalisa {

 

  private static void lisaa(int h, int m, int lisa_min) {

    int yht_min = h*60 + m + lisa_min;

    h = yht_min / 60;

    m = yht_min % 60;

  }

 

  private static void tulosta(int h, int m) {

    System.out.println("" + h + ":" + m);

  }

 

  public static void main(String[] args) {

    int h=12,m=15;

    tulosta(h,m);

    lisaa(h,m,55);

    tulosta(h,m);

  }

 

}

 

Tämä ei tietenkään toimisi!  Hyvä (C-) - kääntäjä jopa varoittaisi että:

Warn :  aikalisa.cpp(8,2):'m' is assigned a value that is never used

Warn :  aikalisa.cpp(7,2):'h' is assigned a value that is never used

 

Mutta miksi ohjelma ei toimisi?  Seuraavan selityksen voi ehkä ohittaa ensimmäisellä lukukerralla.  Tutkitaanpa tarkemmin mitä aliohjelmakutsussa oikein tapahtuu.  Oikaisemme seuraavassa hieman joissakin kohdissa liian tekniikan kiertämiseksi, mutta emme kovin paljoa.  Esimerkki on kirjoitettu vastaavasta C++-ohjelmasta.  Javassa periaatteessa tapahtuu samalla tavalla. Katsotaanpa ensin miten kääntäjä kääntäisi aliohjelmakutsun (Borland C++ 5.1, 32-bittinen käännös, rekisterimuuttujat kielletty jottei optimointi tekisi konekielisestä ohjelmasta liian monimutkaista):

lisaa(h,m,55);

 

muistiosoite assembler         selitys

-------------------------------------------------------------------------

004010F9     push 0x37           pinoon 55

004010FB     push [ebp-0x08]     pinoon m:n arvo

004010FE     push [ebp-0x04]     pinoon h:n arvo

00401101     call lisaa          mennään aliohjelmaan lisää

00401106     add esp,0x0c        poistetaan pinosta 12 tavua (3 x int)

 

Kun saavutaan aliohjelmaan lisaa, on pino siis seuraavan näköinen:

muistiosoite  sisältö          selitys

------------------------------------------------------------------------

064FDEC       00401106 <-ESP   paluuosoite kun aliohjelma on suoritettu

064FDF0       0000000C         h:n arvo, eli 12

064FDF4       0000000F         m:n arvo, eli 15

064FDF8       00000037         lisa_min, eli 55

 

Eli aliohjelmaan saavuttaessa aliohjelmalla on käytössään vain arvot 12,15 ja 55.  Näitä se käyttää tässä järjestyksessä omien parametriensa arvoina, eli m,h,lisa_min.

Esimerkiksi Pascal ja C/C++ -kielissä olisi tarjota tähän sellainen ratkaisu, että aliohjelman parametrit olisivatkin viitteitä (tai osoittimia) kutsuvan ohjelman muuttujiin ja niihin tehty muutos muuttaisi suoraan kutsuvan ohjelman muuttujia.  Javassa tämä on mahdollista vain olioille, koska oliot välitettiin viitteinä.

C++:    void lisaa(int &h, int &m, int lisamin);           kutsu: lisaa(h,m,55);

Pascal: procedure lisaa(var h,m:integer; lisamin:integer); kutsu: lisaa(h,m,55);

C:      void lisaa(int *h, int *m, int lisamin);           kutsu: lisaa(&h,&m,55)

 

Tehtävä 8.20  Muotoilu?

Kokeilepa laittaa ajaksi esim. 12:05.  Mitä tulostuu?  Miten vian voisi korjata?

Tehtävä 8.21  Tiedon lukeminen

Kirjoita aliohjelma lue_kello, joka kysyy ja lukee arvon kellonajalle, syöttö muodossa 12:15.

 

1.6.6 Aliohjelmien kirjoittaminen

Uuden aliohjelmien kirjoittaminen kannattaa aina aloittaa aliohjelmakutsun kirjoittamisesta vähintään testiohjelmaan.  Näin voidaan suunnitella mitä parametrejä ja missä järjestyksessä aliohjelmalle viedään.  Näinhän teimme mittakaava–ohjelmassakin.

1.6.7 Luokkamuuttujat ja suhde lokaaleihin muuttujiin

 Muuttujat voidaan esitellä myös luokan kaikissa metodeissa näkyväksi.  Mikäli muuttujat esitellään kaikkien ohjelman aliohjelmalausesulkujen ulkopuolella, näkyvät muuttujat koko luokan alueella.  Jos muuttujat vielä varustetaan vaikkapa public määreellä, niin luokan ulkopuolisetkin luokat voivat niitä käyttää.  Tällaista on syytä välttää. Seuraava ohjelma on kaikkea muuta kuin hyvän ohjelmointitavan mukainen, mutta pöy­tä­tes­taam­me sen siitä huolimatta:

java-muut\Alisotku.java - parametrin välitystä

/**

 * Mitä ohjelma tulostaa??

 * @author  Vesa Lappalainen

 * @version 1.0, 19.01.2003

 */

public class Alisotku {

    

  /**

   * Palauttaa merkkijonon kokonaislukuna

   * @param s muutettava merkkijono

   * @return merkkijonosta saatu kokonaisluku

   */ 

  private static int i(StringBuffer s) {

    return Integer.parseInt(s.toString());

  }

 

  /**

   * Sijoittaa kokonaisluvun arvon merkkijonoon

   * @param s merkkijono johon tulos sijoitetaan

   * @param i kokonaisluku joka sijoitetaan

   */

  private static void set(StringBuffer s,int i) {

    s.delete(0, s.length()).append(""+i);

  }

        

/* 01 */ static int a; static StringBuffer b; static int c;

/* 02 */

/* 03 */ private static void ali_1(StringBuffer a, int b)

/* 04 */ {

/* 05 */   int d;

/* 06 */   d  = i(a);

/* 07 */   c  = b + 3;

/* 08 */   b  = d - 1;

/* 09 */   a.append(""+(c - 5));

/* 10 */ }

/* 11 */

/* 11 */ static private void ali_2(StringBuffer a, StringBuffer b)

/* 13 */ {

/* 14 */   int c;

/* 15 */   c  = i(a) + i(b);

/* 16 */   set(a,9 - c);

/* 17 */   set(b,32);

/* 18 */ }

/* 19 */

/* 20 */ public static void main(String[] args) {

/* 21 */   StringBuffer d = new StringBuffer(); b = new StringBuffer();

/* 22 */   a=1; set(b,2); c=3; set(d,4);

/* 23 */   ali_1(d,c);

/* 24 */   ali_2(b,d);

/* 25 */   ali_1(d,3+i(d));

/* 26 */   System.out.println("" + a + " " + b + " " + c + " " + d);

/* 27 */ }

}

 

Käsittelemme (huonosti nimettyjä) metodeja i ja set "operaattoreina", eli oletamme niiden toiminnan tunnetuksi, eikä pöytätestissä askelleta niihin sisälle.

Pöytätestin tekeminen aloitetaan piirtämällä sarakkeet kutakin isompaa ohjelmassa olevaa kokonaisuutta varten.  Esimerkissä näitä ovat

·        suoritettava lause

·        luokkamuuttujat

·        main-metodi

·        metodit ali_1 ja ali_2

·        keko

·        lisäksi kannattaa laskea välitulokset jonnekin auki

Sitten kukin sarake jaetaan vielä osiin siinä olevien muuttujien määrän mukaan.  Kekoa varten tarvitaan karkeasti yhtä monta saraketta kuin ohjelmassa on suoritettavia new-operaattoreita (tai String a = "kissa"; tyyppisiä lauseita) .

Lyhyyden vuoksi olemme seuraavassa merkinneet N1 = ensimmäinen new:llä luotu olio ja N2 on toinen.  Lisäksi on otettu c-mäinen merkintä &N1, eli viite olioon N1.  Merkintä L.c tarkoittaa seuraavassa luokan c -muuttuja (jos on vaara sekaantua muuhun).  Merkintää := on käytetty välilaskutoimituksissa erottamaan sijoitusta = -merkistä.  Merkintä * muuttujien yläpuolella on muistutuksena sitä, että kyseessä on viitemuuttujat ja niiden käsittely muuttaa aina jotakin muuta muistipaikkaa.  Pöytätestissä siis sarakkeet ovat muistipaikkoja ja rivit muistipaikkojen arvo tiettynä ajanhetkenä.  Muistipaikka on merkitty harmaalla jos se ei ole voimassa tiettynä ajanhetkenä.

 

luokan

main

ali_1

ali_2

keko

apulaskut

          

  

*  

  

   

*  

  

  

*  

*  

  

SB

SB

 

   lause 

a

b

c

d

a

b

d

a

b

c

N1

N2

 

01 int a;

 0

null

 0

 

 

 

 

 

 

 

 

 

 

21 d = new

 

 &N2

 

&N1

 

 

 

 

 

 

""

""

syntyy tyhjät merkkijonot

22 a=1; b=2

 1

 o->

 3

o->

   

  

  

   

   

  

"4"

"2"

 

23 ali_1(d,c

  

  

  

   

&N1

 3

 

   

   

  

 

 

ali_1(&N1,c)

05 int d

 

 

 

 

 

 

 ?

 

 

 

 

 

 

06 d = i(a);

  

  

  

   

   

  

 4

   

   

  

 

 

d:=i(N1)=4

07 c = b+3;

  

  

 6

   

   

  

  

   

   

  

 

 

L.c:= 3+3 = 6

08 b = d-1;

  

  

  

   

   

 3

  

   

   

  

 

 

b:= 4-1 = 3

09 a.ap(c-5)

  

  

  

  

o->

  

  

   

   

  

"41"

 

L.c-5=1; N1:="4"+"1"="41"

24 ali_2(b,d

  

  

  

   

   

  

  

&N2

&N1

 

 

 

ali_2(&N2,&N1)

14 int c;

 

 

 

 

 

 

 

 

 

 ?

 

 

 

15 c=i(a)+i(

  

  

  

   

   

  

  

   

   

43

 

 

c:=41+2 = 43

16 set(a,9-c

  

 

  

   

   

  

  

o->

   

  

 

"-34"

N2:=9-c=-34; 

17 set(b,32)

 

  

  

   

   

  

  

   

o->

  

"32"

 

 

25 ali_1(d,3

  

  

  

   

&N1

35

 

   

   

  

 

 

ali_1(&N1,3+32)

06 d = i(a)

  

  

  

   

   

  

32

   

   

  

 

 

d:=i(N1)=32

07 c = b+3;

  

  

38

   

   

  

  

   

   

  

 

 

L.c:= 35+3 = 38

12 b = d-1;

  

  

  

   

   

 5

  

   

   

  

 

 

b:= 32-1 = 31

09 a.ap(c-5

  

  

 

   

o->

  

  

   

   

  

 

 

L.c-5=33; N1:="32"+"33"="3233"

26 printl

  

  

  

   

   

  

  

   

   

  

"3233"

 

Tulostus: 1 -34 38 3233

 

  

  

  

   

   

  

  

   

   

  

 

 

          =============

 

Luokkamuuttujat ovat rinnastettavissa globaaleihin muuttujiin. Samoin kun seuraavassa luvussa päästään käsiksi varsinaiseen olio-ohjelmointiin, niin myös julkiset attribuutit ovat rinnastettavissa globaaleihin muuttujiin. Globaaleiden muuttujien käyttöä tulee ohjelmoinnissa välttää.  Tuskin mistään on tullut yhtä paljon ohjelmointivirheitä, kuin vahingossa muutetuista globaaleista muuttujista!

Käytännössä pöytätestiä voidaan monesti korvata hyvällä debuggerilla. Debuggerista valitettavasti ei useinkaan näe suorituksen historiaa. Ennen kun debuggerit eivät olleet niin yleisiä, korvattiin niitä sijoittamalla ohjelmakoodin sekaan muuttujien arvoja tulostavia lauseita.  Joissakin tapauksissa tähänkin vielä joudutaan turvautumaan.

Tehtävä 8.22  Muuttujien näkyvyys

Pöytätestaa seuraava ohjelma:

 

java-muut\Alisotk2.java - parametrin välitystä

/**

 * Mitä ohjelma tulostaa??

 * @author  Vesa Lappalainen

 * @version 1.0, 19.01.2003

 */

public class Alisotk2 {

 

  private static int i(StringBuffer s) {

    return Integer.parseInt(s.toString());

  }

  private static void set(StringBuffer s,int i) {

    s.delete(0, s.length()).append(""+i);

  }

 

/* 01 */ private static StringBuffer b; private static int c;

/* 02 */

/* 03 */ private static void s_1(StringBuffer a, int b)

/* 04 */ {

/* 05 */   int d;

/* 06 */   d  = i(a);

/* 07 */   c  = b + 3;

/* 08 */   b  = d - 1;

/* 09 */   set(a,c - 5);

/* 10 */ }

/* 11 */

/* 12 */ private static void a_2(int a, StringBuffer b)

/* 13 */ {

/* 14 */   c  = a + i(b);

/* 15 */   { int c; c = i(b);

/* 16 */   a = 8 * c; }

/* 17 */   set(b,175);

/* 18 */ }

/* 19 */

/* 20 */ public static void main(String[] args) {

/* 21 */   StringBuffer a = new StringBuffer("4"); int d=9;

/* 22 */   System.out.println("" + a + " " + b + " " + c + " " + d);

/* 23 */   b=new StringBuffer("3"); c=2; d=1;

/* 24 */   s_1(b,c);

/* 25 */   a_2(d,a);

/* 26 */   s_1(a,3+d);

/* 27 */   System.out.println("" + a + " " + b + " " + c + " " + d);

/* 28 */ }

}