Helsingin yliopisto / Tietojenkäsittelytieteen laitos / 581258-1 Johdatus ohjelmointiin
Copyright © 1998 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.

4.3 Luokan käyttö

(Muutettu viimeksi 4.3.1998)

Luvussa 4.1 tutustuttiin ideatasolla luokan ja olion käyttöön mallinnuksessa ja abstraktin tietotyypin toteuttamisessa. Tämä luku kertaa vanhoja ja esittelee uusia luokan käyttötapoja. Pääpaino on uusissa tavoissa.

Luokka ja olio mallinnuksessa

Luvussa 4.1 kirjoitin:
Olio-ohjelmoinnin perusajatus on ongelmakentän - sovellusalueen - kuvaaminen olioina, olioiden ominaisuuksina ja toimintoina sekä olioiden välisinä suhteina. Sovellusalueen käsitteet ja toimijat mallinnetaan olioina. Koska nuo käsitteet ja toimijat ovat usein pysyvämpiä kuin yksittäiset tiedonkäsittelytarpeet, olioiden rakenteen ja toiminnan voi olettaa säilyvän vaikka yksittäisen sovelluksen rakennetta muutetaan tai kun luodaan kokonaan uusia sovelluksia.

Ja:
Olioajattelu voidaan nähdä mallintamisena, jossa luokka vastaa jotakin yleiskäsitettä... Luokan ilmentymä on jokin erityinen olio, joka kuuluu tuohon luokkaan.

Jos tehtävänä olisi vaikkapa ratkoa luentokurssin jotakin tietojenkäsittelytehtävää, ongelmakentästä voitaisiin yrittää löytää olioita seuraavasti:

Joihinkin ongelmiin tämänkaltainen analyysi puree hyvin, joihinkin vähemmän luontevasti. Tällä kurssilla asiaa ei ole mahdollista syvemmin käsitellä. Mielenkiintoinen uusi kirja aiheesta on Kai Koskimies: Pieni oliokirja, Suomen Atk-kustannus Oy, 1997

Luokka abstraktin tietotyypin toteutuksena

Tästä on ollut useita esimerkkejä, aina KuuLaskurista ja PikkuVarastosta lähtien.

Kirjoitin luvussa 4.1:
... tietorakenteen toteutus ja käyttö eristetään toisistaan. Tietorakenteen käyttäjä tuntee vain operaatioita, joilla (abstraktin) tietorakenteen tilaa muutetaan ja kysytään. Tietorakenteen toteuttaja puolestaan ei ota kantaa siihen, mihin tietorakennetta käytetään. Käyttäjää ja toteuttajaa yhdistää pelkästään sopimus siitä, mitä operaatiot (abstraktissa) tietorakenteessa saavat aikaan. Usein tietorakenteen toteutuksen piilottamista kutsutaan kapseloinniksi.

Abstraktin tietotyypin voi rakentaa olio-ohjelmoinnin välinein seuraavasti:

Luokka tietueena

Kun laaditaan luokka, jossa on vain julkisia kenttiä eikä lainkaan metodeita, saadaan rakenne, jota kutsutaan tietueeksi (esim. Pascalin record, C:n struct).

Tietueen idea on, että muuttujalla on ns. kenttiä: alimuuttujia, joiden tyypit voivat olla keskenään erilaisia (vrt. taulukkotyyppiin, joka kokoaa yhteen muuttujaan keskenään samantyyppisiä alimuuttujia).

Tietueen ja sen kenttien käyttöön liittyy usein erilaisia operaatioita, jotka on luontevaa rakentaa itse luokkaan. Tällöin päädytään "tavalliseen" luokkaan. Luokan käyttö tietueena saattaakin olla merkki huonosta ohjelmansuunnittelusta!

Esimerkki: Laaditaan henkilötietue, jossa on kenttä henkilön nimelle, pituudelle ja iälle.

  public class Henkilo {
     public String nimi;
     public double pituus;
     public int ika;
  }
  ...
 
  Henkilo pomo = new Henkilo();
  Henkilo sihteeri = new Henkilo();

  pomo.nimi = "Maija";
  pomo.pituus = 163;
  pomo.ika = 24;

  sihteeri.nimi = "Pekka";
  sihteeri.pituus = 184;
  sihteeri.ika = 48;

Huom: Koska tietueet ovat olioita, sijoitus on viitteen kopiointi:
  Henkilo paasihteeri = sihteeri;
  ++paasihteeri.ika;

  // sihteerikin vanhentui!

Huom: Tämä tapa ei mitenkään varmista kenttien oikeaa käyttöä; mikään ei estä asettamasta pituudeksi lukua -3.14 eikä iäksi lukua -123456. Abstraktia tietotyyppiä käyttäen tätä ongelmaa ei tule, kun aksessessorit vain ohjelmoidaan viisaasti!

Esimerkki: Lukupari (esim. tason piste, kompleksiluku, ...) voidaan toteuttaa vaikkapa:

  public class Pari {
    public double x, y;

    public Pari(double x, double y) {
      this.x = x;
      this.y = y;
    }
  }

...

  Pari a = new Pari(3.14, -7);
  Pari b = new Pari(0.23, 10.1);
  Pari c = new Pari(0,0);

  c.x = a.x + b.y;
  c.y = a.y + b.x;

  // Mitä tekee a = b; ?

Lukuparin voi toteuttaa vieläkin yksinkertaisemminkin:
  public class Pari {
    public double x, y;
  }

...

  Pari a = new Pari();
  Pari b = new Pari();
  Pari c = new Pari(); 

  a.x = 3.14; a.y = -7;
  b.x = 0.23; b.y = 10.1;
  c.x = a.x + b.y;
  c.y = a.y + b.x;


Oman toString()-metodin tekeminen lukuparille voisi olla perusteltua. Jos jatketaan edellistä esimerkkiä ja tulostetaan:
  System.out.println(a + " " + b + " " + c);
saadaan esimerkiksi:
Pari@80ca74b Pari@80ca74c Pari@80ca74d
Ohjelmoidaan tyypille Pari oma tulostusmetodi:
  public class Pari {
    public double x, y;

    public String toString() {
      return "("+x+","+y+")";
    }
  }
Nyt samalla tulostusoperaatiolla saadaan kauniisti:
  (3.14,-7.0) (0.23,10.1) (13.24,-6.77)


Linkitetyt rakenteet

Luokan oma nimi on käytettävissa luokassa. Kun tehdään tietue, jonka jokin kenttä (tai useammat kentät) on luokan tyyppiä, voidaan ohjelmoida ns. linkitettyjä rakenteita.

Ns. linkitetty lista voidaan toteuttaa seuraavasti:

  public class Lista {
    int tieto;
    Lista linkki;
  }

  ...

  Lista p = new Lista();
  p.tieto = 7;
  p.linkki = new Lista();
  p.linkki.tieto = 3;
  p.linkki.linkki = new Lista();
  p.linkki.linkki.tieto = 19;

  for(Lista q=p; q!=null; q=q.linkki)
    System.out.println(q.tieto); 

Huom: Myös tässä tapauksessa olio-ohjelmointityyli voisi olla viisaampi ratkaisu:
public class Lista {

  public int tieto;
  private Lista linkki;

  public void lisaaAlkuun(int tieto) {
    ...
  }
  public void lisaaLoppuun(int tieto) {
    ...
  }
  public Lista annaSeuraava() {
    ...
  }
  // jne., jne, ...

}
  ...
  Lista p = new Lista();
  p.lisaaAlkuun(7);
  p.lisaaLoppuun(7);
  p.lisaaLoppuun(19);

  ...

Luokka ohjelmakirjastona

Luokkaan voi kerätä yleiskäyttöisiä vakioita ja algoritmeja. Monet Javan valmiit välineet on määritelty kirjastoluokkina, esimerkiksi Math-luokka sisältää vakiot PI ja E sekä joukon laskennallisia metodeita (trigonometriset funktiot, ym. ym.)

Kentät ja metodit määritellään luokkakohtaisina: static. Käyttäjälle tarjotut ovat public, apuvälineet private.

Tarvittaessa kenttiä voi alustaa staattisella alustuslohkolla.

Kirjastoluokka on syytä määritellä final-määreellä. Tällöin luokkaa ei voi periä (kts. kappale 4.4). Kun luokkaan lisäksi määritellään yksityinen (private) konstruktori, luokasta ei voi luoda myöskään ilmentymiä!

Esimerkki:

// satunnaissatuja
public final class Satuja {

  private Satuja() { } // ilmentymien esto!!

  // vakio:

  public static final double VAKIO = 123.456;


  // luokan latauskerran (ladataan kun 1. kerran viitataan)
  // vakio:

  private static double apuvaki;

  static {                    // arvotaan kun luokka ladataan!
    apuvaki = Math.random();  // so. oma vakio joka käyttökerralla
  }

  public static final double SATUVAKIO=apuvaki;


  // kirjastometodeja:

  public static int randomInt(int max) {
    return (int)(Math.random()*max) + 1;
  }

  public static char randomChar() { // koodit 34-126  ('"'-'~')
    return (char)(randomInt(93)+33);

// ...

  }
}

...

   // kirjaston käyttöä:

     for (int i=1; i<50; ++i)
        System.out.println(Satuja.randomChar()+" "+Satuja.randomInt(i));

     System.out.println("Ihan vakio:"+Satuja.VAKIO+
                        ", latauskohtainen vakio: "+Satuja.SATUVAKIO);



Luokka "vanhanaikaisena" ohjelmana

Javalla voi vallan mainiosti ohjelmoida perinteisen "proseduraalisen paradigman" mukaisesti Pascalin ja C:n tapaan. Se ei välttämättä ole kovin järkevää, joten seuraava esimerkki olkoon varoittava:
public class Ohjelma{

  // "globaalit vakiot ja muuttujat":

  static final int KOKO = 10;
  static int luku, indeksi;
  static double[] taulu = new double[KOKO];

  // "proseduurit" tai "void-funktiot":

  static void arvoluvut(int skaalaus) {
    for (int i=0; i<KOKO; ++i) 
      taulu[i] = Math.random()*skaalaus;
  }

  static void tervetuloa() {
    System.out.println("   Tervetuloa\n"
                      +"   ==========");
  }

  // "funktiot" tai "arvon palauttavat funktiot":

  static double siniToiseen(double argu) {
    return Math.sin(argu)*Math.sin(argu);
  }

  // "pääohjelma":

  public static void main(String[] args) {
    tervetuloa();
    arvoluvut(14);
    for (int i=0; i<KOKO; ++i)
       System.out.println(siniToiseen(taulu[i]));
  }
    
}


Takaisin luvun 4 sisällysluetteloon.