Tämä materiaali on lisensoitu Creative Commons BY-NC-SA-lisenssillä, joten voit käyttää ja levittää sitä vapaasti, kunhan alkuperäisten tekijöiden nimiä ei poisteta. Jos teet muutoksia materiaaliin ja haluat levittää muunneltua versiota, se täytyy lisensoida samanlaisella vapaalla lisenssillä. Materiaalien käyttö kaupalliseen tarkoitukseen on ilman erillistä lupaa kielletty.
Tekijät: Arto Vihavainen ja Matti Luukkainen
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.
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ä .
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!
Tutustutaan uudestaan ensimmäisellä viikolla FizzBuzz-ongelmaan, mutta tehdään tällä kertaa versio, missä on toistolause mukana.
Ratkaistaan ongelma osissa siten, että jokaisen osan tuotos on ohjelma, jonka toimivuutta voi testata. Toteutetaan osat seuraavasti:
Jos mietit "Miksi kolmella ja viidellä jaolliset luvut tehdään ensin?", tutustu kurssimateriaalin ensimmäisen viikon lukuun 6.9 uudestaan.
Tehdään ensin ohjelma, joka kysyy käyttäjältä syötettä. Syötteen lukeminen onnistuu Scanner
-apuvälineen avulla. 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 luetun luvun tulostus. Tämän avulla lukemisen toimintaa voi testata.
System.out.println("Syötä positiivinen luku: "); int luku = Integer.parseInt(lukija.nextLine()); System.out.println(luku);
Ohjelman 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
Lisätään seuraavaksi ohjelmaan osa, joka tarkistaa, että luetuksi luvuksi hyväksytään vain positiivinen luku.
Eräs tapa saada käyttäjä syöttämään positiivinen luku on kysyä lukua uudestaan kunnes luku on positiivinen.
Kysytään lukua käyttäjältä. Niin pitkään kuin käyttäjän syöttämä luku on negatiivinen, kysytään lukua uudestaan. Tätä voi jatkaa ikuisesti tai ainakin siihen asti, että 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 kunnes ehto luku < 0
ei ole enää totta toistolauseen lopussa. Kun luku on suurempi tai yhtäsuuri kuin nolla ja toistolauseen suoritus on lopussa, ehto ei ole enää totta, ja suoritus jatkuu toistolausetta seuraavalta riviltä.
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
Muutetaan tulostusta siten, että jos syötetty luku on kolmella ja viidellä jaollinen, tulostetaan "FizzBuzz". Muuten tulostetaan syötetty 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()); } if (luku % 3 == 0 && luku % 5 == 0) { System.out.println("FizzBuzz"); } else { System.out.println(luku); }
Syötä positiivinen luku: 3 3
Syötä positiivinen luku: 15 FizzBuzz
Lisätään tulostuksen tarkistuksessa käytettävään ehtolauseeseen "else if" -ehto, jolla tarkistetaan onko luku jaollinen viidellä. Jos luku on jaollinen viidellä, tulostetaan merkkijono "Buzz".
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()); } if (luku % 3 == 0 && luku % 5 == 0) { System.out.println("FizzBuzz"); } else if (luku % 5 == 0) { System.out.println("Buzz"); } else { System.out.println(luku); }
Syötä positiivinen luku: 5 Buzz
Syötä positiivinen luku: 10 Buzz
Lisätään tulostuksen tarkistuksessa käytettävään ehtolauseeseen "else if" -ehto, jolla tarkistetaan onko luku jaollinen kolmella. Jos luku on jaollinen kolmella, tulostetaan merkkijono "Fizz".
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()); } if (luku % 3 == 0 && luku % 5 == 0) { System.out.println("FizzBuzz"); } else if (luku % 5 == 0) { System.out.println("Buzz"); } else if (luku % 3 == 0) { System.out.println("Fizz"); } else { System.out.println(luku); }
Syötä positiivinen luku: 3 Fizz
Syötä positiivinen luku: 5 Buzz
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.
int tulostettava = 1; while (tulostettava <= luku) { // tee jotain tulostettava++; }
Lisätään toistolauseeseen aiemmin rakentamamme ehtolause, minkä perusteella tulostetaan "FizzBuzz", "Fizz", "Buzz" tai luku. Joudumme muokkaamaan ehtolausetta siten, että käytämme muuttujaa "tulostettava" aiemmin käytetyn muuttujan "luku" sijaan.
int tulostettava = 1; while (tulostettava <= luku) { if (tulostettava % 3 == 0 && tulostettava % 5 == 0) { System.out.println("FizzBuzz"); } else if (tulostettava % 5 == 0) { System.out.println("Buzz"); } else if (tulostettava % 3 == 0) { System.out.println("Fizz"); } else { System.out.println(tulostettava); } tulostettava++; }
Liitetään edellinen kokonaisuus ohjelmaamme luvun lukemisen jälkeen.
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 % 5 == 0) { System.out.println("Buzz"); } else if (tulostettava % 3 == 0) { System.out.println("Fizz"); } else { System.out.println(tulostettava); } tulostettava++; }
Ohjelma toimii nyt seuraavasti:
Syötä positiivinen luku: 16 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16
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.
Huom! Tässä tehtävässä käsitellään useita tähän mennessä kurssilla olleita asioita. Kertaa ennen tehtävän tekemistä edellisten viikkojen materiaalista erityisesti muuttujien ja toistolausekkeiden käyttö.
Tehtäväpohjassa ei tule testejä mukana, vaan ne ovat TMC-palvelimella. Ennen kuin palautat tehtävän, varmista että se toimii esimerkkisyötteillä. Mikäli tehtävä ei toimi kun palautat sen palvelimelle, saat palvelimelta virheviestin yhteydessä tietoa syötteistä, millä ohjelma ei toiminut.
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ä äänenvoimakkuuskirjauksia. 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 näyttää. Käyttäjän antamat syötteet on merkitty punaisella värillä.
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.
Huom! Tarkista, että ohjelman tulosteen muoto on oikeanlainen. Pelkkä keskiarvon tulostaminen ei esimerkiksi riitä vastaukseksi.
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.
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.
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.
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
) muuttujan (tässä 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:
void
-määre palautettavan muuttujan tyyppinä.
public static void metodiJokaEiPalautaMitaan() { // metodin runko }
int
-määre palautettavan muuttujan tyyppinä.
public static int metodiJokaPalauttaaKokonaisLuvun() { // metodin runko, tarvitsee return-komennon }
String
-määre palautettavan muuttujan tyyppinä.
public static String metodiJokaPalauttaaTekstin() { // metodin runko, tarvitsee return-komennon }
double
-määre palautettavan muuttujan tyyppinä.
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 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; }
Metodia kutsuttaessa metodi laskee ensin parametreina annettujen luvun summan, ja palauttaa arvon metodista.
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 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ä 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
Huom! Tämä tehtävä on kahden yksittäisen tehtäväpisteen arvoinen.
Tuttu robottimme on taas täällä uusin ominaisuuksin. Robotti osaa nyt myös nostaa esineitä kyytiin.
Robotti käynnistyy maailmassa, missä ympyrät ovat nostettavia esineitä:
Toteutetaan tässä ohjelma, jonka avulla robotti nostaa kaikki esineet.
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. Komennolla Ohjain.sammuta();
robotti sammutetaan.
Robottia voi kääntää komennoilla Ohjain.vasen();
ja Ohjain.oikea();
, jotka kääntävät robottia vastapäivään ja myötäpäivään. Robotin liikuttaminen tapahtuu komennoilla Ohjain.liiku();
, joka siirtää robottia yhden askeleen robotin osoittamaan suuntaan, ja Ohjain.liikuMonta(int montako);
, joka liikuttaa robottia annetun määrän askelia osoitettuun suuntaan.
Toteuta ensin ohjelma, jonka avulla robotti ajaa kaikkien nostettavien esineiden yli.
Vinkki: Kannattaa käyttää kahta sisäkkäistä toistolausetta, joista toinen käsittelee kartan rivejä, ja toinen sarakkeita. Sisäkkäiset toistolausekkeet voi toteuttaa esimerkiksi seuraavasti:
int y = 0; while (y < jotain) { int x = 0; while (x < jotainMuuta) { // tee jotain x++; } y++; }
Kun robottisi ajaa kaikkien esineiden yli, voi lopputilanne näyttää esimerkiksi seuraavalta:
Lisää ohjelmaasi esineiden nostamistoiminnallisuus. Tässä jakojäännöksestä on hyötyä!
Esineen nostamiskomento on Ohjain.nosta();
. Komento nostaa robotin alla olevan esineen kyytiin.
Huom! Robotin kauha menee rikki jos esinettä yritetään nostaa ruudusta, missä sellaista ei ole. Et siis voi kutsua komentoa Ohjain.nosta();
kartan jokaisessa ruudussa.
Kun robotti saa esineet nostettua, voi lopputilanne näyttää esimerkiksi seuraavalta.
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 seuraavasti:
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:
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.
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 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)!
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
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
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.
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.
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.
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 :/
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
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
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!
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
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()
.
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ä
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!
Screencast aiheesta:
Tee ohjelma, joka kysyy käyttäjältä kaksi sanaa. Tämän jälkeen ohjelma kertoo onko toinen sana ensimmäisen sanan osana. Käytä ohjelmassa merkkijonon metodia indexOf
.
Anna 1. sana: suppilovahvero Anna 2. sana: ilo Sana 'ilo' on sanan 'suppilovahvero' osana.
Anna 1. sana: suppilovahvero Anna 2. sana: suru Sana 'suru' ei ole sanan 'suppilovahvero' osana.
Huom: toteuta ohjelmasi tulostus täsmälleen samassa muodossa kuin esimerkin tulostus!
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
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?
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ää.
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.
ArrayList tarjoaa monia hyödyllisiä metodeita:
public static void main(String[] args) { 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"); } }
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 Veera listalla kolmantena Verna Juha ei ole opettajien listalla
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("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
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("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 }
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("Anne"); opettajat.add("Anna"); 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.
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("Anne"); opettajat.add("Martin"); opettajat.add("Matti"); opettajat.add("Veera"); 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 Anne, jne
Vaikka for
-komento voi tuntua aluksi hieman oudolta, kannattaa sen käyttöön ehdottomasti totutella!
Screencast aiheesta:
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 }
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()
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:
Anne Antti Martin Matti Veera
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önTee 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
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
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.
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
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.
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("Pihla"); tyypit.add("Verna"); 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, Pihla, Verna, Simppa] [Pekka, Pihla, Simppa]
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.
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<>();
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
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
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
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.
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
Kaksi maailman johtavista ohjelmistonkehittäjistä, Martin Fowler ja Kent Beck, ovat lausuneet kirjassa Refactoring: Improving the Design of Existing Codeseuraavasti seuraavasti:
[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ä.
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.
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); } }
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.
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"); }
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"); }
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ä!
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(lista, 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.
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!
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.
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]
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 }
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.
Harjoitellaan vielä hieman ArrayList-joukkojen yhdistelyä.
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.
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.
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ä.
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.
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.
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.
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.