Helsingin yliopisto / Tietojenkäsittelytieteen laitos
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.

2.8 Taulukko-olioista

(Muutettu viimeksi 12.10.2006)

Luku esittelee taulukoiden idean, määrittelyn ja käytön. Myös matriiseihin tutustutaan. Taulukoiden käsittelyn perusalgoritmeihin perehdytään: etsiminen ja järjestäminen tulevat tutuiksi. Luvun lopussa nähdään, miten taulukon elementit voivat olla mitä vain Javan olioita.

Taulukon idea

Usein ohjelmassa joudutaan käsittelemään keskenään samatyyppisiä muuttujia "samaan tapaan". Tähän mennessä opituilla muuttujilla on jokaisella ollut aina oma erityinen nimensä ja siksi jokaisen muuttujan käsitteleminen on jouduttu ohjelmoimaan erikseen:

Olkoon ohjelmointitehtävänä tulostaa 100 syöttölukua käänteisessä järjestyksessä. Ainoa mahdollisuus ratkaista ongelma jo opitulla kalustolla on:

   private static Scanner lukija = new Scanner(System.in);
   ...

   int luku1, luku2, 
       .......           // nuo pisteet eivät valitettavasti ole Javaa!
       luku99, luku100;

   // luetaan 100 lukua:

   System.out.println("Anna luku 1");
   luku1 = lukija.nextInt();
   System.out.println("Anna luku 2");
   luku2 = lukija.nextInt();
       .......           // nuo pisteet eivät valitettavasti ole Javaa!

   System.out.println("Anna luku 100");
   luku100 = lukija.nextInt();

   // tulostetaan luvut käänteisessä järjestyksessä:

   System.out.println("Luvut lopusta alkuun:");
   System.out.println(luku100); 
   System.out.println(luku99);
        .......           // nuo pisteet eivät valitettavasti ole Javaa!
   System.out.println(luku2); 
   System.out.println(luku1); 


Kovin kävisi työlääksi ohjelman laatiminen tuohon tapaan! Jokaista muuttujaa luku1, luku2, ..., luku100 käsitellään ihan samaan tapaan ja kuitenkin joudutaan kirjoittamaan kaikki käsittelyt erikseen!

Taulukon idea on, että yksi muuttujan nimi voi tarkoittaa kokonaista jonoa keskenään samantyyppisiä muuttujia. Kutakin tuollaista "alimuuttujaa" kutsutaan taulukon alkioksi eli komponentiksi. Yksittäisiin "alimuuttujiin" viitatataan indeksoimalla taulukkomuuttujaa:

Jos vaikkapa luku on tuollainen taulukkomuuttuja, ilmaus

   luku[7] 
tarkoittaa muuttujan luku "alimuuttujaa", jonka indeksi on 7. Tuota "alimuuttujaa" voidaan käsitellä ihan samaan tapaan kuin mitä tahansa samantyyppistä tavallista muuttujaa. Jos siis taulukkomuuttujan luku alkiotyyppi on int, voidaan kirjoittaa:
   luku[7] = 56;
   luku[7] += 3;
   luku[7] = luku[7]*luku[7];
   ++luku[7];
   luku[7] = lukija.nextInt();
   System.out.println(luku[7]);
   
   jne, jne ....   

Ja nyt edellinen esimerkki voidaan ohjelmoida:

    int[] luku = new int[101]; // Tämä selviää pian!

    // luetaan 100 lukua:

    for (int i=1; i<=100; ++i) {
      System.out.println("Anna luku "+i);
      luku[i] = lukija.nextInt();
    }

    // tulostetaan luvut käänteisessä järjestyksessä:

    System.out.println("Luvut lopusta alkuun:");
    for (int i=100; i>=1; --i)
      System.out.println(luku[i]);

Näin siis samalla ilmauksella
    luku[i]
tarkoitetaan i:n arvosta riippuen milloin mitäkin int-muuttujista
    luku[1], luku[2], ... luku[99], luku[100].

Huom: Indeksilauseke voi toki olla muutakin kuin pelkkä vakio tai muuttuja:
    luku[7+j] = luku[i-3] * luku[i*i];


Taulukko on olio

Edellisessä esimerkissä taulukko määriteltiin:
  int[] luku = new int[101];

[Huom: On valitettavasti mahdollista määritellä samaa tarkoittaen myös
  int luku[] = new int[101];
Onneton menettely on kotoisin eräistä Javan edeltäjistä. Koska edellinen tapa on paljon selkeämpi, kurssilla käytetään ainoastaan sitä!]

Taulukot ovat siis Javassa olioita: Ne luodaan konstruktorilla, niitä käsitellään viitteinä, ..., parametrina välitettyä taulukkoa voi muuttaa, metodi voi palauttaa arvonaan viitteen taulukkoon, ...

Sääntöjä:

  1. Taulukon indeksit ovat aina int-tyyppisiä.
  2. Taulukon ensimmäinen indeksi on aina 0.
  3. Taulukon koko on aina konstruktorin kutsussa annettu kokonaislukuarvo ja siis suurin käytössä oleva indeksi on koko-1 (koska ensimmäinen on aina 0).
  4. Taulukon käyttäjä on vastuussa siitä, että indeksinä ei käytetä koskaan arvoa joka ei ole välillä [0,koko-1]. Jos käytetään, ohjelman suoritus keskeytyy virhetilanteeseen.
  5. Taulukon alkioilla on oletusalkuarvot (esim. 0, 0.0, false, null), koska alkiot ovat oikeastaan taulukko-olion kenttiä, ks. luku 4.2.
  6. Taulukko-olion koko ei voi koskaan muuttua. (Mutta taulukkomuuttuja voi tietenkin saada arvokseen uuden - vaikkapa erikokoisen - taulukon.)
  7. Taulukon koon saa selville ilmauksella .length Jos muuttujan luku arvona on 101-alkionen taulukko-olio, luku.length on 101. (Taulukko-olioilla on julkinen kenttä length.)
Koska taulukot siis aina alkavat indeksistä 0, on "Java-henkisempää" kirjoittaa edellä nähty esimerkki seuraavasti (KaannaLuvut.java):
import java.util.Scanner;

public class KaannaLuvut {
  private static Scanner lukija = new Scanner(System.in);

  public static void main(String[] args) {

    int[] luku = new int[100];

    // luetaan 100 lukua:

    for (int i=0; i < luku.length; ++i) {
      System.out.println("Anna luku "+(i+1));
      luku[i] = lukija.nextInt();
    }

    // tulostetaan luvut käänteisessä järjestyksessä:

    System.out.println("Luvut lopusta alkuun:");
    for (int i=luku.length-1; i >= 0; --i)
      System.out.println(luku[i]);
  }
}

Ohjelmointitekniikkaa: taulukon alkioiden läpikäynti

Jonkin tietorakenteen, "tietojoukon", kaikkien alkioiden läpikäynti yksi kerrallaan on hyvin yleinen tilanne ohjelmoinnissa. Kyseessä voi olla esimerkiksi jonkin erityisen arvon omaavan alkion indeksin etsintä, vastaus kysymykseen, onko tietorakenteessa jokin erityinen alkio, tms., yms.

Taulukon alkioiden läpikäynti on aina syytä tehdä yleisellä tavalla

    for (int i=0; i < luku.length; ++i)
      ...
Näin ohjelmaa ei tarvitse muuttaa, jos taulukon kokoa halutaan muuttaa tai vaikkapa tehdä ohjelmasta yleisempi versio; taulukko-olio voidaan luoda myös kysytyn kokoiseksi (KaannaLuvut2.java):
import java.util.Scanner;

public class KaannaLuvut2 {
  private static Scanner lukija = new Scanner(System.in);

  public static void main(String[] args) {

    int lukumäärä;

    do {
      System.out.println("Montako lukua käännetään? (Vähintään yksi!)");
      lukumäärä = lukija.nextInt();
    } while (lukumäärä < 1);

    int[] luku = new int[lukumäärä]; // Huom: muuttuja!

    // Loppuosa ohjelmasta säilyy aivan ennallaan!

    for (int i=0; i < luku.length; ++i) {
      System.out.println("Anna luku "+(i+1));
      luku[i] = lukija.nextInt();
    }

    // tulostetaan luvut käänteisessä järjestyksessä:

    System.out.println("Luvut lopusta alkuun:");
    for (int i=luku.length-1; i >= 0; --i)
      System.out.println(luku[i]);
  }
}

Javan versio 1.5 toi mukanaan uuden tavan käydä läpi taulukon kaikki alkiot ilman tarvetta indeksoida itse, "omin käsin". 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;
  }

Monenlaisia taulukoita

Seuraava ohjelma antaa esimerkkejä monenlaisista taulukkomuuttujien määrittelyistä, taulukko-olioiden konstruoinnista ja taulukoiden käytöstä:

public class Tauluesimerkkeja  {
  public static void main(String[] args) {

    // int-taulukoita:

    int[] kA;               // taulukkomuuttuja, EI vielä taulukkoa!
    int[] kB = new int[10];

    int[] kC = {5, -23, 12, 31, 7}; 
                           // tämä ilmaus konstruoi taulukon int[5]
                           // ja asettaa alkioille alkuarvot!!
                           // Näitä voi käyttää VAIN muuttujia
                           // määriteltäessä!

    for (int i=0; i<kB.length; ++i) // 7:n kertotaulu
       kB[i] = 7*(i+1);

    for (int i=0; i<kB.length; ++i)
       System.out.println(kB[i]);

    kA = kC;             // KOPIOIDAAN VIITE
    kA[1] = 123456;      // MYÖS kC[1] MUUTTUU!
    System.out.println(kA[1] + " " + kC[1]);

    kC = new int[5];  // luodaan uusi taulukko kC:n arvoksi
    kC[1] = -98;
    System.out.println(kA[1] + " " + kC[1]);


    // double-taulukoita:

    double[] dA = new double[5];
    double[] dB = {3.14, 7.21, 0.0, 9.1}; // double[4]!

    dA[2] = dB[0] + dB[3];


    // boolean-taulukoita:

    boolean[] tA = new boolean[8];
    boolean[] tB = {true, true, false};  // boolean[3]!

    tA[1] = tB[0] || tB[2];
    if (tA[1]) 
      System.out.println("Tosi on!");


    // char-taulukoita:

    char[] mA = new char[10];
    char[] mB = {'q', 'w', 'e'};         // char[3]!

    mB[1] = 'u';
    System.out.println("Merkkialkion oletusalkuarvo on" + mA[2] + ".");


    // String-taulukoita:

    String[] jA = new String[3];
    String[] jB = {"kissa", "hiiri"};      // String[2]!

    jA[0] = jB[1];
    jA[2] = jB[0];
    jA[1] = "koira";
    for (String alkio : jA)
      System.out.println(alkio);
    jB[1] = jA[1];
  }
}
Ohjelma tulostaa;
7
14
21
28
35
42
49
56
63
70
123456 123456
123456 -98
Tosi on!
Merkkialkion oletusalkuarvo on.
hiiri
koira
kissa

Matriisit - taulukko-olioita taulukossa

Matriisi on tietojen esitys muodossa rivit-sarakkeet, esimerkiksi:
    ---------------
    | 5   2   -3  |
    | 9  -1    4  |
    ---------------
Tässä matriisissa on kaksi riviä ja kolme saraketta.

Toisinaan matriisia kutsutaan myös kaksiulotteiseksi taulukoksi. Myös esimerkiksi kolmiulotteinen taulukko on mahdollinen, samoin neliulotteinen...

Javassa oikeastaan ei ole kaksiuluotteisia taulukoita, mutta koska Javan taulukoiden komponenttityyppi voi olla mikä tahansa Javan tyyppi, myös taulukkotyyppi voi olla taulukon komponenttina! Näin saadaan luotua rakenteita jotka muistuttavat moniulotteisia taulukoita. Tässä luvussa tutustutaan vain "kaksiulotteisiin" taulukoihin, matriiseihin. Ja niitä kutsutaan jatkossa kaksiulotteisiksi ilman sitaatteja.

Määrittely

    int[][] matriisiA;
tarkoittaa, että muuttujan matriisiA tyyppi on int[]-taulukoista muodostuva taulukko.

Määrittely

    int[][] matriisiA = new int[2][3];
asettaa muuttujan alkuarvoksi 2*3 matriisin, jonka alkioiden alkuarvo on 0.

Määrittely

    int[][] matriisiB = { {5, 2, -3}, {9, -1, 4} };
puolestaan luo muuttujan matriisiB arvoksi 2*3 matriisin annetuin alkuarvoin.

Myös kaksiulotteisen taulukon alkioihin viitataan indeksoiden:

    matriisiB[0]
tarkoittaa int[]-taulukkoa
    {5, 2, -3}
Ilmaus
    matriisiB[0][1]
puolestaan tarkoittaa int-alkiota, jonka arvo on 2.

Kaksiulotteisen taulukon alkiot voidaan läpikäydä vaikkapa lauseella:

    for (int rivi=0; rivi<matriisi.length; ++rivi)
      for (int sarake=0; sarake<matriisi[rivi].length; ++sarake) 
        // alialgoritmi alkion matriisi[rivi][sarake]  käsittelyyn

Jos toistettavassa alialgoritmissa ei tarvita tietoa alkioiden indekseistä (riveistä ja sarakkeista) eikä käsittelyjärjestyksestä, läpikäynti voidaan ohjelmoida käyttäen for-each-toistoa:

    for (int[] rivi : matriisi)
      for (int alkio : rivi)
        // alialgoritmi yhden alkion käsittelyyn

Laaditaan esimerkkisovellus, joka lukee 2*3-matriisin ja tulostaa sen 7:llä kerrottuna (so. jokainen alkio kerrotaan 7:llä). Lopuksi ohjelma laskee ja tulostaa "7:llä kerrotun" matriisin alkioiden summan. (Matriisi7.java).

import java.util.Scanner;

public class Matriisi7 {
  private static Scanner lukija = new Scanner(System.in);

  public static void main(String[] args) {

    int[][] matriisi = new int[2][3];

    // luetaan arvot:

    for (int rivi=0; rivi<matriisi.length; ++rivi)
      for (int sarake=0; sarake<matriisi[rivi].length; ++sarake) {
        System.out.println("Anna alkio "+rivi+", "+sarake);
        matriisi[rivi][sarake] = lukija.nextInt();
      }

    // kerrotaan alkiot 7:llä:

    for (int rivi=0; rivi<matriisi.length; ++rivi)
      for (int sarake=0; sarake<matriisi[rivi].length; ++sarake)
        matriisi[rivi][sarake] *= 7;  // operaatio '*='kertoo!

    // tulostetaan matriisi:

    System.out.println("Seitsemällä kerrottuna:");
    for (int rivi=0; rivi<matriisi.length; ++rivi) {
      for (int sarake=0; sarake<matriisi[rivi].length; ++sarake)
        System.out.print(matriisi[rivi][sarake]+"\t");
      System.out.println();
    }

    // lasketaan alkioiden summa:

    int summa = 0;
    for (int[] rivi : matriisi)   // nyt tarvitaan vain alkioiden arvot!
      for (int alkio : rivi)
        summa = summa + alkio;
    System.out.println("Alkioiden summa on " + summa + ".");

  }
}


Ohjelmointitekniikkaa: etsiminen taulukosta

Melko tyypillinen ohjelmointitehtävä on sen selvittäminen, onko taulukossa kysyttyä alkiota. Jos taulukko ei ole järjestyksessä, ns. peräkkäishaku on normaali menettely.

Laaditaan metodi, joka saa parametrinaan int[]-tyyppisen arvon eli kokonaislukutaulukon ja selvittää, löytyykö taulukosta toisena parametrina annettua kokonaislukua. Jos kysytty arvo löytyy, metodi palauttaa haettavan luvun indeksin arvonaan (jos haettavia on taulukossa monia, palautetaan niistä ensimmäisen indeksi). Jos kysyttyä ei löydy, metodin arvo on -1 (Hae.java):

public class Hae {  // Peräkkäishaku int-taulukosta

  private static int hae(int[] taulu, int haettava) {

    for (int i=0; i<taulu.length; ++i) 
      if (taulu[i] == haettava)
        return i; // tärppäsi, lopetetaan samantien

    return -1; // päästiin loppuun eikä löytynyt
  }

  public static void main(String[] args) { // testipääohjelma

    int[] a = {40, 20, 50, 10, 30};

    System.out.println(hae(a, 10));
    System.out.println(hae(a, 20));
    System.out.println(hae(a, 30));
    System.out.println(hae(a, 40));
    System.out.println(hae(a, 50));
    System.out.println(hae(a, 60));
  }
}
Testiohjelma tulostaa:
3
1
4
0
2
-1

Jos taulukko on järjestyksessä, ns. binäärihaku on erittäin tehokas väline arvon hakemiseen taulukosta (BinHae.java):

public class BinHae {  // Binäärihaku järjestetystä 
                       // int-taulukosta

  private static int binHae(int[] taulu, int haettava) {

    int vasen = 0;
    int oikea = taulu.length-1;
    int keski;

    while (vasen <= oikea) {
      keski = (vasen+oikea)/2;

      if (haettava == taulu[keski])
        return keski;     // löytyi ja lopetetaan!

      if (haettava < taulu[keski])
        oikea = keski-1;
      else
        vasen = keski+1;
    }
    return -1;  // hakualue tuli tyhjäksi eikä löytynyt
  }

  public static void main(String[] args) { // testipääohjelma

    int[] a = {10, 20, 30, 40, 50};

    System.out.println(binHae(a, 5));
    System.out.println(binHae(a, 10));
    System.out.println(binHae(a, 20));
    System.out.println(binHae(a, 30));
    System.out.println(binHae(a, 35));
    System.out.println(binHae(a, 40));
    System.out.println(binHae(a, 50));
    System.out.println(binHae(a, 60));

  }
}
Tulostus:
-1
0
1
2
-1
3
4
-1


Ohjelmointitekniikkaa: taulukon järjestäminen

Taulukon alkioiden järjestäminen on myös usein tarvittu toimenpide. Järjestämisalgoritmeja on monenlaisia, tehokkaita ja vähemmän tehokkaita.

Yksi tehoton, mutta helposti ymmärrettävä järjestämisalgoritmi on ns. yksinkertainen vaihtojärjestäminen (nousevaan järjestykseen) (VaihtoJarj.java):

public class VaihtoJarj { // Yksinkertainen vaihtojärjestäminen

   private static void vaihtoJarjesta(int[] taulu) {
     for (int i=0; i < taulu.length-1; ++i) 
       for (int j=i+1; j < taulu.length; ++j)
         if (taulu[i] > taulu[j]) { // onko järjestys väärä?
            int apu = taulu[i];
            taulu[i] = taulu[j];
            taulu[j] = apu;
         }
   }

  public static void main(String[] args) { // testipääohjelma

    int[] a = {40, 20, 50, 10, 30};

    for (int i=0; i<a.length; ++i)
       System.out.print(a[i]+" ");
    System.out.println();

    vaihtoJarjesta(a);

    for (int i=0; i<a.length; ++i)
       System.out.print(a[i]+" ");
    System.out.println();

    // arvotaan taulukko:

    int[] b = new int[18];
    for (int i=0; i<b.length; ++i)
      b[i] = (int)(300*Math.random());

    for (int i=0; i<b.length; ++i)
       System.out.print(b[i]+" ");
    System.out.println();

    vaihtoJarjesta(b);

    for (int i=0; i<b.length; ++i)
       System.out.print(b[i]+" ");
    System.out.println();
  }
}
Tulostus (esimerkiksi):
40 20 50 10 30
10 20 30 40 50
234 49 212 257 273 219 289 104 81 129 158 247 133 161 205 72 229 54
49 54 72 81 104 129 133 158 161 205 212 219 229 234 247 257 273 289

Yksinkertaisen vaihtojärjestämisen (vaikkapa nousevaan järjestykseen) idea on seuraava: Toinen tehoton järjestämismenetelmä on ns. kuplajärjestäminen (bubble sort). Sen idean voisi kuvailla seuraavasti: Kuplajärjestäminen voidaan ohjelmoida vaikka (KuplaJarj.java):

   private static void kuplaJarjesta(int[] taulu) {
     for (int i=taulu.length; i > 0; --i) 
       for (int j=0; j < i-1; ++j)
         if (taulu[j] > taulu[j+1]) { // onko järjestys väärä?
            int apu = taulu[j];
            taulu[j] = taulu[j+1];
            taulu[j+1] = apu;
         }
   }


Edellisiä hieman parempi järjestämisalgoritmi on ns. lisäysjärjestäminen (insertion sort) (LisaysJarj.java):

  private static void lisaysJarjesta(int[] taulu) {
    for (int i=1; i<taulu.length; ++i) {
      int apu = taulu[i];
      int j = i;
      while (j > 0 && taulu[j-1] > apu) {
        taulu[j] = taulu[j-1]; 
        --j;
      }
      taulu[j] = apu;
    }
  }

Järjestämisalgoritmien (kuten muidenkin algoritmien!) tehokkuutta voidaan analysoida matemaattisesti: kaikki edelläesitetyt järjestämisalgoritmit kuuluvat ns. luokkaan O(n2). Se tarkoittaa suurinpiirtein, että "jos järjestettävän taulukon koko kaksinkertaistetaan, algoritmin suoritusaika nelinkertaistuu". Parhaat ns. "yleiset" järjestämisalgoritmit "toimivat ajassa O(n*log n)".

Toisaalta suoritusaikoja voidaan myös kokeellisesti mitata. Pieni vertailu antaa seuraavia suoritusaikoja kun järjestetään 50000-alkoinen int-taulukko (ohjelma VertaileJarjAlgoritmeja.java) (Ajat riippuvat tietysti käytettävästä tietokoneesta, mutta suhteet säilynevät. Esimerkin ajat ovat millisekunteina.):

                arvottu          valmiiksi      käänteisessä
                alkujärjestys    järjestetty    järj. oleva
                                 taulukko       taulukko
              
  vaihtojärj.      4908            4678           8584

  kuplajärj.       9846            4849           8236

  lisäysjärj.      2777               3           5560

Kuten taulukosta näkee, lisäysjärjestäminen on muita paljon nopeampi vaikka se kuuluukin niiden kanssa samaan "tehokkuusluokkaan". Ja erityisesti melkein järjestyksessä olevien taulukoiden järjestämisessä se on ylivoimainen.

String-olioita taulukossa

Taulukko on siis itse olio: se luodaan new-ilmauksella, viite siihen voi olla muuttujan arvona, viite taulukkoon voidaan välittää parametrina, metodi voi palauttaa arvonaan viitteen taulukkoon, ...

Myös taulukon komponentit voivat olla olioita eli tarkemmin sanottuna viitteitä olioihin!

String-olioita sisältävä taulukkomuuttuja voidaan määritellä:

   String[] taulu;
Metodin muuttujana taululla ei ole mitään alkuarvoa, luokassa määriteltynä sillä on alkuarvo null.

Erityisesti on siis syytä huomata, että määrittely String[] taulu; EI siis luo taulukko-oliota, se vain nimeää muuttujan, joka voidaan asettaa viittaamaan taulukko-olioon. Taulukko-olio on siis luotava erikseen:

  String[] taulu = new String[5]; 
asettaa muuttujan taulu alkuarvoksi viisialkioisen String-taulukko-olion. Ja tässä on syytä huomata erityisesti, että määrittely new String[5] luo taulukko-olion, jonka alkioilla EI ole arvonaan vielä mitään String-olioita. String-taulukon alkioilla on vain oletusalkuarvo null, viite "ei mihinkään olioon"!

Lause

  String[] taulu = {"kissa", "hiiri", "koira"};
asettaa muuttujan taulu alkuarvoksi kolmialkioisen String-taulukko-olion, jonka alkioilla on luetellut alkuarvot.

Huom: Pääohjelmametodin pakollinen parametri

  String[] args
on siis String-taulukko. Sen alkuarvoksi tulevat automaattisesti ohjelman käynnistyskomentoa seuraavat välilyönnein toisistaan erotetut merkkijonot, ns. komentoriviparametrit. Jos ohjelma Ohjelma käynnistetään komennolla
  java Ohjelma apina ja gorilla
pääohjelman args-parametri saa alkuarvokseen kolmialkioisen String-taulukon, jonka alkioilla on arvot "apina", "ja" ja "gorilla".

Esimerkkejä String-taulukoista (STauluKoe.java):

public class STauluKoe {

  // apuväline taulukon tulostamiseen:
  private static void tulostaSTaulu(String[] taulu) {
    System.out.println("\n ---- taulukon koko: "+taulu.length+":");
    for (String alkio : taulu)    // (for-each!)
      System.out.println(alkio);
  }

  public static void main(String[] args) {

    tulostaSTaulu(args);       // komentoriviparametrit 

    String[] ekaTaulu,
             tokaTaulu = new String[5],
             kolmasTaulu = {"kissa", "hiiri", "koira"};

    tulostaSTaulu(kolmasTaulu);

    tulostaSTaulu(tokaTaulu);  // null-alkuarvot

    for (int i=0; i<tokaTaulu.length; ++i)
      tokaTaulu[i] = "(" + i + ")";

    tulostaSTaulu(tokaTaulu);

    //  tulostaSTaulu(ekaTaulu); OLISI TÄSSÄ VIRHEELLINEN!

    ekaTaulu = kolmasTaulu;    // kopioidaan viite!
    tulostaSTaulu(ekaTaulu);

    ekaTaulu = tokaTaulu;
    tulostaSTaulu(ekaTaulu);

    ekaTaulu[1] = args[2];
    tulostaSTaulu(ekaTaulu);

    if (args.length > 2) {
      ekaTaulu[1] = args[2];
      ekaTaulu[3] = args[0];
      tulostaSTaulu(ekaTaulu);
    }
  }
}
Kun ohjelma käynnistetään komennolla:
  java STauluKoe apina ja gorilla
saadaan tulostus:
 ---- taulukon koko: 3:
apina
ja
gorilla

 ---- taulukon koko: 3:
kissa
hiiri
koira

 ---- taulukon koko: 5:
null
null
null
null
null

 ---- taulukon koko: 5:
(0)
(1)
(2)
(3)
(4)

 ---- taulukon koko: 3:
kissa
hiiri
koira

 ---- taulukon koko: 5:
(0)
(1)
(2)
(3)
(4)

 ---- taulukon koko: 5:
(0)
gorilla
(2)
apina
(4)

(*) String-oliot ja char-taulukot

String-luokan ilmentymät eliString-oliot ovat kerran synnyttyään muuttumattomia.

Varsinaiseen "merkkipeliin" luokat StringBuilder ja StringBuffer ovat käyttökelpoisempia. Tällä kurssilla noita luokkia ei kuitenkaan opetella. Tietoa niistä löytyy Javan API-kuvaksesta: StringBuilder, StringBuffer.

Koska on kuitenkin hyödyllistä oppia tekemään asiat "omin käsin", tällä kurssilla tutustutaan merkkitaulukon käyttämiseen merkkijonojen käsittelyssä:

Määrittelyt

    char[] jono1 = new char[10];
    char[] jono2  = {'k','i','s','s','a'};
asettavat muuttujan jono1 arvoksi 10-alkioisen merkkitaulukon ja muuttujan jono2 arvoksi 5-alkioisen merkkitaulukon. Jälkimmäisen alkioille annetaan alkuarvot, edellisen alkiot saavat oletusalkuarvon (merkkityypin oletusalkuarvo taulukossa ja luokan kenttänä on ns. null-merkki '\u0000').

Nyt voidaan ohjelmoida vaikkapa:

     for (char alkio : jono2)   // for-each!
        System.out.print(alkio);
     System.out.println();

     for (int i=0; i<jono1.length; ++i)
        jono1[i] = '*';

     for (int i=0; i<jono2.length; ++i)
       jono1[9-i] = jono2[i];

     for (char alkio : jono1)   // for-each!
       System.out.print(alkio);
     System.out.println(); 
Lauseet tulostavat:
kissa
*****assik


String-olioita voi muuntaa char[]-taulukoiksi String-luokan metodilla: Merkkitaulukosta puolestaan voi luoda String-olion konstruktorilla: Laaditaan esimerkkiohjelma, joka lukee merkkijonon ja tulostaa sen sellaisena, jossa kaikki 'a'-merkit on korvattu merkillä 'ä' ja 'A'-merkit on korvattu merkillä 'Ä' (Aeta.java):
import java.util.Scanner;

public class Aeta {
  private static Scanner lukija = new Scanner(System.in);
 
  public static void main(String[] args) {

    System.out.println("Anna merkkijono");
    String jono = lukija.nextLine();

    char[] mtaulu = jono.toCharArray();

    for (int i=0; i<mtaulu.length; ++i)
      if (mtaulu[i] == 'a')
        mtaulu[i] = 'ä';
      else if (mtaulu[i] == 'A')
        mtaulu[i] = 'Ä';

    jono = new String(mtaulu);

    System.out.println("Merkkijono muunnettuna:");
    System.out.println(jono);
  }
}

Huom: Merkkijonotaulukko (char[]-olio) on mahdollista tulostaa sellaisenaankin (koska println-metodi on kuormitettu!):
    System.out.println(mtaulu);


(*) Omia olioita taulukossa

Myös omia tyyppejä voidaan käyttää taulukon komponenttityyppinä! Esimerkiksi määrittely
   Pikkuvarasto[] varastot = new Pikkuvarasto[4];
määrittelee Pikkuvarasto[]-tyyppisen muuttujan ja asettaa sen alkuarvoksi neljästä Pikkuvarasto-tyyppisestä muuttujasta muodostuvan taulukon. Taulukon alkioiden alkuarvo on null. Toisin sanoen: nelialkionen taulukko-olio luodaan ja sijoitetaan muuttujan varastot arvoksi, mutta taulukon alkioiden arvona ei vielä ole Pikkuvarasto-olioita! Niitä voidaan sitten luoda erikseen:
   varastot[0] = new Pikkuvarasto(10, "Mehu");
   varastot[1] = new Pikkuvarasto(123.4, "Olut");
   ...

Tämän jälkeen taulukon alkioita - noita luotuja Pikkuvarasto-olioita - voidaan käyttää aivan samaan tapaan kuin aiemmin:
   varastot[0].vieVarastoon(30.0);
   System.out.println(varastot[0].paljonkoOn());
   varastot[0].otaVarastosta(7.0);
   System.out.println(varastot[0]);

   varastot[2] = varastot[0];
   System.out.println(varastot[2].mikäNimi());

(*) Taulukko luokan kenttänä

Kuten muitakin tyyppejä myös taulukkotyyppiä voi käyttää luokan kenttänä.

Toteutetaan luokka AlaspainLaskureita, sadasta alaspäin laskevien laskureiden toteutukseksi:

Toteutus (AlaspainLaskureita.java):
public class AlaspainLaskureita {

  // tietorakenne:

  private int[] laskurit;

  // konstruktori:

  public AlaspainLaskureita(int kpl) {
    if (kpl<=1) 
      laskurit = new int[1];
    else
      laskurit = new int[kpl];
    for (int i=0; i<laskurit.length; ++i)
      laskurit[i] = 100;  
  }

  public int seuraava(int n) {
    if (n<0 || n>=laskurit.length)
      return -1;
    int arvo = laskurit[n];
    if (laskurit[n]>0) 
      --laskurit[n];
    return arvo;
  }

  // testipääohjelma:

  public static void main(String[] args) {
    
    AlaspainLaskureita a = new AlaspainLaskureita(10);
    AlaspainLaskureita b = new AlaspainLaskureita(-10);

    // vähentyminen:
    System.out.println(a.seuraava(3));
    System.out.println(a.seuraava(3));
    System.out.println(a.seuraava(3));

    // virheellinen alkio:
    System.out.println(a.seuraava(12));

    // loppuuko väheneminen:
    for (int i=0; i<98; ++i) {
       int apu = a.seuraava(7);
    }
    System.out.println(a.seuraava(7));
    System.out.println(a.seuraava(7));
    System.out.println(a.seuraava(7));
    System.out.println(a.seuraava(7));

    // tuliko yhden mittaiseksi väärästä konstruoinnista:
    System.out.println(b.seuraava(0));
    System.out.println(b.seuraava(0));
    System.out.println(b.seuraava(1));
  }
}
Testipääohjelma tulostaa:
100
99
98
-1
2
1
0
0
100
99
-1


Takaisin luvun 2 sisällysluetteloon.