Tämä ei ole uusin versio kurssista. Katso myös Ohjelmoinnin MOOC 2019: https://ohjelmointi-19.mooc.fi.
Tehtävät
Kolmannen osan tavoitteet

Osaa luoda metodeja, jotka palauttavat arvon. Ymmärtää miten ohjelman suoritus etenee metodikutsun yhteydessä. Ymmärtää muuttujien näkyvyyden ja olemassaolon. Tuntee käsitteen merkkijono ja osaa käsitellä merkkijonoja (erit. vertailu). Tuntee listarakenteen ja osaa lisätä ja poistaa listalla olevia alkioita. Tuntee käsitteen indeksi ja osaa käydä listan läpi while-toistolauseen avulla.

Metodit jatkuvat

Metodien avulla ohjelmia voi pilkkoa pienemmiksi osakokonaisuuksiksi. Jos metodit nimetään kuvaavasti, ohjelmoija pystyy seuraamaan sovelluksen toimintaa metodien nimien perusteella. Tämä helpottaa ohjelman ymmärrettävyyttä niin alkuperäisen ohjelmoijan kuin ohjelmaa myöhemmin mahdollisesti ylläpitävän ohjelmoijan tai ohjelmoijien osalta. Jatketaan metodeihin tutustumista.

Metodit ja muuttujien näkyvyys

Edellisessä osassa käsitellyt muuttujien olemassaoloon liittyvät säännöt pätevät myös metodien yhteydessä. Muuttuja ei ole olemassa ennen sen esittelyä, ja muuttuja on olemassa vain niiden aaltosulkujen sisällä kuin missä se on esitelty. Metodien yhteydessä tämä tarkoittaa sitä, että metodeilla on pääsy vain niihin muuttujiin, jotka ovat määritelty metodien sisällä, tai jotka metodi saa parametrina. Alla oleva esimerkki konkretisoi tilanteen, missä kasvataKolme-metodin sisällä yritetään muuttaa main-metodissa määritellyn luku-muuttujan arvoa.

// pääohjelma
public static void main(String[] args) {
    int luku = 1;
    kasvataKolmella();
}

// metodi
public static void kasvataKolmella() {
    luku = luku + 3;
}

Yllä oleva ohjelma ei toimi, sillä metodi kasvataKolmella ei näe pääohjelman muuttujaa luku. Tarkemmin ottaen, metodi kasvataKolmella ei edes tiedä mistä muuttujasta luku on kyse, sillä muuttujaa ei ole määritelty metodissa kasvataKolmella tai sen parametreissa.

Yleisemmin voi todeta, että pääohjelman muuttujat eivät näy metodien sisälle, ja metodin muuttujat eivät näy muille metodeille tai pääohjelmalle. Ainoa keino viedä metodille tietoa metodin ulkopuolelta on parametrin avulla.

Metodille annettavat muuttujat kopioituvat

Muistellaan vielä edellisestä osasta erittäin oleellista metodien toimintaan liittyvää ominaisuutta: parametrien arvot kopioituvat. Käytännössä metodien parametrit ovat erillisiä muiden metodien muuttujista (tai parametreista), vaikka niillä olisikin sama luku. Kun metodikutsun yhteydessä metodille annetaan muuttuja, muuttujan arvo kopioituu kutsuttavan metodin metodimäärittelyssä olevan parametrimuuttujan arvoksi. Kahdessa eri metodissa olevat muuttujat ovat siis erillisiä toisistaan.

Tarkastellaan seuraavaa esimerkkiä, missä pääohjelmassa määritellään muuttuja luku. Muuttuja luku annetaan parametrina metodille kasvataKolmella.

// pääohjelma
public static void main(String[] args) {
    int luku = 1;
    System.out.println("Pääohjelman muuttujan luku arvo: " + luku);
    kasvataKolmella(luku);
    System.out.println("Pääohjelman muuttujan luku arvo: " + luku);
}

// metodi
public static void kasvataKolmella(int luku) {
    System.out.println("Metodin parametrin luku arvo: " + luku);
    luku = luku + 3;
    System.out.println("Metodin parametrin luku arvo: " + luku);
}

Ohjelman suoritus aiheuttaa seuraavanlaisen tulostuksen.

Pääohjelman muuttujan luku arvo: 1
Metodin parametrin luku arvo: 1
Metodin parametrin luku arvo: 4
Pääohjelman muuttujan luku arvo: 1

Kun metodin sisällä kasvatetaan muuttujan luku arvoa kolmella, se onnistuu. Tämä ei kuitenkaan näy pääohjelmassa olevassa muuttujassa luku. Pääohjelmassa oleva muuttuja luku on eri kuin metodissa oleva muuttuja luku.

Parametri luku kopioidaan metodin käyttöön, eli metodia kasvataKolmella varten luodaan oma muuttuja nimeltä luku, johon pääohjelmassa olevan muuttujan luku arvo kopioidaan metodikutsun yhteydessä. Metodissa kasvataKolmella oleva muuttuja luku on olemassa vain metodin suorituksen ajan, eikä sillä ole yhteyttä pääohjelman samannimiseen muuttujaan.

Metodi voi palauttaa arvon

Metodin määrittelyssä kerrotaan palauttaako metodi arvon. Jos metodi palauttaa arvon, tulee metodimäärittelyn yhteydessä kertoa palautettavan arvon tyyppi. Muulloin määrittelyssä käytetään avainsanaa void. Tähän mennessä tekemämme metodit ovat määritelty avainsanaa void käyttäen eli eivät ole palauttaneet arvoa.

public static void kasvataKolmella() {
    ...
}

Avainsana void tarkoittaa että metodi ei palauta mitään. Jos haluamme, että metodi palauttaa arvon, tulee avainsanan void paikalle asettaa palautettavan muuttujan tyyppi. Seuraavassa esimerkissä on määritelty metodi palautetaanAinaKymppi, joka palauttaa kokonaislukutyyppisen (int) muuttujan (tässä arvon 10).

Konkreettinen arvon palautus tapahtuu komennolla return, jota seuraa palautettava arvo (tai muuttujan nimi, jonka arvo palautetaan).

public static int palautetaanAinaKymppi() {
    return 10;
}

Yllä määritelty metodi palauttaa sitä kutsuttaessa int-tyyppisen arvon 10. Jotta metodin palauttamaa arvoa voisi käyttää, tulee se ottaa talteen muuttujaan. Tämä tapahtuu samalla tavalla kuin normaali muuttujan arvon asetus, eli yhtäsuuruusmerkillä.

public static void main(String[] args) {
    int luku = palautetaanAinaKymppi();

    System.out.println("metodi palautti luvun " + luku);
}

Metodin paluuarvo sijoitetaan int-tyyppiseen muuttujaan aivan kuin mikä tahansa muukin int-arvo. Paluuarvo voi toimia myös osana mitä tahansa lauseketta.

double luku = 4 * palautetaanAinaKymppi() + (palautetaanAinaKymppi() / 2) - 8;

System.out.println("laskutoimituksen tulos " + luku);

Kaikki tähän mennessä näkemämme muuttujatyypit voidaan palauttaa metodista.

Palautettavan arvon tyyppi Esimerkki
Metodi ei palauta arvoa
public static void metodiJokaEiPalautaMitaan() {
    // metodin runko
}
Metodi palauttaa int-tyyppisen muuttujan
public static int metodiJokaPalauttaaKokonaisLuvun() {
    // metodin runko, tarvitsee return-komennon
}
Metodi palauttaa double-tyyppisen muuttujan
public static double metodiJokaPalauttaaLiukuluvun() {
    // metodin runko, tarvitsee return-komennon
}

Jos metodille määritellään paluuarvon tyyppi, on sen pakko palauttaa arvo. Esimerkiksi seuraava metodi on virheellinen.

public static int virheellinenMetodi() {
    System.out.println("Väitän palauttavani kokonaisluvun, mutten palauta sitä.");
}

Ylläolevassa metodissa tulee olla komento return, jota seuraa palautettavan arvon tyyppi.

Komento return lopettaa metodin suorituksen

Kun metodin suorituksessa päädytään komentoon return, metodin suoritus päättyy ja metodista palautetaan arvo sitä kutsuneelle metodille.

Ohjelmointikielen kääntäjä sekä käyttämämme ohjelmointiympäristö tietää, että return-komentoa seuraavia lähdekoodirivejä ei koskaan suoriteta. Jos ohjelmoija lisää lähdekoodia return-komennon jälkeen paikkaan, mitä ei metodin suorituksessa voida koskaan saavuttaa, ohjelmointiympäristö antaa virheviesti.

Seuraavanlainen metodi on virheellinen ohjelmointiympäristön näkökulmasta.

public static int virheellinenMetodi() {
    return 10;
    System.out.println("Väitän palauttavani kokonaisluvun, mutten palauta sitä.");
}

Seuraava metodi taas toimii, sillä -- vaikka return-komennon alla on lähdekoodia -- jokainen metodin lause on mahdollista saavuttaa.

public static int virheellinenMetodi(int parametri) {
    if (parametri > 10) {
        return 10;
    }

    System.out.println("Parametrina saatu luku on kymmenen tai pienempi.");

    return parametri;
}

Metodi voi palauttaa lausekkeen evaluoinnin tuloksen

Palautettavan arvon ei tarvitse olla täysin ennalta määritelty, vaan se voidaan laskea. Metodista arvon palauttavalle return-komennolle voidaan antaa myös lauseke, joka evaluoituu ennen kuin arvo palautetaan.

Seuraavassa esimerkissä määritellään metodi summa, joka laskee kahden muuttujan arvon yhteen ja palauttaa summan. Yhteen laskettavien muuttujien eka ja toka arvot saadaan metodin parametrina.

public static int summa(int eka, int toka) {
    return eka + toka;
}

Kun metodin suorituksessa päädytään lauseeseen return eka + toka;, evaluoidaan lauseke eka + toka, jonka arvo lopulta palautetaan.

Metodin kutsutaan seuraavasti. Alla metodia käytetään laskemaan luvut 2 ja 7 yhteen. Metodikutsusta saatava paluuarvo asetetaan muuttujaan lukujenSumma:

int lukujenSumma = summa(2, 7);
// lukujenSumma on nyt 9

Laajennetaan edellistä esimerkkiä siten, että käyttäjä syöttää luvut.

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

    System.out.print("Anna ensimmäinen luku: ");
    int eka = Integer.parseInt(lukija.nextLine());

    System.out.print("Anna toinen luku: ");
    int toka = Integer.parseInt(lukija.nextLine());

    System.out.print("Luvut ovat yhteensä: " + summa(eka, toka));
}

public static int summa(int eka, int toka) {
    return eka + toka;
}

Yllä olevassa esimerkissä metodin paluuarvoa ei aseteta muuttujaan, vaan sitä käytetään suoraan osana tulostusta. Tällöin tulostuskomennon suoritus tapahtuu siten, että tietokone selvittää ensin tulostettavan merkkijonon "Luvut ovat yhteensä: " + summa(eka, toka). Ensin tietokone etsii muuttujat eka ja toka, ja kopioi niiden arvot metodin summa parametrien arvoiksi. Tämän jälkeen metodissa lasketaan parametrien arvot yhteen, jonka jälkeen summa palauttaa arvon. Tämä arvo asetetaan metodikutsun summa paikalle, jonka jälkeen summa liitetään merkkijonon "Luvut ovat yhteensä: " jatkeeksi.

Koska metodille annettavat arvot kopioidaan metodin parametreihin, metodin parametrien nimillä ja metodin kutsujan puolella määritellyillä muuttujien nimillä ei ole oikeastaan mitään tekemistä keskenään. Edellisessä esimerkissä sekä pääohjelman muuttujat että metodin parametrit olivat "sattumalta" nimetty samoin (eli eka ja toka). Seuraava toimii täysin samalla tavalla vaikka muuttujat ovatkin eri nimisiä:

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

    System.out.print("Anna ensimmäinen luku: ");
    int luku1 = Integer.parseInt(lukija.nextLine());

    System.out.print("Anna toinen luku: ");
    int luku2 = Integer.parseInt(lukija.nextLine());

    System.out.print("Luvut ovat yhteensä: " + summa(luku1, luku2));
}

public static int summa(int eka, int toka) {
    return eka + toka;
}

Nyt pääohjelman muuttujan luku1 arvo kopioituu metodin parametrin eka arvoksi ja pääohjelman muuttujan luku2 arvo kopioituu metodin parametrin toka arvoksi.

Seuraavassa esimerkissä metodia summa kutsutaan kokonaisluvuilla, jotka saadaan summa-metodin paluuarvoina.

int eka = 3;
int toka = 2;

int monenLuvunSumma = summa(summa(1, 2), summa(eka, toka));
// 1) suoritetaan sisemmät metodit:
//    summa(1, 2) = 3   ja summa(eka, toka) = 5
// 2) suoritetaan ulompi metodi:
//    summa(3, 5) = 8
// 3) muuttujan monenLuvunSumma arvoksi siis tulee 8

Metodin omat muuttujat

Muuttujien määrittely metodissa tapahtuu tutulla tavalla. Seuraava metodi laskee parametrina saamiensa lukujen keskiarvon. Keskiarvon laskemisessa käytetään apumuuttujia summa ja ka.

public static double keskiarvo(int luku1, int luku2, int luku3) {

    int summa = luku1 + luku2 + luku3;
    double ka = summa / 3.0;

    return ka;
}

Metodin kutsu voi tapahtua esim seuraavasti

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

    System.out.print("Anna ensimmäinen luku: ");
    int eka = Integer.parseInt(lukija.nextLine());

    System.out.print("Anna toinen luku: ");
    int toka = Integer.parseInt(lukija.nextLine());

    System.out.print("ja kolmas luku: ");
    int kolmas = Integer.parseInt(lukija.nextLine());

    double keskiarvonTulos = keskiarvo(eka, toka, kolmas);

    System.out.print("Lukujen keskiarvo: " + keskiarvonTulos);
}

Huomaa että metodin sisäiset muuttujat summa ja ka eivät näy pääohjelmaan. Tyypillinen ohjelmoinnin harjoittelussa eteen tuleva virhe on yrittää käyttää metodia seuraavasti.

public static void main(String[] args) {
    int eka = 3;
    int toka = 8;
    int kolmas = 4;

    keskiarvo(eka, toka, kolmas);

    // yritetään käyttää metodin sisäistä muuttujaa, EI TOIMI!
    System.out.print("Lukujen keskiarvo: " + ka);
}

Myös seuraavanlaista virhettä näkee usein.

public static void main(String[] args) {
    int eka = 3;
    int toka = 8;
    int kolmas = 4;

    keskiarvo(eka, toka, kolmas);

    // yritetään käyttää pelkkää metodin nimeä, EI TOIMI!
    System.out.print("Lukujen keskiarvo: " + keskiarvo);
}

Eli tässä yritettiin käyttää pelkkää metodin nimeä muuttujamaisesti. Toimiva tapa metodin tuloksen sijoittamisen apumuuttujaan lisäksi on suorittaa metodikutsu suoraan tulostuslauseen sisällä:

public static void main(String[] args) {
    int eka = 3;
    int toka = 8;
    int kolmas = 4;

    // kutsutaan metodia tulostuslauseessa, TOIMII!
    System.out.print("Lukujen keskiarvo: " + keskiarvo(eka, toka, kolmas));
}

Tässä siis ensin tapahtuu metodikutsu joka palauttaa arvon 5.0 joka sitten tulostetaan tulostuskomennon avulla.

Screencast aiheesta:

Täydennä tehtäväpohjassa olevaa metodia summa siten, että se laskee ja palauttaa parametrina olevien lukujen summan.

Tee metodi seuraavaan runkoon:

public static int summa(int luku1, int luku2, int luku3, int luku4) {
    // kirjoita koodia tähän
    // muista että metodissa on oltava (lopussa) return!
}

public static void main(String[] args) {
    int vastaus = summa(4, 3, 6, 1);
    System.out.println("Summa: " + vastaus);
}

Ohjelman tulostus:

Summa: 14

Huom: kun tehtävässä sanotaan että metodin pitää palauttaa jotain, tarkoittaa tämä sitä että metodissa tulee olla määritelty paluutyyppi ja return-komento jolla haluttu asia palautetaan. Metodi ei itse tulosta (eli käytä komentoa System.out.println(..)), tulostuksen hoitaa metodin kutsuja, eli tässä tapauksessa pääohjelma.

Tee metodi pienin, joka palauttaa parametrina saamistaan luvuista pienemmän arvon. Jos lukujen arvo on sama, voidaan palauttaa kumpi tahansa luvuista.

public static int pienin(int luku1, int luku2) {
    // kirjoita koodia tähän
    // älä tulosta metodin sisällä mitään

    // lopussa oltava komento return
}

public static void main(String[] args) {
    int vastaus =  pienin(2, 7);
    System.out.println("Pienin: " + vastaus);
}

Ohjelman tulostus:

Pienin: 2

Tee metodi suurin, joka saa kolme lukua ja palauttaa niistä suurimman. Jos suurimpia arvoja on useita, riittää niistä jonkun palauttaminen. Tulostus tapahtuu pääohjelmassa.

public static int suurin(int luku1, int luku2, int luku3) {
    // kirjoita koodia tähän
}

public static void main(String[] args) {
    int vastaus =  suurin(2, 7, 3);
    System.out.println("Suurin: " + vastaus);
}

Ohjelman tulostus:

Suurin: 7

Tee metodi keskiarvo, joka laskee parametrina olevien lukujen keskiarvon. Metodin sisällä tulee käyttää apuna edellä tehtyä metodia summa!

Tee metodi seuraavaan runkoon:

public static int summa(int luku1, int luku2, int luku3, int luku4) {
    // voit kopioida metodin summa toteutuksen tänne
}

public static double keskiarvo(int luku1, int luku2, int luku3, int luku4) {
    // kirjoita koodia tähän
    // laske alkioiden summa kutsumalla metodia summa
}

public static void main(String[] args) {
    double vastaus = keskiarvo(4, 3, 6, 1);
    System.out.println("Keskiarvo: " + vastaus);
}

Ohjelman tulostus:

Keskiarvo: 3.5

Muistathan miten kokonaisluku (int) muutetaan desimaaliluvuksi (double)!

Mitä metodia kutsuttaessa tapahtuu -- kutsupino

Kun metodia kutsutaan, kutsuvan metodin suoritus jää odottamaan kutsutun metodin suorittamista. Tätä voidaan visualisoida kutsupinon avulla. Kutsupino tarkoittaa metodien kutsumisen muodostamaa pinoa -- juuri suoritettevana oleva metodi on aina pinon päällimmäisenä, ja metodin suorituksen päättyessä palataan pinossa seuraavana olevaan metodiin. Tarkastellaan seuraavaa ohjelmaa:

public static void main(String[] args) {
    System.out.println("Hei maailma!");
    tulostaLuku();
    System.out.println("Hei hei maailma!");
}

public static void tulostaLuku() {
    System.out.println("Luku");
}

Kun ohjelma käynnistetään, suoritus alkaa main-metodin ensimmäiseltä riviltä. Tällä rivillä olevalla komennolla tulostetaan teksti "Heippa maailma!". Ohjelman kutsupino näyttää seuraavalta:

main

Kun tulostuskomento on suoritettu, siirrytään seuraavaan komentoon, missä kutsutaan metodiatulostaLuku. Metodin tulostaLuku kutsuminen siirtää ohjelman suorituksen metodin tulostaLuku alkuun, jolloin main-metodi jää odottamaan metodin tulostaLuku suorituksen loppumista. Metodin tulostaLuku sisällä ollessa kutsupino näyttää seuraavalta.

tulostaLuku
main

Kun metodi tulostaLuku on suoritettu, palataan kutsupinossa metodia tulostaLuku yhtä alempana olevaan metodiin, eli metodiin main. Kutsupinosta poistetaan tulostaLuku, ja jatketaan main-metodin suoritusta tulostaLuku-metodikutsun jälkeiseltä riviltä. Kutsupino on nyt seuraavanlainen:

main

Kutsupino ja metodin parametrit

Tarkastellaan kutsupinoa tilanteessa, missä metodille on määritelty parametreja.

public static void main(String[] args) {
    int alku = 1;
    int loppu = 5;

    tulostaTahtia(alku, loppu);
}

public static void tulostaTahtia(int alku, int loppu) {
    while (alku < loppu) {
        System.out.print("*");
        alku++;
    }
}

Ohjelman suoritus alkaa main-metodin ensimmäiseltä riviltä, jota seuraavilla riveillä luodaan muuttujat alku ja loppu, sekä asetetaan niihin arvot. Ohjelman tilanne ennen metodin tulostaTahtia-kutsumista:

main
  alku = 1
  loppu = 5

Kun metodia tulostaTahtia kutsutaan, main-metodi jää odottamaan. Metodikutsun yhteydessä metodille tulostaTahtia luodaan uudet muuttujat alku ja loppu, joihin asetetaan parametreina annetut arvot. Nämä kopioidaan main-metodin muuttujista alku ja loppu. Metodin tulostaTahtia suorituksen ensimmäisellä rivillä ohjelman tilanne on seuraavanlainen.

tulostaTahtia
  alku = 1
  loppu = 5
main
  alku = 1
  loppu = 5

Kun toistolauseessa suoritetaan komento alku++, muuttuu tällä hetkellä suoritettavaan metodiin liittyvän alku-muuttujan arvo.

tulostaTahtia
  alku = 2
  loppu = 5
main
  alku = 1
  loppu = 5

Metodin main muuttujien arvot eivät siis muutu. Metodin tulostaTahtia suoritus jatkuisi tämän jälkeen jokusen hetken. Kun metodin tulostaTahtia suoritus loppuu, palataan takaisin main-metodin suoritukseen.

main
  alku = 1
  loppu = 5

Tarkastellaan samaa ohjelman suorituksen askeleittaisella visualisoinnilla. Visualisointiin käyttämämme sovellus kasvattaa kutsupinoa alaspäin -- oikealla laidalla ylin on aina main-metodi, jonka alle tulee kutsuttavat metodit.

Kutsupino ja arvon palauttaminen metodista

Tarkastellaan vielä esimerkkiä, missä metodi palauttaa arvon. Ohjelman main-metodi kutsuu erillistä kaynnista-metodia, jossa luodaan kaksi muuttujaa, kutsutaan summa-metodia, ja tulostetaan summa-metodin palauttama arvo.

public static void main(String[] args) {
    kaynnista();
}

public static void kaynnista() {
    int eka = 5;
    int toka = 6;

    int summa = summa(eka, toka);

    System.out.println("Summa: " + summa);
}

public static int summa(int luku1, int luku2) {
    return luku1 + luku2;
}

Metodin kaynnista suorituksen alussa kutsupino näyttää seuraavalta, sillä siihen päädyttiin main-metodista. Metodilla main ei tässä esimerkissä ole omia muuttujia:

kaynnista
main

Kun kaynnista-metodissa on luotu muuttujat eka ja toka, eli sen ensimmäiset kaksi riviä on suoritettu, on tilanne seuraava:

kaynnista
  eka = 5
  toka = 6
main

Komento int summa = summa(eka, toka); luo metodiin kaynnista muuttujan summa, ja kutsuu metodia summa. Metodi kaynnista jää odottamaan. Koska metodissa summa on määritelty parametrit luku1 ja luku2, luodaan ne heti metodin suorituksen alussa, ja niihin kopioidaan parametrina annettujen muuttujien arvot.

summa
  luku1 = 5
  luku2 = 6
kaynnista
  eka = 5
  toka = 6
  summa // ei arvoa
main

Metodin summa suoritus laskee muuttujien luku1 ja luku2 arvot yhteen. Komento return palauttaa lukujen summan kutsupinossa yhtä alemmalle metodille, eli metodille kaynnista. Palautettu arvo asetetaan muuttujan summa arvoksi.

kaynnista
  eka = 5
  toka = 6
  summa = 11
main

Tämän jälkeen suoritetaan tulostuskomento, jonka jälkeen palataan main-metodiin. Kun suoritus on main-metodin lopussa, ohjelman suoritus loppuu.

Merkkijonot

Tutustutaan seuraavaksi uuteen muuttujatyyppiin eli merkkijonoihin (String). Merkkijonomuuttuja määritellään kertomalla sen tyyppi (String) sekä nimi. Tätä seuraa muuttujan arvo, joka on hipsujen sisällä olevaa tekstiä. Alla luodaan merkkijonomuuttuja taikasana, joka sisältää arvon "abrakadabra".

String taikasana = "abrakadabra";

Merkkijonomuuttujan antaminen tulostuskomennolle (tai oikeastaan mille tahansa metodille) parametrina onnistuu tutulla tavalla. Alla määritellään merkkijono, joka tulostetaan.

String taikasana = "abrakadabra";
System.out.println(taikasana);
abrakadabra

Merkkijonojen lukeminen ja tulostaminen

Merkkijonon lukeminen onnistuu tutun Scanner-apuvälineen tarjoamalla nextLine-metodilla. Alla oleva ohjelma lukee käyttäjän nimen ja tulostaa sen seuraavalla rivillä (esimerkissä käyttäjän syöttämä teksti on merkitty punaisella):

Scanner lukija = new Scanner(System.in);

System.out.print("Mikä on nimesi? ");
String nimi = lukija.nextLine(); // Luetaan käyttäjältä rivi tekstiä ja asetetaan se muuttujaan nimi

System.out.println(nimi);
Mikä on nimesi? Venla
Venla

Merkkijonoja voi myös yhdistellä. Jos operaatiota + sovelletaan kahden merkkijonon välille, syntyy uusi merkkijono, jossa kaksi merkkijonoa on yhdistetty. Huomaa nokkela välilyönnin käyttö lauseen "muuttujien" osana!

String tervehdys = "Hei ";
String nimi = "Lilja";
String hyvastely = " ja näkemiin!";

String lause = tervehdys + nimi + hyvastely;

System.out.println(lause);
Hei Lilja ja näkemiin!

Jos toinen operaation + kohteista on merkkijono, muutetaan myös toinen operaation kohteista merkkijonoksi. Alla olevassa esimerkissä kokonaisluku 2 on muutettu merkkijonoksi "2", ja siihen on yhdistetty merkkijono.

String teksti = "tuossa on kokonaisluku";
System.out.println(teksti + " --> " + 2);
System.out.println(2 + " <-- " + teksti);
tuossa on kokonaisluku --> 2
2 <-- tuossa on kokonaisluku

Aiemmin tutuksi tulleet laskusäännöt sekä sulkeiden noudattaminen pätee myös merkkijonoja käsiteltäessä.

String teksti = " oho!";
System.out.println("Neljä: " + (2 + 2) + teksti);
System.out.println("Mutta! kaksikymmentäkaksi: " + 2 + 2 + teksti);
Neljä: 4 oho!
Mutta! kaksikymmentäkaksi: 22 oho!

Seuraavassa on vielä käyttäjää tervehtivä ohjelma pääohjelmarungon kanssa. Ohjelman nimi on Tervehdys.

import java.util.Scanner;

public class Tervehdys {

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

        System.out.print("Kenelle sanotaan hei: ");
        String nimi = lukija.nextLine();

        System.out.println("Hei " + nimi);
    }
}

Kun yllä oleva ohjelma ajetaan, pääset kirjoittamaan syötteen. NetBeansin tulostusvälilehti näyttää ajetun ohjelman jälkeen seuraavalta (käyttäjä syöttää nimen "Venla").

Tee ohjelma joka lukee käyttäjältä merkkijonon ja tulostaa merkkijonon kolmesti peräkkäin.

Mikä tulostetaan? kukka

kukkakukkakukka

Esimerkissä punainen väri tarkoittaa käyttäjän kirjoittamaa tekstiä. Tätä käytäntöä noudatetaan jatkossa esimerkeissä.

Merkkijonojen ja lukujen lukeminen

Käyttäjän kanssa keskustelevan ohjelman runko:

import java.util.Scanner;

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

        // koodi tähän
    }
}

Merkkijonon lukeminen:

String merkkijono = lukija.nextLine();

Kokonaisluvun lukeminen:

int kokonaisluku = Integer.parseInt(lukija.nextLine());

Merkkijonojen vertailu ja equals

Merkkijonoja ei voi vertailla yhtäsuuri kuin (==) operaatiolla. Merkkijonojen vertailuun käytetään erillistä equals-komentoa, joka liittyy aina verrattavaan merkkijonoon.

String teksti = "kurssi";

if (teksti.equals("marsipaani")) {
    System.out.println("Teksti-muuttujassa on teksti marsipaani.");
} else {
    System.out.println("Teksti-muuttujassa ei ole tekstiä marsipaani.");
}

Komento equals liitetään aina siihen verrattavaan tekstimuuttujaan, "tekstimuuttuja piste equals teksti". Tekstimuuttujaa voidaan myös verrata toiseen tekstimuuttujaan.

String teksti = "kurssi";
String toinenTeksti = "pursi";

if (teksti.equals(toinenTeksti)) {
    System.out.println("Samat tekstit!");
} else {
    System.out.println("Eri tekstit!");
}

Merkkijonoja vertailtaessa on syytä varmistaa että verrattavalla tekstimuuttujalla on arvo. Jos muuttujalla ei ole arvoa, ohjelma tuottaa virheen NullPointerException, joka tarkoittaa ettei muuttujan arvoa ole asetettu tai se on tyhjä (null).

Seuraavassa käännetään !:n eli negaatio-operaation avulla ehdon arvo päinvastaiseksi:

System.out.println("Eihän merkkijono ole 'maito'");
String merkkijono = "piimä";

if (!(merkkijono.equals("maito"))) {  // tosi jos ehto merkkijono.equals("maito") on epätosi
    System.out.println("ei ollut!");
} else {
    System.out.println("oli");
}
ei ollut!

Negaatio-operaatio, eli !ehto, kääntää siis totuusarvon ympäri.

int eka = 1;
int toka = 3;

boolean onkoSuurempi = eka > toka;

if (!onkoSuurempi) {
    System.out.println("1 ei ole suurempi kuin 3");
}
1 ei ole suurempi kuin 3

Tee ohjelma, joka pyytää käyttäjää kirjoittamaan merkkijonon. Jos käyttäjä kirjoittaa merkkijonon "totta", tulostetaan merkkijono "Oikein meni!", muulloin tulostetaan merkkijono "Koitappa uudelleen!".

Kirjoita merkkijono: totta
Oikein meni!
Kirjoita merkkijono: tottapa
Koitappa uudelleen!

Tee ohjelma, joka tunnistaa seuraavat käyttäjät:

tunnus salasana
aleksi tappara
elina kissa

Ohjelma näyttää käyttäjälle henkilökohtaisen viestin tai ilmoittaa, jos tunnus tai salasana on väärin.

Anna tunnus: aleksi
Anna salasana: tappara
Olet kirjautunut järjestelmään
Anna tunnus: elina
Anna salasana: kissa
Olet kirjautunut järjestelmään
Anna tunnus: aleksi
Anna salasana: jokerit
Virheellinen tunnus tai salasana!

HUOM: muista, että merkkijonoja ei voi vertailla ==-operaatiolla!

HUOM: Todellisuudessa kirjautumistoiminnallisuutta ei tule toteuttaa, eikä yleensä toteutetakkaan näin. Kirjautumistoiminnallisuuden toteuttamiseen tutustutaan mm. tietokantojen perusteet -kurssilla.

Merkkijonoilta voi kysyä niiden pituutta kirjoittamalla merkkijonon perään .length() eli kutsumalla merkkijonolle sen pituuden kertovaa metodia.

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());

Edellä kutsutaan metodia length() kolmelle eri merkkijonolle. Kutsu banaani.length() kutsuu nimenomaan merkkijonon banaani pituuden kertovaa metodia, kun taas kurkku.length() on merkkijonon kurkku pituuden kertovan metodin kutsu. Pisteen vasemman puoleinen osa kertoo kenen metodia kutsutaan.

Tee ohjelma, joka kysyy käyttäjän nimen ja ilmoittaa, kuinka monta kirjainta siinä on. Toteuta merkkijonon pituuden selvittäminen erilliseen metodiin public static int laskeKirjaimet(String merkkijono).

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

Huom! Rakenna ohjelmasi niin että laitat pituuden laskemisen omaan metodiinsa: public static int laskeKirjaimet(String merkkijono). Testit testaavat sekä metodia laskeKirjaimet että koko ohjelman toimintaa.

Toistolauseen käyttö merkkijonojen kanssa käy samalla tavalla kuin muiden muuttujien kanssa. Alla olevassa esimerkissä luetaan käyttäjältä merkkijonoja, kunnes käyttäjä syöttää tyhjän merkkijonon (eli painaa vain enteriä). Tämän jälkeen tulostetaan pisin merkkijono sekä pisimmän merkkijonon pituus.

Scanner lukija = new Scanner(System.in);

String pisin = "";

while (true) {
    System.out.println("Syötä sana, tyhjä lopettaa.");
    String syote = lukija.nextLine();

    if (syote.equals("")) {
        break;
    }

    if (pisin.length() < syote.length()) {
        pisin = syote;
    }
}

if (pisin.length() > 0) {
    System.out.println("Pisin merkkijono: " + pisin + " (pituus: " + pisin.length() + ")");
} else {
    System.out.println("Ei järkeviä syötteitä...");
}

Tässä tehtävässä luodaan ohjelma joka kyselee käyttäjältä salasanaa. Jos salasana menee oikein, nähdään salainen viesti.

Anna salasana: nauris
Väärin!
Anna salasana: lanttu
Väärin!
Anna salasana: porkkana
Oikein!

Salaisuus on: znvavbfgv grugl!

Toteutetaan ohjelma kolmessa askeleessa.

Salasanan kysyminen

Ohjelmarunkoon on määritelty muuttuja String salasana, jolle on asetettu arvoksi porkkana -- älä muuta tätä salasanaa. Toteuta lisätoiminnallisuus, jossa ohjelma kysyy käyttäjältä salasanaa ja vertailee sitä muuttujassa salasana olevaan arvoon. Muista mitä erityistä merkkijonojen vertailussa on!

Anna salasana: nauris
Väärin!
Anna salasana: porkkana
Oikein!
Anna salasana: bataatti
Väärin!

Salasanan kysyminen kunnes käyttäjä vastaa oikein

Muokkaa ohjelmaa siten, että se kysyy salasanaa kunnes käyttäjä syöttää oikean salasanan. Toteuta salasanan jatkuva kysyminen while (true) { ... } -toistolausekkeen avulla. Toistolausekkeesta pääsee pois, jos ja vain jos käyttäjän syöttämä salasana on sama kuin muuttujassa salasana oleva arvo.

Anna salasana: nauris
Väärin!
Anna salasana: lanttu
Väärin!
Anna salasana: porkkana
Oikein!

Salainen viesti

Lisää ohjelmaan oma salainen viestisi joka näytetään kun käyttäjä kirjoittaa salasanan oikein. Se voi olla mitä tahansa!

Anna salasana: nauris
Väärin!
Anna salasana: lanttu
Väärin!
Anna salasana: porkkana
Oikein!

Salaisuus on: znvavbfgv grugl!

Ylläoleva salaisuus on salattu käyttäen Rot13-algoritmia.

Muita merkkijonojen metodeja

Merkkijonosta halutaan usein lukea jokin tietty osa. Tämä onnistuu mekkkijonojen eli String-luokan metodilla substring. Metodia substring 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 String-tyyppinen, voidaan metodin paluuarvo ottaa talteen String-tyyppiseen muuttujaan loppuosa.

String kirja = "8 veljestä";

String loppuosa = kirja.substring(2);
System.out.println("7 " + loppuosa); // tulostaa: 7 veljestä
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

String-luokan metodit tarjoavat myös mahdollisuuden etsiä tekstistä tiettyä sanaa. Esimerkiksi sana "erkki" sisältyy tekstiin "merkki". Metodi indexOf() etsii sille parametrina annettua sanaa merkkijonosta. Jos sana löytyy, metodi indexOf() palauttaa sanan ensimmäisen kirjaimen indeksin, eli paikan (muista että paikkanumerointi alkaa nollasta!). Jos taas sanaa ei merkkijonosta löydy, metodi palauttaa arvon -1.

String sana = "merkkijono";

int indeksi = sana.indexOf("erkki"); //indeksin arvoksi tulee 1
System.out.println(sana.substring(indeksi)); //tulostetaan "erkkijono"

indeksi = sana.indexOf("jono"); //indeksin arvoksi tulee 6
System.out.println(sana.substring(indeksi)); //tulostetaan "jono"

indeksi = sana.indexOf("kirja"); //sana "kirja" ei sisälly sanaan "merkkijono"
System.out.println(indeksi); // tulostetaan -1
System.out.println(sana.substring(indeksi)); // virhe!

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!

Metodille indexOf voi antaa haettavan merkkijonon lisäksi parametrina myös indeksin, mistä lähtien merkkijonoa haetaan. Esimerkiksi

String sana = "merkkijono";

int indeksi = sana.indexOf("erkki"); // indeksin arvoksi tulee 1
System.out.println(sana.substring(indeksi)); //tulostetaan "erkkijono"

indeksi = sana.indexOf("erkki", 2); // indeksin arvoksi tulee -1 sillä erkkiä ei löydy lopusta
System.out.println(sana.substring(indeksi)); // tapahtuu virhe!

Merkkijonon... metodit?

Merkkijonot poikkeavat luonteeltaan hieman esimerkiksi kokonaisluvuista. Kokonaisluvut ovat "pelkkiä arvoja" -- niiden avulla voi tehdä laskutoimituksia ja niiden arvon voi tulostaa:

int x = 1;
int y = 3;

y = 3 * x + 2;

System.out.println("y:n arvo nyt: " + y);
y:n arvo nyt: 5

Merkkijonot taas ovat hieman "älykkäämpiä" ja tietävät esimerkiksi pituutensa:

String sana1 = "Ohjelmointi";
String sana2 = "Java";

System.out.println("merkkijonon " + sana1 + " pituus: " + sana1.length());
System.out.println("merkkijonon " + sana2 + " pituus: " + sana2.length());

Tulostuu:

merkkijonon Ohjelmointi pituus on 11
merkkijonon Java pituus on 4

Pituus saadaan selville kutsumalla merkkijonon metodia length(). Merkkijonoilla on joukko muitakin metodeja. Kokonaisluvuilla eli int:eillä ei ole metodeja ollenkaan, ne eivät itsessään "osaa" mitään. Mistä tässä oikein on kyse?

Olioihin liittyy sekä metodeja että arvoja

Merkkijonot ovat olioita, joihin liittyy sekä merkkijonon teksti että metodeja, joilla tekstiä voi käsitellä. Termi olio tarkoittaa tietynlaista muuttujaa. Jatkossa tulemme näkemään hyvin paljon muitakin olioita kuin merkkijonoja.

Olion metodia kutsutaan lisäämällä muuttujan nimen perään piste ja metodin nimi. Näiden lisäksi tulee sulut sekä mahdolliset parametrit:

String sana1 = "Ohjelmointi";
String sana2 = "Java";

sana1.length();    // kutsutaan merkkijono-olion sana1 metodia length()
sana2.length();    // kutsutaan merkkijono-olion sana2 metodia length()

Metodikutsu kohdistuu nimenomaan siihen olioon, mille metodia kutsutaan. Yllä kutsumme ensin sana1-nimisen merkkijonon length()-metodia, sitten merkkijonon sana2 metodia length().

Vanha tuttumme lukija on myös olio:

Scanner lukija = new Scanner(System.in);

Lukijat ja merkkijonot ovat molemmat oliota, mutta ne ovat kuitenkin varsin erilaisia. Lukijoilla on mm. metodi nextLine() jota merkkijonoilla ei ole. Javassa oliot "synnytetään" eli luodaan melkein aina komennolla new, merkkijonot muodostavat tässä suhteessa poikkeuksen! -- Merkkijonoja voi luoda kahdella tavalla:

String banaani = new String("Banaani");
String porkkana = "porkkana";

Kumpikin ylläolevista riveistä luo uuden merkkijono-olion. Merkkijonojen luonnissa new-komentoa käytetään kuitenkin hyvin harvoin, sillä Java-ohjelmointikielen toteuttajat ovat tehneet merkkijonojen luomiseen lyhyemmän (ei new-komentoa tarvitsevan) tavan.

Olion "tyypistä" puhuttaessa puhutaan usein luokista. Merkkijonojen luokka on String, lukijoiden luokka taas on Scanner. Opimme jatkossa luokista ja olioista paljon lisää.

Tehtäväpohjassa tulee mukana ohjelma, joka kysyy käyttäjältä kahta merkkijonoa. Tämän jälkeen ohjelma tulostaa indeksit, joista toinen merkkijono löytyy ensimmäisessä merkkijonossa. Ohjelman esimerkkitulostus on seuraava:

Mistä haetaan: ski-bi dibby dib yo da dub dub
Mitä haetaan: dib
Merkkijono dib löytyy kohdasta 7
Merkkijono dib löytyy kohdasta 13

Muokaa ohjelmaa siten, että ohjelma ei tulosta esiintymiskohtia, mutta tulostaa esiintymiskertojen yhteislukumäärän. Ohjelman tulee muokkauksen jälkeen toimia seuraavasti:

Mistä haetaan: ski-bi dibby dib yo da dub dub
Mitä haetaan: dib
Merkkijonon dib esiintymiskertoja: 2

Voit olettaa, että haettava merkkijono ei itsessään sisällä toistuvaa hahmoa. Haettava ei siis voi olla esim. "voivoi" (mitä harmia tästä voisi tulla jos merkkijono olisi esimerkiksi "voivoivoivoi"?).

Crowdsorcerer: Arvioi tehtäviä

Otetaan hetkeksi askel taaksepäin ja muistellaan viime osaa. Palataan tämän jälkeen takaisin olioiden pariin.

Ohjelmointikurssin toisessa osassa loimme ensimmäisiä omia tehtäviä Crowdsorcererin avulla. Nyt on erinomainen hetki vertaisarviointiin -- on aika arvioida Crowdsorcereriin lähetettyjä tehtäviä! Anna vertaispalautetta kahdesta jonkun toisen kurssilaisen lähettämästä tehtävästä ja arvioi lopuksi itse tekemääsi tehtävää. Itse tekemäsi tehtävä näkyy vain jos olet tehnyt sen -- jos et tehnyt tehtävää, pääset arvioimaan yhden ylimääräisen tehtävän.

Vertaisarviointi

Alla on kolme Crowdsorcereriin tehtyä tehtävää: kaksi jonkun kurssitoverisi lähettämää ja yksi itsearviointia varten. Niiden yhteydessä on muistin virkistykseksi ohjeistus, jonka pohjalta kyseiset tehtävänannot on tehty.

Tarkastele jokaisen tehtävän eri osia: tehtävänantoa, tehtäväpohjaa ja malliratkaisua sekä testaukseen käytettäviä syötteitä ja tulosteita. Arvioi niiden selkeyttä, vaikeutta ja sitä, kuinka hyvin ne vastaavat ohjeistukseensa.

Voit vaihtaa näkymää tehtäväpohjan ja mallivastauksen välillä painamalla lähdekoodin yläpalkin painikkeita. Palautteenannon avuksi on annettu väittämiä. Voit valita kuinka samaa mieltä niiden kanssa olet painamalla hymiöitä. Annathan myös sanallista palautetta sille varattuun kenttään! Lisää vielä tehtävää mielestäsi kuvaavia tageja ja paina Lähetä.

Anna arvio kummallekin vertaispalautetehtävälle ja lopuksi vielä omallesi.

Muista olla reilu ja ystävällinen. Hyvä palaute on rehellistä, mutta kannustavaa!

Voit halutessasi ladata arvioitavan tehtävän tehtäväpohjan ja malliratkaisun koneellesi, ja testata niiden käyttöä. Molemmat tulevat ZIP-paketeissa, jolloin sinun täytyy purkaa ne, ennen kuin voit avata ne NetBeansissä.

Vertaisarvioitavien tehtävien ohjeistus: Ehtolause
Tee tehtävä, jonka tarkoitus on laittaa opiskelija koodaamaan ohjelma, joka lukee käyttäjältä kokonaislukusyötteen, tarkastelee sitä ehtolauseen avulla ja tulostaa merkkijonon. Anna testejä vasten syöte-esimerkki ja ohjelman tuloste tuolla syötteellä.

Useita arvoja sisältävä lista

Tutustuimme hetki sitten merkkijonojen toimintaan. Merkkijonot ovat olioita. Tämä tarkoittaa sitä, että jokaisella merkkijonolla on oma arvo (eli tässä tapauskessa teksti) sekä metodeja, joilla arvoa voi käsitellä.

Tutustutaan seuraavaksi erääseen toiseen hyvin paljon käytettyyn olioon.

Ohjelmoidessa tulee vastaan tilanteita, joissa haluamme käsitellä useita arvoja. Epäkäytännöllinen mutta tähän mennessä käytännössä ainoa tapa on ollut määritellä jokaiselle arvolle oma muuttuja.

String sana1;
String sana2;
String sana3;
// ...
String sana10;

Tämä ratkaisu on oikeastaan kelvoton -- ajattele ylläoleva esimerkki vaikkapa tuhannella sanalla.

Ohjelmointikielet tarjoavat apuvälineitä, joiden avulla on helppo säilyttää useita arvoja. Tutustumme seuraavaksi Java-ohjelmointikielen ehkäpä eniten käytettyyn apuvälineeseen ArrayListiin (linkki vie Javan omaan dokumentaatioon), joka on useamman arvon säilömiseen tarkoitettu lista.

ArrayList

ArrayList on Javan valmiiksi tarjoama työväline, joka piilottaa listan arvojen käsittelyyn tarvittavan konkreettisen ohjelmakoodin. ArrayList-muuttujan tyyppi on ArrayList, jonka lisäksi sille määritellään listalle lisättävien arvojen tyyppi. Esimerkiksi merkkijonoja sisältävän ArrayListin tyyppi on ArrayList<String> ja kokonaislukuja sisältävän ArrayListin tyyppi on ArrayList<Integer>. Listan luominen tapahtuu komennolla new ArrayList<>();.

Yhteen listaan lisätään aina samantyyppisiä arvoja.

Seuraavassa esimerkissä esitellään merkkijonoja tallentava ArrayList, johon lisätään muutama merkkijono. Tämän jälkeen tulostetaan listan nollannessa kohdassa oleva arvo eli ensimmäinen merkkijono (ohjelmoijat aloittavat laskemisen aina nollasta, lue tarkemmin täältä).

import java.util.ArrayList;

public class SanalistaEsimerkki {

    public static void main(String[] args) {
        ArrayList<String> sanalista = new ArrayList<>();

        sanalista.add("Ensimmäinen");
        sanalista.add("Toinen");

        System.out.println(sanalista.get(0));
    }
}
Ensimmäinen

Ohjelmaan on toteutettu valmiina pohja, joka lukee käyttäjältä syötteitä listalle. Syötteiden lukeminen päätetään kun käyttäjä syöttää tyhjän merkkijonon.

Ohjelma tulostaa tämän jälkeen listan ensimmäisen arvon. Muokkaa ohjelmaa siten, että ensimmäisen arvon sijaan tulostetaan kolmas arvo. Huomaa, että ohjelmoijat aloittavat laskemisen nollasta! Ohjelma saa rikkoutua rikkoutua jos listalla ei ole vähintään kolmea arvoa. Tarkastele minkälainen virheilmoitus ohjelmasta tällöin tulee.

Terho
Elina
Aleksi
Mari

Aleksi
Elina
Aleksi
Mari

Mari

Jos ArrayListiltä haetaan arvoa olemattomasta sijainnista, ohjelma tulostaa viestin IndexOutOfBoundsException.

import java.util.ArrayList;

public class Esimerkki {

    public static void main(String[] args) {
        ArrayList<String> sanalista = new ArrayList<>();

        sanalista.add("Ensimmäinen");
        sanalista.add("Toinen");

        System.out.println(sanalista.get(2));
    }
}

Virheviesti antaa myös pientä vinkkiä ArrayList-olion kapseloimasta toteutuksesta. Se siis kertoo metodit, joita kutsuttaessa virhe tapahtui.

Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 2, Size: 2
  at java.util.ArrayList.rangeCheck(ArrayList.java:653)
  at java.util.ArrayList.get(ArrayList.java:429)
  at Esimerkki.main(Esimerkki.java:(rivi))
  Java Result: 1

Lista on erittäin hyödyllinen kun halutaan tallentaa muuttujien arvoja myöhempää käsittelyä varten. Sillä on myös helpohko tehdä virheitä.

Tehtäväpohjassa on listaa käyttävä ohjelma. Muokkaa ohjelmaa siten, että sen suorittaminen tuottaa aina virheen IndexOutOfBoundsException. Ohjelman tulee olla sellainen, että käyttäjän ei tarvitse antaa konelle syötettä (esim. näppäimistöltä).

Ohjelmassa näkyy myös toisenlainen tapa listan läpikäyntiin -- palaamme siihen myöhemmin kurssilla.

Javan tarjoamien työvälineiden tuominen ohjelman käyttöön

Jokaisella Javan tarjoamalla työvälineellä on nimi ja sijainti. Valmiin työvälineen -- tai luokan -- tuominen ohjelman käyttöön tapahtuu import-käskyllä. Käskylle kerrotaan luokan sijainti ja luokan nimi. Esimerkiksi ArrayListin käyttöönotto vaatii komennon import java.util.ArrayList; ohjelman ylälaitaan.

import java.util.ArrayList;

public class ListaOhjelma {

    public static void main(String[] args) {
        ArrayList<String> sanalista = new ArrayList<>();

        sanalista.add("Ensimmäinen");
        sanalista.add("Toinen");
    }
}

Sama pätee myös muillekin Javan luokille. Olemmekin jo aiemmin käyttäneet lukemiseen tarkoitettua Scanner-luokkaa, jonka on saanut käyttöön komennolla import java.util.Scanner;

Useamman apuvälineen käyttöönotto on helppoa. Käyttöön tuotavat apuvälineet listataan allekkain ohjelman ylälaitaan.

import java.util.ArrayList;
import java.util.Scanner;

public class ListaOhjelma {

    public static void main(String[] args) {
        Scanner lukija = new Scanner(System.in);
        ArrayList<String> sanalista = new ArrayList<>();

        sanalista.add("Ensimmäinen");
        sanalista.add(lukija.nextLine());
    }
}

Listaan liittyviä metodeja

ArrayListillä on useita hyödyllisiä metodeja. Metodin toiminnallisuus suoritetaan aina sille listaoliolle, mihin liittyen metodia kutsutaan -- yhteys määritellään pisteellä. Alla olevassa esimerkissä käsitellään vain yhtä listaoliota, eli listaa opettajat.

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

opettajat.add("Veera");
opettajat.add("Jyri");
opettajat.add("Verna");
opettajat.add("Mikko");
opettajat.add("Pihla");
opettajat.add("Sami");

System.out.println("opettajien lukumäärä " + opettajat.size());

System.out.println("listalla ensimmäisenä " + opettajat.get(0));
System.out.println("listalla kolmantena " + opettajat.get(2));

opettajat.remove("Juha");

if (opettajat.contains("Juha")) {
    System.out.println("Juha on opettajien listalla");
} else {
    System.out.println("Juha ei ole opettajien listalla");
}
opettajien lukumäärä 6
listalla ensimmäisena Veera
listalla kolmantena Verna
Juha ei ole opettajien listalla

Alla taas on luotu kaksi listaa.

ArrayList<String> tehtavat1 = new ArrayList<>();
ArrayList<String> tehtavat2 = new ArrayList<>();

tehtavat1.add("Ada Lovelace");
tehtavat1.add("Hei Maailma! (Ja Mualima!)");
tehtavat1.add("Kuusi");

tehtavat2.add("Positiivisen luvun lisääminen");
tehtavat2.add("Työntekijän eläkevakuutus");

System.out.println("Listan 1 koko " + tehtavat1.size());
System.out.println("Listan 2 koko " + tehtavat2.size());

System.out.println("Ensimmäisen listan eka arvo " + tehtavat1.get(0));
System.out.println("Toisen listan vika arvo " + tehtavat2.get(tehtavat2.size() - 1));
Listan 1 koko 3
Listan 2 koko 2
Ensimmäisen listan eka arvo Ada Lovelace
Toisen listan vika arvo Työntekijän eläkevakuutus

Jokainen lista on siis oma erillinen olionsa ja listan metodit käsittelevät aina sitä listaa, mille metodia kutsutaan. Alla yhteenveto listan metodeista.

Listalle lisääminen: add

Listan metodi add lisää listalle metodille parametrina annetun arvon. Huomaa, että listalle lisättävän arvon tulee olla saman tyyppinen kuin listan määrittelyssä kuvattu tyyppi. Alla olevassa esimerkissä lista on määritelty sisältämään merkkijonoja, ja listalle lisätään merkkijono.

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

// listalle lisääminen
lista.add("Arvo");

Listalla olevien arvojen lukumäärä: size

Listan metodi size palauttaa listalla olevien arvojen lukumäärän. Lukumäärä on kokonaisluku (int) ja sitä voidaan käyttää osana lauseketta tai se voidaan asettaa kokonaislukumuuttujaan myöhempää käyttöä varten.

ArrayList<String> lista = new ArrayList<>();
System.out.println("Listalla arvoja: " + lista.size());

lista.add("Eka");
System.out.println("Listalla arvoja: " + lista.size());

int arvoja = lista.size();

lista.add("Toka");
System.out.println("Listalla arvoja: " + arvoja);
Listalla arvoja: 0
Listalla arvoja: 1
Listalla arvoja: 1

Tehtäväpohjassa on ohjelma, joka lukee käyttäjältä syötteitä. Muokkaa ohjelman toimintaa siten, että kun syötteiden lukeminen lopetetaan, ohjelma tulostaa listalla olevien arvojen lukumäärän.

Terho
Elina
Aleksi
Mari

Yhteensä: 4
Juno
Elizabeth
Mauri
Irene
Outi
Lauri
Iisa
Risto
Markus
Ville
Oskari

Yhteensä: 11

Huom! Et saa käyttää arvojen lukumäärän laskemiseen kokonaislukumuuttujaa.

Listan tietystä kohdasta hakeminen: get

Listan metodi get palauttaa listan parametrina annettuun paikkaan liittyvä sisältö. Parametri annetaan kokonaislukuna. Listan paikkojen numerointi alkaa nollasta, eli ensimmäisenä lisätty arvo on paikassa numero 0, toisena lisätty paikassa numero 1 jne.

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

lista.add("Eka");
lista.add("Toka");
lista.add("Kolmas");

System.out.println("Paikka 1 eli toinen paikka: " + lista.get(1));
System.out.println("Paikka 0 eli ensimmäinen paikka: " + lista.get(0));
Toka
Eka

Tehtäväpohjassa on ohjelma, joka lukee käyttäjältä syötteitä. Muokkaa ohjelman toimintaa siten, että kun syötteiden lukeminen lopetetaan, ohjelma tulostaa sekä ensiksi että viimeksi luetun arvon. Voit olettaa, että listalle luetaan vähintään kaksi arvoa.

Terho
Elina
Aleksi
Mari

Terho
Mari
Juno
Elizabeth
Mauri
Irene
Outi
Lauri
Iisa
Risto
Markus
Ville
Oskari

Juno
Oskari

Huom! Listan koon palauttava metodi on tässä hyödyllinen.

Listan tietystä kohdasta poistaminen: remove

Listan metodi remove poistaa listalta parametrina annettuun paikkaan liittyvän arvon. Parametri annetaan kokonaislukuna.

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

lista.add("Eka");
lista.add("Toka");
lista.add("Kolmas");

lista.remove(1);

System.out.println("Paikka 0 eli ensimmäinen: " + lista.get(0));
System.out.println("Paikka 1 eli toinen: " + lista.get(1));
Eka
Kolmas

Jos parametri on listan sisältämien arvojen tyyppinen, mutta ei kokonaisluku (kokonaislukua käytetään paikasta poistamiseen), voidaan sitä käyttää arvon poistamiseen listalta suoraan.

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

lista.add("Eka");
lista.add("Toka");
lista.add("Kolmas");

lista.remove("Eka");

System.out.println("Paikka 0 eli ensimmäinen: " + lista.get(0));
System.out.println("Paikka 1 eli toinen: " + lista.get(1));
Toka
Kolmas

Jos lista sisältää kokonaislukuja, ei listalta voi poistaa lukua antamalla remove-metodille luvun int-tyyppisenä parametrina. Tämä poistaisi luvun annetusta indeksistä. Kokonaislukutyyppisten arvojen poistaminen tapahtuu muuttamalla arvot Integer-tyyppisiksi, eli kapseloimalla ne Integer-olioiksi, Integer-luokan metodilla valueOf.

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

lista.add(15);
lista.add(18);
lista.add(21);
lista.add(24);

lista.remove(2);
lista.remove(Integer.valueOf(15));

System.out.println("Paikka 0 eli ensimmäinen: " + lista.get(0));
System.out.println("Paikka 1 eli toinen: " + lista.get(1));
18
24

Onko listalla: contains

Listan metodi contains kertoo löytyykö haettua arvoa listalta. Metodi saa haettavan arvon parametrina, ja se palauttaa totuustyyppisen arvon (boolean) true tai false.

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

lista.add("Eka");
lista.add("Toka");
lista.add("Kolmas");

System.out.println("Löytyykö eka? " + lista.contains("Eka"));

if (lista.contains("Toka")) {
    System.out.println("Toka löytyi");
}
Löytyykö eka? true
Toka löytyi

Tehtäväpohjassa on ohjelma, joka lukee käyttäjältä syötteitä. Lisää ohjelmaan toiminnallisuus, missä syötteiden lukemisen jälkeen kysytään vielä yhtä merkkijonoa. Ohjelma kertoo tämän jälkeen löytyikö käyttäjän syöttämä merkkijono listalta vai ei.

Terho
Elina
Aleksi
Mari

Ketä etsitään? Mari
Mari löytyi!
Terho
Elina
Aleksi
Mari

Ketä etsitään? Leevi
Leevi ei löytynyt!
Listalla olevan arvon paikka eli indeksi

Listan paikkojen numerointi eli indeksointi alkaa aina nollasta. Listan ensimmäinen arvo on indeksissä 0, toinen arvo indeksissä 1, kolmas arvo indeksissä 2 ja niin edelleen.

Ylläolevassa listassa ensimmäisenä on arvo 6 ja toisena arvo 1. Jos ylläolevaan listaan lisättäisiin uusi arvo kutsumalla luvut-listan metodia add parametrilla 8, menisi luku 8 listan indeksiin 6 eli seitsemänneksi luvuksi.

Vastaavasti kutsumalla metodia get parametrilla 4, listalta haettaisiin viidettä lukua.

Toteuta ohjelma varaston hallinnointiin. Varaston hallinta tarjoaa käyttäjälle neljä komentoa: lisaa, poista, hae ja lopeta. Jos käyttäjä syöttää merkkijonon "lopeta", tulee varastonhallintaohjelman suorituksen päättyä. Jos käyttäjä syöttää komennon "lisaa", "poista", tai "hae", käyttäjältä kysytään esinettä. Tämän jälkeen käyttäjän syöttämä esine joko lisätään varastoon, poistetaan varastosta tai sitä haetaan varastosta.

Alla on esimerkki ohjelman odotetusta suorituksesta.

Varastonhallinta.

Syötä komento (lisaa, poista, hae, lopeta):
lisaa
Syötä esine:
nakki

Syötä komento (lisaa, poista, hae, lopeta):
hae
Syötä esine:
nakki
Esine nakki löytyy varastosta.

Syötä komento (lisaa, poista, hae, lopeta):
poista
Syötä esine:
nakki

Syötä komento (lisaa, poista, hae, lopeta):
hae
Syötä esine:
nakki
Esinettä nakki ei löydy varastosta.

Syötä komento (lisaa, poista, hae, lopeta):
lopeta

Pisteytys:

  • (1p) lopetus, lisääminen, ja hakeminen toimii.
  • (2p) lopetus, lisääminen, poistaminen, ja hakeminen toimii.

Listan läpikäynti

Seuraavassa esimerkissä listalle lisätään neljä nimeä, jonka jälkeen listan sisältö tulostetaan.

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

opettajat.add("Sami");
opettajat.add("Samu");
opettajat.add("Anne");
opettajat.add("Anna");

System.out.println(opettajat.get(0));
System.out.println(opettajat.get(1));
System.out.println(opettajat.get(2));
System.out.println(opettajat.get(3));
Sami
Samu
Anne
Anna

Esimerkki on kömpelö. Entä jos listalla olisi enemmän arvoja? Tai vähemmän? Entäs jos emme tietäisi listalla olevien arvojen määrää?

Tehdään ensin välivaiheen versio jossa pidetään kirjaa tulostettavasta paikasta muuttujan paikka avulla:

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

opettajat.add("Sami");
opettajat.add("Samu");
opettajat.add("Anne");
opettajat.add("Anna");

int paikka = 0;

System.out.println(opettajat.get(paikka));
paikka++;

System.out.println(opettajat.get(paikka));  // paikka = 1
paikka++;

System.out.println(opettajat.get(paikka));  // paikka = 2
paikka++;

System.out.println(opettajat.get(paikka));  // paikka = 3
paikka++;

Huomaamme, että ylläolevassa ohjelmassa on toistoa. Voimme hyödyntää toistolausetta ja kasvattaa muuttujaa paikka niin kauan kunnes se kasvaa liian suureksi:

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

opettajat.add("Sami");
opettajat.add("Samu");
opettajat.add("Anne");
opettajat.add("Anna");

int paikka = 0;
while (paikka < opettajat.size()) {
    System.out.println(opettajat.get(paikka));
    paikka++;
}

// muistatko miksi paikka <= opettajat.size() ei toimi?

Nyt tulostus toimii riippumatta listalla olevien alkioiden määrästä.

For-each

Jos listan arvojen läpikäynnissä ei tarvita tietoa listan arvon sijannista (arvoa ei tarvitse esimerkiksi muuttaa, vaihtaa jne), voi listan läpikäyntiin käyttää myös ns. for-each-lausetta.

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

opettajat.add("Sami");
opettajat.add("Samu");
opettajat.add("Anne");
opettajat.add("Anna");

for (String opettaja: opettajat) {
    System.out.println(opettaja);
}

Yllä olevaa lausetta käytetään listan jokaisen arvon läpikäyntiin. Lause määritellään muodossa for (MuuttujanTyyppi muuttujanArvo: listanNimi), missä MuuttujanTyyppi on listalla olevien arvojen tyyppi ja muuttujanArvo on muuttuja, johon listan arvo asetetaan jokaisen läpikäynnin yhteydessä.

Listalle lisättävien muuttujien tyyppi

Lista voi sisältää vain tietyntyyppisiä arvoja. Nämä arvot määritellään listaa luodessa, esimerkiksi ArrayList<String> sisältää merkkijonotyyppisiä muuttujia.

Aiemmin käyttämämme muuttujatyypit int ja double sekä lähes kaikki muut pienellä alkukirjaimella kirjoitettavat muuttujat ovat alkeistyyppisiä ja ne eivät käy suoraan listan sisältämien alkioiden tyypiksi. Jos haluamme lisätä alkeistyyppisiä muuttujia listalle, tulee ne määritellä niiden viittaustyppisten muuttujatyyppien avulla. Esimerkiksi int-tyyppisten muuttujien tyypiksi tulee asettaa Integer. Vastaavasti double-tyyppisten muuttujien tyypiksi tulee asettaa Double.

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

nimet.add("Sami");
nimet.add("Samu");
nimet.add("Anne");
nimet.add("Anna");


ArrayList<Integer> iat = new ArrayList<>();

iat.add(6);
iat.add(11);
iat.add(38);
iat.add(14);

int paikka = 0;
while (paikka < iat.size()) {
    System.out.print("Nimi: " + nimet.get(paikka));
    System.out.print("\t");

    int ika = iat.get(paikka);
    System.out.println("Ikä: " + ika);

    // tai:
    // System.out.print("Ikä: " + iat.get(paikka));
    System.out.println();

    paikka++;
}
Nimi: Sami    Ikä 6
Nimi: Samu    Ikä 11
Nimi: Anne    Ikä 38
Nimi: Anna    Ikä 14
Miksei ArrayList<int> toimi?

Tämä rajoitus liittyy siihen, miten ArrayList on toteutettu. Javan muuttujat voidaan jakaa kahteen kategoriaan: alkeistyyppisiin muuttujiin ja viittaustyyppisiin muuttujiin. Alkeistyyppiset muuttujat kuten int ja double sisältävät niihin liittyvät arvot. Viittaustyyppiset muuttujat taas, kuten esimerkiksi ArrayList sisältävät viitteen paikkaan, joka sisältää muuttujaan liittyvät arvot.

Hieman yksinkertaistaen: alkeistyyppiset muuttujat pystyvät sisältämään vain rajatun määrän tietoa, kun taas viitteen taakse tietoa voi säilöä lähes rajattomasti.

ArrayList olettaa, että sen sisältämät muuttujat ovat viittaustyyppisiä. Java muuntaa automaattisesti int-tyyppisen muuttujan Integer-tyyppiseksi kun se lisätään listalle, sama tapahtuu myös kun muuttuja haetaan listalta. Vastaava muunnos tapahtuu myös double-tyyppiselle muuttujalle, josta tulee Double-tyyppinen muuttuja.

Palaamme tähän jatkossakin, sillä muuttujat vaikuttavat oleellisesti ohjelmamme suoritukseen.

Ohjelmaan on toteutettu valmiina pohja, joka lukee käyttäjältä lukuja listalle. Syötteiden lukeminen päätetään kun käyttäjä syöttää luvun -1.

Lisää ohjelmaan toiminnallisuus, joka lukujen lukemisen jälkeen tulostaa listalla olevien lukujen summan.

72
2
8
11
-1

Summa: 93

Ohjelmaan on toteutettu valmiina pohja, joka lukee käyttäjältä lukuja listalle. Syötteiden lukeminen päätetään kun käyttäjä syöttää luvun -1.

Lisää ohjelmaan toiminnallisuus, joka etsii listalta listan suurimman luvun ja tulostaa sen arvon. Ohjelman pitäisi toimia seuraavasti.

72
2
8
93
11
-1

Listan suurin luku: 93

Ota mallia allaolevasta pienintä lukua etsivästä lähdekoodista.

// oletetaan, että käytössämme on lista, jossa on kokonaislukuja

int pienin = lista.get(0);

int indeksi = 0;
while (indeksi < lista.size()) {
    int luku = lista.get(indeksi);
    if (pienin > luku) {
        pienin = luku;
    }

    indeksi++;
}

System.out.println("Listan pienin luku: " + pienin);

Toteuta ohjelma, joka lukee käyttäjältä lukuja listalle. Syötteiden lukeminen päätetään kun käyttäjä syöttää luvun -1.

Kun lukujen lukeminen lopetetaan, laske listalla olevien lukujen keskiarvo ja tulosta se.

72
2
8
11
-1

Keskiarvo: 23.25

Ohjelmaan on toteutettu valmiina pohja, joka lukee käyttäjältä lukuja listalle. Syötteiden lukeminen päätetään kun käyttäjä syöttää luvun -1.

Lisää ohjelmaan toiminnallisuus, joka kysyy käyttäjältä lukua ja kertoo luvun indeksin. Jos lukua ei löydy, tulee siitä ilmoittaa erikseen (vihje: contains!).

72
2
8
8
11
-1

Mitä etsitään? 2
Luku 2 on indeksissä 1
72
2
8
8
11
-1

Mitä etsitään? 7
Lukua 7 ei löydy.
72
2
8
8
11
-1

Mitä etsitään? 8
Luku 8 on indeksissä 2
Luku 8 on indeksissä 3

Toteuta ohjelma, joka lukee käyttäjältä lukuja. Kun käyttäjä syöttää luvun 9999, lukujen lukeminen lopetetaan. Ohjelma tulostaa tämän jälkeen pienimmän listalla olevan luvun sekä indeksit, joista pienin luku löytyy. Pienin luku voi siis esiintyä useamman kerran.

72
2
8
8
11
9999

Pienin luku on 2
Pienin luku löytyy indeksistä 1
72
44
8
8
11
9999


Pienin luku on 8
Pienin luku löytyy indeksistä 2
Pienin luku löytyy indeksistä 3

Yhteenveto

Kolmannessa osassa syvennyttiin metodeihin ja tutustuttiin sekä merkkijonoihin että listoihin. Huomasimme myös, että merkkijonot ja listat ovat erilaisia kuin muuttujat, sillä jokaiseen merkkijonoon ja muuttujaan liittyy "omat" metodit. Tarkemmin tarkasteltuna jokaisella merkkijonolla on samat metodit, mutta metodit käsittelevät aina tiettyä merkkijonoa, eli sitä, jonka kohdalla metodia kutsutaan.

Tämä johdattelee meitä olio-ohjelmoinnin saloihin, johon syvennytään tarkemmin seuraavassa osassa. Oliot ovat ohjelmassa käytettäviä kokonaisuuksia, joista jokaisella on oma sisäinen tila, ja joiden sisäistä tilaa voidaan muuttaa olioon liittyvillä metodeilla. Olion rakennuspiirrustukset määritellään olion luokassa -- esimerkiksi merkkijonon metodit on määritelty Javan luokassa String ja käyttämämme listan metodit Javan luokassa ArrayList.

Tutustutaan lopuksi ohjelmaan, jossa käytetään juuri harjoittelemiamme asioita.

Tehdään klassinen ajatustenlukuohjelma. Ohjelmassa käyttäjää pyydetään syöttämään luku 0 tai 1. Tietokoneen tehtävänä on arvata käyttäjän syöttämä luku. Tätä toistetaan kunnes käyttäjä ei enää halua pelata.

Alla esimerkki mahdollisesta ajatustenlukijasta. Kun painat nollaa tai ykköstä, tietokone yrittää arvata painalluksesi. Jos tietokone arvaa oikein, tietokone saa pisteen. Muuten saat pisteen. Kumpi saa ensin 25 pistettä?

Koneen voitot: 0
Koneen tappiot: 0

 

Lukeeko ohjelma ajatuksia? Ei. Se yrittää tosin olla ovela -- ohjelma tallentaa käyttäjän aiemmat päätökset ja pyrkii ennustamaan tulevaa niiden perusteella. George Santayanan sanoin, to know your future you must know your past.

Toteutetaan seuraavaksi samankaltainen sovellus Java-kielellä. Ohjelman käyttöliittymä on seuraava.

Syötä nolla tai ykkönen, ihan sama, tiedän sen.

Syötä 0 tai 1: 1
Syötit 1, arvasin 0.
Tietokoneen voitot: 0
Pelaajan voitot: 1

Syötä 0 tai 1: 1
Syötit 1, arvasin 0.
Tietokoneen voitot: 0
Pelaajan voitot: 2

Syötä 0 tai 1: 0
Syötit 0, arvasin 0.
Tietokoneen voitot: 1
Pelaajan voitot: 2

Syötä 0 tai 1: 0
Syötit 0, arvasin 1.
Tietokoneen voitot: 1
Pelaajan voitot: 3

...

Lähtökohta sovellukselle on seuraava runko:

System.out.println("Syötä nolla tai ykkönen, ihan sama, tiedän sen.");
Scanner lukija = new Scanner(System.in);

ArrayList<Integer> luvut = new ArrayList<>();
int voitot = 0;

while (true) {
    // peli päättyy kun jommalla kummalla on yli 25 pistettä
    if (voitot >= 25 || luvut.size() - voitot >= 25) {
        break;
    }

    // arvaustoiminnallisuus saa käyttöönsä aiemmat syötteet
    int arvaus = arvaa(luvut);

    System.out.print("Syötä 0 tai 1: ");
    int luku = Integer.parseInt(lukija.nextLine());
    if (luku != 0 && luku != 1) {
        System.out.println("höpönlöpön..");
        continue;
    }

    if (luku == arvaus) {
        voitot++;
    }

    luvut.add(luku);


    System.out.println("Syötit " + syote + ", arvasin " + arvaus + ".");
    System.out.println("Tietokoneen voitot: " + voitot);
    System.out.println("Pelaajan voitot: " + (syotteet.size() - voitot));

    System.out.println();
}

System.out.println("Peli päättyi.");

Ohjelma lukee käyttäjältä syötteitä kunnes tietokone on voittanut 25 peliä tai käyttäjä on voittanut 25 peliä.

Ajatustenlukijan "älykkyyden" ytimessä on käyttäjän aiempien syötteiden tarkastelu. Kohta rakennettavassa esimerkissä -- ja ajatustenlukijassa -- tehdään oletus, että pelaajan kaksi viimeistä valintaa ovat aina tärkeät ja kertovat seuraavasta valinnasta. Tämän oletuksen perusteella voimme käydä koko syötehistorian läpi ja etsiä peräkkäisiä siirtoja, jotka ovat samat kuin pelaajan kaksi viimeistä siirtoa. Vastaavien peräkkäisten siirtojen löytyessä laitamme niitä seuranneen siirron muistiin. Kun pelihistoria on käyty läpi, valitsemme "arvaukseksi" siirron, joka on esiintynyt eniten.

Tarkastellaan tätä vielä esimerkin kautta. Oletetaan, että käyttäjän syötehistoria on seuraavanlainen.

0 1 0 1 1 0 1 1 1 0 1 0 1 0 1 0 0 0 1 0 1 0 0 1

Käyttäjän kaksi viimeistä syötettä ovat 0 ja 1.

0 1 0 1 1 0 1 1 1 0 1 0 1 0 1 0 0 0 1 0 1 0 0 1

Ensimmäisen kerran syötteet 0 ja 1 esiintyvät syötelistan indekseissä 0 ja 1. Näitä seuraa luku nolla.

0 1 0 1 1 0 1 1 1 0 1 0 1 0 1 0 0 0 1 0 1 0 0 1

nollia: 1

Seuraavan kerran syötteet 0 ja 1 esiintyvät syötelistan indekseissä 2 ja 3. Näitä seuraa luku yksi.

0 1 0 1 1 0 1 1 1 0 1 0 1 0 1 0 0 0 1 0 1 0 0 1

nollia: 1
ykkösiä: 1

Seuraavan kerran syötteet 0 ja 1 esiintyvät syötelistan indekseissä 5 ja 6. Näitä seuraa luku 1.

0 1 0 1 1 0 1 1 1 0 1 0 1 0 1 0 0 0 1 0 1 0 0 1

nollia: 1
ykkösiä: 2

Seuraavan kerran syötteet 0 ja 1 esiintyvät syötelistan indekseissä 9 ja 10. Näitä seuraa luku 0.

0 1 0 1 1 0 1 1 1 0 1 0 1 0 1 0 0 0 1 0 1 0 0 1

nollia: 2
ykkösiä: 2

Tämä jatkuu kunnes lista on käyty läpi. Lopussa tilanne on seuraava.

0 1 0 1 1 0 1 1 1 0 1 0 1 0 1 0 0 0 1 0 1 0 0 1

nollia: 6
ykkösiä: 2

Pelaaja on syöttänyt nollan ja ykkösen jälkeen kuusi kertaa nollan, kaksi kertaa ykkösen. Kone päätyy arvaamaan, että pelaaja syöttää seuraavaksi nollan. Koneen tulee siis myös valita nolla.

Kahden peräkkäisen listan viimeisiä lukuja vastaavan lukuparin löytäminen onnistuu toistolauseen avulla. Alla oleva esimerkki tulostaa "hep" aina kun tarkasteltavat luvut vastaavat listan viimeisiä lukuja.

public static int arvaa(ArrayList<Integer> luvut) {

    int viimeinen = luvut.get(luvut.size() - 1);
    int toiseksiViimeinen = luvut.get(luvut.size() - 2);

    int indeksi = 2;

    while (indeksi < luvut.size()) {
        if (luvut.get(indeksi - 2) == toiseksiViimeinen &&
                luvut.get(indeksi - 1) == viimeinen) {
            System.out.println("hep");
        }

        indeksi++;
    }

    return 0;
}

Lisätään metodiin toiminnallisuus nollien ja ykkösten laskemiseen. Mikäli tarkasteltava lukupari vastaa listan viimeisiä lukuja ja tarkasteltavaa lukuparia seuraa nolla, kasvatetaan havaittujen nollien määrää yhdellä. Vastaavasti, mikäli tarkasteltava lukupari vastaa listan viimeisiä lukuja ja tarkasteltavaa lukuparia seuraa ykkönen, kasvatetaan havaittujen ykkösten määrää yhdellä.

public static int arvaa(ArrayList<Integer> luvut) {

    int viimeinen = luvut.get(luvut.size() - 1);
    int toiseksiViimeinen = luvut.get(luvut.size() - 2);

    int indeksi = 2;
    int nollia = 0;
    int ykkosia = 0;

    while (indeksi < luvut.size()) {
        if (luvut.get(indeksi - 2) == toiseksiViimeinen &&
                luvut.get(indeksi - 1) == viimeinen) {
            System.out.println("hep");

            if (luvut.get(indeksi) == 0) {
                nollia++;
            } else {
                ykkosia++;
            }

        }

        indeksi++;
    }

    return 0;
}

Nyt ohjelma laskee pelaajan viimeisiä siirtoja mahdollisesti seuraavien nollien ja ykkösten määrän. Palautetaan metodista arvo 0 mikäli nollia on enemmän kuin ykkösiä. Muulloin palautetaan arvo 1.

public static int arvaa(ArrayList<Integer> luvut) {

    int viimeinen = luvut.get(luvut.size() - 1);
    int toiseksiViimeinen = luvut.get(luvut.size() - 2);

    int indeksi = 2;
    int nollia = 0;
    int ykkosia = 0;

    while (indeksi < luvut.size()) {
        if (luvut.get(indeksi - 2) == toiseksiViimeinen &&
                luvut.get(indeksi - 1) == viimeinen) {
            System.out.println("hep");

            if (luvut.get(indeksi) == 0) {
                nollia++;
            } else {
                ykkosia++;
            }

        }

        indeksi++;
    }

    if (nollia > ykkosia) {
        return 0;
    }

    return 1;
}

Metodi toimii melko hyvin, mutta siinä on vielä ongelma. Mikäli metodille annetaan parametrina lista, jossa on alle 2 arvoa, ohjelma heittää poikkeuksen. Korjataan metodi -- sovitaan, että mikäli lukuja on alle 3, arvaus on aina 0.

public static int arvaa(ArrayList<Integer> luvut) {
    if (luvut.size() < 3) {
        return 0;
    }

    int viimeinen = luvut.get(luvut.size() - 1);
    int toiseksiViimeinen = luvut.get(luvut.size() - 2);

    int indeksi = 2;
    int nollia = 0;
    int ykkosia = 0;

    while (indeksi < luvut.size()) {
        if (luvut.get(indeksi - 2) == toiseksiViimeinen &&
                luvut.get(indeksi - 1) == viimeinen) {
            System.out.println("hep");

            if (luvut.get(indeksi) == 0) {
                nollia++;
            } else {
                ykkosia++;
            }

        }

        indeksi++;
    }

    if (nollia > ykkosia) {
        return 0;
    }

    return 1;
}

Tässä tehtävänäsi on tehdä edellistä esimerkkiä mukaileva ohjelma. Ohjelmassa on kuitenkin pieni muutos edelliseen esimerkkiin verrattuna -- sen sijaan, että ohjelma kysyy käyttäjältä lukuja, ohjelman tulee kysyä käyttäjältä merkkijonoja. Käyttäjän tulee syöttää joko "h" (heads = kruuna) tai "t" (tails = klaaava) ja tietokoneen tulee pyrkiä arvaamaan käyttäjän syöte.

Noudata edellä kuvattua strategiaa ajatustenlukijan tekoälyn toteuttamiseksi. Tässä strategia vielä lyhyesti:

  • Mikäli käyttäjä on syöttänyt alle 3 syötettä, tietokoneen tulee arvata "h" eli kruuna.
  • Muulloin, tietokoneen tulee tarkastella pelaajan aiempia valintoja ja etsiä sopiva valinta. Sopivan valinnan etsiminen tehdään tarkastelemalla käyttäjän kahta viimeistä syötettä ja vertailemalla niitä koko historiaan. Mikäli historian mukaan kahta viimeistä syötettä seuraa useammin "h" kuin "t", tulee tulee tietokoneen pelata "h". Muulloin tietokoneen tulee pelata "t".

Muistathan että merkkijonoja ei saa vertailla kahdella yhtäsuuruusmerkillä.

Ote odotetusta ohjelman tulostuksesta ohjelman suorituksen alusta.

Syötä h tai t: h
Syötit h, arvasin h.
Tietokoneen voitot: 1
Pelaajan voitot: 0

Syötä h tai t: h
Syötit h, arvasin h.
Tietokoneen voitot: 2
Pelaajan voitot: 0

Syötä h tai t: t
Syötit t, arvasin h.
Tietokoneen voitot: 2
Pelaajan voitot: 1

Syötä h tai t: t
Syötit t, arvasin t.
Tietokoneen voitot: 3
Pelaajan voitot: 1

Syötä h tai t: h
Syötit h, arvasin t.
Tietokoneen voitot: 3
Pelaajan voitot: 2

Syötä h tai t: t
Syötit t, arvasin t.
Tietokoneen voitot: 4
Pelaajan voitot: 2

Syötä h tai t: h
Syötit h, arvasin t.
Tietokoneen voitot: 4
Pelaajan voitot: 3

Syötä h tai t: t
Syötit t, arvasin t.
Tietokoneen voitot: 5
Pelaajan voitot: 3

Syötä h tai t: t
Syötit t, arvasin h.
Tietokoneen voitot: 5
Pelaajan voitot: 4

...

Sisällysluettelo