Tehtävät
Viidennen osion tavoitteet

Kurssin viidennessä osiossa kertaamme hieman toistoa sekä pohdimme lohkoja. Tavoitteena on, että ymmärrät viimeistään nyt miten lohkot vaikuttavat muuttujien olemassaolonn. Opit useampia merkkijonojen käsittelyyn käytettäviä metodeja, sekä opit käyttämään taulukoita. Otat myös ensimmäiset askeleet listojen käsittelyyn virtana.

Lohkoista ja sisäkkäisistä toistolauseista

Aaltosululla { alkavaa ja aaltosululla } päättyvää aluetta, joka sisältää nollasta rajattomaan määrään lauseita kutsutaan lohkoksi. Kuten olemme aiemmin nähneet, lohkoja käytetään luokkaan kuuluvan alueen rajaamisessa, metodille kuuluvan alueen rajaamisessa sekä ehto- ja toistolauseiden alueen rajaamisessa. Avautuvalle aaltosululle tulee aina löytyä vastaava sulkeva pari.

Eräs tärkeä, liian pieneen rooliin jäänyt aaltosulkuihin ja lohkoihin liittyvä tieto on se, että lohkon sisällä määritellyt muuttujat ovat olemassa vain kyseisen lohkon sisällä.

Alla olevassa esimerkissä määritellään ehtolauseeseen liittyvän lohkon sisällä tekstimuuttuja teksti, joka on olemassa vain lohkon sisällä. Lohkossa esitellyn muuttujan tulostus lohkon ulkopuolella ei toimi!

int luku = 5;

if (luku == 5) {
    String teksti = "Oho!";
}

// muuttujaa teksti ei ole olemassa täällä:
// se määriteltiin edelläolevan lohkon sisällä
System.out.println(teksti);

Lohkossa voidaan käyttää ja muuttaa sen ulkopuolella määriteltyjä muuttujia, kunhan ne on määritelty ennen lohkoa.

int luku = 5;

if (luku == 5) {
    luku = 6;
}

System.out.println(luku); // tulostaa luvun 6
String teksti = "Jee!";
int luku = 5;

if (luku == 5) {
    teksti = "Oho!";
}

System.out.println(teksti); // tulostaa "Oho!"

Lohkon sisällä voi olla lähes mitä tahansa koodia. Esimerkiksi while-toistolauseen määrittelemän lohkon sisällä voi olla toinen while-toistolauseke. Tarkastellaan seuraavaa ohjelmaa.

int rivinumero = 0;

while (rivinumero < 3) {
    System.out.print(rivinumero + ": ");

    int luku = 0;
    while (luku < 3) {
        System.out.print(luku + " ");
        luku++;
    }

    System.out.println();
    rivinumero++;
}

Ohjelman tulostus on seuraava:

0: 0 1 2
1: 0 1 2
2: 0 1 2

Eli mitä ohjelmassa tapahtuukaan? Jos ajatellaan pelkkää ulommaista while-lohkoa, on toiminnallisuus helppo ymmärtää.

int rivinumero = 0;

while (rivinumero < 3) {
    System.out.print(rivinumero + ": ");

    // sisempi while

    System.out.println();
    rivinumero++;
}

Ensin rivinumero=0 ja tulostuu 0: ja rivinvaihto. Tämän jälkeen rivinumero kasvaa ja tulostuu ykkönen, jne., eli ulompi while saa aikaan seuraavan:

0:
1:
2:

Myös sisempi while on helppo ymmärtää erillään. Se saa aina aikaan tulosteen 0 1 2. Kun yhdistämme nämä kaksi, huomaamme, että sisempi while-toistolause suorittaa tulosteensa aina juuri ennen ulomman while-toistolauseen tulostamaa rivinvaihtoa.

int luku = 0;
while (luku < 3) {
    System.out.print(luku + " ");
    luku++;
}
0 1 2
Toiminnallisuuden yhdistäminen vai pilkkominen

Edellä kuvasimme lohkojen toimintaa esimerkillä, missä yhdistimme kaksi erillistä toistolausetta yhteen. Samalla ohjelmasta kuitenkin muodostui hieman monimutkaisempi: mitä enemmän palasia lisäämme yhteen, sitä enemmän kokonaisuuden hallinta vie keskittymisestä.

Jos ohjelmaa haluttaisiin jatkokehittää, voisikin olla näppärä toteuttaa se osissa metodeina. Tällöin yhden metodin vastuulla voisi olla esimerkiksi sisempi toistolause, ja toisen metodin vastuulla ulompi toistolause.

Toisen muuttujan käyttö toiston ehtona

Tutkitaan seuraavaa muunnosta edelliseen esimerkkiin:

int rivinumero = 0;

while (rivinumero < 3) {
    System.out.print(rivinumero + ": ");

    int luku = 0;
    while (luku <= rivinumero) {
        System.out.print(luku + " ");
        luku++;
    }

    System.out.println();
    rivinumero++;
}

Sisemmän toistolausekkeen toistojen määrä riippuukin nyt ulommassa toistolausekkeessa käytettävän muuttujan rivinumero arvosta. Eli kun rivinumero=0, tulostaa sisempi toistolause luvun 0, kun rivinumero=1, tulostaa sisempi toistolauseke "0 1 ", jne. Koko ohjelman tulostus on seuraava:

0: 0
1: 0 1
2: 0 1 2

Seuraava ohjelma tulostaa lukujen 1..10 kertotaulun.

int rivinumero = 1;

while (rivinumero <= 10) {
    int sarake = 1;

    while (sarake <= 10) {
        System.out.print(rivinumero * sarake + " ");
        sarake++;
    }

    System.out.println();
    rivinumero++;
}

Tulostus näyttää seuraavalta:

1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27 30
4 8 12 16 20 24 28 32 36 40
5 10 15 20 25 30 35 40 45 50
6 12 18 24 30 36 42 48 54 60
7 14 21 28 35 42 49 56 63 70
8 16 24 32 40 48 56 64 72 80
9 18 27 36 45 54 63 72 81 90
10 20 30 40 50 60 70 80 90 100

Ylimmällä rivillä on luvun 1 kertotaulu. Alussa rivinumero=1 ja sisemmän toistolauseen muuttuja sarake saa arvot 1...10. Jokaisella rivinumero, sarake -arvoparilla tulostetaan niiden tulo. Eli alussa rivinumero=1, sarake=1, sitten rivinumero=1, sarake=2, ..., rivinumero=1, sarake=10 seuraavaksi rivinumero=2, sarake=1, jne.

Kertotaulu-ohjelman voi pilkkoa pienempiin osiin. Voimme määritellä metodit public void tulostaKertotaulunRivi(int kerroin, int montakokertaa) ja public void tulostaKertotaulu(int mihinAsti).

public class Kertotaulu {

    public void tulosta(int mihinAsti) {
        int rivinumero = 1;

        while (rivinumero <= mihinAsti) {
            tulostaKertotaulunRivi(rivinumero, mihinAsti);

            System.out.println();
            rivinumero++;
        }
    }


    public void tulostaKertotaulunRivi(int rivi, int lukuja) {
        int sarake = 1;

        while (sarake <= lukuja) {
            System.out.print(rivi * sarake + " ");
            sarake++;
        }
    }
}

Nyt kutsu new Kertotaulu().tulosta(5); tulostaa allaolevan kertotaulun.

1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
4 8 12 16 20
5 10 15 20 25

Luo luokka Lukutulostin ja tee sille seuraavat toiminnallisuudet.

Lukuporras

Toteuta luokalle Lukutulostin metodi public void lukuporras(int luku). Metodin tulee toimia seuraavasti.

Lukutulostin tulostin = new Lukutulostin();
tulostin.lukuporras(2);
System.out.println();
tulostin.lukuporras(3);
1
1 2

1
1 2
1 2 3
Lukutulostin tulostin = new Lukutulostin();
tulostin.lukuporras(5);
System.out.println();
tulostin.lukuporras(2);
1
1 2
1 2 3
1 2 3 4
1 2 3 4 5

1
1 2

Jatkuva lukuporras

Toteuta luokalle Lukutulostin metodi public void jatkuvaLukuporras(int luku). Metodin tulee toimia seuraavasti.

Lukutulostin tulostin = new Lukutulostin();
tulostin.jatkuvaLukuporras(2);
System.out.println();
tulostin.jatkuvaLukuporras(3);
1
2 3

1
2 3
4 5 6
Lukutulostin tulostin = new Lukutulostin();
tulostin.jatkuvaLukuporras(5);
System.out.println();
tulostin.jatkuvaLukuporras(2);
1
2 3
4 5 6
7 8 9 10
11 12 13 14 15

1
2 3

Kertokolmio

Toteuta luokalle Lukutulostin metodi public void kertokolmio(int luku). Kertokolmio-metodin tulee toimia seuraavien esimerkkien mukaisesti.

Lukutulostin tulostin = new Lukutulostin();
tulostin.kertokolmio(2);
1
2 4
Lukutulostin tulostin = new Lukutulostin();
tulostin.kertokolmio(5);
System.out.println();
tulostin.kertokolmio(3);
1
2 4
3 6 9
4 8 12 16
5 10 15 20 25

1
2 4
3 6 9

Lohkot ja luokat

Luokan määrittely alkaa luokan nimellä, jota seuraa lohko.

// luokan Esimerkki määrittely
public class Esimerkki {

}

Tämän lohkon sisälle tulee oliomuuttujat, eli ne muuttujat, joita luokasta tehtävät oliot saavat omakseen.

// luokan Esimerkki määrittely
public class Esimerkki {
  // oliomuuttujat
}

Oliomuuttujien lisäksi luokan määrittelyyn käytettävä lohko sisältää konstruktoreja ja metodeja.

// luokan Esimerkki määrittely
public class Esimerkki {
  // oliomuuttujat

  // konstruktorit

  // metodit
}

Jokainen konstruktori ja metodi myös määrittelee oman lohkonsa, jonka sisällä olevat lauseet suoritetaan konstruktoria tai metodia kutsuttaessa.

// luokan Esimerkki määrittely
public class Esimerkki {
  // oliomuuttujat

  // konstruktorit
  public Esimerkki() {
    // konstruktorin oma lohko
  }

  // metodit
  public void tulostaLuku() {
    // metodin tulostaLuku oma lohko
  }
}

Konstruktoreissa ja metodeissa voi olla myös sisäisiä lohkoja.

// luokan Esimerkki määrittely
public class Esimerkki {
  // oliomuuttujat

  // konstruktorit
  public Esimerkki() {
    // konstruktorin oma lohko
  }

  // metodit
  public void tulostaLuku() {
    // metodin tulostaLuku oma lohko
    int luku = 5;
    if (luku > 4) {
      // lisää lohkoja!
    }
  }
}

Merkkijonot

Tutustumme seuraavaksi merkkijonoihin hieman tarkemmin. Olemme käyttäneet merkkijonoja jo aiemmin sekä oppineet vertailemaan niitä toisiinsa merkkijonon equals-metodilla. Merkkijonot ovat String-tyyppisiä olioita, joilla on käytössä kaikki String-luokkaan liittyvät metodit. Toisin kuin käytännössä lähes kaikkien muiden olioiden tapauksessa, String-olioiden tapauksessa luomiseen ei tarvitse new-kutsua.

String elain = "Koira";

if (elain.equals("Koira")) {
    System.out.println(elain + " sanoo vuh vuh");
} else if (elain.equals("Kissa")) {
    System.out.println(elain + " sanoo miau miau");
}

Lause String elain = "Koira"; on Javan tarjoama lyhennys lauseesta String elain = new String("Koira");. Yllä oleva lähdekoodi on siis käytännössä sama seuraavan kanssa.

String elain = new String("Koira");

if (elain.equals("Koira")) {
    System.out.println(elain + " sanoo vuh vuh");
} else if (elain.equals("Kissa")) {
    System.out.println(elain + " sanoo miau miau");
}

String on Javan mukana tuleva luokka, jota ohjelmoija voi käyttää merkkijonojen käsittelyyn.

Javan API ja dokumentaatio

Ohjelmointikielet tarjoavat tyypillisesti merkittävän määrän valmiita apuvälineitä ohjelmoijan käyttöön. Javassa nämä apuvälineet ovat listattuna Javan tarjoamassa APIssa. Javan API sisältää dokumentaation jokaisesta Javan mukana tulevasta luokasta.

Javan APIa voi tarkastella osoitteessa https://docs.oracle.com/javase/8/docs/api/index.html?overview-summary.html. Kun avaat sivun, voit käydä vasemmassa alalaidassa olevaa listaa läpi, ja etsiä sieltä String-luokan. Kun klikkaat String-luokan linkkiä, pääset sivulle https://docs.oracle.com/javase/8/docs/api/java/lang/String.html. Sivu sisältää kaikki String-luokan tarjoamat metodit.

Ohjelmoijana kehittymiseen liittyy valmiiden dokumentaatioiden lukeminen. Otamme tällä kurssilla pieniä askeleita tähän liittyen. Vaikka edellä mainittu Javan API voi tuntua tällä hetkellä hyvin monimutkaiselta ja jopa pelottavalta, älä huoli. Sitä ei tarvitse missään nimessä opetella ulkoa. Sen lukeminen ja tiedon etsiminen sieltä tulee kurssin aikana hiljalleen tutuksi.

Tutustutaan seuraavaksi muutamaan yleiskäyttöiseen String-luokan tarjoamaan metodiin. Metodit ovat (1) merkkijonon pituuden selvittämiseen käytettävä length(), (2) yksittäisen merkin hakemiseen käytetty charAt(int indeksi), (3) merkkijonon osajonon ottamiseen käytetty substring(int alku, int loppu) ja sen variantti substring(int alku), (4) merkkijonosta etsimiseen käytetty indexOf(String etsittava), sekä (5) merkkijonon osiin jakamiseen tarkoitettu split(String jakaja).

Merkkijonon pituus

Metodi length() palauttaa merkkijono-olion pituuden kokonaislukuna.

String banaani = "banaani";
String kurkku = "kurkku";
String yhdessa = banaani + kurkku;

System.out.println("Banaanin pituus on " + banaani.length());
System.out.println("Kurkku pituus on " + kurkku.length());
System.out.println("Sanan " + yhdessa + " pituus on " + yhdessa.length());

Yllä metodia length() kutsutaan kolmelle eri merkkijono-oliolle. Kutsu banaani.length() kutsuu metodia length() oliolle banaani, ja kutsu kurkku.length() kutsuu metodia length() oliolle kurkku. Tässä vaiheessa tiedämme jo, että pisteen vasemman puoleinen osa kertoo minkä olion metodia kutsutaan.

Toteuta ohjelma, joka kysyy käyttäjän nimen ja ilmoittaa, kuinka monta kirjainta käyttäjän syöttämässä nimessä on.

Anna nimi: Pekka
Kirjainmäärä: 5
Anna nimi: Katariina
Kirjainmäärä: 9

Yksittäinen merkki merkkijonosta

Javassa on erillinen char-tietotyyppi yksittäisiä merkkejä varten. Yksittäiseen char-muuttujaan voi tallentaa yhden merkin. Merkin asetus char-tyyppiseen muuttujaan tapahtuu asetuslausekkeella -- toisin kuin merkkijonomuuttuja, joka aloitetaan ja lopetetaan lainausmerkillä "merkkijono", yksittäinen merkki aloitetaan ja lopetetaan puolilainausmerkillä 'a'. Merkin tulostaminen onnistuu tutulla tulostuskomennolla:

char merkki = 'a';
System.out.println(merkki);
a

Merkkijonolta voidaan pyytää yksittäisiä merkkejä niiden indeksin perusteella. Tämä onnistuu metodilla charAt(int indeksi), joka saa parametrina halutun merkin indeksin merkkijonossa. Kuten yleensä, indeksin laskeminen alkaa nollasta, eli esimerkiksi neljäs merkki on indeksissä kolme.

String kirja = "Kalavale";

char merkki = kirja.charAt(3);
System.out.println("Merkkijonon " + kirja + " neljäs merkki on on " + merkki);
System.out.println("Merkkijonon " + kirja + " ensimmäinen merkki on " + kirja.charAt(0));
Merkkijonon Kalavale neljäs merkki on a
Merkkijonon Kalavale ensimmäinen merkki on K

Koska merkkijonon merkkien indeksointi alkaa paikasta 0, on viimeisen merkin indeksi "merkkijonon pituus miinus yksi", eli esimerkiksi kirja.charAt(kirja.length() - 1).

String elain = "Kissa";

char viimeinen = elain.charAt(elain.length() - 1);
System.out.println("Kissa-merkkijonon viimeinen merkki on " + viimeinen);
Kissa-merkkijonon viimeinen merkki on a

Merkin hakeminen olemattomasta paikasta, eli vaikkapa indeksistä -1 tai metodin length() palauttaman arvon määrittelemästä kohdasta aiheuttaa virhetilanteen, ja kaataa ohjelman. Alla olevassa esimerkissä yritämme hakea kirjainta kohdasta jota ei ole olemassa.

public class Main {

    public static void main(String[] args) {
        String merkkijono = "Olipa kerran..";
        System.out.println(merkkijono.charAt(merkkijono.length()));
    }
}
Exception in thread "main" java.lang.StringIndexOutOfBoundsException:
    String index out of range: 14
        at java.lang.String.charAt(String.java:658)
        at Main.main(Main.java:5)

Ylläoleva virheviesti kertoo että yritimme hakea merkkiä indeksistä 14, joka on merkkijonon ulkopuolella. Virheellinen tapahtuma alkoi kun suoritettiin Main-luokan rivillä 5 olevaa lausetta. Lauseessa kutsutaan luokan String metodia charAt, ja virhe tapahtuu String-luokan rivillä 658. Kuten huomaat, virheet kertovat myös Javan tarjoamien luokkien omasta toiminnallisuudesta. Olemme kuitenkin enemmän kiinnostuneita Main-luokasta sekä muista toteuttamistamme luokista, sillä ne ovat hyvin paljon todennäköisempiä lähteitä virhetilanteille.

Tee ohjelma, joka kysyy käyttäjän nimen ja ilmoittaa nimen ensimmäisen merkin.

Anna nimi: Pekka
Ensimmäinen kirjain: P
Anna nimi: Katariina
Ensimmäinen kirjain: K

Tee ohjelma, joka kysyy käyttäjän nimen ja ilmoittaa nimen viimeisen merkin.

Anna nimi: Pekka
Viimeinen kirjain: a
Anna nimi: Katariina
Viimeinen kirjain: a

Merkkijonojen yhteydessä voi käyttää toistolausetta aivan kuten muissakin ohjelmissa. Alla olevassa esimerkissä tulostetaan syötetystä merkkijonosta kaikki merkit paitsi ensimmäinen ja viimeinen.

Scanner lukija = new Scanner(System.in);

String luettu = lukija.nextLine();
System.out.println("Tarkastellaan merkkijonoa " + luettu);

int indeksi = 1;
while (indeksi < luettu.length() - 2) {
    System.out.println("Indeksissä " + indeksi + " on " + luettu.charAt(indeksi));
    indeksi++;
}
Saippuakauppias
Tarkastellaan merkkijonoa Saippuakauppias
Indeksissä 1 on a
Indeksissä 2 on i
Indeksissä 3 on p
Indeksissä 4 on p
Indeksissä 5 on u
Indeksissä 6 on a
Indeksissä 7 on k
Indeksissä 8 on a
Indeksissä 9 on u
Indeksissä 10 on p
Indeksissä 11 on p
Indeksissä 12 on i
Indeksissä 13 on a

Tee ohjelma, joka kysyy käyttäjän nimen ja ilmoittaa sen kolme ensimmäistä merkkiä, jokaisen merkin erikseen. Jos käyttäjän syöttämän merkkijonon pituus on alle kolme merkkiä, ei ohjelma tulosta mitään.

Anna nimi: Pekka
1. kirjain: P
2. kirjain: e
3. kirjain: k
Anna nimi: me

Huom: ole tässä ja seuraavassa tehtävässä erityisen tarkka tulostusasun suhteen! Tulostuksessa tulee olla yksi välilyönti sekä pisteen että kaksoispisteen jälkeen!

Tee ohjelma, joka kysyy käyttäjän nimen ja ilmoittaa sen merkit erikseen.

Anna nimi: Pekka
1. kirjain: P
2. kirjain: e
3. kirjain: k
4. kirjain: k
5. kirjain: a
Anna nimi: Katariina
1. kirjain: K
2. kirjain: a
3. kirjain: t
4. kirjain: a
5. kirjain: r
6. kirjain: i
7. kirjain: i
8. kirjain: n
9. kirjain: a

Tee ohjelma, joka kysyy käyttäjän nimen ja tulostaa sen väärinpäin.

Anna nimi: Pekka
Väärinpäin: akkeP
Anna nimi: Katariina
Väärinpäin: aniirataK

Vihje: Yksittäisen merkin (ilman rivinvaihtoa) saa tulostettua komennolla System.out.print().

Toteuta luokka Sanatulostin, jossa on seuraavat toiminnallisuudet.

Sanaporras

Toteuta luokalle Sanatulostin merkkijonon vastaanottava konstruktori sekä metodi public void sanaporras(int luku), joka toimii seuraavasti:

Sanatulostin tulostin = new Sanatulostin("Ananas");
tulostin.sanaporras(2);
System.out.println();
tulostin.sanaporras(4);
A
na

A
na
nas
Anan
Sanatulostin tulostin = new Sanatulostin("Sauna");
tulostin.sanaporras(3);
S
au
naS

Laskeva sanaporras

Toteuta luokalle Sanatulostin metodi public void laskevaSanaporras(int luku) joka toimii seuraavasti:

Sanatulostin tulostin = new Sanatulostin("Ananas");
tulostin.laskevaSanaporras(2);
System.out.println();
tulostin.laskevaSanaporras(3);
An
a

Ana
na
s
Sanatulostin tulostin = new Sanatulostin("Sauna");
tulostin.laskevaSanaporras(4);
Saun
aSa
un
a

Sanapyramidi

Toteuta luokalle Sanatulostin metodi public void sanapyramidi(int luku), joka toimii seuraavasti:

Sanatulostin tulostin = new Sanatulostin("Nauris");
tulostin.sanapyramidi(3);
System.out.println();
tulostin.sanapyramidi(1);
N
au
ris
Na
u

N
Sanatulostin tulostin = new Sanatulostin("Saippuakauppias");
tulostin.sanapyramidi(4);
System.out.println();
tulostin.sanapyramidi(2);
S
ai
ppu
akau
ppi
as
S

S
ai
p

Merkkijonon osajono

Merkkijonosta halutaan usein lukea jokin tietty osa. Tämä onnistuu metodilla substring. Sitä voidaan käyttää kahdella tavalla: yksiparametrisenä palauttamaan merkkijonon loppuosa tai kaksiparametrisena palauttamaan parametrien määrittelemä osajono merkkijonosta:

String kirja = "Kalavale";

System.out.println(kirja.substring(4));
System.out.println(kirja.substring(2, 6));
vale
lava

Koska substring-metodin paluuarvo on tyyppiä String, voidaan metodin paluuarvo ottaa talteen String-tyyppiseen muuttujaan loppuosa.

String kirja = "8 veljestä";

String loppuosa = kirja.substring(2);
System.out.println("7 " + loppuosa);
7 veljestä

Tee ohjelma, joka tulostaa sanan alkuosan. Ohjelma kysyy käyttäjältä sanan ja alkuosan pituuden. Käytä ohjelmassa metodia substring.

Anna sana: esimerkki
Alkuosan pituus: 4
Tulos: esim
Anna sana: esimerkki
Alkuosan pituus: 7
Tulos: esimerk

Tee ohjelma, joka tulostaa sanan loppuosan. Ohjelma kysyy käyttäjältä sanan ja loppuosan pituuden. Käytä ohjelmassa merkkijonon metodia substring.

Anna sana: esimerkki
Loppuosan pituus: 4
Tulos: rkki
Anna sana: esimerkki
Loppuosan pituus: 7
Tulos: imerkki

Merkkijonon sijainnin etsiminen

String-luokan metodit tarjoavat myös mahdollisuuden etsiä merkkijonosta annettua merkkijonoa. Esimerkiksi merkkijono "erkki" löytyy merkkijonosta "merkki" alkaen indeksistä 1. Metodi indexOf(String etsitty) etsii merkkijono-oliosta metodille parametrina annettua merkkijonoa. Jos etsitty merkkijono löytyy, metodi indexOf(String etsittava) palauttaa merkkijono-olion indeksin, josta etsittävä merkkijono alkaa. Jos taas merkkijonoa ei löydy, metodi palauttaa arvon -1.

String sana = "merkkijono";

int indeksi = sana.indexOf("erkki");
System.out.println(indeksi);
System.out.println(sana.substring(indeksi));

indeksi = sana.indexOf("jono");
System.out.println(indeksi);
System.out.println(sana.substring(indeksi));

indeksi = sana.indexOf("kirja");
System.out.println(indeksi);
System.out.println(sana.substring(indeksi));
1
erkkijono
6
jono
-1
Exception in thread "main" java.lang.StringIndexOutOfBoundsException:
  String index out of range: -1
  at ...

Screencast aiheesta:

Tee ohjelma, joka kysyy käyttäjältä kaksi sanaa. Tämän jälkeen ohjelma kertoo onko toinen sana ensimmäisen sanan osana. Käytä ohjelmassa merkkijonon metodia indexOf.

Anna 1. sana: suppilovahvero
Anna 2. sana: ilo
Sana 'ilo' on sanan 'suppilovahvero' osana.
Anna 1. sana: suppilovahvero
Anna 2. sana: suru
Sana 'suru' ei ole sanan 'suppilovahvero' osana.

Huom: toteuta ohjelmasi tulostus täsmälleen samassa muodossa kuin esimerkin tulostus!

Merkkijonon jakaminen osiin

Merkkijonon jakaminen osiin tapahtuu metodilla split(String jakaja), jolle annetaan parametrina merkkijono, minkä perusteella jakaminen tapahtuu. Metodi split(String jakaja) palauttaa merkkijonotaulukon, eli eräänlaisen listan merkkijonoja.

String pilkottava = "Olipa kerran elämä";
String[] palat = pilkottava.split(" ");

int indeksi = 0;
while(indeksi < palat.length) {
    String pala = palat[indeksi];
    System.out.println(pala);
    indeksi++;
}
Olipa
kerran
elämä

Taulukko

Taulukko on useamman arvon säilömiseen tarkoitettu olio. Toisin kuin listat, sen sisältämien arvojen lukumäärä on määrätty taulukon luonnin yhteydessä, eikä taulukon perään voi lisätä uusia arvoja. Taulukko-olio esitellään lauseella tyyppi[] taulukko = new tyyppi[koko];, missä tyyppi on taulukon sisältämien arvojen tyyppi ja koko on taulukon arvojen lukumäärä.

Alla olevassa esimerkissä luodaan merkkijonoja sisältävä taulukko, jossa on kolme paikkaa. Tämän jälkeen paikkaan 0 asetetaan arvo "Hei" ja paikkaan 1 asetetaan arvo "maailma". Tämän jälkeen taulukon sisältö tulostetaan.

String[] taulukko = new String[3];
taulukko[0] = "Hei";
taulukko[1] = "maailma";

int indeksi = 0;
while(indeksi < taulukko.length) {
    String arvo = taulukko[indeksi];
    System.out.println(arvo);
    indeksi++;
}
Hei
maailma
null

Taulukon pituuden saa selville siihen liittyvän kaikille näkyvän kokonaislukutyyppisen muuttujan length avulla. Esimerkiksi taulukko.length. Tiettyyn taulukon kohtaan viitataan hakasulkujen avulla. Alla olevassa esimerkissä vaihdetaan taulukon indeksien 1 ja 2 sisältö päittäin.

String[] taulukko = new String[3];
taulukko[0] = "Hei";
taulukko[1] = "maailma";

String apu = taulukko[1];
taulukko[1] = taulukko[2];
taulukko[2] = apu;

int indeksi = 0;
while(indeksi < taulukko.length) {
    String arvo = taulukko[indeksi];
    System.out.println(arvo);
    indeksi++;
}
Hei
null
maailma

Jos indeksillä osoitetaan taulukon ohi, eli alkioon jota ei ole olemassa, niin saadaan virheilmoitus ArrayIndexOutOfBoundsException. Virhe kertoo ettei haluttua indeksiä ole olemassa kyseisessä taulukossa.

Tee ohjelma, joka kysyy käyttäjältä merkkijonoa. Ohjelma pilkkoo tämän jälkeen merkkijonon osiin välilyöntien perusteella ja tulostaa osat yksitellen.

Mikä lause?
Mikä
lause?
Talo on punainen
Talo
on
punainen

Peltipoliisit ovat tien varrella sijaitsevia valvontakameroita. Peltipoliisit sekä mittaavat autoilijan nopeuden että ottavat tarvittaessa autosta valokuvan. Kuvista tunnistetaan tämän jälkeen rekisterinumero, ja tieto rekisterinumerosta ja nopeudesta lähetetään käsittelyyn.

Käsiteltävä tieto tulee ohjelmaan aina merkkijonona, joka sisältää rekisterinumeron ja mitatun nopeuden. Syötetty tieto on muodossa rekisterinumero;nopeus.

Tehtävänäsi on toteuttaa ohjelma, joka lukee ylläolevassa muodossa olevia syötteitä käyttäjältä. Lukeminen lopetetaan kun käyttäjä syöttää tyhjän merkkijonon. Ohjelman tulee tämän jälkeen kertoa (1) suurin mittaus sekä siihen liittynyt rekisterinumero, (2) pienin mittaus sekä siihen liittynyt rekisterinumero, sekä (3) mitattujen nopeuksien keskiarvo. Voit olettaa, että syötteitä on aina vähintään yksi. Tehtävä on kahden tehtäväpisteen arvoinen.

SUT-11;102
REP-11;122
PON-1;62
HA-LOL;622

Suurin: HA-LOL, 622
Pienin: PON-1, 62
Keskiarvo: 227.0
HA-LOL;622

Suurin: HA-LOL, 622
Pienin: HA-LOL, 622
Keskiarvo: 622.0

Listan käsittely arvojen virtana

Olemme tähän mennessä käyttäneet while-toistolausetta oikeastaan kaikkeen toistamista vaativaan toimintaan. Tarkastellaan seuraavaksi menetelmiä, jotka on tarkoitettu erityisesti listojen käsittelyyn. Käytämme esimerkkidatana Avoindata.fi-palvelussa olevia tietoaineistoja.

Tarkastellaan Helsingin pyöräilijämääriä tunneittain vuoden 2014 alusta alkaen (löytyy osoitteesta https://www.avoindata.fi/data/fi/dataset/helsingin-pyorailijamaarat). Tiedoston sarakkeet sisältävät mittauspisteet (ensimmäinen sarake on päivämäärä ja tunti), rivit tuntikohtaiset mittaukset mittauspisteille. Sarakkeiden arvot ovat eritelty puolipisteillä. Alla esimerkissä tiedoston ensimmäiset 11 riviä, otsakeriviä on leikattu materiaalin luettavuuden takia.

Päivämäärä;Huopalahti (asema);Kaisaniemi;Kulosaaren silta et.;....
ke 1 tammi 2014 00:00;;1;;;;;;2;5;3;;11;;;7;8
ke 1 tammi 2014 01:00;;3;;;;;;6;5;1;;8;;;5;4
ke 1 tammi 2014 02:00;;3;;;;;;1;1;1;;14;;;2;11
ke 1 tammi 2014 03:00;;2;;;;;;0;2;0;;7;;;5;3
ke 1 tammi 2014 04:00;;4;;;;;;1;1;1;;9;;;1;4
ke 1 tammi 2014 05:00;;2;;;;;;0;1;1;;7;;;1;2
ke 1 tammi 2014 06:00;;3;;;;;;6;2;2;;0;;;1;3
ke 1 tammi 2014 07:00;;1;;;;;;5;7;3;;3;;;3;5
ke 1 tammi 2014 08:00;;2;;;;;;1;1;0;;2;;;3;9
ke 1 tammi 2014 09:00;;2;;;;;;1;6;0;;4;;;0;8

Yllä olevassa datassa on tammikuun ensimmäisen päivän ensimmäisen 10 tunnin tiedot. Huopalahden asemalta ei ole vielä yhtäkään mittausta (asemalla ei vielä mittauspistettä). Kaisaniemen mittauspisteen ohi kulkee 1 pyöräilijä puolenyön ja yhden välillä, 3 pyöräilijää yhden ja kahden välillä, 3 pyöräilijää kahden ja kolmen välillä jne.

Kun datan lukee merkkijonomuodossa listalle siten, että jokainen rivi vastaa yhtä merkkijonoa, on sen käsittely merkkijonojen tarjoamien metodien avulla melko suoraviivaista. Seuraavissa aliluvuissa oletetaan, että käytössämme on ArrayList-tyyppinen lista merkkijonoja, joista jokainen merkkijono vastaa yhtä pyöräilymäärätilaston riviä.

Lista ja while-toistolause

Kun ohjelmoija käyttää while-toistolausetta, hän käy listan arvoja läpi listan indeksi kerrallaan. Ohjelmoijan tulee tehdä toistolauseelle lopetusehto, jonka lisäksi indeksimuuttujaa tyypillisesti päivitetään toistolauseen lohkossa. Alla olevassa esimerkissä käsitellään pyöräilydataa ja etsitään kaisaniemen vuoden 2014 keskimääräinen tuntikohtainen ja päiväkohtainen pyöräilijämäärä voidaan laskea seuraavasti.

ArrayList<String> pyorailijat = new ArrayList<>();

// listalle on luettu koko tilastodata

int tunteja = 0;
int pyorailijoita = 0;

int indeksi = 1;
while (indeksi < pyorailijat.size()) {
    String rivi = pyorailijat.get(indeksi);
    indeksi++;

    String[] palat = rivi.split(";");

    if (!palat[0].contains("2014")) {
        continue;
    }

    tunteja++;

    if(!palat[2].isEmpty()) {
        pyorailijoita += Integer.parseInt(palat[2]);
    }
}

System.out.println();
System.out.println("Pyöräilijöitä keskimäärin tunnissa: " + (1.0 * pyorailijoita / tunteja));
System.out.println("Pyöräilijöitä keskimäärin päivässä: " + (24.0 * pyorailijoita / tunteja));

Tehtäväpohjassa on valmiiksi toteutettuna luokka TiedostonLukija, jota käytetään tiedoston sisällön lukemiseen. Luokan metodi lueTiedosto(String tiedosto) palauttaa ArrayList-tyyppisen merkkijonoja sisältävän listan, missä on kaikki luetussa tiedostossa olevat rivit.

Tehtäväpohjassa tulee mukana myös edellisessä esimerkissä nähty pyöräilijädata.

Muokkaa tehtäväpohjassa olevaa edellisestä esimerkistä kopioitua ohjelmaa siten, että se kysyy käyttäjältä vuotta ja kuukautta, ja laskee Baanan tunti- ja päiväkohtaisen keskimääräisen käyttäjämäärän annetulle vuodelle ja kuukaudelle. Baana löytyy tilaston viimeisestä sarakkeesta; voit olettaa, että käyttäjä syöttää kuukauden sopivassa muodossa (esim "tammi").

2014
tammi

Pyöräilijöitä keskimäärin tunnissa: 22.38978494623656
Pyöräilijöitä keskimäärin päivässä: 537.3548387096774
2015
touko

Pyöräilijöitä keskimäärin tunnissa: 117.77553763440861
Pyöräilijöitä keskimäärin päivässä: 2826.6129032258063

Tehtäväpohjan mukana tuleva tiedosto "helsingin-pyorailijamaarat.csv" on osoitteessa https://www.avoindata.fi/data/fi/dataset/helsingin-pyorailijamaarat olevasta Helsingin kaupunkisuunnitteluviraston tietoaineistosta muokattu versio. Alkuperäinen tietoaineisto on julkaistu avoimella Creative Commons (CC BY 4.0) -lisenssillä.

Lista ja arvojen virta

Tutustutaan listan läpikäyntiin listan arvojen virtana (stream). Kun listan arvoja käsitellään virtana, ohjelmoija kertoo mitä kullekin listan arvolle tulee tehdä, mutta ei esimerkiksi erikseen pidä kirjaa indeksistä tai kullakin hetkellä käsiteltävästä muuttujasta.

Alla olevassa esimerkissä luomme ensin listasta virran metodilla stream(). Tämän jälkeen muunnamme arvot toiseen muotoon virran metodilla map -- muunto toteutetaan String-luokan metodilla split. Seuraavaksi rajaamme metodilla filter tarkastelusta pois ne rivit, joiden päivämäärässä ei ole merkkijonoa "2014". Lopulta keräämme arvot uudelle listalle metodilla collect.

ArrayList<String> pyorailijat = new ArrayList<>();

// listalle on luettu koko tilastodata

ArrayList<String[]gt; rajatut =
    pyorailijat.stream()
               .map(merkkijono -> merkkijono.split(";"))
               .filter(taulukko -> taulukko[0].contains("2014"))
               .collect(Collectors.toCollection(ArrayList::new));


// .. tehdään vielä laskentaa

Haluamme tietää pyöräilijöiden keskimääräisen lukumäärän tunti- ja päiväkohtaisesti. Rajataan seuraavaksi edellä kerätyt arvot siten, että käsittelemme vain merkkijonoja, joissa kaisaniemeen liittyy arvoja. Tämän jälkeen muunnetaan jäljellä olevat arvot kokonaislukuvirraksi metodilla mapToInt. Kokonaislukuvirralla on valmiina metodi average, joka laskee virrassa olevien kokonaislukujen keskiarvon -- keskiarvo saadaan lopulta metodilla getAsDouble.

ArrayList<String> pyorailijat = new ArrayList<>();

// listalle on luettu koko tilastodata

ArrayList<String[]> rajatut =
    pyorailijat.stream()
               .map(merkkijono -> merkkijono.split(";"))
               .filter(taulukko -> taulukko[0].contains("2014"))
               .collect(Collectors.toCollection(ArrayList::new));

double keskiarvo = rajatut.stream()
                          .filter(taulukko -> !taulukko[2].isEmpty())
                          .mapToInt(taulukko -> Integer.parseInt(taulukko[2]))
                          .average()
                          .getAsDouble();

System.out.println("Pyöräilijöitä keskimäärin tunnissa: " + keskiarvo);
System.out.println("Pyöräilijöitä keskimäärin päivässä: " + (24.0 * keskiarvo));

Koko keskiarvon laskemisen voi myös tehdä yhtenä virran käsittelynä. Alla jokainen rivi on erikseen kommentoitu.

ArrayList<String> pyorailijat = new ArrayList<>();

// listalle on luettu koko tilastodata

double keskiarvo =
    // luodaan virta listasta pyorailijat
    pyorailijat.stream()
               // muunnetaan listan merkkijonot merkkijonotaulukoiksi
               // muunnos tapahtuu merkkijonon metodilla split
               .map(merkkijono -> merkkijono.split(";"))
               // nyt virta sisältää merkkijonotaulukkoja
               // huomioidaan vain ne merkkijonotaulukot, joissa ensimmäisessä
               // indeksissä (pvm) merkkijono "2014"
               .filter(taulukko -> taulukko[0].contains("2014"))
               // taulukon indeksissä 2 on kaisaniemen pyöräilijätilastot
               // varmistetaan, että otamme huomioon vain ne rivit, joissa
               // kaisaniemeen liittyen dataa
               .filter(taulukko -> !taulukko[2].isEmpty())
               // muunnetaan kaisaniemen pyöräilijöiden lukumäärä
               // kokonaisluvuksi, ja tehdään virrasta lukuvirta
               .mapToInt(taulukko -> Integer.parseInt(taulukko[2]))
               // kutsutaan lukuvirralle average-metodia. metodi kerää kaikki
               // jäljellä olevat luvut yhteen ja laskee niiden keskiarvon
               .average()
               // double-tyyppinen keskiarvo saadaan lopulta keskiarvon
               // metodilla getAsDouble
               .getAsDouble();

System.out.println("Pyöräilijöitä keskimäärin tunnissa: " + keskiarvo);
System.out.println("Pyöräilijöitä keskimäärin päivässä: " + (24.0 * keskiarvo));

Tehdään lämmittelytehtävä ja tutustutaan sen jälkeen tarkemmin yllä juuri tapahtuneeseen.

Tehtäväpohjassa on valmiiksi toteutettuna luokka TiedostonLukija, jota käytetään tiedoston sisällön lukemiseen. Luokan metodi lueTiedosto(String tiedosto) palauttaa ArrayList-tyyppisen merkkijonoja sisältävän listan, missä on kaikki luetussa tiedostossa olevat rivit.

Tehtäväpohjassa tulee mukana myös tuttu pyöräilijädata.

Muokkaa tehtäväpohjassa olevaa edellisestä esimerkistä kopioitua ohjelmaa siten, että se kysyy käyttäjältä vuotta ja kuukautta, ja laskee Baanan tunti- ja päiväkohtaisen keskimääräisen käyttäjämäärän annetulle vuodelle ja kuukaudelle. Baana löytyy tilaston viimeisestä sarakkeesta; voit olettaa, että käyttäjä syöttää kuukauden sopivassa muodossa (esim "tammi").

2014
tammi

Pyöräilijöitä keskimäärin tunnissa: 22.38978494623656
Pyöräilijöitä keskimäärin päivässä: 537.3548387096774
2015
touko

Pyöräilijöitä keskimäärin tunnissa: 117.77553763440861
Pyöräilijöitä keskimäärin päivässä: 2826.6129032258063

Tehtäväpohjan mukana tuleva tiedosto "helsingin-pyorailijamaarat.csv" on osoitteessa https://www.avoindata.fi/data/fi/dataset/helsingin-pyorailijamaarat olevasta Helsingin kaupunkisuunnitteluviraston tietoaineistosta muokattu versio. Alkuperäinen tietoaineisto on julkaistu avoimella Creative Commons (CC BY 4.0) -lisenssillä.

Virta (stream)

Listan muuntaminen arvojen virraksi luo tilanteen, missä ohjelmoijan vastuulla on kertoa mitä yksittäisille arvoille tulee tehdä. Arvoja voidaan rajata (filter) ja muuntaa (map), ja ne voidaan lopuksi kerätä (collect) tai jokaiselle arvolle (forEach) voidaan suorittaa jonkinlainen muu toiminto.

Alla olevassa kuvassa on kuvattu virran toimintaa. Lähtötilanteena (1) on lista, jossa on arvoja. Kun listalle kutsutaan stream()-metodia, (2) luodaan virta listan arvoista. Arvoja käsitellään tämän jälkeen yksitellen. Virran arvoja voidaan (3) rajata metodilla filter. Tämä poistaa virrasta ne arvot, jotka ovat rajauksen ulkopuolella. Virran metodilla map voidaan (4) muuntaa virrassa olevia arvoja muodosta toiseen. Metodi collect (5) kerää virrassa olevat arvot arvot sille annettuun kokoelmaan, esim. listalle.

Yllä tekstuaalisesti kuvattu virran toiminta kuvana.

 

Jos arvot haluaisi ottaa talteen, tulee ne asettaa esimerkiksi listamuuttujaan. Listan käsittely virtana ei muuta alkuperäistä listaa. Alla vielä edellinen kuva lähdekoodina siten, että kerätyt arvot asetetaan uuteen listamuuttujaan. Lopulta arvot tulostetaan.

ArrayList<Integer> lista = new ArrayList<>();
lista.add(3);
lista.add(7);
lista.add(4);
lista.add(2);
lista.add(6);

ArrayList<Integer> uusi =
    lista.stream()
         .filter(luku -> luku > 5)
         .map(luku -> luku * 2)
         .collect(Collectors.toCollection(ArrayList::new));

uusi.stream().forEach(luku -> {
    System.out.println(luku);
});
12
14

Lisää virran metodeista löytyy Javan Stream APIsta osoitteesta https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html. Kuten aiemmin on todettu, kaikkea tätä ei tarvitse opetella heti ulkoa. Sen kyllä oppii tehdessä.

Yksittäisten arvojen käsittely - forEach

Virta voidaan päättää yksittäisten arvojen käsittelyyn forEach-metodilla tai listan arvot voidaan kerätä toiselle listalle. Tarkastellaan ensin listan arvojen käsittelyä. Virran metodille forEach määritellään lohko, joka suoritetaan jokaiselle virran arvolle. Alla olevassa esimerkissä listalle lisätään kolme arvoa, jonka jälkeen listasta luodaan virta, jonka arvot tulostetaan.

ArrayList<String> lista = new ArrayList<>();
lista.add("eka");
lista.add("toka");
lista.add("kolmas");

lista.stream().forEach(arvo -> {
    System.out.println(arvo);
});
eka
toka
kolmas

Tarkastellaan metodia forEach hieman tarkemmin. Metodille annetaan parametrina tieto siitä, miten kutakin arvoa tulee käsitellä (arvo -> toiminallisuus). Määrittelyssä on käytössä arvoa kuvaava muuttuja, joka määritellään nuolen vasemmalla puolella (yllä arvo). Nuolen oikealle puolelle kirjoitetaan lauseet, jotka arvolle tulee suorittaa. Jos lauseita on vain yksi, voidaan lohkon sijaan kirjoittaa vain komento -- jos lauseita on useampi, määritellään aaltosuluilla rajattu lohko sekä sen sisälle suoritettavat lauseet.

Alla on toinen esimerkki, missä tulostetaan vain ne merkkijonot, jotka sisältävät merkin "o".

ArrayList<String> lista = new ArrayList<>();
lista.add("eka");
lista.add("toka");
lista.add("kolmas");

lista.stream().forEach(arvo -> {
    if (arvo.contains("o")) {
        System.out.println(arvo);
    }
});
toka
kolmas

Kirjoita ohjelma, joka lukee käyttäjältä merkkijonoja. Lukeminen tulee lopettaa kun käyttäjä syöttää tyhjän merkkijonon. Tulosta tämän jälkeen käyttäjän syöttämät merkkijonot.

eka
toka
kolmas
eka
toka
kolmas
Mikä ihmeen x -> ???

Virran arvoja käsitellään virtaan liittyvillä metodeilla. Metodeille voidaan antaa parametrina funktio, joka määrää mitä arvolle tulee tehdä. Rajaamiseen käytetylle metodille filter annetaan funktio, joka palauttaa arvon true tai false; muuntamiseen käytetylle metodille map annetaan funktio, joka muuntaa arvon johonkin toiseen muotoon.

Tarkastellaan edellisessä esimerkissä olleita funktioita hieman tarkemmin. Funktio luku -> luku > 5 sisältää sekä arvon määrittelyn että lausekkeen, mikä tulee evaluoida. Saman voi kirjoittaa useammassa muodossa, kts. alla.

// alkuperäinen
.filter(luku -> luku > 5)

// on sama kuin
.filter(luku -> {
    return luku > 5;
})

// on sama kuin
.filter(luku -> {
    if (luku > 5) {
        return true;
    }

    return false;
})

Vastaavasti funktio luku -> luku * 2 sisältää myös arvon määrittelyn että lausekkeen, mikä tulee evaluoida. Tämänkin voi kirjoittaa useammassa muodossa.

// alkuperäinen
.map(luku -> luku * 2)

// on sama kuin
.map(luku -> {
    return luku * 2;
})

Arvojen rajaaminen - filter

Käsiteltäviä arvoja voidaan rajata filter-metodin avulla. Metodille annetaan parametrina lause tai lohko, jonka paluuarvona on totuusarvoinen muuttuja. Paluuarvon perusteella päätetään rajataanko käsiteltävä arvo pois jatkokäsittelystä. Aiemmin näkemämme esimerkin, missä tulostetaan vain ne merkkijonot, joissa esiintyy merkkijono "o" voi toteuttaa myös seuraavasti.

ArrayList<String> lista = new ArrayList<>();
lista.add("eka");
lista.add("toka");
lista.add("kolmas");

lista.stream().filter(arvo -> arvo.contains("o")).forEach(arvo -> {
    System.out.println(arvo);
});
toka
kolmas

Metodia voi käyttää myös muiden arvojen kuten lukujen rajaamiseen. Alla olevassa esimerkissä näytetään miten metodi filter toimii kun rajauksia halutaan tehdä useampia. Esimerkissä otetaan huomioon vain kymmentä pieneemmät luvut, jotka ovat jaollisia kahdella.

ArrayList<Integer> lista = new ArrayList<>();
lista.add(8);
lista.add(-4);
lista.add(13);
lista.add(47);
lista.add(5003);

lista.stream().filter(luku -> {
    if(luku < 10) {
         return true;
    }

    if(luku % 2 == 0) {
        return true;
    }

    return false;
}).forEach(arvo -> {
    System.out.println(arvo);
});
8
-4

Kirjoita ohjelma, joka lukee käyttäjältä lukuja. Kun käyttäjä syöttää negatiivisen luvun, lukeminen lopetetaan. Tulosta tämän jälkeen ne luvut, jotka ovat välillä 1-5.

7
14
4
5
4
-1
4
5
4

Arvojen muuntaminen - map

Käsittelemäämme dataa tulee usein muuntaa muodosta toiseen. Oletetaan, että käytössämme on seuraava luokka Piste, ja että käytössämme on listallinen "x,y"-muodossa olevia pisteitä.

public class Piste {
    int x;
    int y;

    public Piste(int x, int y) {
        this.x = x;
        this.y = y;
    }

    // muita metodeja

    public String toString() {
        return "(" + this.x + ", " + this.y + ")";
    }
}

Virta tarjoaa metodin map, joka mahdollistaa arvojen muuntamiseen muodosta toiseen. Metodille määritellään arvokohtainen toiminnallisuus, joka palauttaa jonkun toisen tyyppisen muuttujan. Esimerkiksi alla metodi pilkkoo merkkijonon paloiksi ja käyttää näitä paloja Piste-olioiden luomiseen.

ArrayList<String> lista = new ArrayList<>();
lista.add("32,5");
lista.add("16,3");
lista.add("31,6");

lista.stream().map(arvo -> {
    String[] palat = arvo.split(",");
    return new Piste(Integer.parseInt(palat[0]), Integer.parseInt(palat[1]));
}).forEach(piste -> System.out.println(piste));
(32, 5)
(16, 3)
(31, 6)

Kuten muiden virran metodien tapauksessa, myös map-metodin tapauksessa toteutuksen voi tehdä ilman aaltosulkuja. Seuraavassa esimerkissä käydään kokonaislukuja sisältävä lista läpi, valitaan sieltä positiiviset luvut, joiden arvo on alle 50, ja kerrotaan arvot lopulta kahdella.

ArrayList<Integer> lista = new ArrayList<>();
lista.add(8);
lista.add(-4);
lista.add(13);
lista.add(47);
lista.add(5003);

lista.stream().filter(luku -> luku > 0 && luku < 50)
              .map(luku -> luku * 2)
              .forEach(luku -> {
                  System.out.println(luku);
              });
16
26
94

Metodi map on hyödyllinen esimerkiksi olioita läpikäytäessä, sillä sen avulla voidaan rajata tarkasteluun vain tietty osa olioiden arvoista. Seuraavassa esimerkissä tarkastellaan Henkilo-olioiden nimiä.

List<Henkilo> lista = new ArrayList<>();
// lisätään listalle henkilot

lista.stream().map(h -> h.getNimi()).forEach(n -> System.out.println(n));

Virran muunnos toisenlaiseen muotoon

Oletuksena käyttöön tuleva virta tarjoaa toiminnallisuuksia arvojen rajaamiseen, muuntamiseen sekä keräämiseen. Virran voi muuntaa myös kokonaislukuvirraksi, jolla on erityisiä kokonaislukuihin liittyviä toiminnallisuuksia.

Alla oleva esimerkki laskee kaikkien listalla olevien alkioiden keskiarvon ja tulostaa sen.

List<Integer> lista = new ArrayList<>();
lista.add(1);
lista.add(3);
lista.add(5);
lista.add(7);

double keskiarvo = lista.stream().mapToInt(i -> i).average().getAsDouble();
System.out.println(keskiarvo);
3.5

Arvojen kerääminen - collect

Virralla on myös metodi collect, jonka avulla virran arvot voidaan kerätä annettuun kokoelmaan. Olemme toistaiseksi käyttäneet kurssilla vain listoja, joten pysytään niissä vielä toistaiseksi.

ArrayList<String> lista = new ArrayList<>();
lista.add("eka");
lista.add("toka");
lista.add("kolmas");

ArrayList<String> toinen = lista.stream().collect(Collectors.toCollection(ArrayList::new));
toinen.stream().forEach(arvo -> {
    System.out.println(arvo);
});
eka
toka
kolmas

UNESCO määrittelee lukutaidon seuraavasti: "ability to identify, understand, interpret, create, communicate and compute, using printed and written materials associated with varying contexts. Literacy involves a continuum of learning in enabling individuals to achieve their goals, to develop their knowledge and potential, and to participate fully in their community and wider society".

Tehtäväpohjassa on mukana UNESCOn avoimen datan tietopankista kerätty tilasto eri maiden arvioiduista tai raportoiduista lukutaidoista viimeisen kahden vuoden ajalta. Tiedosto sisältää arviot sekä yli 15-vuotiaiden naisten että yli 15-vuotiaiden miesten lukutaidosta. Tiedoston lukutaito.csv yksittäisen rivin muoto on seuraava: teema, ikäryhmä, sukupuoli, maa, vuosi, lukutaitoprosentti. Alla on esimerkkinä tiedoston viisi ensimmäistä riviä.

Adult literacy rate, population 15+ years, female (%),United Republic of Tanzania,2015,76.08978
Adult literacy rate, population 15+ years, female (%),Zimbabwe,2015,85.28513
Adult literacy rate, population 15+ years, male (%),Honduras,2014,87.39595
Adult literacy rate, population 15+ years, male (%),Honduras,2015,88.32135
Adult literacy rate, population 15+ years, male (%),Angola,2014,82.15105
  

Tehtävänäsi on luoda ohjelma, joka arvioi annetun datan pohjalta (1) miesten keskimääräistä lukutaitoa, (2) naisten keskimääräistä lukutaitoa, ja (3) tunnistaa maan, jossa on korkein raportoitu tai arvioitu lukutaito. Käsitellä jokainen rivi täysin erillisenä maana. Merkkijonomuodossa olevan liukuluvun saa muunnettua doubleksi komennolla Double.parseDouble(String luku);, esim. double arvo = Double.parseDouble("76.08978"); -- myös virran metodi mapToDouble saattaa olla hyödyllinen.

Ohjelman mukana tulee myös aiemmista tehtävistä tuttu tiedostonlukija. Tulostuksen tulee olla seuraavankaltainen:

Miesten lukutaidon keskiarvo: ?8.1??84?65116279
Naisten lukutaidon keskiarvo: 8?.?????32??81???
Korkein lukutaito on maassa: ???

Tulostukseen tulevat arvot tulee luonnollisesti laskea ohjelman toimesta. Ylläolevaan tulostusesimerkkiin on annettu muutamia arvoja omien tulosten vertailuun miesten ja naisten lukutaidon keskiarvoihin.

Olemme tässä osiossa keskittyneet merkkijonoihin sekä menetelmiin listan läpikäyntiin. Nyt on hyvä astua askel taaksepäin ja miettiä koko kurssin tähänastista sisältöä. Osion viimeisessä tehtävässä tehtävänäsi on toteuttaa tekstimuotoinen Hirsipuu-peli.

Alla vaatimuksia peliltä:

  • Pelissä tulee olla 9 mahdollisuutta väärän kirjaimen valintaan.
  • Pelissä arvattava sana tulee kysyä käyttäjältä ennen pelin alkua.
  • Pelin tulee näyttää arvattava sana siten, että tuntemattomat osat on piilotettuna.
  • Pelaajan arvauksen tulee olla täsmälleen 1 merkin mittainen, ja pelaajaa ei rokoteta jos hän arvaa samaa merkkiä uudelleen.
  • Peli loppuu kun pelaaja on tehnyt 9 väärää arvausta tai kun hän saa arvattua koko sanan. Jos pelaaja voittaa, hän näkee lopuksi merkkijonon "Voitit!". Jos pelaaja häviää, hän näkee lopuksi merkkijonon "Hävisit!".

Ohjelmassa on vain muutama testi, jotka tarkastelevat syötteitä ja tulosteita. Saat päättää ohjelman rakenteen täysin itse.

Alla esimerkkipelin kulku. Huomaa, että alla olevassa esimerkissä myös välilyönnin käyttö on ollut hyväksyttävää.

  Mitä merkkijonoa arvataan? turingin kone
  Sana: _ _ _ _ _ _ _ _ _ _ _ _ _
  Arvauksia jäljellä: 9
  Arvatut:
  Arvaus: s

  Sana: _ _ _ _ _ _ _ _ _ _ _ _ _
  Arvauksia jäljellä: 8
  Arvatut: s
  Arvaus: a

  Sana: _ _ _ _ _ _ _ _ _ _ _ _ _
  Arvauksia jäljellä: 7
  Arvatut: s a
  Arvaus: i

  Sana: _ _ _ i _ _ i _ _ _ _ _ _
  Arvauksia jäljellä: 7
  Arvatut: s a i
  Arvaus: p

  Sana: _ _ _ i _ _ i _ _ _ _ _ _
  Arvauksia jäljellä: 6
  Arvatut: s a i p
  Arvaus: p

  Olet arvannut jo kyseisen merkin!

  Sana: _ _ _ i _ _ i _ _ _ _ _ _
  Arvauksia jäljellä: 6
  Arvatut: s a i p
  Arvaus: u

  Sana: _ u _ i _ _ i _ _ _ _ _ _
  Arvauksia jäljellä: 6
  Arvatut: s a i p u
  Arvaus: a

  Olet arvannut jo kyseisen merkin!

  Sana: _ u _ i _ _ i _ _ _ _ _ _
  Arvauksia jäljellä: 6
  Arvatut: s a i p u
  Arvaus: k

  Sana: _ u _ i _ _ i _ _ k _ _ _
  Arvauksia jäljellä: 6
  Arvatut: s a i p u k
  Arvaus: a

  Olet arvannut jo kyseisen merkin!

  Sana: _ u _ i _ _ i _ _ k _ _ _
  Arvauksia jäljellä: 6
  Arvatut: s a i p u k
  Arvaus: u

  Olet arvannut jo kyseisen merkin!

  Sana: _ u _ i _ _ i _ _ k _ _ _
  Arvauksia jäljellä: 6
  Arvatut: s a i p u k
  Arvaus: p

  Olet arvannut jo kyseisen merkin!

  Sana: _ u _ i _ _ i _ _ k _ _ _
  Arvauksia jäljellä: 6
  Arvatut: s a i p u k
  Arvaus: p

  Olet arvannut jo kyseisen merkin!

  Sana: _ u _ i _ _ i _ _ k _ _ _
  Arvauksia jäljellä: 6
  Arvatut: s a i p u k
  Arvaus: z

  Sana: _ u _ i _ _ i _ _ k _ _ _
  Arvauksia jäljellä: 5
  Arvatut: s a i p u k z
  Arvaus: x

  Sana: _ u _ i _ _ i _ _ k _ _ _
  Arvauksia jäljellä: 4
  Arvatut: s a i p u k z x
  Arvaus: c

  Sana: _ u _ i _ _ i _ _ k _ _ _
  Arvauksia jäljellä: 3
  Arvatut: s a i p u k z x c
  Arvaus: v

  Sana: _ u _ i _ _ i _ _ k _ _ _
  Arvauksia jäljellä: 2
  Arvatut: s a i p u k z x c v
  Arvaus: b

  Sana: _ u _ i _ _ i _ _ k _ _ _
  Arvauksia jäljellä: 1
  Arvatut: s a i p u k z x c v b
  Arvaus: n

  Sana: _ u _ i n _ i n _ k _ n _
  Arvauksia jäljellä: 1
  Arvatut: s a i p u k z x c v b n
  Arvaus: m

  Hävisit!

Sisällysluettelo