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 |
|
Metodi palauttaa int -tyyppisen muuttujan |
|
Metodi palauttaa double -tyyppisen muuttujan |
|
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.
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ä.
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.
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ä.
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.
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!
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
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 ...