Sisällysluettelo

Tehtävät

Kysely: Opiskeluun liittyvät tahtotilat ja strategiat

Tutkimus: Opiskeluun liittyvät tahtotilat ja strategiat

Käytämme tietoja kurssiemme kehittämiseen ja opetuksen tutkimukseen.

Seuraavilla kysymyksillä kartoitetaan opiskeluun liittyviä toimintatapojasi. Tässä kyselyssä ei ole oikeita tai vääriä vastauksia, vastaa niin tarkasti kuin pystyt. Käytä alla määriteltyä skaalaa vastausten antamiseen. Jos olet sitä mieltä, että väite pätee sinuun erittäin hyvin, valitse 7; jos olet sitä mieltä, että väite ei päde sinuun lainkaan, valitse 1. Jos väite on enemmän tai vähemmän totta kohdallasi, valitse sinua parhaiten kuvaava arvo lukujen 1 ja 7 väliltä.

 

Väite ei päde minuun lainkaan.
1
2
3
4
5
6
7
Väite pätee minuun erittäin hyvin.

 

Kysely: Ohjelmointirutiini ja muuttujien ymmärrys, osa 2

Tutkimus: Ohjelmointirutiini ja muuttujien ymmärrys

Käytämme tietoja kurssiemme kehittämiseen ja opetuksen tutkimukseen.

Tässä kyselyssä kartoitetaan tähän mennessä kertynyttä ohjelmointirutiiniasi sekä muuttujien ymmärrystä. Vastaathan kyselyyn nykyisen osaamisesi perusteella, älä siis esimerkiksi kokeile koodinpätkiä tietokoneella ennen vastauksen antamista.

 

Kysymys 1

Tutki seuraavaa koodia.

int a = 23;
int b = 11;
int c = 61;
a = b;
c = a;
b = c;

Kun koodi on suoritettu, mitkä ovat muuttujien a, b ja c arvot?

Muuttujan a arvo on

Muuttujan b arvo on

Muuttujan c arvo on


Kysymys 2

Oleta että kokonaislukumuuttujat musta ja valkoinen ovat alustettu, ja niihin on asetettu jonkinlainen arvo. Kirjoita ohjelmakoodi, joka vaihtaa muuttujien arvot päittäin.


Kysymys 3

Oleta että käytössäsi on neljä kokonaislukumuuttujaa a, b, c ja d.

Kirjoita ohjelmakoodi, joka siirtää edellämainituissa muuttujissa olevat arvot oikealta vasemmalle, missä vasemmanpuolimmaisimman muuttujan (tässä a) arvo asetetaan lopuksi muuttujan oikeanpuolimmaisen muuttujan (tässä d) arvoksi. Koodin tulisi toimia kuten allaolevassa kuvassa.

Esimerkiksi, jos alussa a=1, b=2, c=3 ja d=4, kirjoittamasi koodin suorituksen jälkeen muuttujien arvojen pitäisi olla a=2, b=3, c=4 ja d=1 (kuten ylläolevassa kuvassa). Luonnollisesti, koodin pitäisi toimia myös muille muuttujien arvoille.

 

Muutama hyödyllinen vinkki

 

  • Uudelleennimentä

    Muuttujat, metodit ja ensi viikolla opittavat luokat kannattaa nimetä kuvaavasti. Usein käy niin, että ensin valittu nimi on epäkuvaava, ja ohjelmoija haluaa muuttaa nimeä. NetBeans:issa nimen muuttaminen on suoraviivaista. Maalaa huono muuttujan nimi jostain kohtaa koodiasi hiirellä. Paina (yhtäaikaa) ctrl ja r ja kirjoita muuttujalle/metodille uusi nimi. Kaikki kohdat, missä kyseinen muuttuja on käytössä, muuttuvat samalla.

  • Projektien sulkeminen

    Kun huomaat, että tehtäväpohjia alkaa kertymään ohjelmointiympäristöösi, voit sulkea jo suoritettuja tehtäviä. Sulkeminen tapahtuu klikkaamalla tehtävän nimeä oikealla hiirennapilla, ja valitsemalla close. Valmiiden tehtävien sulkeminen ohjelmointiympäristöstäsi hieman nopeamman, sillä se ei välitä suljetuista tehtävistä .

  • Projektien testaaminen ilman testejä

    Vaikka monessa tehtävässä on mukana testit, joiden avulla voit tarkistaa onko ratkaisusi oikea, testaa ohjelmiasi myös käsin. Yksi näppärä tapa on tehdä koodiin tulostuskomentoja System.out.println("");, joiden avulla voit tarkistaa missä kohtaa ohjelmakoodin suoritus on meneillään.

    Seuraavassa kappaleessa näytetään askeleittainen lähestymistapa, missä jokaista askelta myös testataan manuaalisesti. Tutustu siihen!

14 Ongelman ratkaiseminen paloittain

Tutustutaan uudestaan ensimmäisellä viikolla FizzBuzz-ongelmaan, mutta tehdään tällä kertaa versio, missä on toistolause mukana: 'Kirjoita ohjelma, joka kysyy käyttäjältä positiivista lukua, ja tulostaa kaikki luvut yhdestä käyttäjän syöttämään lukuun. Jos luku on kolmella jaollinen, luvun sijaan tulostetaan "Fizz". Jos luku on viidellä jaollinen, luvun sijaan tulostetaan "Buzz". Jos luku on sekä kolmellä että viidellä jaollinen, luvun sijaan tulostetaan "FizzBuzz"'. Muulloin tulostetaan luku.

Ratkaistaan ongelma osissa siten, että jokaisen osan tuotos on ohjelma, jonka toimivuutta voi testata.

14.1 Osa 1: käyttäjän syöttämän luvun lukeminen

Tehdään ensin ohjelma, joka kysyy käyttäjältä syötettä. Syötteen lukeminen onnistuu Scanner-apuvälineen avulla, ja luettu syöte muutetaan luvuksi komennon Integer.parseInt-avulla.

import java.util.Scanner;

public class FizzBuzz {

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

        System.out.println("Syötä positiivinen luku: ");
        int luku = Integer.parseInt(lukija.nextLine());

    }
}

Lisätään ohjelmaan myös luetun luvun tulostus, minkä avulla ohjelmaa lukemisen toimintaa voi testata.

System.out.println("Syötä positiivinen luku: ");
int luku = Integer.parseInt(lukija.nextLine());

System.out.println(luku);

Nyt kun ohjelman käynnistää, tulostus on esimerkiksi seuraavanlainen.

Syötä positiivinen luku:
3
3

Ohjelma toimii "väärin" negatiivisilla luvuilla, sillä se ei tarkista syötteen oikeellisuutta.

Syötä positiivinen luku:
-73
-73

Tehdään seuraavaksi ohjelmaan osa, joka tarkistaa, että luetuksi luvuksi hyväksytään vain positiivinen luku.

14.2 Osa 2: Positiivisen luvun lukeminen

Yksi tapa varmistaa, että käyttäjä syöttää positiivisen luvun, on kysyä lukua uudestaan käyttäen syöttäessä negatiivisen luvun. Tätä voi jatkaa ikuisesti tai ainakin siihen asti, että syötetty luku on positiivinen. Lisätään luvun lukemisen jälkeen oma toistolause, jossa käyttäjältä kysytään lukua kunnes syötetty luku on positiivinen.

System.out.println("Syötä positiivinen luku: ");
int luku = Integer.parseInt(lukija.nextLine());

while (luku < 0) {
    System.out.println("Syöttämäsi luku ei ollut positiivinen! Syötä uusi luku: ");
    luku = Integer.parseInt(lukija.nextLine());
}

System.out.println(luku);

Toistolauseketta while (luku < 0) { ... } jatketaan niin pitkään kuin ehto luku < 0 on totta. Kun luku on suurempi tai yhtäsuuri kuin nolla, ehto ei ole enää totta, ja suoritus jatkuu toistolausekkeen lopusta.

Syötä positiivinen luku:
3
3
Syötä positiivinen luku:
-8
Syöttämäsi luku ei ollut positiivinen! Syötä uusi luku:
-1
Syöttämäsi luku ei ollut positiivinen! Syötä uusi luku:
2
2

14.3 Osa 3: Kaikki luvut yhdestä syötettyyn lukuun

Seuraava askel on kaikkien lukujen tulostaminen yhdestä käyttäjän syöttämään lukuun. Käytetään tulostuksessa apumuuttujaa tulostettava, jonka alkuarvoksi asetetaan 1, sekä toistolausetta. Toistolauseessa tulostetaan muuttujan tulostettava arvo, sekä kasvatetaan sitä aina yhdellä. Toistolausetta toistetaan niin pitkään kun tulostettava on pienempi tai yhtä suurin kuin käyttäjän syöttämä luku.

System.out.println("Syötä positiivinen luku: ");
int luku = Integer.parseInt(lukija.nextLine());

while (luku < 0) {
    System.out.println("Syöttämäsi luku ei ollut positiivinen! Syötä uusi luku: ");
    luku = Integer.parseInt(lukija.nextLine());
}

int tulostettava = 1;
while (tulostettava <= luku) {
    System.out.println(tulostettava);
    tulostettava++;
}

System.out.println(luku);

Ohjelma toimii nyt seuraavasti:

Syötä positiivinen luku:
3
1
2
3
3

Koska emme muuttaneet luvun syöttämistä tai positiivisen luvun tarkistusta, voimme olettaa, että ne toimivat vieläkin. Poistetaan vielä lopussa ollut tarkistustulostus, sillä toistolauseen tulostus näyttää meille, että ohjelma toimii halutusti.

System.out.println("Syötä positiivinen luku: ");
int luku = Integer.parseInt(lukija.nextLine());

while (luku < 0) {
    System.out.println("Syöttämäsi luku ei ollut positiivinen! Syötä uusi luku: ");
    luku = Integer.parseInt(lukija.nextLine());
}

int tulostettava = 1;
while (tulostettava <= luku) {
    System.out.println(tulostettava);
    tulostettava++;
}
Syötä positiivinen luku:
3
1
2
3

Luvut tulostuvat oikein. Haluamme seuraavaksi alkaa toteuttamaan Fizz ja Buzz-osiota.

14.4 Osa 4: Fizz ja kolmella jaolliset

FizzBuzz-ongelmaan kuuluu oleellisesti seuraava lukujen tulostus: Jos luku on kolmella jaollinen, luvun sijaan tulostetaan "Fizz". Jos luku on viidellä jaollinen, luvun sijaan tulostetaan "Buzz". Jos luku on sekä kolmellä että viidellä jaollinen, luvun sijaan tulostetaan "FizzBuzz"'. Muulloin tulostetaan luku.

Toteutaan ensin tulostus, missä kolmella jaolliset luvut tulostavat merkkijonon "Fizz". Muulloin tulostetaan luku. Ehtolauseena tarkistus on seuraavanlainen jos käytössä muuttuja luku:

if (luku % 3 == 0) {
    System.out.println("Fizz");
} else {
    System.out.println(luku);
}

Lisätään ylläoleva ehtolause toistolauseen osaksi, ja kokeillaan ohjelman toimintaa. Tulostuksessa tulostamme aina muuttujan tulostettava arvoa, joten vaihdetaan se toistolauseeseen.

System.out.println("Syötä positiivinen luku: ");
int luku = Integer.parseInt(lukija.nextLine());

while (luku < 0) {
    System.out.println("Syöttämäsi luku ei ollut positiivinen! Syötä uusi luku: ");
    luku = Integer.parseInt(lukija.nextLine());
}

int tulostettava = 1;
while (tulostettava <= luku) {

    if (tulostettava % 3 == 0) {
        System.out.println("Fizz");
    } else {
        System.out.println(tulostettava);
    }

    tulostettava++;
}
Syötä positiivinen luku:
6
1
2
Fizz
4
5
Fizz

Näyttäisi toimivan ok! Mennään seuraavaan osaa, eli Buzzin tulostamiseen.

14.5 Osa 5: Buzz ja viidellä jaolliset

Toteutimme edellisessä osassa kolmella jaollisten kohdalla "Fizz"-merkkijonon tulostamisen. Lisätään koodiin tarkistus viidellä jaollisuudesta.

Toteutaan ensin tulostus, missä kolmella jaolliset luvut tulostavat merkkijonon "Fizz" ja viidellä jaolliset tulostavat merkkijonon "Buzz". Muulloin tulostetaan luku. Ehtolauseena tarkistus on seuraavanlainen jos käytössä muuttuja luku:

if (luku % 3 == 0) {
    System.out.println("Fizz");
} else if (luku % 5 == 0) {
    System.out.println("Buzz");
} else {
    System.out.println(luku);
}

Vaihdetaan ylläoleva ehtolause edellisessä osassa aikaansaadun Fizz-kohdan paikalle, ja kokeillaan ohjelman toimintaa. Tulostuksessa käytetään yhä muuttujaa tulostettava.

System.out.println("Syötä positiivinen luku: ");
int luku = Integer.parseInt(lukija.nextLine());

while (luku < 0) {
    System.out.println("Syöttämäsi luku ei ollut positiivinen! Syötä uusi luku: ");
    luku = Integer.parseInt(lukija.nextLine());
}

int tulostettava = 1;
while (tulostettava <= luku) {

    if (tulostettava % 3 == 0) {
        System.out.println("Fizz");
    } else if (tulostettava % 5 == 0) {
        System.out.println("Buzz");
    } else {    
        System.out.println(tulostettava);
    }

    tulostettava++;
}

Kokeillaan ohjelmaa.

Syötä positiivinen luku:
9
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz

Toimii ok, eikö?

Osa lukijoista saattaa huomata, että testasimme ohjelmaa vain pienillä syötteillä. Tarkistetaan ohjelman toiminta vielä hieman isommalla luvulla:

Syötä positiivinen luku:
16
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
Fizz
16

Luku 15 on jaollinen viidellä ja kolmella. Koska ehtolausekkeessamme on ensin tarkistus if (tulostettava % 3 == 0) {..., suoritetaan se ja poistutaan ehtolausekkeesta. Luvun 15 kohdalla ei päästä ehtoon else if (tulostettava % 5 == 0) {....

Onneksi FizzBuzz-tehtävänanto kertoo mitä tuolloin pitää tehdä: Jos luku on sekä kolmellä että viidellä jaollinen, luvun sijaan tulostetaan "FizzBuzz"'.. Toteutetaan se seuraavaksi.

14.6 Osa 6: FizzBuzz ja kolmella ja viidellä jaolliset

Toteutetaan ohjelman viimeinen osa, eli kolmella ja viidellä jaollisten lukujen tarkastaminen. Pohditaan ensin koko lausetta Jos luku on kolmella jaollinen, luvun sijaan tulostetaan "Fizz". Jos luku on viidellä jaollinen, luvun sijaan tulostetaan "Buzz". Jos luku on sekä kolmellä että viidellä jaollinen, luvun sijaan tulostetaan "FizzBuzz"'. Muulloin tulostetaan luku.. Kirjaimellisesti ohjetta seuraten ehdosta tulisi seuraanlainen:

if (luku % 3 == 0) {
    System.out.println("Fizz");
} else if (luku % 5 == 0) {
    System.out.println("Buzz");
} else if (luku % 3 == 0 && luku % 5 == 0) {
    System.out.println("FizzBuzz");
} else {
    System.out.println(luku);
}

Kun ylläolevaa simuloi, huomaamme seuraavanlaisen ongelman: Jos luku on 3, tulostus on "Fizz", eli oikein. Jos luku on 5, tulostus on "Buzz", eli oikein. Jos luku on taas 15, tulostus on "Fizz", mikä ei ole toivottua -- koska luku on jaollinen sekä kolmella että viidellä, haluaisimme että ohjelma tulostaa merkkijonon "FizzBuzz". Syynä tälle on se, että luku 15 on kolmella jaollinen, ja if-else if-else if-else -tyyppisissä ehtolauseissa suoritetaan ensimmäiseen totta olevaan ehtoon liittyvä koodi -- tässä tapauksessa System.out.println("Fizz");.

Muokataan ehtoa siten, että ensin tarkistetaan eniten rajattu sääntö, eli sääntö, missä luvun pitää olla sekä kolmella että viidellä jaollinen. Ehtolause on nyt seuraavanlainen.

if (luku % 3 == 0 && luku % 5 == 0) {
    System.out.println("FizzBuzz");
} else if (luku % 3 == 0) {
    System.out.println("Fizz");
} else if (luku % 5 == 0) {
    System.out.println("Buzz");
} else {
    System.out.println(luku);
}

Nyt ohjelmaa simuloidessa, myös luku 15 toimii oikein: tulostus on "FizzBuzz". Lisätään ehtolause aiemmin tehtyyn ohjelmaan:

System.out.println("Syötä positiivinen luku: ");
int luku = Integer.parseInt(lukija.nextLine());

while (luku < 0) {
    System.out.println("Syöttämäsi luku ei ollut positiivinen! Syötä uusi luku: ");
    luku = Integer.parseInt(lukija.nextLine());
}

int tulostettava = 1;
while (tulostettava <= luku) {

    if (tulostettava % 3 == 0 && tulostettava % 5 == 0) {
        System.out.println("FizzBuzz");
    } else if (tulostettava % 3 == 0) {
        System.out.println("Fizz");
    } else if (tulostettava % 5 == 0) {
        System.out.println("Buzz");
    } else {    
        System.out.println(tulostettava);
    }

    tulostettava++;
}

Tarkistetaan vielä lopuksi ohjelman toiminta.

Osa lukijoista saattaa huomata, että testasimme ohjelmaa vain pienillä syötteillä. Tarkistetaan ohjelman toiminta vielä hieman isommalla luvulla:

Syötä positiivinen luku:
16
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16

14.7 Yhteenveto

Yllä olevassa esimerkissä ratkaistiin klassinen FizzBuzz-ongelma askeleittain, ja näytettiin minkälaisia askeleita vastaavissa ongelmissa kannattaa käyttää. Askeleittainen eteneminen mahdollisti pienet, testattavat välikohdat ohjelmassa. Tämän lisäksi, aiemmin toimineet osat kuten syötteen lukeminen sai lopuksi vähemmän huomiota, ja pystyimme keskittymään FizzBuzz-ongelmaan.

Pyri simuloimaan tätä askeleittain tekemistä myös tulevissa tehtävässä. Kannattaa aina aloittaa pienestä ohjelman osasta, kuten lukemisesta, mitä laajentamalla voi edetä kohti ongelman ratkaisua.

Tehtävä 48: Desibelimittaukset

Kertaa ennen tämän tehtävän tekemistä edellisten viikkojen materiaalista muuttujien ja toistolausekkeiden käyttö.


Kumpulan kerrostalovahdit RY jakaa kerrostaloasukkaille desibelimittareita. Mittareista saadaan kokonaislukuarvona mittaushetken desibelimäärä, mikä kuvaa äänenvoimakkuutta.

Tehtävänäsi on toteuttaa ohjelma, mihin voidaan tehdä äänenvoimmakkuuskirjauksia. Ohjelman tulee lukea käyttäjän antamasta syötteestä desibelilukuja kunnes käyttäjä syöttää luvun 9999. Desibeliluvuiksi kelpaavat nollat ja positiiviset luvut. Käyttäjän syöttämiä negatiivisia lukuja ei tule ottaa huomioon, ja voit olettaa, että syötteet ovat kokonaislukuja (int).

Kun käyttäjä syöttää luvun 9999, ohjelman tulee tulostaa käyttäjän syöttämien desibelilukujen keskiarvo tai ilmoitus siitä, ettei keskiarvoa voida laskea (koska ei syötetty yhtään lukua).

Käytä Javan Scanner-lukijaa käyttäjän syötteen lukemiseen.

Luku 9999 annetaan vain lopettamisen merkiksi, eikä sitä tule ottaa mukaan keskiarvoon.

Alla on annettu esimerkki siitä, miltä ohjelman käyttäminen voi näyttää. Käyttäjän antamat syötteet on merkitty punaisella värillä. Sinun EI ole pakko tuottaa pilkulleen samanlaista tulostetta, mutta ohjelman on laskettava oikea tulos syötteen perusteella tähän tapaan.

Anna desibelilukuja, lopeta luvulla 9999.
Anna desibeliluku:
100
Anna desibeliluku:
80
Anna desibeliluku:
0
Anna desibeliluku:
-30
Anna desibeliluku:
50
Anna desibeliluku:
9999
Desibelilukujen keskiarvo on 57.5.

Tässä taas keskiarvoa ei saada määritettyä:

Anna desibelilukuja, lopeta luvulla 9999.
Anna desibeliluku:
-8
Anna desibeliluku:
9999
Yhtään desibelilukua ei syötetty.

Toteuta yllämainittu ohjelma. Huom! Kannattanee miettiä ensin hetki ohjelman rakennetta, ja alkaa vasta sitten tekemään. Tehtävässä ei ole automaattisia testejä, jolloin pääset itse tarkistamaan milloin ohjelma oikeasti toimii. Varmista että ohjelmasi toimii erilaisilla syötteillä ennen tehtävän palautusta. Tehtävä vastaa kahta yksittäistä ohjelmointitehtävää.

15 Lisää metodeista

Edellisellä viikolla harjoittelimme muuttujien ja toistolauseen käyttöä, sekä opettelimme kirjoittamaan omia metodeja. Metodien avulla voimme jäsennellä ohjelmaa pienemmiksi kokonaisuuksiksi, ja ratkoa nämä kokonaisuudet yksi kerrallaan. Tämä helpottaa sekä ongelmanratkaisua että parantaa ohjelman luettavuutta niin alkuperäisen ohjelmoijan kuin ohjelmaa myöhemmin mahdollisesti ylläpitävänkin ohjelmoijan osalta. Tästä lähtien oikeastaan jokainen tekemämme ohjelma sisältää metodeita.

15.1 Metodit ja muuttujien näkyvyys

Edellisellä viikolla nähdyt säännöt muuttujien olemassaolosta pätevät myös metodien yhteydessä. Muuttujaa 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. Tämä näkyy konkreettisesti seuraavassa esimerkissä, missä yritetään muuttaa metodin sisältä pääohjelmassa olevaa 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, se ei edes tiedä mistä muuttujasta luku on kyse, sillä sitä 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.

15.2 Metodille annettavat muuttujat kopioituvat

Tarkastellaan seuraavaa esimerkkiä, missä pääohjelmassa määritellään muuttuja luku, joka annetaan parametrina metodille kasvataKolmella. Tulostuksessa on kuitenkin jotain omituista.

// 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);
}

Kun yllä oleva ohjelma suoritetaan, nähdään seuraavanlainen tulostus.

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. Syynä on se, että 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ä siis ole sama kuin pääohjelmassa oleva samanniminen muuttuja.

Jotta saisimme luku-muuttujan uuden arvon pääohjelman käyttöön, tulee metodin palauttaa arvo.

15.3 Metodin paluuarvot

Metodi voi palauttaa arvon. Tähän mennessä kurssilla olleissa esimerkeissä ja tehtävissä metodit eivät palauttaneet mitään. Tämä on merkitty kirjoittamalla metodin ylimmälle riville heti nimen vasemmalle puolelle void.

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) arvon 10. Konkreettinen arvon palautus tapahtuu aina komennolla return:

public static int palautetaanAinaKymppi() {
    return 10;
}

Ylläoleva metodi siis 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 muuttujatyypit, mitä olemme tähän mennessä nähneet, voidaan palauttaa metodista:

public static void metodiJokaEiPalautaMitaan() {
    // metodin runko
}

public static int metodiJokaPalauttaaKokonaisLuvun() {
    // metodin runko, tarvitsee return-komennon
}

public static String metodiJokaPalauttaaTekstin() {
    // metodin runko, tarvitsee return-komennon
}

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 String virheellinenMetodi() {
    System.out.println("Väitän palauttavani merkkijonon, mutten palauta sitä.");
}

Seuraavassa esimerkissä määritellään metodi summan laskemiseen. Yhteen laskettavien muuttujien eka ja toka arvot saadaan metodin parametrina.

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

Metodin kutsutaan seuraavasti. Alla metodia summa 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 ensin selvittää 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ä muuttujan 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

Tehtävä 49: Robottiohjain

Katsotaan uudestaan kurssin ensimmäisten tehtävien joukossa tehtyä tehtävää Robottiohjain, missä tavoitteena oli saada luotua robottia ohjaamalla seuraavanlainen kuvio:

Suuremman suorakulmion leveys on 7 askelta ja korkeus on 5 askelta, ja pienemmän suorakulmion leveys on 3 askelta, ja korkeus 2 askelta.

Tee sama uudestaan, mutta käytä kaikkea tähän asti kertynyttä tietoa. Olet tähän mennessä käyttänyt ainakin jo metodeja ja toistolauseita, joista on todennäköisesti tässä hyötyä. Pohdi ennen ohjelmoinnin aloitusta minkälaisia palasia tarvitset robotin liikuttamiseen, kirjoita ne esimerkiksi kommentteina koodiin, ja lähde vasta sitten ohjelmoimaan niitä.

Muistelua siitä, miten robottiohjain toimii. Pääohjelma ottaa robottikomponentin käyttöönsä kun koodin yläosassa on rivi import robotti.Ohjain;. Komennolla Ohjain.kaynnista(); robottiohjain käynnistää robotin, ja luo ikkunan, jossa robotin kulkemista voi seurata, ja komennolla Ohjain.sammuta(); sammutetaan robotti. Robottia liikutetaan komennoilla Ohjain.ylos();, Ohjain.oikealle();, Ohjain.alas();, ja Ohjain.vasemmalle();. Seuraavassa on esimerkkiohjelma robottiohjaimen käytöstä siten, että robottia siirretään kaksi ruutua ylöspäin:

import robotti.Ohjain;

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

        // käynnistetään robotti
        Ohjain.kaynnista();

        Ohjain.ylos();
        Ohjain.ylos();

        // sammutetaan robotti
        Ohjain.sammuta();
    }
}

15.4 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. Yksi yleinen aloittelijan virhe olisikin yrittää käyttää metodia seuraavasi:

public static void main(String[] args) {
    // annetaan arvot muuttujille eka, toka ja kolmas

    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) {
    // annetaan arvot muuttujille eka, toka ja kolmas

    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:

Tehtävä 50: Lukujen summa

Tee metodi summa, joka laskee 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.

Tehtävä 51: Pienin

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

Tehtävä 52: Suurin

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

Tehtävä 53: Lukujen keskiarvo

Tee metodi keskiarvo, joka laskee parametrina olevien lukujen keskiarvon. Metodin sisällä tulee käyttää apuna tehtävän 50 metodia summa!

Tee metodi seuraavaan runkoon:

public static int summa(int luku1, int luku2, int luku3, int luku4) {
    // kopioi koodi tehtävästä 50
}

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)!

15.5 Mitä metodia kutsuttaessa tapahtuu?

Kun metodia kutsutaan, kutsuva metodi jää odottamaan kutsutun metodin suorittamista. Tätä voidaan visualisoida kutsupinon avulla. 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

15.6 Metodia kutsuvan metodin muuttujat

Tarkastellaan edellisessä aliluvussa nähtyä kutsupinoa hieman monimutkaisemman esimerkin kautta.

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. Kun metodin tulostaTahtia suoritus loppuu, palataan takaisin main-metodin suoritukseen.

main
  alku = 1
  loppu = 5

15.7 Arvon palauttaminen metodista

Tarkastellaan vielä kolmatta kutsupinoesimerkkiä. 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, ohjelma sammuu.

16 Merkkijonot

Tässä luvussa tutustaan tarkemmin Javan merkkijonoihin, eli String:eihin. Olemme jo käyttäneet String-tyyppisiä muuttujia tulostuksen yhteydessä sekä oppineet vertailemaan merkkijonoja toisiinsa. Merkkijonoja vertailtiin toisiinsa kutsumalla merkkijonon equals()-metodia.

String elain = "Koira";

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

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.

Tehtävä 54: Nimen pituus

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.

16.1 Yksittäiset kirjaimet -- char

Javassa on erillinen char-tietotyyppi kirjaimia varten. Yksittäiseen char-muuttujaan voi tallentaa yhden kirjaimen. Kirjaimen asetus char-tyyppiseen muuttujaan tapahtuu yhtäsuuruusmerkin avulla. Toisin kuin merkkijonomuuttuja, joka aloitetaan ja lopetetaan lainausmerkillä "merkkijono", yksittäinen merkkimuuttuja aloitetaan ja lopetetaan puolilainausmerkillä 'a'. Merkin tulostaminen onnistuu tutulla tulostuskomennolla:

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

Merkkijonolta voidaan kysyä sen kirjaimia niiden paikan perusteella. Tämä onnistuu merkkijonon metodilla charAt(), jolle annetaan parametrina merkkijonon merkin kohta. Huomaa kuitenkin, että kohdan laskeminen alkaa nollasta, joten esimerkiksi neljäs kirjain on kohdassa kolme.

String kirja = "Kalavale";

char merkki = kirja.charAt(3);
System.out.println("Kirjan neljäs kirjain on " + merkki);
System.out.println("Ensimmäinen merkki on " + kirja.charAt(0));
Kirjan neljäs kirjain on a
Ensimmäinen merkki on K

Koska merkkijonon kirjaimet numeroidaan (eli teknisemmin ilmaistuna merkkijonoja indeksoidaan) alkaen paikasta 0, on merkkijonon viimeisen kirjaimen numero eli indeksi "merkkijonon pituus miinus yksi", eli kirja.charAt(kirja.length() - 1).

String elain = "Kissa";

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

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

char merkki = kirja.charAt(kirja.length()); // ei toimi :/

Tehtävä 55: Ensimmäinen kirjain

Tee ohjelma, joka kysyy käyttäjän nimen ja ilmoittaa nimen ensimmäisen kirjaimen. Rakenna ohjelmasi niin että laitat ensimmäisen kirjaimen hakemisen omaan metodiinsa: public static char ensimmainenKirjain(String merkkijono). Testit testaavat sekä metodia ensimmainenKirjain että koko ohjelman toimintaa.

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

Tehtävä 56: Viimeinen kirjain

Tee ohjelma, joka kysyy käyttäjän nimen ja ilmoittaa nimen viimeisen kirjaimen. Rakenna ohjelmasi niin että laitat viimeisen kirjaimen hakemisen omaan metodiinsa: public static char viimeinenKirjain(String merkkijono). Testit testaavat sekä metodia viimeinenKirjain että koko ohjelman toimintaa.

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

Tehtävä 57: Kolme ensimmäistä kirjainta

Tee ohjelma, joka kysyy käyttäjän nimen ja ilmoittaa sen kolme ensimmäistä kirjainta erikseen. Jos nimen pituus on alle kolme, ei ohjelma tulosta mitään. Tehtävässä ei edellytetä erillisten metodien luomista.

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

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

Tehtävä 58: Kirjaimet erikseen

Tee ohjelma, joka kysyy käyttäjän nimen ja ilmoittaa sen kirjaimet erikseen. Tehtävässä ei edellytetä erillisen metodin luomista.

Anna nimi: Pekka
1. kirjain: P
2. kirjain: e
3. kirjain: k
4. kirjain: k
5. kirjain: a

Vihje: while-toistolauseesta on tässä apua :)

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

Tehtävä 59: Nimen kääntäminen

Tee ohjelma, joka kysyy käyttäjän nimen ja tulostaa sen väärinpäin. Erillistä metodia nimen kääntämiselle ei tarvitse tehdä.

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

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

16.2 Muita merkkijonojen metodeja

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

String kirja = "Kalavale";

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

Koska substring-metodin paluuarvo on 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ä

Tehtävä 60: Alkuosa

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

Tehtävä 61: Loppuosa

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!

Screencast aiheesta:

Tehtävä 62: Sana sanassa

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!

Tehtävä 63: Merkkijonon kääntäminen

Tee metodi kaanna, joka kääntää annetun merkkijonon. Käytä metodille seuraavaa runkoa:

public static String kaanna(String merkkijono) {
    // kirjoita koodia tähän
}

public static void main(String[] args) {
    System.out.print("Anna merkkijono: ");
    String merkkijono = lukija.nextLine();
    System.out.println("Väärinpäin: " + kaanna(merkkijono));
}

Vihje: joudut todennäköisesti kokoamaan metodin sisällä käänteisen merkkijonon merkki kerrallaan. Kokoamisessa kannattaa käyttää apuna String-tyyppistä apumuuttujaa. Aluksi apumuuttujan arvo on tyhjä merkkijono. Tämän jälkeen merkkijonon perään laitetaan uusia merkkejä merkki kerrallaan. Muistele tässä myös miten teit tehtävän Nimen kääntäminen.

String apu = "";

// ...
// lisätään merkki apu-nimisen muuttujan perään
apu = apu + merkki;

Ohjelman tulostus:

Anna merkkijono: esimerkki
Väärinpäin: ikkremise

17 Oliot ja metodit

Merkkijonot poikkeavat luonteeltaan hieman esimerkiksi kokonaisluvuista. Kokonaisluvut ovat "pelkkiä arvoja", niiden avulla voi laskea ja niitä voi tulostella ruudulle:

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?

17.1 Olioihin liittyy sekä metodeja että arvoja

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

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

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

Metodikutsu kohdistuu nimenomaan sihen 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 hyvin harvoin.

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

18 ArrayList eli "oliosäiliö"

Ohjelmoidessa tulee usein vastaan tilanteita, joissa haluaisimme pitää muistissa esimerkiksi useita erilaisia merkkijonoja. Todella huono tapa olisi määritellä jokaiselle oma muuttujansa:

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

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

Ohjelmointikielet tarjoavat tyypillisesti ohjelmoijalle apuvälineitä, joiden avulla on helppo säilyttää ohjelmassa useita olioita. Tutustumme nyt Java-ohjelmointikielen ehkäpä eniten käytettyyn oliosäiliöön ArrayList:iin.

Seuraava ohjelmanpätkä ottaa käyttöönsä merkkijono-olioita tallentavan ArrayList:in sekä tallettaa listalle pari merkkijonoa.

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");
    }
}

Yllä olevan pääohjelman ensimmäinen rivi luo sanaLista-nimisen merkkijonoja tallettavan listan. Listamuuttujan tyypin nimi on ArrayList<String>, eli ArrayList, joka sisältää String-tyyppisiä muuttujia. Itse lista luodaan sanomalla new ArrayList<>();.

Huom: Jotta ArrayList toimisi, on ohjelman ylälaitaan kirjoitettava import java.util.ArrayList; tai import java.util.*;

Kun lista on luotu, siihen lisätään kaksi merkkijonoa kutsumalla listan metodia add. Tila ei lopu listalla missään vaiheessa kesken, eli periaatteessa listalle saa lisätä niin monta merkkijonoa kun "koneen" muistiin mahtuu. Sisäisesti ArrayList on nimensä mukaisesti lista. Lisätyt merkkijonot menevät automaattisesti ArrayList:in loppuun.

18.1 ArrayList:in metodeja

ArrayList tarjoaa monia hyödyllisiä metodeita:

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

    opettajat.add("Juha");
    opettajat.add("Jyri");
    opettajat.add("Markus");
    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");
    }
}

Ensin luodaan merkkijonolista, jolle lisätään 6 nimeä. size kertoo listalla olevien merkkijonojen lukumäärän. Huom: kun metodia kutsutaan, on kutsu muotoa opettajat.size(), eli metodin nimeä edeltää piste ja sen listan nimi kenen metodia kutsutaan.

Merkkijonot ovat listalla siinä järjestyksessä missä ne listalle laitettiin. Metodilla get(i) saadaan tietoon listan paikan i sisältö. Listan alkioiden paikkanumerointi alkaa nollasta, eli ensimmäisenä lisätty on paikassa numero 0, toisen a lisätty paikassa numero 1 jne.

Metodin remove avulla voidaan listalta poistaa merkkijonoja. Jos käytetään metodia muodossa remove("merkkejä"), poistetaan parametrina annettu merkkijono. Metodia voi käyttää myös siten, että sille annetaan parametrina luku. Tällöin listalta poistetaan luvun määrittelemässä indeksissä oleva merkkijono. Esimerkiksi, kutsu remove(3) poistaa listalla neljäntenä olevan merkkijonon.

Esimerkin lopussa kutsutaan ArrayList:in metodia contains, jonka avulla kysytään sisältääkö lista parametrina annetun merkkijonon. Jos lista sisältää parametrina annetun merkkijonon, palauttaa metodi contains arvon true.

Ohjelman tulostus:

opettajien lukumäärä 6
listalla ensimmäisena Juha
listalla kolmantena Markus
Juha ei ole opettajien listalla

18.2 ArrayList:in läpikäynti

Seuraavassa esimerkissä lisätään listalle 4 nimeä ja tulostetaan listan sisältö:

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

    opettajat.add("Sami");
    opettajat.add("Samu");
    opettajat.add("Tuomo L");
    opettajat.add("Tuomo K");

    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
Tuomo L
Tuomo K

Ratkaisu on kuitenkin erittäin kömpelö. Entä jos listalla olisi enemmän alkiota? Tai vähemmän? Entäs jos ei olisi edes tiedossa listalla olevien alkioiden määrää?

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

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

    opettajat.add("Sami");
    opettajat.add("Samu");
    opettajat.add("Tuomo L");
    opettajat.add("Tuomo K");

    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
}

Vanhan tutun while-komennon avulla voimme kasvataa muuttujaa paikka niin kauan kunnes se kasvaa liian suureksi:

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

    opettajat.add("Sami");
    opettajat.add("Samu");
    opettajat.add("Tuomo L");
    opettajat.add("Tuomo K");

    int paikka = 0;
    while (paikka < opettajat.size()) {  // muistatko miksi paikka <= opettajat.size() ei toimi?
        System.out.println(opettajat.get(paikka));
        paikka++;
    }
}

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

Jos halutaan käydä kaikki listan alkiot läpi yksitellen, voi While-toistolauseen lisäksi käyttää seuraavassa esiteltävää for-each -toistolauseketta.

18.3 for-each

Vaikka komennosta käytetään nimitystä for-each, komennon nimi on pelkästään for. for:ista on olemassa kaksi versiota, perinteinen (jonka nopea esittely oli jo edellisellä viikolla mutta mitä alame varsinaisesti käyttämään vasta viikolla 6) ja "for-each" jota käytämme nyt.

ArrayList:in alkioiden läpikäynti for-each:illa on helppoa:

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

    opettajat.add("Antti");
    opettajat.add("Pekka");
    opettajat.add("Juhana");
    opettajat.add("Martin");
    opettajat.add("Matti");

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

Kuten huomaame, ei listalla olevien merkkijonojen paikkanumeroista tarvitse välittää, vaan for käy listan sisällön läpi "automaattisesti".

Komennon for aaltosulkujen sisällä olevassa koodissa käytetään muuttujaa opettaja, joka on määritelty for-rivillä kaksoispisteen vasemmalla puolella. Käy niin, että kukin listalla opettajat oleva merkkijono asetetaan vuorollaan muuttujan opettaja arvoksi. Eli kun for:iin mennään, on opettaja ensin Antti, forin toisella toistolla opettaja on Pekka, jne

Vaikka for-komento voi tuntua aluksi hieman oudolta, kannattaa sen käyttöön ehdottomasti totutella!

Screencast aiheesta:

Tehtävä 64: Sanat

Tee ohjelma, joka kysyy käyttäjältä sanoja, kunnes käyttäjä antaa tyhjän merkkijonon. Sitten ohjelma tulostaa käyttäjän antamat sanat uudestaan. Kokeile tässä for-toistolauseketta. Käytä ohjelmassa ArrayList-rakennetta, joka määritellään seuraavasti:

ArrayList<String> sanat = new ArrayList<>();
Anna sana: Mozart
Anna sana: Schubert
Anna sana: Bach
Anna sana: Sibelius
Anna sana: Liszt
Anna sana:
Annoit seuraavat sanat:
Mozart
Schubert
Bach
Sibelius
Liszt

Vihje: tyhjä merkkijono voidaan havaita seuraavasti

String sana = lukija.nextLine();

if (sana.isEmpty()) {  // myös tämä tomisi: sana.equals("")
    // sana oli tyhjä eli pelkkä enterin painallus
}

Tehtävä 65: Toistuva sana

Tee ohjelma, joka kysyy käyttäjältä sanoja, kunnes käyttäjä antaa saman sanan uudestaan. Käytä ohjelmassa ArrayList-rakennetta, joka määritellään seuraavasti:

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

Kun sama sana toistuu, ilmoittaa ohjelma asiasta seuraavasti:

Anna sana: porkkana
Anna sana: selleri
Anna sana: nauris
Anna sana: lanttu
Anna sana: selleri
Annoit uudestaan sanan selleri

Vihje: Muista arraylistin metodi .contains()

18.4 Listan järjestäminen, kääntäminen ja sekoittaminen

ArrayList:n sisältö on helppo järjestää suuruusjärjestykseen. Suuruusjärjestys merkkijonojen yhteydessä tarkoittaa aakkosjärjestystä. Järjestäminen tapahtuu seuraavasti:

import java.util.ArrayList;
import java.util.Collections;

public class Ohjelma {

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

        // ...

        Collections.sort(opettajat);

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

Tulostuu:

Antti
Arto
Juhana
Martin
Matti
Pekka

Annetaan siis lista parametriksi metodille Collections.sort. Huom! Jotta Collections:in apuvälineet toimisivat, on ohjelman yläosassa oltava import java.util.Collections; tai import java.util.*;

Collections:ista löytyy muutakin hyödyllistä:

  • shuffle sekoittaa listan sisällön, metodista voi olla hyötyä esimerkiksi peleissä
  • reverse kääntää listan sisällön

Tehtävä 66: Sanat käänteisesti

Tee ohjelma, joka kysyy käyttäjältä sanoja, kunnes käyttäjä antaa tyhjän merkkijonon. Sitten ohjelma tulostaa käyttäjän antamat sanat päinvastaisessa järjestyksessä, eli viimeinen syötetty sana ensin jne.

Anna sana: Mozart
Anna sana: Schubert
Anna sana: Bach
Anna sana: Sibelius
Anna sana: Liszt
Anna sana:
Annoit seuraavat sanat:
Liszt
Sibelius
Bach
Schubert
Mozart

Tehtävä 67: Sanat aakkosjärjestyksessä

Tee edellistä tehtävää vastaava ohjelma, jossa sanat tulostetaan aakkosjärjestyksessä.

Anna sana: Mozart
Anna sana: Schubert
Anna sana: Bach
Anna sana: Sibelius
Anna sana: Liszt
Anna sana:
Annoit seuraavat sanat:
Bach
Liszt
Mozart
Schubert
Sibelius

18.5 ArrayList metodin parametrina

ArrayList:in voi antaa metodille parametrina:

public static void tulosta(ArrayList<String> lista) {
    for (String sana : lista) {
        System.out.println(sana);
    }
}

public static void main(String[] args) {
    ArrayList<String> lista = new ArrayList<>();
    lista.add("Java");
    lista.add("Python");
    lista.add("Ruby");
    lista.add("C++");

    tulosta(lista);
}

Parametrin tyyppi siis määritellään listaksi täsmälleen samalla tavalla eli ArrayList<String> kuin listamuuttujan määrittely tapahtuu.

Huomaa, että parametrin nimellä ei ole merkitystä:

public static void tulosta(ArrayList<String> tulostettava) {
    for (String sana : tulostettava) {
        System.out.println(sana);
    }
}

public static void main(String[] args) {
    ArrayList<String> ohjelmointikielet = new ArrayList<>();
    ohjelmointikielet.add("Java");
    ohjelmointikielet.add("Python");
    ohjelmointikielet.add("Ruby");
    ohjelmointikielet.add("C++");

    ArrayList<String> maat = new ArrayList<>();
    maat.add("Suomi");
    maat.add("Ruotsi");
    maat.add("Norja");

    tulosta(ohjelmointikielet);    // annetaan metodille parametriksi lista ohjelmointikielet

    tulosta(maat);                 // annetaan metodille parametriksi lista maat
}

Ohjelmassa on nyt kaksi listaa ohjelmointikielet ja maat. Metodille annetaan ensin tulostettavaksi lista ohjelmointikielet. Metodi tulosta käyttää parametriksi saamastaan listasta sisäisesti nimellä tulostettava! Seuraavaksi metodille annetaan tulostettavaksi lista maat. Jälleen metodi käyttää parametrinaan saamasta listasta sisäisesti nimeä tulostettava.

Tehtävä 68: Listan alkioiden lukumäärä

Tee metodi public static int laskeAlkiot(ArrayList<String> lista) joka palauttaa listan alkioiden määrän. Metodisi ei siis tulosta mitään vaan palauttaa return:illa alkioiden lukumäärän seuraavan esimerkin mukaisesti

ArrayList<String> lista = new ArrayList<>();
lista.add("Moi");
lista.add("Ciao");
lista.add("Hello");
System.out.println("Listalla on alkioita:");
System.out.println(laskeAlkiot(lista));
Listalla on alkioita:
3

18.6 ArrayList:in kopioituminen metodin parametriksi

Metodin sisällä on mahdollisuus vaikuttaa parametrina saadun listan sisältöön. Seuraavassa esimerkissä metodi poistaEnsimmainen nimensä mukaisesti poistaa listalla ensimmäisenä olevan merkkijonon (mitähän tapahtuu jos listalla ei ole mitään?).

public static void tulosta(ArrayList<String> tulostettava) {
    for (String sana : tulostettava) {
        System.out.println(sana);
    }
}

public static void poistaEnsimmainen(ArrayList<String> lista) {
    lista.remove(0);  // poistetaan listalta ensimmäinen eli "nollas"
}

public static void main(String[] args) {
    ArrayList<String> ohjelmointikielet = new ArrayList<>();
    ohjelmointikielet.add("Pascal");
    ohjelmointikielet.add("Java");
    ohjelmointikielet.add("Python");
    ohjelmointikielet.add("Ruby");
    ohjelmointikielet.add("C++");

    tulosta(ohjelmointikielet);

    poistaEnsimmainen(ohjelmointikielet);

    System.out.println();  // tulostetaan tyhjä rivi

    tulosta(ohjelmointikielet);
}

Tulostuu:

Pascal
Java
Python
Ruby
C++

Java
Python
Ruby
C++

Vastaavalla tavalla metodi voisi esim. lisätä parametrina saamaansa listaan lisää merkkijonoja. Palaamme tähän liittyviin taustatekijöihin hieman myöhemmin tällä viikolla.

Tehtävä 69: Poista viimeinen

Tee metodi public static void poistaViimeinen(ArrayList<String> lista) joka poistaa listalla viimeisenä olevan alkion. Tällöin esimerkiksi seuraava koodi:

ArrayList<String> tyypit = new ArrayList<>();
tyypit.add("Pekka");
tyypit.add("Mauri");
tyypit.add("Jore");
tyypit.add("Simppa");

System.out.println("Tyypit:");
System.out.println(tyypit);

// tyypit järjestykseen!
tyypit.sort();

// heitetään viimeinen mäkeen!
poistaViimeinen(tyypit);

System.out.println(tyypit);

Tulostaa:

Tyypit:
[Pekka, Mauri, Jore, Simppa]
[Jore, Mauri, Pekka]

Kuten edellisen tehtävän esimerkkitulostuksesta näemme, voi ArrayList:in tulostaa sellaisenaan. Tulostusmuoto ei kuitenkaan yleensä ole halutun kaltainen ja tulostus joudutaan hoitamaan itse esim. for-komennon avulla.

18.7 Lukuja ArrayList:issä

ArrayList:eihin voi tallettaa minkä tahansa tyyppisiä arvoja. Jos talletetaan kokonaislukuja eli int:ejä, tulee muistaa pari yksityiskohtaa. Kokonaislukuja eli int:ejä tallettava lista tulee määritellä muodossa ArrayList<Integer>, eli int:n sijaan tulee kirjoittaa Integer.

Kun listalle talletetaan int-lukuja, ei metodi remove toimi aivan odotetulla tavalla:

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

    luvut.add(2);
    luvut.add(8);
    luvut.add(12);

    // yrittää poistaa luvun listan indeksistä 2, eli ei toimi odotetulla tavalla!
    luvut.remove(2);

    // tämä poistaa listalta luvun 2
    luvut.remove(Integer.valueOf(2));
}

Eli luvut.remove(2) yrittää poistaa listalla indeksissä 2 olevan alkion ("listan kolmas alkio"). Komento toimii oikein, mutta poistaa luvun 12 listalta. Jos halutaan poistaa luku 2, täytyy käyttää hieman monimutkaisempaa muotoa: luvut.remove(Integer.valueOf(2));

Listalle voi tallettaa myös liukulukuja eli double:ja ja merkkejä eli char:eja. Tällöin listat luodaan seuraavasti (huomaa kirjoitusmuoto):

ArrayList<Double> doublet = new ArrayList<>();
ArrayList<Character> merkit = new ArrayList<>();

Tehtävä 70: Lukujen summa

Tee metodi summa, joka laskee parametrinaan saamansa kokonaislukuja sisältävän, eli tyyppiä ArrayList<Integer> olevan listan summan.

Tee metodi seuraavaan runkoon:

public static int summa(ArrayList<Integer> lista) {
    // kirjoita koodia tähän
}

public static void main(String[] args) {
    ArrayList<Integer> lista = new ArrayList<>();
    lista.add(3);
    lista.add(2);
    lista.add(7);
    lista.add(2);

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

    lista.add(10);

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

Ohjelman tulostus:

Summa: 14
Summa: 24

Tehtävä 71: Lukujen keskiarvo

Tee metodi keskiarvo, joka laskee parametrinaan saamansa kokonaislukuja sisältävän listan lukujen keskiarvon. Metodin on laskettava parametriensa summa käyttäen apuna edellisen tehtävän metodia summa.

Tee metodi seuraavaan runkoon:

public static double keskiarvo(ArrayList<Integer> lista) {
    // kirjoita koodia tähän
}

public static void main(String[] args) {
    ArrayList<Integer> lista = new ArrayList<>();
    lista.add(3);
    lista.add(2);
    lista.add(7);
    lista.add(2);

    System.out.println("Keskiarvo: " + keskiarvo(lista));
}

Ohjelman tulostus:

Keskiarvo: 3.5

Tehtävä 72: Suurin

Tee metodi suurin, joka palauttaa parametrina saamansa kokonaislukuja sisältävän listan suurimman luvun.

public static int suurin(ArrayList<Integer> lista) {
    // kirjoita koodia tähän
}

public static void main(String[] args) {
    ArrayList<Integer> lista = new ArrayList<>();
    lista.add(3);
    lista.add(2);
    lista.add(7);
    lista.add(2);

    System.out.println("Suurin: " + suurin(lista));
}

Ohjelman tulostus:

Suurin: 7

Tehtävä 73: Varianssi

Tee metodi varianssi, joka laskee ja palauttaa saamansa kokonaislukuja sisältävän listan otosvarianssin. Ohjeen varianssin laskemiseksi voit katsoa esimerkiksi tehtävän lopusta tai Wikipediasta kohdasta populaatio- ja otosvarianssi. Jos sana varianssi kuulostaa pelottavalta, voit toki kysyä apua myös pajassa.

Tee metodi käyttäen apuna aiemmissa tehtävissä tehtyä metodia keskiarvo. Huom! Kutsu metodia kuitenkin vain kertaalleen yhden varianssin laskemisen aikana.

public static double varianssi(ArrayList<Integer> lista) {
    // kirjoita koodia tähän
}

public static void main(String[] args) {
    ArrayList<Integer> lista = new ArrayList<>();
    lista.add(3);
    lista.add(2);
    lista.add(7);
    lista.add(2);

    System.out.println("Varianssi: " + varianssi(lista));
}

Ohjelman tulostus:

Varianssi: 5.666667

(Lukujen keskiarvo on 3.5, joten otosvarianssi on ((3 - 3.5)² + (2 - 3.5)² + (7 - 3.5)² + (2 - 3.5)²)/(4 - 1) ˜ 5,666667.)

Kun listassa on N kappaletta lukuja voidaan otosvarianssi lasketaan seuraavasti:

((luku1 - keskiarvo)² + (luku2 - keskiarvo)² + ... + (lukuN - keskiarvo)²) / (N - 1)

Huom! Muistathan kokeillessasi ohjelmaa, että yhden alkion kokoisen listan (otos)varianssia ei ole määritelty! Kaavassa tapahtuu tällöin nollalla jakaminen. Java esittää nollalla jakamisen tuloksen epänumerona NaN

19 Ohjeita koodin kirjoittamiseen ja ongelmanratkaisuun

Kaksi maailman johtavista ohjelmistonkehittäjistä, Martin Fowler ja Kent Beck, ovat lausuneet kirjassa Refactoring: Improving the Design of Existing Codeseuraavasti seuraavasti:

  • Fowler: "Any fool can write code that a computer can understand. Good programmers write code that humans can understand."
  • Beck: "I'm not a great programmer, I'm just a good programmer with great habits."

[Päivitys: aiemmin molemmat lainaukset olivat merkitty Kent Beckille. Kiitos Esko Luontolalle tämän virheen ilmoittamisesta.]

Molemmat käytännössä korostavat ohjelmakoodin kirjoitusta siten, että muutkin ymmärtävät mistä siinä on kyse. Otamme lisää askelia Fowlerin ja Beckin viitoittamalla tiellä.

19.1 Oikein sisennetty ja "hengittävä" koodi

Tarkastellaan koodia joka ensin lisää listalle lukuja ja tulostaa listan sisällön. Tämän jälkeen listalta poistetaan kaikki tietyn luvun esiintymät ja tulostetaan lista uudelleen.

Kirjoitetaan koodi ensin huonosti ja jätetään se sisentämättä:

public static void main(String[] args) {
ArrayList<Integer> luvut = new ArrayList<>();
luvut.add(4);
luvut.add(3);
luvut.add(7);
luvut.add(3);
System.out.println("luvut alussa:");

for (int luku : luvut) {
System.out.println(luku);
}

while (luvut.contains(3)) {
luvut.remove(Integer.valueOf(3));
}

System.out.println("luvut poiston jälkeen:");

for (int luku : luvut) {
System.out.println(luku);
}
}

Vaikka sisentämätön koodi toimii, on sitä hyvin ikävä lukea. Sisennetään koodi oikein (NetBeansissa sisennyksen saa korjattua automaattisesti painamalla alt+shift+f), ja erotellaan loogiset kokonaisuudet rivinvaihdoin:

public static void main(String[] args) {
    ArrayList<Integer> luvut = new ArrayList<>();
    luvut.add(4);
    luvut.add(3);
    luvut.add(7);
    luvut.add(3);

    System.out.println("luvut alussa:");

    // tässä tulostetaan luvut
    for (int luku : luvut) {
        System.out.println(luku);
    }

    // tarkastetaan onko listalla luku 3
    while (luvut.contains(3)) {
        luvut.remove(Integer.valueOf(3));  // jos löytyi, niin poistetaan se
    }
    // tehdään tämä whilessä jotta saadaan kaikki kolmoset poistetua!

    System.out.println("luvut poiston jälkeen:");

    // tässä tulostetaan luvut
    for (int luku : luvut) {
        System.out.println(luku);
    }
}

Nyt koodissa alkaa olla jo järkeä. Esimerkiksi tulostus ja kolmosten poisto ovat omia loogisia kokonaisuuksia, joten ne on erotettu rivinvaihdolla. Koodissa on ilmavuutta ja koodin lukeminen alkaa olla miellyttävämpää.

Koodiin on vieläpä kirjoitettu kommentteja selventämään muutaman kohdan toimintaa.

19.2 Copy-pasten eliminointi metodeilla

Ohjelmoijan lähes pahin mahdollinen perisynti on copy-paste -koodi, eli samanlaisen koodinpätkän toistaminen koodissa useaan kertaan. Esimerkissämme listan tulostus tapahtuu kahteen kertaan. Tulostuksen hoitava koodi on syytä erottaa omaksi metodikseen ja kutsua uutta metodia pääohjelmasta:

public static void main(String[] args) {
    ArrayList<Integer> luvut = new ArrayList<>();
    luvut.add(4);
    luvut.add(3);
    luvut.add(7);
    luvut.add(3);

    System.out.println("luvut alussa:");

    // tässä tulostetaan luvut
    tulosta(luvut);

    while (luvut.contains(3)) {
      luvut.remove(Integer.valueOf(3));
    }

    System.out.println("luvut poiston jälkeen:");

    // tässä tulostetaan luvut
    tulosta(luvut);
}

public static void tulosta(ArrayList<Integer> luvut) {
    for (int luku : luvut) {
        System.out.println(luku);
    }
}

19.3 Erillisten tehtävien erottaminen omiksi, selkeästi nimetyiksi metodeiksi

Koodi alkaa olla jo selkeämpää. Selvästi erillinen kokonaisuus, eli listan tulostus on oma helposti ymmärrettävä metodinsa. Uuden metodin esittelyn myötä myös pääohjelman luettavuus on kasvanut. Huomaa että uusi metodi on nimetty mahdollisimman kuvaavasti, eli siten että metodin nimi kertoo mitä metodi tekee. Ohjelmaan kirjoitetut kommentit tässä tulostetaan luvut ovatkin tarpeettomia, joten poistetaan ne.

Ohjelmassa on vielä hiukan siistimisen varaa. Pääohjelma on vielä sikäli ikävä, että siistien metodikutsujen seassa on vielä suoraan listaa käsittelevä "epäesteettinen" koodinpätkä. Erotetaan tämäkin omaksi metodikseen:

public static void main(String[] args) {
    ArrayList<Integer> luvut = new ArrayList<>();
    luvut.add(4);
    luvut.add(3);
    luvut.add(7);
    luvut.add(3);

    System.out.println("luvut alussa:");
    tulosta(luvut);

    poista(luvut, 3);

    System.out.println("luvut poiston jälkeen:");
    tulosta(luvut);
}

public static void tulosta(ArrayList<Integer> luvut) {
    for (int luku : luvut) {
        System.out.println(luku);
    }
}

public static void poista(ArrayList<Integer> luvut, int poistettava) {
    while (luvut.contains(poistettava)) {
        luvut.remove(Integer.valueOf(poistettava));
    }
}

Loimme yllä loogiselle kokonaisuudelle -- tietyn luvun kaikkien esiintymien poistolle -- oman kuvaavasti nimetyn metodin. Lopputuloksena oleva pääohjelma on nyt erittäin ymmärrettävä, lähes suomen kieltä. Molemmat metodit ovat myös erittäin yksinkertaisia ja selkeitä ymmärtää.

Kent Beck olisi varmaan tyytyväinen aikaansaannokseemme, koodi on helposti ymmärrettävää, helposti muokattavaa eikä sisällä copy-pastea.

20 Totuusarvojen käyttö

Totuusarvoinen eli boolean-muuttuja voi saada vain kaksi arvoa true tai false. Seuraavassa esimerkki booleanin käytöstä:

int luku1 = 1;
int luku2 = 5;

boolean ekaSuurempi = true;

if (luku1 <= luku2) {
    ekaSuurempi = false;
}

if (ekaSuurempi==true) {
    System.out.println("luku1 suurempi");
} else {
    System.out.println("luku1 ei ollut suurempi");
}

Eli ensin asetetaan totuusarvon ekaSuurempi arvoksi tosi eli true. Ensimmäinen if tarkastaa onko luku1 pienempi tai yhtä pieni kuin luku2. Jos näin on, vaihdetaan totuusarvon arvoksi epätosi eli false. Myöhempi if valitsee tulostuksen totuusarvoon perustuen.

Totuusarvon käyttö ehtolauseessa on itseasiassa edellistä esimerkkiä yksinkertaisempaa, jälkimmäinen if voidaan kirjoittaa seuraavasti:

if (ekaSuurempi) {  // tarkoittaa samaa kuin ekaSuurempi==true
    System.out.println("luku1 suurempi");
} else {
    System.out.println("luku1 ei ollut suurempi");
}

Eli jos halutaan tarkistaa että booleanmuuttujan arvo on tosi, eli ole tarvetta kirjoittaa ==true, pelkkä muuttujan nimi riittää!

Epätoden tarkastaminen onnistuu negaatio-operaation eli huutomerkin avulla:

if (!ekaSuurempi) {  // tarkoittaa samaa kuin ekaSuurempi==false
    System.out.println("luku1 ei ollut suurempi");
} else {
    System.out.println("luku1 suurempi");
}

20.1 totuusarvon palauttava metodi

Totuusarvot ovat erityisen käteviä jonkun asian voimassaolon tarkistavien metodien paluuarvoina. Tehdään metodi joka tarkastaa sisältääkö sen parametrina saama lista ainoastaan positiivisia lukuja (tulkitaan 0 positiiviseksi). Tieto positiivisuudesta palautetaan totuusarvona.

public static boolean kaikkiPositiivisia(ArrayList<Integer> luvut) {
    boolean eiNegatiivisia = true;

    for (int luku : luvut) {
        if (luku < 0) {
            eiNegatiivisia = false;
        }
    }
    // jos jonkun listan luvuista arvo oli pienempi kuin 0, on eiNegatiivisia nyt false
    return eiNegatiivisia;
}

Metodilla on totuusarvoinen apumuuttuja eiNegatiivisia. Apumuuttujan arvoksi asetetaan ensin true. Metodi käy läpi kaikki listan luvut. Jos jonkun (siis vähintään yhden) luvun arvo on pienempi kuin nolla, asetetaan apumuuttujan arvoksi false. Lopuksi palautetaan apumuuttujan arvo. Apumuuttuja on edelleen true jos yhtään negatiivista lukua ei löytynyt, muuten false.

Metodia käytetään seuraavasti:

public static void main(String[] args) {

    ArrayList<Integer> luvut = new ArrayList<>();
    luvut.add(3);
    luvut.add(1);
    luvut.add(-1);

    boolean vastaus = kaikkiPositiivisia(luvut);

    if (vastaus) {  // tarkoittaa siis samaa kuin vastaus == true
        System.out.println("luvut positiivisia");
    } else {
        System.out.println("joukossa oli ainakin yksi negatiivinen");
    }
}

Vastauksen tallettaminen ensin muuttujaan ei yleensä ole tarpeen, ja metodikutsu voidaan kirjottaa suoraan ehdoksi:

ArrayList<Integer> luvut = new ArrayList<>();
luvut.add(4);
luvut.add(7);
luvut.add(12);
luvut.add(9);

if (kaikkiPositiivisia(luvut)) {
    System.out.println("luvut positiivisia");
} else {
    System.out.println("joukossa oli ainakin yksi negatiivinen");
}

20.2 Komento return ja metodin lopetus

Metodin suoritus loppuu välittömästi kun metodissa suoritetaan return-käsky. Käyttämällä tätä tietoa hyväksi voimme kirjoittaa kaikkiPositiivisia-metodin hiukan suoraviivaisemmin ja selkeämmin.

public static boolean kaikkiPositiivisia(ArrayList<Integer> luvut) {
    for (int luku : luvut) {
        if (luku < 0) {
            return false;
        }
    }

    // jos tultiin tänne asti, ei yhtään negatiivista löytynyt
    // siispä palautetaan true
    return true;
    }
}

Eli jos lukujen listaa läpikäydessä törmätään negatiiviseen lukuun, voidaan metodista poistua heti palauttamalla false. Jos listalla ei ole yhtään negatiivista, päädytään loppuun ja voidaan palauttaa true. Olemme päässeet metodissa kokonaan eroon apumuuttujan käytöstä!

Tehtävä 74: Onko luku listalla monta kertaa

Tee metodi onkoListallaUseasti, joka saa parametrinaan kokonaislukuja sisältävän listan ja int-luvun. Jos luku esiintyy listalla yli yhden kerran, metodi palauttaa true ja muulloin false.

Ohjelman rakenne on seuraava:

public static boolean onkoListallaUseasti(ArrayList<Integer> lista, int luku) {
    // kirjoita koodia tähän
}

public static void main(String[] args) {
    ArrayList<Integer> lista = new ArrayList<>();
    lista.add(3);
    lista.add(2);
    lista.add(7);
    lista.add(2);

    System.out.println("Anna luku: ");
    int luku = Integer.parseInt(lukija.nextLine());
    if (onkoListallaUseasti(luvut, luku)) {
        System.out.println(luku + " on listalla useasti.");
    } else {
        System.out.println(luku + " ei ole listalla useasti.");
    }
}
  Anna luku: 2
  Luku on on listalla useasti.
  Anna luku: 3
  Luku ei ole listalla useasti.

Tehtävä 75: Palindromi

Tee metodi palindromi, joka kertoo, onko merkkijono palindromi (merkkijonon sisältö on sama alusta loppuun ja lopusta alkuun luettuna).

Metodi voi käyttää apuna metodia kaanna tehtävästä 63. Metodin tyyppi on boolean, joten se pa lauttaa jokoarvon true (merkkijono on palindromi) tai false (merkkijono ei ole palindromi).

public static boolean palindromi(String merkkijono) {
    // kirjoita koodia tähän
}

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

    System.out.println("Anna merkkijono: ");
    String merkkijono = lukija.nextLine();
    if (palindromi(merkkijono)) {
        System.out.println("Merkkijono on palindromi!");
    } else {
        System.out.println("Merkkijono ei ole palindromi!");
    }
}

Ohjelman tulostuksia:

Anna merkkijono: saippuakauppias
Merkkijono on palindromi!
Anna merkkijono: esimerkki
Merkkijono ei ole palindromi!

20.3 Komento Continue: "Portsari"

Ohjelmoijalla voi tulee silloin tällöin toistolausekkeissa vastaan poikkeustapauksia, joiden käsittelyn haluaa jättää väliin. Tähän asti olemme ratkoneet poikkeustapaukset if-else -komennoilla. Tehdään ohjelma, joka lukee näppäimistöltä sanoja, ja tallentaa ne listalle. Jos sana on "lopeta", ohjelma lopettaa suorituksen. Listalle ei myöskään lisätä sanoja, joissa ei ole mitään, eli ne ovat tyhjiä. Yksi tapa toteuttaa ohjelma on seuraava.

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

public class SanojenLukija {

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

        while (true) {
            String sana = lukija.nextLine();
            if (sana.equals("lopeta")) {
                break;
            }

            if (!sana.isEmpty()) {
                sanat.add(sana);
            }
        }

        System.out.println("Kiitos!");
    }
}

Yllä olevassa lähestymistavassa ehto "jos sana ei ole tyhjä" on erikoistapaus, jonka sisältämä koodi suoritetaan vain jos tapaus toteutuu.

Toinen tapa on käyttää komentoa continue, jonka avulla saamme koodin suorituksen alkamaan uudestaan toistolauseen alusta (for-toistolauseen tapauksessa for-ehto suoritetaan myös, eli esimerkiksi muuttujaan haetaan uusi arvo). Voimme muokata ylläolevaa koodia siten, että sanan tyhjyys on erikoistapaus, jonka tapauksessa palaamme takaisin koodin alkuun komennolla "continue". Muutoin koodin suoritus jatkuu, ja sana lisätään tyhjälle listalle.

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

public class SanojenLukija {

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

        while (true) {
            String sana = lukija.nextLine();
            if (sana.equals("lopeta")) {
                break;
            }

            // portsari: jos sana on tyhjä, jatkeataan toistolauseen alusta
            if (sana.isEmpty()) {
                continue;
            }

            sanat.add(sana);
        }

        System.out.println("Kiitos!");
    }
}

Komennon continue avulla ohjelmakoodista voi saada selkeämpää siten, että oleellinen koodi pysyy samalla tasolla muun koodin kanssa.

20.4 ArrayListin kopioiminen

Joskus on tarpeen muodostaa ArrayLististä kopio, johon voi tehdä muutoksia vaikuttamatta alkuperäisen ArrayListin sisältöön. Kopion voi muodostaa luomalla uuden ArrayListin käyttäen vanhaa ArrayListiä parametrina:

ArrayList<String> nimet = new ArrayList<>();
nimet.add("Kyösti");
nimet.add("Risto");
nimet.add("Carl");
nimet.add("Urho");

//luodaan kopio nimet-listasta
ArrayList<String> kopio = new ArrayList<>(nimet);
//järjestetään kopio
Collections.sort(kopio);

System.out.println(kopio);  //tulostuu [Carl, Kyösti, Risto, Urho]
System.out.println(nimet);  //tulostuu [Kyösti, Risto, Carl, Urho]

Tehtävä 76: KaikkiEri

Tee metodi kaikkiEri, joka palauttaa true jos sen parametrina saamassa kokonaislukuja sisältävässä listassa olevat luvut ovat kaikki erisuuruisia. Metodi ei saa muuttaa listan sisältöä.

Seuraavassa kaksi esimerkkiä metodin toiminnasta:

public static void main(String[] args) {
    ArrayList<Integer> lista1 = new ArrayList<>();
    lista1.add(3);
    lista1.add(7);
    lista1.add(1);

    boolean eri = kaikkiEri(lista1);
    // muuttujan eri arvo true

    ArrayList<Integer> lista2 = new ArrayList<>();
    lista2.add(2);
    lista2.add(3);
    lista2.add(7);
    lista2.add(1);
    lista2.add(3);
    lista2.add(99);

    eri = kaikkiEri(lista2);
    // muuttujan eri arvo false sillä luku 3 on listalla kahteen kertaan
}

21 Metodit ja parametrien kopioituminen

Tarkastellaan muutamaa metodeihin liittyvää tärkeää yksityiskohtaa.

Edellisen viikon alussa oli esimerkki, jossa yritettiin muuttaa pääohjelmassa olevan muuttujan arvoa metodin sisällä.

public static void main(String[] args) {
    int luku = 1;
    kasvataKolmella();
    System.out.println("luku on " + luku);
}

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

Ohjelma ei toimi, sillä metodi ei pääse käsiksi pääohjelman muuttujaan luku.

Tämä johtuu siitä, että pääohjelman muuttujat eivät näy metodien sisään. Ja yleisemmin: minkään metodin muuttujat eivät näy muille metodeille. Koska pääohjelma main on myös metodi, pätee sääntö myös pääohjelmalle. Ainoa keino viedä metodille tietoa ulkopuolelta on parametrin avulla.

Yritetään korjata edellinen esimerkki välittämällä pääohjelman muuttuja luku parametrina metodille.

public static void main(String[] args) {
    int luku = 1;
    kasvataKolmella(luku);
    System.out.println(luku);  // tulostaa 1, eli arvo luku ei muuttunut
}

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

Ohjelma ei toimi toivotulla tavalla. Metodissa olevat parametrit ovat eri muuttujia kuin pääohjelmassa esitellyt muuttujat. Edellä metodi siis kasvattaa samannimistä, mutta ei samaa parametria luku.

Kun metodille annetaan parametri, parametrin arvo kopioidaan uuteen muuttujaan metodissa käytettäväksi. Yllä olevassa esimerkissä metodille kasvataKolmella annetusta muuttujasta luku luodaan kopio, jota metodin sisällä lopulta käsitellään. Metodi käsittelee siis pääohjelmassa olevan muuttujan kopiota, ei alkuperäistä muuttujaa -- pääohjelmametodissa olevalle muuttujalle luku ei tehdä mitään.

Voidaan ajatella, että pääohjelmametodi main ja metodi kasvataKolmella toimivat kumpikin omassa kohtaa tietokoneen muistia. Allaolevassa kuvassa on main:in muuttujaa luku varten oma "lokero". Kun metodia kutsutaan, tehdään tälle oma muuttuja luku jonka arvoksi kopioituu main:in luku-muuttujan arvo eli 1. Molemmat luku-nimiset muuttujat ovat kuitenkin täysin erillisiä, eli kun metodissa kasvataKolmella muutetaan sen luku-muuttujan arvoa, ei muutos vaikuta millään tavalla pääohjelman muuttujaan luku.

Allaoleva kuva antaa lisävalaisua tilanteeseen.

Metodista saa toki välitettyä tietoa kutsujalle käyttäen paluuarvoa, eli palauttamalla arvon return-komennolla. Edellinen saadaan toimimaan muuttamalla koodia hiukan:

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

    System.out.println(luku);  // tulostaa 4, sillä luku on saanut arvokseen metodin palauttaman arvon
}

public static int kasvataKolmellaJaPalauta(int luku) {
    luku = luku + 3;

    return luku;
}

Edelleen on niin, että metodi käsittelee pääohjelman luku-muuttujan arvon kopiota. Pääohjelmassa metodin palauttama arvo sijoitetaan muuttujaan luku, joten muutos tulee tämän takia voimaan myös pääohjelmassa. Huomaa, että edellisessä ei ole mitään merkitystä sillä, mikä nimi metodin parametrilla on. Koodi toimii täysin samoin oli nimi mikä tahansa, esim.

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

    System.out.println(luku);
}

public static int kasvataKolmellaJaPalauta(int kasvatettavaLuku) {
    kasvatettavaLuku = kasvatettavaLuku + 3;

    return kasvatettavaLuku;
}

Huomasimme että metodissa olevat parametrit ovat eri muuttujia kuin metodin kutsujassa esitellyt muuttujat. Ainoastaan parametrin arvo kopioituu kutsujasta metodiin.

Asia ei kuitenkaan ole ihan näin yksinkertainen. Jos metodille annetaan parametrina ArrayList, käy niin että sama lista näkyy metodille ja kaikki metodin listalle tekemät muutokset tulevat kaikkialla voimaan.

public static void poistaAlussaOleva(ArrayList<Integer> lista) {
    lista.remove(0); // poistaa paikassa 0 olevan luvun
}
ArrayList<Integer> luvut = new ArrayList<>();
luvut.add(4);
luvut.add(3);
luvut.add(7);
luvut.add(3);

System.out.println(luvut); // tulostuu [4,3,7,3]

poistaAlussaOleva(luvut);

System.out.println(luvut); // tulostuu [3,7,3]

Toisin kuin int-tyyppinen parametri, lista ei kopioidu vaan metodi käsittelee suoraan parametrina annettua listaa.

Tilannetta valaisee allaoleva kuva. Toisin kuin int-tyyppinen muuttuja, ArrayList ei sijaitsekaan samalla tapaa "lokerossa", vaan muuttujan nimi, eli mainin tapauksessa luvut onkin ainoastaan viite paikkaan missä ArrayList sijaitsee. Yksi tapa ajatella asiaa, on että ArrayList on "langan päässä", eli listan nimi luvut on lanka jonka toisesta päästä lista löytyy. Kun metodikutsun parametrina on ArrayList, käykin niin että metodille annetaan "lanka" jonka päässä on sama lista jonka metodin kutsuja näkee. Eli main:illa ja metodilla on kyllä molemmilla oma lanka, mutta langan päässä on sama lista ja kaikki muutokset mitä metodi tekee listaan tapahtuvat täsmälleen samaan listaan jota pääohjelma käyttää. Tästä viikosta alkaen tulemme huomaamaan että Java:ssa hyvin moni asia on "langan päässä".

Huomaa jälleen että parametrin nimi metodin sisällä voi olla aivan vapaasti valittu, nimen ei tarvitse missään tapauksessa olla sama kuin kutsuvassa metodissa oleva nimi. Edellä listaa kutsutaan metodin sisällä nimellä lista, metodin kutsuja taas näkee saman listan luvut-nimisenä.

Miksi int-parametrista ainoastaan arvo kopioituu metodille mutta parametrin ollessa ArrayList metodi käsittelee suoraan listan sisältöä? Javassa ainoastaan alkeistietotyyppisten eli tyyppien int, double, char, boolean (ja muutamien muiden joita emme ole käsitelleet) arvot kopioidaan metodille. Muun tyyppisten parametrien tapauksessa metodille kopioidaan viite, ja metodista käsitellään viitteen takana olevaa parametria suoraan. Ei-alkeistyyppiset muuttujat -- eli viittaustyyppiset muuttujat ovat siis edellisen kuvan tapaan "langan päässä" -- metodille välitetään lanka parametriin, ja näin metodi käsittelee parametria suoraan.

Tehtävä 77: Yhdistelya

Harjoitellaan vielä hieman ArrayList-joukkojen yhdistelyä.

77.1 ArrayListien yhdistäminen

Toteuta metodi public static void yhdista(ArrayList<Integer> eka, ArrayList<Integer> toka), joka lisää toisena parametrina toka olevassa ArrayListissa olevat luvut ensimmäisenä parametrina olevaan ArrayList:iin eka. Alkioiden talletusjärjestyksellä ei ole väliä, ja sama alkio voi päätyä listalle useamman kerran. Esimerkki metodin toiminnasta:

ArrayList<Integer> lista1= new ArrayList<>();
ArrayList<Integer> lista2= new ArrayList<>();

lista1.add(4);
lista1.add(3);

lista2.add(5);
lista2.add(10);
lista2.add(7);

yhdista(lista1, lista2);

System.out.println(lista1); // tulostuu [4, 3, 5, 10, 7]

System.out.println(lista2); // tulostuu [5, 10, 7]
  

Listalle voi lisätä toisen listan sisällön ArrayList-luokan tarjoaman addAll-metodin avulla. Lista saa parametrinä toisen listan, jonka alkiot listalle lisätään.

77.2 Joukkoyhdistäminen

Toteuta metodi joukkoYhdista joka toimii muuten samoin kuin edellisen tehtävän yhdista-metodi, mutta parantele sitä niin, että yhdistäminen lisää listaan eka lukuja vain, jos ne eivät jo ennestään löydy listalta. Tehtävässä kannattaa käyttää hyväkseen ArrayListin contains-metodia, jolla voit tarkistaa sisältääkö lista jo jonkin luvun.

Tehtävä 78: Fotari

Käytössämme on kuvankäsittelyohjelma nimeltä Fotari, joka tarjoaa toiminnallisuuden ladattavan kuvan vaalentamiseen. Fotaria käytetään komentoriviltä seuraavasti:

Mikä kuva avataan? puu.jpg
komento (lopeta, vaalenna)?

Komento avaa ikkunan, jossa käsiteltävä kuva näkyy.

Komento vaalenna vaalentaa kuvaa. Esimerkiksi allaoleva käyttötapaus luo seuraavanlaisen kuvan.

Mikä kuva avataan? puu.jpg
komento (lopeta, vaalenna)? vaalenna
komento (lopeta, vaalenna)? vaalenna
komento (lopeta, vaalenna)?

Tässä tehtävässä Fotariin lisätään lisää toiminnallisuutta. Käytössäsi ovat kuvat puu.jpg, ilta.jpg ja kukka.jpg, sekä niistä pienemmät versiot puu-small.jpg, ilta-small.jpg ja kukka-small.jpg. Näiden lisäksi käytössä on myös pörhyliäinen kissa fluffy.jpg. Älä poista kuvia tehtäväpohjasta!

Hieman esitietoa: Tietokoneelle tallennetut kuvat koostuvat pikseleistä, jotka ovat käytännössä pienimpiä ruudulla näkyviä pisteitä. Jokainen pikseli sisältää tietyn määrän punaista, vihreää, ja sinistä väriä, joista pikselin väri koostuu. Punaisen, vihreän, ja sinisen määrä on luku, joka on väliltä 0-255.

Huom! Voit palauttaa tehtävän jokaisen välikohdan jälkeen myös palvelimelle, jolloin voit saada osan tehtäväpisteistä.

78.1 Kuvan tummentaminen

Lisää käyttöliittymään komento tummenna, jonka avulla avattua kuvaa tummennetaan. Toteuta tummennustoiminnallisuus uuteen metodiin public static void tummenna() ja ota sen toteutukseen mallia komennosta vaalenna. Yhden tummennuskomennon pitää vähentää jokaista väriarvoa kolmellakymmenellä, mutta kuitenkin siten, että yksikään väriarvo ei mene alle nollan.

Mikä kuva avataan? puu.jpg
komento (lopeta, vaalenna, tummenna)? tummenna
komento (lopeta, vaalenna, tummenna)?

Yllä olevalla komentosarjalla avattava kuva tummenee, ja näyttää seuraavankaltaiselta.

78.2 Negatiivi

Lisää käyttöliittymään komento negatiivi, jonka avulla avatusta kuvasta luodaan negatiivi. Negatiivi luodaan asettamalla jokaiseen pikseliin väriarvoon luvun 255 ja aiemman väriarvon erotus. Toteuta negatiivitoiminnallisuus uuteen metodiin public static void negatiivi().

Mikä kuva avataan? puu.jpg
komento (lopeta, vaalenna, tummenna, negatiivi)? negatiivi
komento (lopeta, vaalenna, tummenna, negatiivi)?

Yllä olevalla komentosarjalla avattavasta kuvasta luodaan negatiivi, joka näyttää seuraavankaltaiselta.

78.3 Peilaus

Lisää käyttöliittymään komento peilaa, jonka avulla avatusta kuvasta luodaan osittainen peilikuva. Osittainen peilikuva luodaan kopioimalla oikean laidan pikseleitä vasemmalle siten, että x-koordinaattiin 0 tulee koordinaatissa kuvan leveys - 1 olevat väriarvot, x-koordinaattiin 1 tulee koordinaatissa kuvan leveys - 2 olevat väriarvot, x-koordinaattiin 2 tulee koordinaatissa kuvan leveys - 3 olevat väriarvot jne. Peilaaminen siis kopioi kuvan oikean laidan sisällön kuvan vasemmalle laidalle, mutta ei kopioi vasenta laitaa oikealle laidalle.

Toteuta peilaustoiminnallisuus uuteen metodiin public static void peilaa().

Mikä kuva avataan? puu.jpg
komento (lopeta, vaalenna, tummenna, negatiivi, peilaa)? peilaa
komento (lopeta, vaalenna, tummenna, negatiivi, peilaa)?

Yllä olevalla komentosarjalla avattavasta kuvasta luodaan osittain peilattu kuva, joka näyttää seuraavankaltaiselta.

78.4 Andy Warhol

Lisää käyttöliittymään komento andywarhol, jonka avulla kuvasta luodaan itse toivomasi versio. Voit esimerkiksi sumentaa kuvaa, pilkkoa sitä pienempiin palasiin, muunnella sen värejä tai kaikkea näitä yhdessä. Toteuta metodi public static void andywarhol();, sekä käyttöliittymän toiminnallisuus.

Mikä kuva avataan? puu.jpg
komento (lopeta, vaalenna, tummenna, negatiivi, peilaa, andywarhol)? andywarhol
komento (lopeta, vaalenna, tummenna, negatiivi, peilaa, andywarhol)?

Yllä on eräs kuva, mikä on tehty Fotarilla. Voit hyödyntää tässä myös komentoa Fotari.punaiset(x, y, etaisyys) ym., missä etaisyys-parametrille voi antaa arvoksi käyttäjän syöttämän arvon.