Arto Vihavainen ja Matti Luukkainen
Tämä materiaali on tarkoitettu Helsingin Yliopiston Tietojenkäsittelytieteen laitoksen syksyn 2012 Ohjelmoinnin perusteet- ja jatkokurssille. Materiaali pohjautuu keväiden 2012, 2011 ja 2010 kurssimateriaaleihin, joiden sisältöön ovat vaikuttaneet Matti Paksula, Antti Laaksonen, Pekka Mikkola, Juhana Laurinharju, Martin Pärtel, Joel Kaasinen ja Mikael Nousiainen
Lue materiaalia siten, että teet samalla itse kaikki lukemasi esimerkit. Esimerkkeihin kannattaa tehdä pieniä muutoksia ja tarkkailla, miten muutokset vaikuttavat ohjelman toimintaan. Äkkiseltään voisi luulla, että esimerkkien tekeminen myös itse ja niiden muokkaaminen hidastaa opiskelua. Tämä ei kuitenkaan pidä ollenkaan paikkansa. Ohjelmoimaan ei ole vielä tietääksemme kukaan ihminen oppinut lukemalla (tai esim. luentoa kuuntelemalla). Oppiminen perustuu oleellisesti aktiiviseen tekemiseen ja rutiinin kasvattamiseen. Esimerkkien ja erityisesti erilaisten omien kokeilujen tekeminen on parhaita tapoja "sisäistää" luettua tekstiä.
Pyri tekemään tai ainakin yrittämään tehtäviä sitä mukaa kuin luet tekstiä. Jos et osaa heti tehdä jotain tehtävää, älä masennu, sillä saat ohjausta tehtävän tekemiseen pajassa.
Tekstiä ei ole tarkoitettu vain kertaalleen luettavaksi. Joudut varmasti myöhemmin palaamaan jo aiemmin lukemiisi kohtiin tai aiemmin tekemiisi tehtäviin. Tämä teksti ei sisällä kaikkea oleellista ohjelmointiin liittyvää. Itse asiassa ei ole olemassa mitään kirjaa josta löytyisi kaikki oleellinen. Eli joudut joka tapauksessa ohjelmoijan urallasi etsimään tietoa myös omatoimisesti. Kurssin harjoitukset sisältävät jo jonkun verran ohjeita, mistä suunnista ja miten hyödyllistä tietoa on mahdollista löytää.
Muutamiin kohtiin olemme myös liittäneet screencasteja joita katsomalla voi pelkän valmiin koodin lukemisen sijaan seurata miten ohjelma muodostuu. Myös luennoilla tulet näkemään paljon livekoodausta.
Nykyaikainen ohjelmointi tapahtuu lähes poikkeuksetta ohjelmointiympäristössä. Ohjelmointiympäristö sisältää joukon ohjelmoijaa auttavia aputoimintoja. Ohjelmointiympäristö ei rakenna ohjelmaa ohjelmoijan puolesta, mutta se muunmuassa vinkkaa helpoista virheistä ohjelmakoodissa ja auttaa ohjelmoijaa hahmottamaan ohjelman rakennetta.
Käytämme tällä kurssilla NetBeans-nimistä ohjelmointiympäristöä. Ohjeet NetBeansin ja kurssilla käytettävän tehtävien palautusautomaatin käyttöön löydät täältä.
Ohje ohjelmoinnin aloittamiseen NetBeans-ohjelmointiympäristössä löytyy samasta paikasta kuin edellinen ohje.
Jatkossa julkaisemme pikkuhiljaa lisää ohjeita NetBeansin käyttöön. Vaikka ohjelma tuntuisi nyt sekavalta, älä hätäile. NetBeans on loppujenlopuksi hyvin helppokäyttöinen. Perusteet opit 5 minuutissa, ja kurssin myötä opit koko ajan hieman lisää ja joulukuussa olet jo todellinen NetBeans "poweruser".
Ohjelma muodostuu lähdekoodista. Tietokone suorittaa lähdekoodissa olevia komentoja pääsääntöisesti ylhäältä alaspäin ja vasemmalta oikealle. Lähdekoodi talletetaan tekstimuodossa ja suoritetaan jollakin tavalla.
Varsinaisesti ohjelma muodostuu lähdekoodiin kirjoitetuista komennoista. Tietokone suorittaa eri operaatioita, eli toimintoja, komentojen perusteella. Esimerkiksi merkkijonon, eli tekstin, "Hei maailma"-tulostuksessa tärkein komento on System.out.println
.
System.out.println("Hei maailma");
Komento System.out.println
tulostaa sille sulkeiden sisällä annetun merkkijonon. Pääte ln
on lyhenne sanasta line. Komento siis tulostaa rivin, eli kun annettu merkkijono on tulostettu, tulostaa komento myös rivinvaihdon.
Tietokone ei suoraan ymmärrä käyttämäämme ohjelmointikieltä. Tarvitsemme lähdekoodin ja tietokoneen väliin kääntäjän. Ohjelmoidessamme komentoriviä käyttäen, komento javac Hei.java
kääntää Hei.java
-tiedoston tavukoodiksi, jota voidaan ajaa java-tulkin avulla. Käännetty ohjelma ajetaan komentoriviltä komennolla java Hei
, missä Hei on käännetyn java-lähdekooditiedoston nimi.
Käyttäessämme ohjelmointiympäristöä, ohjelmointiympäristö hoitaa lähdekoodin kääntämisen. Valitessamme ohjelman suorittamisen, ohjelmointiympäristö kääntää ja suorittaa ohjelman. Ohjelmointiympäristöt kääntävät ohjelmakoodia ohjelmoijan sitä kirjoittaessa, jolloin yksinkertaiset virheet huomataan ennen ohjelman suoritusta.
Puolipisteellä ;
erotetaan komennot toisistaan. Kääntäjä ja tulkki ei siis ole kiinnostunut lähdekoodissa olevista riveistä, vaan voimme kirjoittaa koko ohjelman yhdelle riville.
Alla olevassa esimerkissä käytetään komentoa System.out.print
, joka on kuten komento System.out.println
ilman rivinvaihtoa. System.out.print
-komento ei siis tulosta rivinvaihtoa tekstin tulostamisen jälkeen.
Esimerkki puolipisteiden käytöstä
System.out.print("Hei "); System.out.print("maailma"); System.out.print("!");
Hei maailma!
Vaikka kääntäjä ja tulkki eivät tarvitse rivinvaihtoja, on niiden käyttö hyvin tärkeää muita ihmisiä ajatellen. Selkeä lähdekoodin osien erottelu vaatii rivinvaihtojen käyttöä. Tätä ja muita lähdekoodin luettavuuteen liittyviä seikkoja tullaan painottamaan tällä kurssilla.
Komennon käsittelemä tieto eli komennon parametrit lähetetään komennolle lisäämällä ne komennon nimen perässä olevien sulkujen ()
sisään. Esimerkiksi System.out.print
-komennon parametriksi annetaan teksti hei seuraavasti: System.out.print("hei")
.
Lähdekoodin kommentit
ovat kätevä tapa merkitä asioita itselle ja muille muistiin. Kommentti on mikä tahansa rivi, joka alkaa kahdella vinoviivalla //
. Kaikki kommenttimerkkiä seuraava samalla rivillä oleva teksti tulkitaan kommentiksi.
// Tulostamme tekstin "Hei maailma" System.out.print("Hei maailma"); System.out.print(" ja kaikki sen ihmiset."); // Lisäämme samalle riville tekstiä. // System.out.print("tätä riviä ei suoriteta koska se on kommentoitu ulos");
Esimerkissä alin rivi esittelee erityisen kätevän käyttökohteen kommenteille: kirjoitettua koodia ei tarvitse poistaa jos haluaa tilapäisesti kokeilla jotain.
Kuten aiemmin huomattiin, tulostamiseen on kaksi komentoa:
System.out.print
tulostaa tekstin ilman loppurivinvaihtoaSystem.out.println
tulostaa tekstin ja loppurivinvaihdonTulostettavan tekstin osana voi olla erikoismerkkejä. Tärkein näistä on \n
, joka vaihtaa riviä. Erikoismerkkejä on muitakin.
System.out.println("Ensimmäinen\nToinen\nKolmas");
Ylläoleva tulostaa suoritettaessa seuraavaa:
Ensimmäinen Toinen Kolmas
Ohjelman "Esimerkki" runko on seuraavanlainen.
public class Esimerkki { public static void main(String[] args) { // ohjelmakoodi } }
Ohjelma sijaitsee samannimisessä .java-päätteisessä tiedostossa. Ohjelman Esimerkki täytyy siis sijaita tiedostossa,
jonka nimi on Esimerkki.java
.
Ohjelmaa suoritettaessa suoritetaan alue, joka on rungossamme merkitty kommentilla ohjelmakoodi. Ohjelmoimme ensimmäisellä viikolla
vain tälle alueelle. Kun puhumme komennoista, esimerkiksi tulostamisesta, tulee komennot kirjoittaa ohjelmarungon sisälle. Esimerkiksi
System.out.print("Tulostettava teksti");
public class Esimerkki { public static void main(String[] args) { System.out.print("Tulostettava teksti"); } }
Jatkossa esimerkeissä ei erikseen näytetä pääohjelmarunkoa.
Ja itse asiaan, eli aloitetaan tehtävien tekeminen!
Tehtävät tehdään NetBeans-ohjelmointiympäristössä. Ohje alkuunpääsemiseksi täältä. Ennen kuin tiedät mitä teet, toimi täsmälleen ohjeen kuvaamalla tavalla. Useimmat seuraavista tehtävänannoista näyttävät mitä pyydetyn tehtävän tulisi tulostaa ruudulle toimiakseen oikein. Jos tehtävänanto on epäselvä, pyydä apua! Tehtävänannon epäselvyys on aina opettajien vika.
HUOM: älä tee tehtäviä siten että pelkästään kirjoitat koodia ja paineletet testinappia. Suorita myös koodia normaaliin tapaan (vihreällä nuolella) ja katso mitä ruudulle tulostuu. Erityisesti jos ohjelma ei meinaa mennä testeistä läpi, kannattaa varmistaa normaalisti suorittamalla että ohjelma toimii silmämääräisesti niinkuin sen pitäisi.
Seuraavissa tehtävissä harjoitellaan tuntuman saamista NetBeansiin ja ruudulle tulostamista.
Muista lukea ensin NetBeans-aloittelijan ohje!
Tee ohjelma, joka tulostaa nimesi.
Ohjelman tulostus voi olla seuraava:
Oskari Opiskelija
Tee ohjelma, jonka tulostus on seuraava:
Hei Maailma! (Ja Mualima!)
Tee ohjelma, jonka tulostus on seuraava:
* *** ***** ******* ********* *
HUOM: kirjoitit todennäköisesti aika monta kertaa System.out.println("...")
. Kokeile kirjoittaa NetBeans:iin (main:in sisään) tyhjälle riville sout ja paina tabulaatoria (näppäin q:n vasemmalla puolella). Mitä tapahtuu? Tämä pieni apuväline säästänee jatkossa runsaasti aikaasi.
Ohjelmoinnissa eräs keskeinen käsite on muuttuja. Muuttuja kannattaa ajatella lokerona, johon voi tallettaa tietoa. Muuttujaan talletettavalla tiedolla on aina tyyppi. Tyyppejä ovat esimerkiksi teksti eli merkkijono (String), kokonaisluku (int), liukuluku (double) ja totuusarvo (boolean). Muuttujaan asetetaan arvo yhtäsuuruusmerkillä (=
).
int kuukausia = 12;
Yllä olevassa asetuslauseessa asetetaan kokonaisluku-tyyppiä (int) olevaan muuttujaan nimeltä kuukausia arvo 12. Asetuslause luetaan "muuttuja kuukausia saa arvon 12"
Muuttujan arvo voidaan yhdistää merkkijonoon +
-merkillä
seuraavan esimerkin mukaisesti.
String teksti = "sisältää tekstiä"; int kokonaisluku = 123; double liukuluku = 3.141592653; boolean onkoTotta = true; System.out.println("Tekstimuuttujan arvo on " + teksti); System.out.println("Kokonaislukumuuttujan arvo on " + kokonaisluku); System.out.println("Liukulukumuuttujan arvo on " + liukuluku); System.out.println("Totuusarvomuuttujan arvo on " + onkoTotta);
Tulostus:
Tekstimuuttujan arvo on sisältää tekstiä Kokonaislukumuuttujan arvo on 123 Liukulukumuuttujan arvo on 3.141592653 Totuusarvomuuttujan arvo on true
Muuttuja säilyttää arvonsa kunnes siihen asetetaan toinen arvo. Huomaa että muuttujan tyyppi kirjoitetaan vain kun muuttuja esitellään ohjelmassa ensimmäistä kertaa.
int kokonaisluku = 123; System.out.println("Kokonaislukumuuttujan arvo on " + kokonaisluku); kokonaisluku = 42; System.out.println("Kokonaislukumuuttujan arvo on " + kokonaisluku);
Tulostuu:
Kokonaislukumuuttujan arvo on 123 Kokonaislukumuuttujan arvo on 42
Kun muuttujan tyyppi on kertaalleen määritelty, ei se enää muutu. Esimerkiksi merkkijonomuuttuja ei voi muuttua kokonaislukumuuttujaksi, eikä siihen voi asettaa kokonaislukua.
String merkkijono = "tsuppadui!"; merkkijono = 42; // Ei onnistu! :(
Liukulukuun voi asettaa kokonaisluvun, sillä kokonaisluku on myös liukuluku
double liukuluku = 0.42; liukuluku = 1; // Onnistuu! :)
Tehtäväpohja sisältää ohjelman, joka tulostaa seuraavaa.
Kanoja: 3 Pekonia (kg): 5.5 Traktori: Ei ole! Tässä vielä tiivistelmä: 3 5.5 Ei ole!
Muuta ohjelmaa annetuista kohdista niin että tuloste on:
Kanoja: 9000 Pekonia (kg): 0.1 Traktori: Zetor Tässä vielä tiivistelmä: 9000 0.1 Zetor
Muuttujan nimeämistä rajoittavat tietyt ehdot. Vaikka muuttujan nimessä voidaan käyttää ääkkösiä, on parempi olla kayttamatta niita, sillä merkistökoodauksesta saattaa tulla ongelmia.
Muuttujan nimessä ei saa olla tiettyjä erikoismerkkejä, kuten huutomerkkejä (!). Välilyönti ei ole sallittu, sillä se erottaa komentojen osat toisistaan. Välilyönti kannattaa korvata camelCase-tyylillä, jolloin nimi muistuttaneeKamelia
. Huom! Muuttujien nimien ensimmäinen kirjain kirjoitetaan aina pienellä:
int camelCaseMuuttuja = 7;
Numeroita voidaan käyttää muuttujan nimessä, kunhan nimi ei ala numerolla. Nimi ei myöskään voi koostua pelkistä numeroista.
int 7muuttuja = 4; // Ei sallittu! int muuttuja7 = 4; // Sallittu, mutta ei kuvaava muuttujan nimi
Muuttujan nimi ei myöskään saa olla jo entuudestaan käytössä. Tälläisiä nimiä ovat mm. aikaisemmin määritellyt muuttujat ja komennot, kuten System.out.print
ja System.out.println
.
int camelCase = 2; int camelCase = 5; // Ei sallittu -- muuttuja camelCase on jo käytössä!
Muuttuja kannattaa nimetä siten, että sen käyttötarkoitus on selvää ilman kommentteja tai miettimistä. Tällä kurssilla muuttujat pitää nimetä kuvaavasti.
Laskentaoperaatiot ovat varsin suoraviivaisia: +
, -
, *
ja /
. Erikoisempana operaationa on %
, joka on jakojäännös, eli modulo. Laskentajärjestys on myös varsin suoraviivainen: operaatiot lasketaan vasemmalta oikealle sulut huomioon ottaen.
int eka = 2; // kokonaislukutyyppinen muuttuja eka saa arvon 2 int toka = 4; // kokonaislukutyyppinen muuttuja toka saa arvon 4 int summa = eka + toka; // kokonaislukutyyppinen muuttuja summa saa arvon eka + toka, eli 2 + 4 System.out.println(summa); // tulostetaan muuttujan summa arvo
int laskuSuluilla = (1 + 1) + 3 * (2 + 5); // 23 int laskuSuluitta = 1 + 1 + 3 * 2 + 5; // 13
Yllä olevan sulkuesimerkin voi suorittaa myös askeleittain.
int laskuSuluilla = (1 + 1); laskuSuluilla = laskuSuluilla + 3 * (2 + 5) // 32 int laskuSuluitta = 1 + 1; laskuSuluitta = laskuSuluitta + 3 * 2; laskuSuluitta = laskuSuluitta + 5; // 13
Laskentaoperaatioita voidaan suorittaa lähes missä tahansa kohdassa ohjelmakoodia.
int eka = 2; int toka = 4; System.out.println(eka+toka); System.out.println(2 + toka - eka - toka);
Kokonaislukujen jako ja jakojäännös ovat hieman hankalampia. Liukuluku ja kokonaisluku menevät helposti sekaisin. Jos kaikki laskuoperaatiossa olevat muuttujat ovat kokonaislukuja, on tulos myös kokonaisluku.
int tulos = 3 / 2; // tulos on 1 (kokonaisluku), sillä 3 ja 2 ovat myös kokonaislukuja
int eka = 3: int toka = 2; double tulos = eka / toka; // nytkin tulos on 1, sillä eka ja toka ovat kokonaislukuja
Jakojäännös-operaation (%) avulla saadaan selville jakojäännös. Esimerkiksi laskun 7 % 2
jakojäännös on 1.
int jakojaannos = 7 % 2; // jakojaannos on 1 (kokonaisluku)
Jos jakolaskun jakaja tai jaettava (tai molemmat!) ovat liukulukuja, tulee tulokseksi myös liukuluku
double kunJaettavaOnLiukuluku = 3.0 / 2; // tulokseksi: 1.5 double kunJakajaOnLiukuluku = 3 / 2.0; // tulokseksi: 1.5
Kokonaisluku voidaan tarvittaessa muuttaa liukuluvuksi lisäämällä sen eteen tyyppimuunnosoperaatio (double)
:
int eka = 3; int toka = 2; double tulos1 = (double)eka / toka; // tulokseksi: 1.5 double tulos2 = eka / (double)toka; // tulokseksi: 1.5 double tulos3 = (double)(eka / toka); // tulokseksi: 1
Jälkimmäisessä tulos pyöristyy väärin sillä laskuoperaatio kokonaisluvuilla suoritetaan ennen tyyppimuunnosta.
Jos jakolaskun tulos asetetaan kokonaislukutyyppiseen muuttujaan, on tulos automaattisesti kokonaisluku
int tulosKokonaislukuKoskaTyyppiKokonaisluku = 3.0 / 2; // tulos automaattisesti kokonaisluku: 1
Seuraava esimerkki tulostaa "1.5", sillä jaettavasta tehdään liukuluku kertomalla se liukuluvulla (1.0 * 3 = 3.0) ennen jakolaskua.
int jaettava = 3; int jakaja = 2; double tulos = 1.0 * jaettava / jakaja; System.out.println(tulos);
Mitä seuraava tulostaa?
int jaettava = 3; int jakaja = 2; double tulos = jaettava / jakaja * 1.0; System.out.println(tulos);
Huomioi, että nimeät muuttujat nyt ja jatkossakin yllä esiteltyjen hyvien käytäntöjen mukaan.
Tee ohjelma, joka laskee, kuinka monta sekuntia on vuodessa. Voit olettaa, että vuodessa on 365 päivää (eli ei ole karkausvuosi).
Ohjelman tulostus on seuraava:
Vuodessa on X sekuntia.
X:n kohdalle tulee ohjelmasi laskema tulos.
Tarkastellaan vielä lähemmin merkkijonojen
yhdistämistä +
-merkinnän avulla.
Jos operaatiota +
sovelletaan kahden merkkijonon välille, syntyy uusi merkkijono, jossa kaksi merkkijonoa on yhdistetty. Huomaa nokkela välilyönnin käyttö lauseen "muuttujien" osana!
String tervehdys = "Hei "; String nimi = "Matti"; String hyvästely = ", ja näkemiin!"; String lause = tervehdys + nimi + hyvästely; System.out.println(lause);
Hei Matti, ja näkemiin!
Jos toinen operaation +
kohteista on merkkijono, syntyy uusi merkkijono, jossa esimerkiksi kokonaisluku 2
on muutettu merkkijonoksi "2" ja tähän yhdistetty haluttu merkkijono.
System.out.println("tuossa on kokonaisluku --> " + 2); System.out.println( 2 + " <-- tuossa on kokonaisluku");
Edellä esitellyt laskusäännöt pätevät täälläkin:
System.out.println("Neljä: " + (2+2)); System.out.println("Mutta! kaksikymmentäkaksi: " + 2 + 2);
Neljä: 4 Mutta! kaksikymmentäkaksi: 22
Edellisiä tietoja yhdistelemällä pystymme tulostamaan muuttujan arvoja ja tekstiä sekaisin:
int x = 10; System.out.println("muuttujan x arvo on: " + x); int y = 5; int z = 6; System.out.println("y on " + y + " ja z on " + z);
Tämä ohjelma tulostaa tietenkin:
muuttujan x arvo on: 10 y on 5 ja z on 6
Tee ohjelma, jonka avulla voidaan laskea kahden kokonaisluvun summa. Ohjelman alussa määritellään kaksi muuttujaa, jotka sisältävät summattavat luvut. Voit tarvittaessa käyttää myös muita muuttujia.
Esimerkiksi jos muuttujissa on luvut 5 ja 4, ohjelman tulostus on seuraava:
5 + 4 = 9
Jos taas muuttujissa on luvut 73457 ja 12888, ohjelman tulostus on seuraava:
73457 + 12888 = 86345
Tee edellistä ohjelmaa vastaava ohjelma, joka laskee kahden kokonaislukumuuttujaan sijoitetun arvon kertolaskun.
Esimerkiksi jos muuttujissa on luvut 2 ja 8, ohjelman tulostus on seuraava:
2 * 8 = 16
Jos taas muuttujissa on luvut 277 ja 111, ohjelman tulostus on seuraava:
277 * 111 = 30747
Kuinka suuren kertolaskun ohjelmasi pystyy laskemaan?
Tähän asti ohjelmamme ovat olleet kovin yksipuolisia. Seuraavaksi luemme syötettä käyttäjältä. Käytämme syötteen lukemiseen erityistä Scanner-apuvälinettä.
Lisätään Scanner valmiiseen pääohjelmarunkoomme. Älä hätäile vaikka pääohjelmarunko saattaa näyttää vaikeaselkoiselta, jatkamme koodausta kuten ennenkin, eli kohtaan mikä on merkattu kommentilla ohjelmakoodi.
import java.util.Scanner; public class OhjelmaRunko { public static void main(String[] args) { Scanner lukija = new Scanner(System.in); // ohjelmakoodi } }
Seuraava koodi lukee käyttäjän nimen ja tulostaa tervehdyksen:
System.out.print("Mikä on nimesi? "); String nimi = lukija.nextLine(); // Luetaan käyttäjältä rivi tekstiä ja asetetaan se muuttujaan nimi System.out.println("Hei, " + nimi);
Mikä on nimesi? Matti
Hei, Matti
(Seuraavassa on yllä oleva ohjelma pääohjelmarungon kanssa. Ohjelman nimi on Tervehdys. Koska ohjelman nimi on Tervehdys, täytyy sen sijaita tiedostossa Tervehdys.java
-- Käyttäessäsi NetBeans-ohjelmointiympäristöä, toimi kuten alussa olleessa ohjeessa tehtiin, mutta muuta ohjelman nimi Heistä (Hei) Tervehdykseksi (Tervehdys)
import java.util.Scanner; public class Tervehdys { public static void main(String[] args) { Scanner lukija = new Scanner(System.in); System.out.print("Kenelle sanotaan hei: "); String nimi = lukija.nextLine(); // Luetaan käyttäjältä rivi tekstiä ja asetetaan sen arvo muuttujaan nimi System.out.print("Hei " + nimi); } }
Kun yllä oleva ohjelma ajetaan, pääset kirjoittamaan syötteen. NetBeansin tulostusvälilehti (alhaalla) näyttää ajetun ohjelman jälkeen seuraavalta (käyttäjä syöttää nimen "Matti").
run: Kenelle sanotaan hei: Matti Hei Matti BUILD SUCCESSFUL (total time: 6 seconds)
Scanner-apuvälineemme ei ole hyvä kokonaislukujen lukemiseen, joten käytämme toista apuvälinettä merkkijonon kokonaisluvuksi muuttamisessa. Komento Integer.parseInt
muuttaa sille annetussa tekstimuuttujassa olevan kokonaisluvun kokonaislukumuuttujaksi. Komennolle annetaan tekstimuuttuja sulkuihin, ja se palauttaa kokonaisluvun joka asetetaan kokonaislukumuuttujaan.
Käytännössä kytkemme kaksi komentoa yhteen. Ensin luemme käyttäjältä rivin, jonka annamme heti komennolle Integer.parseInt
.
System.out.print("Anna kokonaisluku: "); int kokonaisluku = Integer.parseInt(lukija.nextLine()); System.out.println("Annoit " + kokonaisluku);
Kysytään seuraavaksi käyttäjältä nimi, ja sen jälkeen ikä. Tällä kertaa esimerkissä on myös ohjelmarunko mukana.
import java.util.Scanner; public class NimiJaIkaTervehdys { public static void main(String[] args) { Scanner lukija = new Scanner(System.in); System.out.print("Nimesi: "); String nimi = lukija.nextLine(); // Luetaan käyttäjältä rivi tekstiä System.out.print("Kuinka vanha olet: "); int ika = Integer.parseInt(lukija.nextLine()); // luetaan käyttäjältä tekstimuuttuja ja muutetaan se kokonaisluvuksi System.out.println("Nimesi on siis: " + nimi + ", ja ikäsi " + ika + ", hauska tutustua."); } }
Käyttäjän kanssa keskustelevan ohjelman runko:
import java.util.Scanner; public class OhjelmanNimi { public static void main(String[] args) { Scanner lukija = new Scanner(System.in); // koodi tähän } }
Merkkijonon lukeminen:
String merkkijono = lukija.nextLine();
Kokonaisluvun lukeminen:
int kokonaisluku = Integer.parseInt(lukija.nextLine());
Tee ohjelma, joka kysyy käyttäjältä kaksi kokonaislukua ja tulostaa niiden summan.
Anna ensimmäinen luku: 6 Anna toinen luku: 2 Lukujen summa: 8
Esimerkissä punainen väri tarkoittaa käyttäjän kirjoittamaa tekstiä. Tätä käytäntöä noudatetaan jatkossa esimerkeissä.
Tee ohjelma, joka kysyy käyttäjältä kaksi kokonaislukua ja
tulostaa niiden osamäärän. Varmista, että 3 / 2 = 1.5
. Jos desimaaliosa katoaa, lue materiaalin kohdasta
Liukuluvut eli desimaaliluvut missä vika on.
Anna ensimmäinen luku: 3 Anna toinen luku: 2 Jakolasku: 3 / 2 = 1.5
Ympyrän kehän pituus lasketaan kaavalla säde on 2 * pii * säde
.
Tee ohjelma, joka kysyy käyttäjältä ympyrän säteen ja
laskee sen perusteella ympyrän kehän pituuden.
Javasta löytyy valmis piin arvo, saat sen kirjoittamalla
Math.PI
laskutoimitukseen.
Anna ympyrän säde: 20 Ympyrän kehä: 125.66370614359172
Tee ohjelma, joka kysyy käyttäjältä kaksi kokonaislukua ja tulostaa luvuista suuremman.
Vihje: kun kirjoitat NetBeans:iin Math. (eli Math ja perään piste), näet erinäisiä Javan valmiita matemaattisia laskutoimituksia, esim. Math.cos(10)
laskee luvun 10 kosinin. Kokeile, löydätkö Math:sta sopivan työkalun seuraavan ohjelman tekemiseen. Jos et löydä tai osaa tehdä tätä kohtaa, siirry heti eteenpäin. Tutustumme asiaan tarkemmin myöhemmässä vaiheessa.
Anna ensimmäinen luku: 20 Anna toinen luku: 14 Luvuista suurempi: 20
Tee ohjelma, joka kysyy kahden käyttäjän nimet ja iät. Tämän jälkeen ohjelma tulostaa henkilöiden ikien summan.
Kerro nimi: Matti Kerro ikä: 14 Kerro nimi: Arto Kerro ikä: 12 Matti ja Arto ovat yhteensä 26 vuotta vanhoja.
Tehtäväpohjaasi on liitetty mukaan valmis komponentti NHLStatistics
jonka avulla on mahdollista tarkastella
NHL:n pelaajien pistetietoja, eli pelattuja otteluita, tehtyjä maaleja, syöttöjä, pisteitä ja jäähymääriä.
Pääohjelma "importoi" eli ottaa komponentin käyttöönsä lisäämällä koodin yläosaan import nhlstats.NHLStatistics;
. Seuraavassa esimerkkiohjelma 10 parhaan pisteidentekijän tulostamisesta:
import java.util.Scanner; import nhlstats.NHLStatistics; public class Paaohjelma { public static void main(String[] args) throws Exception { Scanner lukija = new Scanner(System.in); System.out.println("10 parasta pistemiestä"); NHLStatistics.sortByPoints(); NHLStatistics.top(10); } }
Tulostuu (tämä oli maanantain 9.1. tilanne):
10 parasta pistemiestä Henrik Sedin VAN 43 11 + 38= 49 36 Phil Kessel TOR 41 24 + 24= 48 10 Claude Giroux PHI 36 18 + 30= 48 16 Joffrey Lupul TOR 41 19 + 28= 47 36 Daniel Sedin VAN 42 18 + 29= 47 32 Steven Stamkos TBL 40 28 + 17= 45 34 Marian Hossa CHI 41 17 + 27= 44 14 Evgeni Malkin PIT 33 16 + 28= 44 30 Jordan Eberle EDM 41 17 + 26= 43 6 Jason Pominville BUF 41 14 + 29= 43 8
Pelaajasta tulostuvat nimi, seuran lyhenne, ottelut, maalit, syötöt, pisteet ja jäähyt.
Ensimmäinen komento NHLStatistics.sortByPoints()
järjestää pelaajat tehtyjen pisteiden mukaiseen järjestykseen.
Toinen komento NHLStatistics.top(10);
tulostaa 10 ensimmäistä pelaajaa senhetkistä järjestystä noudattaen. Parametriksi voidaan antaa mikä tahansa luku.
Vastaavalla tavalla saadaan tulostettua pelaajat tehtyjen maalien, syöttöjen tai jäähyminuuttien mukaan järjestettynä. Eli ensin kutsutaan pelaajat sopivaan järjestykseen laittavaa komentoa, joita ovat:
NHLStatistics.sortByPoints(); // aseta pelaajat pisteiden mukaiseen järjestykseen NHLStatistics.sortByGoals(); // aseta pelaajat tehtyjen maalien mukaiseen järjestykseen NHLStatistics.sortByAssists(); // aseta pelaajat syöttöjen mukaiseen järjestykseen NHLStatistics.sortByPenalties(); // aseta pelaajat jäähyminuuttien mukaiseen järjestykseen
Ja tämän jälkeen tulostetaan pelaajat komennolla top
ja parametrina annetaan tulostettavien pelaajien määrä
Komponentti mahdollistaa myös tietyn pelaajan tilastotietojen kyselyn:
NHLStatistics.searchByPlayer("Jaromir Jagr"); // tulostaa Jaromir Jagr:in tilastot NHLStatistics.searchByPlayer("Koivu"); // tulostaa Mikko ja Saku Koivun tilastot NHLStatistics.searchByPlayer("Teemu"); // tulostaa kaikkien Teemujen tilastot
Sekä tietyn joukkueen kaikkien pelaajien tilastojen tulostuksen:
NHLStatistics.teamStatistics("NYR"); // New York Rangersin tilastot
Joukkueentilastot tulostuvat siinä järjestyksessä mihin ne ovat edelliseksi kutsutulla sortBy...()
-komennolla asetettu.
Joukkueen nimi tulee antaa virallisena kolmekirjaimisena lyhenteenä. Joukkueiden lyhenteet kerrottu esim. täällä. Myös tilastokomponetti kertoo käytössä olevat lyhenteet jos yrität pyytää olemassa olemattoman joukkueen tilastoja.
Tee ohjelmarunkoon seuraavat asiat tekevä ohjelma. Tee asiat juui siinä järjestyksessä kun ne tässä mainitaan. Tee kaikki asiat pääohjelmaan peräkkäin, poistamatta mitään tehtyä osaa.
Huom: kun suoritat ohjelman ensimmäisen kerran, saattaa suoritus kestää jonkun aikaa (internetissä oleva palvelin voi olla lepotilassa ja sen herääminen kestää jonkin aikaa). Tämänjälkeisillä kerroilla suoritus on nopea.
Kun olet lähettänyt tehtävän tarkastettavaksi ja palautusautomaatti hyväksyy sen, voit muutella koodia vapaasti!
Tähän mennessä tekemämme ohjelmat ovat edenneet suoraviivaisesti käskystä toiseen, toimien jokaisella suorituskerralla samalla tavalla. Jotta ohjelman suoritus voisi haarautua erilaisille suorituspoluille käyttäjän esim. käyttäjän antaman syötteen perusteella, tarvitsemme käyttöömme valintakäskyn.
int luku = 11; if ( luku > 10 ) { System.out.println("Luku oli suurempi kuin 10"); }
Ehto ( luku > 10 )
muuntautuu totuusarvoksi true tai false. Valintakäsky if
käsittelee siis lopulta vain ja ainoastaan totuusarvoja. Yllä oleva ehtolause luetaan "jos luku on suurempi kuin 10".
Huomaa, että if
-lauseen perään ei tule puolipistettä, sillä lause ei lopu ehto-osan jälkeen.
Ehdon jälkeen avaava aaltosulku {
aloittaa lohkon (block), jonka sisältö suoritetaan jos ehto on tosi. Lohko loppuu sulkevaan aaltosulkuun }
. Lohko voi olla kuinka pitkä tahansa.
Vertailuoperaattoreita ovat seuraavat:
>
suurempi kuin>=
suurempi tai yhtäsuuri kuin<
pienempi kuin<=
pienempi tai yhtäsuuri kuin==
yhtäsuuri kuin!=
erisuuri kuinint luku = 55; if ( luku != 0 ) { System.out.println("Luku oli erisuuri kuin 0"); } if ( luku >= 1000 ) { System.out.println("Luku oli vähintään 1000"); }
Lohkon sisällä voi olla mitä tahansa koodia, myös toinen valintakäsky.
int x = 45; int luku = 55; if ( luku > 0 ) { System.out.println("Luku on positiivinen"); if ( luku>x ) { System.out.println(" ja suurempi kuin muuttujan x arvo"); System.out.println("muuttujan x arvohan on " + x); } }
Vertailuoperaattoreita voi käyttää myös ehtojen ulkopuolella. Tällöin ehdon totuusarvo asettuu totuusarvomuuttujaan.
int eka = 1; int toka = 3; boolean onkoSuurempi = eka > toka;
Yllä olevassa esimerkissä totuusarvomuuttuja onkoSuurempi
sisältää nyt totuusarvon false.
Totuusarvomuuttujaa voidaan käyttää ehtolauseessa ehtona.
int eka = 1; int toka = 3; boolean onkoPienempi = eka < toka; if ( onkoPienempi ) { System.out.println("1 on pienempi kuin 3!"); }
1 on pienempi kuin 3!
Huomaa, että if-komennon jälkeisen lohkon, eli {-merkkiä seuraavien rivien komentoja ei kirjoiteta samalle tasolle (eli yhtä "vasemmalle") kuin komentoa if, vaan ne sisennetään hieman oikealle. Sisentäminen tapahtuu tabulaattorimerkillä (q:n vasemmalla puolella oleva merkki). Kun lohko sulkeutuu, eli tulee }-merkki, sisennys loppuu. }-merkki on samalla tasolla kuin if.
Sisennys on oleellinen seikka ohjelmien ymmärrettävyyden kannalta. Tällä kurssilla ja kaikkialla "oikeassa elämässä" edellytetään, että koodi sisennetään asiallisesti. NetBeans auttaa sisennyksessä. Ohjelman saa sisennettyä helposti painamalla yhtä aikaa shift, alt ja f.
Jos valinnan ehto on epätotta, eli totuusarvo on false, voidaan suorittaa toinen vaihtoehtoinen lohko koodia, tämä käy komennon else
avulla.
int luku = 4; if ( luku > 5 ) { System.out.println("Lukusi on suurempi kuin viisi!"); } else { System.out.println("Lukusi on viisi tai alle!"); }
Lukusi on viisi tai alle!
Tee ohjelma, joka kysyy käyttäjältä kokonaisluvun ja kertoo, onko se positiivinen (eli suurempi kuin nolla).
Anna luku: 5 Luku on positiivinen.
Anna luku: -2 Luku ei ole positiivinen.
Onhan koodisi varmasti sisennetty oikein?
Kertaa materiaalista kohta Koodin sisennys. Kokeile mitä tapahtuu NetBeansissa kun painat yhtä aikaa shift, alt ja f! Sama toiminnallisuus eli automaattinen sisennys saadaan aikaan valitsemalla yläpalkista Source ja sen alta Format.
Tee ohjelma, joka kysyy käyttäjän ikää ja kertoo, onko tämä täysi-ikäinen (eli 18-vuotias tai vanhempi).
Kuinka vanha olet? 12 Et ole vielä täysi-ikäinen!
Kuinka vanha olet? 32 Olet jo täysi-ikäinen!
Tee ohjelma, joka kysyy käyttäjältä luvun ja ilmoittaa, onko se parillinen vai pariton.
Anna luku: 2 Luku 2 on parillinen.
Anna luku: 7 Luku 7 on pariton.
Vihje: Luvun jakojäännös 2:lla kertoo, onko luku parillinen vai pariton. Jakojäännos taas saadaan %
-operaattorilla, tehtäväpohjassa on lisää ohjeita miten parittomuustarkastus hoituu jakojäännöksen avulla.
Jos valittavissa olevia vaihtoehtoja on enemmän kuin kaksi kannattaa käyttää else if
-komentoa, joka on kuten else
, mutta lisäehdolla. else if
tulee if
-ehdon jälkeen. else if
ehtoja voi olla useita.
int luku = 3; if ( luku == 1 ) { System.out.println("Luku on yksi"); } else if ( luku == 2 ) { System.out.println("Lukuna on kaksi"); } else if ( luku == 3 ) { System.out.println("Kolme lienee lukuna!"); } else { System.out.println("Aika paljon!"); }
Kolme lienee lukuna!
Luetaan ylläoleva esimerkki: 'Jos luku on yksi, tulosta "Luku on yksi", muuten jos luku on kaksi, tulosta "Lukuna on kaksi", muuten jos lukuna on kolme, tulosta "Kolme lienee lukuna". Muulloin, tulosta "Aika paljon!"'.
Merkkijonoja, eli tekstejä, ei voi vertailla yhtäsuuri kuin (==) operaatiolla. Merkkijonojen vertailuun käytetään erillistä equals
-komentoa, joka liittyy aina verrattavaan merkkijonoon.
String teksti = "kurssi"; if ( teksti.equals("marsipaani") ) { System.out.println("Teksti-muuttujassa on teksti marsipaani."); } else { System.out.println("Teksti-muuttujassa ei ole tekstiä marsipaani."); }
Komento equals
liitetään aina siihen verrattavaan tekstimuuttujaan, "tekstimuuttuja piste equals teksti". Tekstimuuttujaa voidaan myös verrata toiseen tekstimuuttujaan.
String teksti = "kurssi"; String toinenTeksti = "pursi"; if ( teksti.equals(toinenTeksti) ) { System.out.println("Samat tekstit!"); } else { System.out.println("Ei samat tekstit!"); }
Merkkijonoja vertailtaessa on syytä varmistaa että verrattavalla tekstimuuttujalla on arvo. Jos muuttujalla ei ole arvoa, ohjelma tuottaa virheen NullPointerException, joka tarkoittaa ettei muuttujan arvoa ole asetettu tai se on tyhjä (null).
Tee ohjelma, joka kysyy käyttäjältä kaksi kokonaislukua ja tulostaa niistä suuremman. Jos luvut ovat yhtä suuret, ohjelma huomaa myös tämän.
Esimerkkitulostuksia:
Anna ensimmäinen luku: 5 Anna toinen luku: 3 Suurempi luku: 5
Anna ensimmäinen luku: 5 Anna toinen luku: 8 Suurempi luku: 8
Anna ensimmäinen luku: 5 Anna toinen luku: 5 Luvut ovat yhtä suuret!
Tee ohjelma, joka ilmoittaa kurssiarvosanan seuraavan taulukon mukaisesti.
pistemäärä | arvosana |
---|---|
0–29 | hylätty |
30–34 | 1 |
35–39 | 2 |
40–44 | 3 |
45–49 | 4 |
50–60 | 5 |
Esimerkkitulostuksia:
Anna pisteet [0-60]: 37 Arvosana: 2
Anna pisteet [0-60]: 51 Arvosana: 5
Valinnan ehto voi olla myös monimutkaisempi, yksittäisistä loogisten operaatioiden avulla koostettu ehto. Loogisia operaatioita ovat:
&&
ehdollinen "ja" (AND)
(tosi1) && (tosi2)
||
ehdollinen "tai" (OR)
(tosi) || (epätosi)
tai (tosi) || (tosi)
!
negaatio (not)
!(epätotta)
int vuosi = 2012; int kuukausi = 9; System.out.println("Tällä hetkellä käynnistyy kurssi:"); if ( vuosi == 2012 && kuukausi == 9 ) { System.out.println("Ohjelmoinnin perusteet"); } if ( vuosi == 2012 && kuukausi == 11 ) { System.out.println("Ohjelmoinnin jatkokurssi"); }
Tällä hetkellä käynnistyy kurssi: Ohjelmoinnin perusteet
System.out.println("Onkohan luku väliltä 5-10: "); int luku = 7; if ( luku > 4 && luku < 11 ) { System.out.println("On! :)"); } else { System.out.println("Ei ollut :(") }
Onkohan luku väliltä 5-10: On! :)
System.out.println("Onkohan luku 1, 50 tai 100: "); int luku = 50; if ( luku == 1 || luku == 50 || luku == 100 ) { System.out.println("On! :)"); } else { System.out.println("Ei ollut :(") }
Onkohan luku 1, 50 tai 100: On! :)
Monimutkaisten ehtojen muodostamisessa tarvitaan usein sulkuja:
int luku = 99; if ( (luku > 0 && luku < 10) || luku > 100 ) { System.out.println("luku oli joko yhden ja yhdeksän väliltä tai yli sata"); } else { System.out.println("luku oli 0 tai pienempi tai väliltä 10-99"); }
Tee ohjelma, joka kysyy käyttäjän iän ja tarkistaa, että se on mahdollinen (ainakin 0 ja korkeintaan 120).
Kuinka vanha olet? 10 OK
Kuinka vanha olet? 55 OK
Kuinka vanha olet? -3 Mahdotonta!
Kuinka vanha olet? 150 Mahdotonta!
Tee ohjelma, joka tunnistaa seuraavat käyttäjät:
tunnus | salasana |
---|---|
aleksi | tappara |
elina | kissa |
Ohjelma näyttää käyttäjälle henkilökohtaisen viestin tai ilmoittaa, jos tunnus tai salasana on väärin.
Anna tunnus: aleksi Anna salasana: tappara Olet kirjautunut järjestelmään
Anna tunnus: elina Anna salasana: kissa Olet kirjautunut järjestelmään
Anna tunnus: aleksi Anna salasana: jokerit Virheellinen tunnus tai salasana!
Vuosi on karkausvuosi, jos se on jaollinen 4:llä. Kuitenkin jos vuosi on jaollinen 100:lla, se on karkausvuosi vain silloin, kun se on jaollinen myös 400:lla.
Tee ohjelma, joka tarkistaa, onko vuosi karkausvuosi.
Anna vuosi: 2011 Vuosi ei ole karkausvuosi.
Anna vuosi: 2012 Vuosi on karkausvuosi.
Anna vuosi: 1800 Vuosi ei ole karkausvuosi.
Anna vuosi: 2000 Vuosi on karkausvuosi.
Valintakomennon avulla saamme ohjelman toimintaan mukaan ehdollisuutta, eli esim. jos käyttäjätunnus ja salasana ovat oikein, päästetään käyttäjä kirjautumaan ohjelmaan ja muuten ei.
Ehdollisuuden lisäksi tarvitsemme toistoa: käyttäjätunnusta ja salasanaa pitää pystyä kysymään uudelleen niin kauan kunnes oikea käyttäjätunnus/salasana-pari on annettu.
Yksinkertaisin toiston muoto on ikuinen toisto. Seuraava ohjelma tulostaa merkkijonoa osaan ohjelmoida! ikuisesti eli "äärettömän monta kertaa":
while ( true ) { System.out.println("osaan ohjelmoida!"); }
Komento while( true )
saa sen aikaan, että siihen liittyvää lohkoa, eli {}
:lla ympäröityjä komentoja suoritetaan äärettömän monta kertaa.
Ikuinen toisto ei yleensä ole se mitä halutaan. Toisto voidaan keskeyttää esim. komennolla break
.
while ( true ) { System.out.println("osaan ohjelmoida!"); System.out.print("jatketaanko (ei lopettaa)? "); String komento = lukija.nextLine(); if ( komento.equals("ei") ) { break; } } System.out.println("kiitos ja kuulemiin.");
Nyt toisto etenee siten että ensin tulostuu osaan ohjelmoida! ja tämän jälkeen ohjelma kysyy käyttäjältä jatketaanko vielä. Jos käyttäjä vastaa ei, suoritetaan komento break
jonka ansiosta toisto lopetetaan ja suoritetaan komento joka tulostaa kiitos ja kuulemiin.
osaan ohjelmoida! jatketaanko (ei lopettaa)? joo osaan ohjelmoida! jatketaanko (ei lopettaa)? jawohl osaan ohjelmoida! jatketaanko (ei lopettaa)? ei kiitos ja kuulemiin.
Toiston sisällä voi tehdä erilaisia asioita. Seuraavassa yksinkertainen laskin. Laskin kysyy käyttäjältä komentoa. Komennossa lopetus suoritetaan break
ja toisto loppuu. Tämän jälkeen kysytään kahta lukua. Jos komento oli summa lasketaan lukujen summa ja tulostetaan se. Jos komento oli erotus toimitaan vastaavasti. Muussa tapauksessa ilmoitetaan että komento on tuntematon.
System.out.println("tervetuloa käyttämään laskinta"); while ( true ) { System.out.print("anna komento (summa, erotus, lopetus): "); String komento = lukija.nextLine(); if ( komento.equals("lopetus") ) { break; } System.out.print("anna luvut "); int eka = Integer.parseInt(lukija.nextLine()); int toka = Integer.parseInt(lukija.nextLine()); if ( komento.equals("summa") ) { int summa = eka+toka; System.out.println( "lukujen summa " + summa ); } else if ( komento.equals("erotus") ) { int erotus = eka-toka; System.out.println( "lukujen erotus " + erotus ); } else { System.out.println( "tuntematon komento" ); } } System.out.println("kiitos ja kuulemiin.");
Screencast joka näyttää miten ohjelma syntyy:
Tässä tehtävässä luodaan ohjelma joka kyselee käyttäjältä salasanaa. Jos salasana menee oikein, nähdään salainen viesti.
Anna salasana: nauris Väärin! Anna salasana: lanttu Väärin! Anna salasana: porkkana Oikein! Salaisuus on: znvavbfgv grugl!
Toteutetaan ohjelma kolmessa askeleessa.
Testipalvelimelta tulevaan ohjelmarunkoon on määritelty muuttuja String salasana
, jolle on asetettu arvoksi porkkana
-- älä muuta tätä salasanaa. Toteuta lisätoiminnallisuus, jossa ohjelma kysyy käyttäjältä salasanaa ja vertailee sitä muuttujassa salasana
olevaan arvoon. Muista mitä erityistä merkkijonojen vertailussa on!
Anna salasana: nauris Väärin!
Anna salasana: porkkana Oikein!
Anna salasana: bataatti Väärin!
Muokkaa ohjelmaa siten, että se kysyy salasanaa kunnes käyttäjä syöttää oikean salasanan. Toteuta salasanan jatkuva kysyminen while-true
-toistolausekkeen avulla. Toistolausekkeesta pääsee pois, jos ja vain jos käyttäjän syöttämä salasana on sama kuin muuttujassa salasana
oleva arvo.
Anna salasana: nauris Väärin! Anna salasana: lanttu Väärin! Anna salasana: porkkana Oikein!
Lisää ohjelmaan oma salainen viestisi joka näytetään kun käyttäjä kirjoittaa salasanan oikein. Se voi olla mitä tahansa!
Anna salasana: nauris Väärin! Anna salasana: lanttu Väärin! Anna salasana: porkkana Oikein! Salaisuus on: znvavbfgv grugl!
Ylläoleva salaisuus on salattu käyttäen Rot13-algoritmia. Toteutamme myöhemmin tällä kurssilla oman salausohjelman.
Saat tehtäväpohjan mukana komponentin nimeltään Kuvaaja
. Kuvaaja
piirtää
sille annetuista luvuista kuvaajan. Lukuja annetaan kuvaajalle näin:
Kuvaaja.lisaaNumero(13.0);
Teemme ohjelman, joka piirtää kuvaajan sille annetuista päivittäisistä lämpötiloista.
Tee ohjelma, joka kysyy käyttäjältä liukulukuja
(double
) ja lisää ne kuvaajaan. Käytä
jälleen while-true
-rakennetta.
Huom:double
-luku luetaan seuraavasti double luku = Double.parseDouble( lukija.nextLine() );
Huom2: ohjelmasi toimii "ikuisessa" silmukassa eli on tarkoitus että sen suoritus ei pääty koskaan. Saat ohjelmasi pysähtymään painamalla NetBeansin konsoli-ikkunan oikeassa laidassa olevaa punaista neliöä.
Paranna edellistä ohjelmaasi niin, että lämpötilat jotka ovat alle -30 tai yli 40 jätetään lisäämättä.
Jatketaan NHL-komponentin käyttöä ja tehdään ohjelma jonka avulla käyttäjä voi kysellä haluamiaan tilastotietoja.
Ohjelma rakentuu samaan tapaan kuin edellä oleva laskin-esimerkki. Ohjelman runko on seuraava:
public static void main(String[] args) throws Exception { Scanner lukija = new Scanner(System.in); System.out.println("NHL-tilastopalvelu"); while ( true ) { System.out.println(""); System.out.print("komento (pisteet, maalit, syotot, jaahyt, pelaaja, joukkue, lopeta): "); String komento = lukija.nextLine(); if ( komento.equals("lopeta")) { break; } if ( komento.equals("pisteet")) { // tulosta 10 parasta pisteiden tekijää } else if ( komento.equals("maalit")) { // tulosta 10 parasta maalintekijää } else if ( komento.equals("syotot")) { // tulosta 10 parasta syöttäjää } else if ( komento.equals("jaahyt")) { // tulosta 10 eniten jäähyjä saanutta } else if ( komento.equals("pelaaja")) { // kysy käyttäjältä kenen tiedot halutaan tulostaa ja tulosta ne } else if ( komento.equals("joukkue")) { // kysy käyttäjältä minkä joukkueen tilastot halutaan ja tulosta ne // huom: tulostus tulee tapahtua pisteidentekojärjestyksessä (eli eniten pisteitä tehneet ensin) } } }
Ohjelma siis pyytää käyttäjältä komentoa ja suorittaa sitten komentoa vastaavan toimenpiteen. Komennot ovat pisteet, maalit, syotot, jaahyt, pelaaja, joukkue, lopeta. Huomaa että komentojen nimissä ä ja ö on korvattu a:lla ja o:lla.
Ohjelmarunkoon on merkattu kommentein ne kohdat jotka sinun tulee täydentää.
Seuraavassa esimerkki miten ohjelman tulisi toimia:
NHL-tilastopalvelu komento (pisteet, maalit, syotot, jaahyt, pelaaja, joukkue): syotot Henrik Sedin VAN 43 11 + 38= 49 36 Erik Karlsson OTT 43 6 + 35= 41 24 Claude Giroux PHI 36 18 + 30= 48 16 Pavel Datsyuk DET 41 13 + 30= 43 10 Brian Campbell FLA 42 3 + 30= 33 4 Daniel Sedin VAN 42 18 + 29= 47 32 Jason Pominville BUF 41 14 + 29= 43 8 Nicklas Backstrom WSH 38 13 + 29= 42 22 Joffrey Lupul TOR 41 19 + 28= 47 36 Evgeni Malkin PIT 33 16 + 28= 44 30 komento (pisteet, maalit, syotot, jaahyt, pelaaja, joukkue): pelaaja kuka pelaaja: Jokinen Olli Jokinen CGY 43 12 + 21= 33 32 Jussi Jokinen CAR 40 4 + 19= 23 30 komento (pisteet, maalit, syotot, jaahyt, pelaaja, joukkue): joukkue mikä joukkue: DET Pavel Datsyuk DET 41 13 + 30= 43 10 Johan Franzen DET 41 16 + 20= 36 34 Valtteri Filppula DET 40 14 + 21= 35 10 Henrik Zetterberg DET 41 8 + 24= 32 14 // lisää pelaajien nimiö komento (pisteet, maalit, syotot, jaahyt, pelaaja, joukkue): lopeta
Huom: kun suoritat ohjelman ensimmäisen kerran, saattaa suoritus kestää jonkun aikaa (internetissä oleva palvelin voi olla lepotilassa ja sen herääminen kestää jonkin aikaa). Tämänjälkeisillä kerroilla suoritus on nopea.
Olemassaolevan muuttujan arvoa halutaan usein muuttaa. Tämä onnistuu tavallisen sijoituslausekkeen avulla. Seuraavassa muuttujan ika
arvoa kasvatetaan yhdellä:
int ika = 1; System.out.println( ika ); // tulostuu 1 ika = ika + 1; // ika:n uusi arvo on ika:n vanha arvo plus yksi System.out.println( ika ); // tulostuu 2
Komento ika = ika + 1
siis kasvattaa muuttujan ika
arvoa yhdellä. Muuttujan arvon kasvattaminen yhdellä onnistuu myös seuraavasti:
int ika = 1; System.out.println( ika ); // tulostuu 1 ika++; // tarkoittaa samaa kuin ika = ika + 1; System.out.println( ika ); // tulostuu 2
Toinen esimerkki:
int pituus = 100; System.out.println( pituus ); // tulostuu 100 pituus = pituus - 50; System.out.println( pituus ); // tulostuu 50 pituus = pituus * 2; System.out.println( pituus ); // tulostuu 100 pituus = pituus / 4; System.out.println( pituus ); // tulostuu 25 pituus--; // sama kuin pituus = pituus-1; System.out.println( pituus ); // tulostuu 24
Tee ohjelma, joka kysyy käyttäjältä kolme lukua ja tulostaa niitten summan. Tee ohjelmastasi seuraavan muotoinen:
Scanner lukija = new Scanner(System.in); int summa = 0; int luettu; // KIRJOITA OHJELMA TÄHÄN // ÄLÄ KÄYTÄ MUITA MUUTTUJIA KUIN lukija, summa JA luettu! System.out.println("Summa: "+summa);
Anna ensimmäinen luku: 3 Anna toinen luku: 6 Anna kolmas luku: 12 Summa: 21
Tee ohjelma, joka lukee käyttäjältä lukuja ja tulostaa niiden summan. Ohjelma lopettaa kyselemisen kun syötetään luku 0. Käytä seuraavaa pohjaa, jonka saat myös palautusautomaatilta:
Scanner lukija = new Scanner(System.in); int summa = 0; System.out.print("Anna lukuja, nolla lopettaa: "); while (true) { int luettu = Integer.parseInt(lukija.nextLine()); if (luettu == 0) { break; } // TEE JOTAIN TÄÄLLÄ System.out.println("Summa nyt: "+summa); } System.out.println("Summa lopussa: "+summa);
Ohjelman tulee toimia seuraavasti:
Anna lukuja, nolla lopettaa: 3 Summa nyt: 3 2 Summa nyt: 5 1 Summa nyt: 6 1 Summa nyt: 7 0 Summa lopussa: 7
Opimme aiemmin toteuttamaan while(true)
-komennon avulla ohjelman, joka toistaa tiettyä komentosarjaa komennon break
suorittamiseen asti.
Komento break
ei ole ainoa tapa lopettaa toistoa. Toistokomennon yleinen muoto on while (ehto)
, jossa ehtona voi olla mikä tahansa totuusarvoinen lauseke, eli ehto voi olla täsmälleen samaa muotoa kuin if
-komentojen ehdot.
Seuraavassa esimerkissä tulostetaan luvut 1, 2, ..., 10. Kun luku
-muuttuja saa arvokseen yli 10, while
-ehto ei ole enää voimassa ja toistaminen lopetetaan.
int luku = 1; while (luku < 11) { System.out.println(luku); luku++; // luku++ tarkoittaa samaa kuin luku = luku + 1 }
Lue ylläoleva "niin pitkään kuin luku on pienempi kuin 11, tulosta luku ja kasvata lukua yhdellä".
Ylläolevassa koodissa ehdossa olevaa muuttujaa luku
kasvatettiin jokaisella kierroksella
yhdellä. Päivitys voi olla mikä tahansa, eli ehdossa olevan muuttujan ei tarvitse suinkaan aina kasvaa yhdellä, esim:
int luku = 1024; while (luku >= 1) { System.out.println(luku); luku = luku / 2; }
Screencast aiheesta:
Jos ohjelmassasi on käytössä esim. muuttuja String sukunimi;
, ei koko muuttujan nimeä tarvitse välttämättä joka kerta kirjoittaa kokonaan. Kokeile mitä tapahtuu kun kirjoitat s
ja sen jälkeen painat yhtäaikaa ctrl ja välilyönti.
HUOM: laitoksen koneilla automaattinen täydennys saadaan aikaan painamalla yhtä aikaa ctrl, alt ja välilyönti.
Vastaavalla tavalla NetBeans osaa täydentää muitakin nimiä, esim. komennon while
aikaansaamiseksi riittää kirjoittaa w ja painaa ctrl+välilyönti...
Muista että saat ruudulle tekstin System.out.println("") kirjoittamalla sout ja painamalla tabulaattoria eli q:n vasemmalla puolella olevaa näppäintä
Tee seuraavat tehtävät while-komennon avulla:
Tee ohjelma, joka tulostaa kokonaisluvut väliltä 1–100.
Ohjelman tulostus on seuraava:
1 2 3 (välissä paljon rivejä) 98 99 100
Tee ohjelma, joka tulostaa kokonaisluvut väliltä 100–1.
Ohjelman tulostus on seuraava:
100 99 98 (välissä paljon rivejä) 3 2 1
Vihje: aseta toistossa käytettävän apumuuttujan arvoksi aluksi 100 ja vähennä muuttujan arvoa yhdellä toiston sisällä.
Tee ohjelma, joka tulostaa parilliset kokonaisluvut väliltä 2–100.
2 4 6 (välissä paljon rivejä) 96 98 100
Kirjoita ohjelma, joka tulostaa kokonaisluvut 1:stä käyttäjän antamaan lukuun asti.
Mihin asti? 3 1 2 3
Mihin asti? 5 1 2 3 4 5
Vihje: käyttäjältä lukemasi luku toimii nyt whilen lopetusehdon ylärajana. Muista että Javassa a <= b
tarkoittaa a pienempi tai yhtä suuri kuin b.
Kirjoita ohjelma, joka kysyy käyttäjältä ensimmäisen ja viimeisen luvun ja tulostaa niiden välissä olevat luvut.
Ensimmäinen: 5 Viimeinen: 8 5 6 7 8
Jos ensimmäinen on suurempi kuin viimeinen ei tulostu mitään:
Ensimmäinen: 16 Viimeinen: 12
Huom! muista että ala- ja yläraja voivat olla myös negatiivisia!
Koska vanhan muuttujan arvon muuttaminen on hyvin yleinen operaatio, on Javassa sitä varten erityiset sijoitusoperaatiot.
int pituus = 100; pituus += 10; // sama kuin pituus = pituus + 10; pituus -= 50; // sama kuin pituus = pituus - 50;
Olemassaolevan muuttujan arvoa muuttava sijoitusoperaatio merkitään muuttuja muutostyyppi= muutos
, esimerkiksi muuttuja += 5
. Huomaa, että muuttujan tyyppi pitää aina kertoa ennen kuin sille voidaan asettaa arvo. Muuttuja tulee siis aina esitellä ennen kuin se on käytettävissä. Muuttujan esittely tapahtuu kertomalla muuttujan tyyppi ja nimi.
Seuraava esimerkki ei toimi, sillä muuttujan pituus
tyyppiä ei ole kerrottu.
pituus = pituus + 100; // ei toimi! pituus += 100; // ei toimi!
Kun tyyppi on kerrottu, laskutkin toimivat oikein.
int pituus = 0; pituus = pituus + 100; pituus += 100; // muuttujan pituus arvo on 200
Myös muille kuin yhteen- ja vähennyslaskuille on Javassa vastaavat sijoitusoperaatiot.
int pituus = 100; pituus *= 10; // sama kuin pituus = 10 * pituus; pituus /= 100; //sama kuin pituus = pituus / 100; pituus %= 3; // sama kuin pituus = pituus % 3; // muuttujan pituus arvo 1
Usein ohjelmissa esiintyy toisto jonka aikana muuttujaan lasketaan jokin toistosta riippuvainen arvo. Seuraava ohjelma laskee tulon 4*3 hieman kömpelöllä tavalla eli summana 3+3+3+3:
int tulos = 0; int i = 0; while ( i < 4 ) { tulos = tulos + 3; i++; // tarkoittaa samaa kuin i = i+1; }
Alussa tulos = 0
. Toistossa muuttujan arvo nousee joka kierroksella 3:lla. Ja koska toistoja on 4, on lopulta muuttujan arvona siis 3*4.
Käyttämällä yllä esiteltyä sijoitusoperaattoria, sama saadaan aikaan seuraavasti:
int tulos = 0; int i = 0; while ( i < 4 ) { tulos += 3; // tämä on siis sama kuin tulos = tulos + 3; i++; // tarkoittaa samaa kuin i = i+1; }
Tee ohjelma, joka laskee summan 1+2+3+...+n, missä n on käyttäjän syöttämä luku.
Esimerkkitulostuksia:
Mihin asti? 3 Summa on 6
Edellisessä esimerkissä laskettiin 1+2+3 = 6
Mihin asti? 7 Summa on 28
Ja nyt laskettiin 1+2+3+4+5+6+7 = 28
Vihje: Tee ohjelma while
-komennon avulla. Käytä ohjelmassasi apumuuttujaa toistokertojen muistamiseen. Lisää jokaisella toistokerralla toistokerrat muistavan muuttujan arvo apumuuttujaan johon lasket summan arvon.
Muuta edellistä tehtävää siten, että käyttäjä määrää summan laskemisen aloituskohdan. Voit olettaa, että käyttäjä antaa ensin pienemmän luvun ja sitten suuremman luvun.
Esimerkkitulostuksia:
Ensimmäinen: 3 Viimeinen: 5 Summa on 12
Ensimmäinen: 2 Viimeinen: 8 Summa on 35
Tee ohjelma, joka laskee luvun n kertoman. Kertoma n! lasketaan kaavalla 1*2*3*...*n. Esimerkiksi 4! = 1*2*3*4 = 24. Lisäksi on määritelty, että 0! = 1.
Esimerkkitulostuksia:
Anna luku: 3 Kertoma on 6
Anna luku: 10 Kertoma on 3628800
Tee ohjelma, joka laskee summan 20+21+22+...+2n,
missä n on käyttäjän syöttämä luku.
Merkintä 2i tarkoittaa luvun 2 korottamista potenssiin i,
esimerkiksi 24 = 2*2*2*2 = 16.
Javassa merkintä ab ei ole käytössä,
mutta potenssin voi laskea komennolla
Math.pow(luku, potenssi)
. Huom. komento palauttaa double
-tyyppisen eli liukulukutyyppisen luvun. Liukulukutyyppisen luvun voi muuttaa kokonaisluvuksi ( int
-tyyppiseksi ) seuraavasti: int tulos = (int) Math.pow(2, 3)
. Lasku asettaa muuttujaan tulos laskun 2 potenssiin 3 arvon.
Esimerkkitulostuksia:
Anna luku: 3 Tulos on 15
Anna luku: 7 Tulos on 255
Yksi suosituimmista ohjelmointivirheistä toistolauseissa on tehdä vahingossa ikuinen silmukka. Seuraavassa yritetään tulostaa ruudulle 10 kertaa "En enää ikinä ohjelmoi ikuista silmukkaa":
int i = 0; while( i<10 ) { System.out.println("En enää ikinä ohjelmoi ikuista silmukkaa"); }
Toistokertojen määrää kontrolloiva muuttuja i
on aluksi 0 ja toistoja on tarkoitus tehdä niin kauan kuin i<10
. Käy kuitenkin hieman hassusti: muuttujan i
arvoa ei muuteta missään, joten toistoehto pysyy ikuisesti totena.
Olemme tehneet peräkkäin jo muutamia toistotehtäviä, joissa toistoehto on suunnilleen muotoa:
int i = 1; while ( i < 10 ) { // ... i++; }
Ylläolevassa rungossa muuttuja i
muistaa toistokertojen määrän, ja lopetusehto perustuu i
:n vertailuun.
Muistellaan taas toiston lopetusta. Toiston lopetuksen ei tarvitse perustua toistokertojen laskemiseen. Katsotaan esimerkkiä jossa kysytään käyttäjän ikää. Jos ikä ei ole välillä 5-85, annetaan huomautus ja kysytään ikä uudelleen. Toistolauseen while-lauseen ehto voi siis olla mitä tahansa totuusarvon tuottavaa.
System.out.println("ikäsi: "); int ika = Integer.parseInt(lukija.nextLine()); while( ika < 5 || ika > 85 ) { // ikä pienempi kuin 5 TAI suurempi kuin 85 System.out.println("Valehtelet"); if ( ika < 5 ) { System.out.println("Olet niin nuori ettet osaa kirjoittaa"); } else if ( ika > 85 ) { System.out.println("Olet niin vanha ettet osaa käyttää tietokonetta"); } System.out.println("syötä ikäsi uudelleen: "); ika = Integer.parseInt(lukija.nextLine(); } System.out.println("Ikäsi on siis "+ ika);
Ohjelman olisi voinut tehdä myös vanhaa tuttua while(true)
-rakennetta käyttäen:
System.out.println("ikäsi: "); while( true ) { int ika = Integer.parseInt(lukija.nextLine()); if ( ika >= 5 && ika <= 85 ) { // ikä 5:n JA 85:n välillä break; // lopetetaan toisto } System.out.println("Valehtelet"); if ( ika < 5 ) { System.out.println("Olet niin nuori ettet osaa kirjoittaa"); } else { // ikä siis yli 85 System.out.println("Olet niin vanha ettet osaa käyttää tietokonetta"); } System.out.println("syötä ikäsi uudelleen: "); } System.out.println("Ikäsi on siis "+ ika);
Tämä tehtäväsarja muodostaa yhden isomman ohjelman, jonka toiminnallisuus toteutetaan pienissä paloissa. Jos et tee tehtäväsarjaa loppuun asti, voit lähettää sen tarkastettavaksi vajaatekoisenakin. Tämä onnistuu painamalla testausnapin oikealla puolella olevasta "submit"-napista eli pienestä ylöspäinosoittavasta nuolesta. Vaikka palautusautomaatti valittaakin vielä tekemättä olevien tehtävänosien testeistä, kirjautuvat jo tekemiesi osien pisteet.
Huom: nyt (ja jatkossa) jokainen isomman tehtävän "alitehtävä" (kuten 36.1) on saman arvoinen tehtävä kuin alikohdaton tehtävä. Tehtävä 36 siis vastaa viittä normaalia tehtävää.
Tee ohjelma, joka kysyy käyttäjältä lukuja (ohjelma tulostaa käyttäjälle aluksi "Syötä luvut:"), kunnes käyttäjä antaa luvun -1. Kun käyttäjä syöttää luvun -1, ohjelma tulostaa "Kiitos ja näkemiin!" ja päättyy.
Syötä luvut: 5 2 4 -1 Kiitos ja näkemiin!
Laajenna edellistä ohjelmaa siten, että ohjelma ilmoittaa käyttäjän syöttämien lukujen summan. (Lukua -1 ei lasketa mukaan.)
Syötä luvut: 5 2 4 -1 Kiitos ja näkemiin! Summa: 11
Laajenna edellistä ohjelmaa siten, että ohjelma ilmoittaa myös käyttäjien antamien lukujen lukumäärän. (Lukua -1 ei lasketa mukaan.)
Syötä luvut: 5 2 4 -1 Kiitos ja näkemiin! Summa: 11 Lukuja: 3
Muuta edellistä ohjelmaa siten, ohjelma ilmoittaa lukujen keskiarvon. (Lukua -1 ei lasketa mukaan.)
Syötä luvut: 5 2 4 -1 Kiitos ja näkemiin! Summa: 11 Lukuja: 3 Keskiarvo: 3.666666666666
Laajenna edellistä ohjelmaa siten, että ohjelma ilmoittaa parillisten ja parittomien lukujen määrän. (Lukua -1 ei lasketa mukaan.)
Syötä luvut: 5 2 4 -1 Kiitos ja näkemiin! Summa: 11 Lukuja: 3 Keskiarvo: 3.666666666666 Parillisia: 2 Parittomia: 1
Edeltävissä tehtävissä tehtiin oikeastaan ainoastaan yksi ohjelma, mutta ohjelman rakentaminen tapahtui hyvin pienissä paloissa. Tämä on ehdottoman suositeltava tapa AINA kun ohjelmoit.
Eli kun teet ohjelmaa, oli se sitten harjoitustehtävä tai oma projektisi, kannattaa edetä hyvin pienissä paloissa. Älä koskaan yritä ratkaista koko ongelmaa kerralla. Aloita jollain helpolla asialla jonka tiedät varmasti osaavasi. Esim. edellisessä tehtäväsarjassa keskityttiin aluksi pelkästään siihen, että osataan pysäyttää ohjelma käyttäjän syöttäessä luvun -1. Kun yksi ohjelman osa on saatu toimimaan, voidaan siirtyä ratkaisemaan jotain seuraavaa varsinaisen ongelman osaongelmaa.
Osa kurssin tehtäviä on edellisten tapaan valmiiksi osiin pilkottuja. Usein osat pitää vielä pilkkoa ohjelmoinnin kannalta vieläkin pienempiin paloihin. Kannattaa tehdä siten, että suoritat ohjelman lähes jokaisen uuden koodirivin jälkeen. Tällöin varmistat, että ratkaisu on etenemässä haluttuun suuntaan.
Lisätietoa: toisto for-lauseen avulla
Edellä esitellyn while-toistolauseen lisäksi Javassa toiston voi hoitaa myös for-toistolauseen avulla. Varsinaisesti rupeamme käyttämään for:ia vasta hieman myöhemmin kurssilla, mutta jo tässä vaiheessa mainittakoon, että toistolause
int i = 0; while ( i ;lt& 10 ) { System.out.println( i ); i++; }
tehtäisiin for:in avulla seuraavasti:
for ( i = 0; i < 10 ; i ++ ) { System.out.println( i ); }
eli for-komento sisältää samalla rivillä indeksimuuttujan alustuksen, toistoehton ja indeksimuuttujan kasvatuksen puolipistein eroteltuna. Voit unohtaa for:in olemassaolon toistaiseksi jos et halua sekoittaa päätäsi sen olemassaololla.
Olemme käyttäneet useita erilaisia komentoja Javassa: sijoitusta, laskutoimituksia, vertailuja, if:iä ja whileä. Ruudulle tulostaminen on tehty "komentoa" System.out.println()
käyttäen. Kahden luvun maksimi osataan laskea "komennolla" Math.max()
. Tuttuja ovat myös lukija.nextLine()
ja sen kanssa usein nähty Integer.parseInt()
.
Huomaamme, että jälkimmäinen joukko edellä lueteltuja komentoja poikkeaa if:istä ja while:stä ym. siinä, että komennon perässä on sulut ja joskus sulkujen sisällä komennolle annettava syöte. "Sulkuihin päättyvät" eivät oikeastaan olekaan komentoja vaan metodeja.
Teknisesti ottaen metodi tarkoittaa koodinpätkää, jota voi kutsua muualta ohjelmakoodista. Koodirivi System.out.println("olen metodille annettava parametri!")
siis tarkoittaa, että kutsutaan metodia, joka suorittaa ruudulle tulostamisen. Metodin suorituksen jälkeen palataan siihen kohtaa missä ennen metodikutsua oltiin menossa. Metodille suluissa annettua syötettä kutsutaan metodin parametriksi.
Parametrin lisäksi metodilla voi olla paluuarvo. Esim. tuttu koodinpätkä:
int luku = Integer.parseInt( lukija.nextLine() );
sisältää kaksi metodikutsua. Ensin kutsutaan sisempänä olevaa metodia lukija.nextLine
. Metodilla on paluuarvonaan käyttäjän syöttämä merkkijono. Seuraavaksi
kutsutaan metodia Integer.parseInt
. Metodikutsun parametrina on merkkijono jonka lukija.nextLine
:n kutsu palautti ja metodin paluuarvona on merkkijonoa vastaava kokonaisluku.
Metodin nimeen näyttää liittyvän piste, esim. lukija.nextLine()
. Oikeastaan tässä metodin nimi onkin pisteen oikeanpuoleinen osa, eli
nextLine()
. Pisteen vasemmanpuoleinen osa, eli tässä lukija
kertoo kenen metodista on kyse.
Eli kyseessä on lukijan metodi nextLine. Opimme hiukan myöhemmin tarkemmin mistä tässä pisteen vasemmanpuoleisessa
osassa on kyse. Tarkka lukija tietysti huomaa, että System.out.println()
:ssa on "kaksi pistettä". Metodin nimi tässä
on println, ja System.out
on se kenen metodista on kyse. Karkeasti ottaen System.out
tarkoittaa koneen näyttöä.
Tähän mennessä käyttämämme metodit ovat kaikki olleet Javan valmiita metodeita. Nyt opettelemme tekemään omia metodeita.
Olemme tähän mennessä ohjelmoineet ohjelmamme siten, että kaikki tapahtuu yhdessä jatkumossa ja koodia luetaan ylhäältä alas.
Edellä mainittiin että "metodi tarkoittaa koodinpätkää, jota voi kutsua muualta ohjelmakoodista". Javan valmiita metodeja on käytetty jo oikeastaan ensimmäisestä ohjelmasta lähtien.
Javan valmiiden metodien käytön lisäksi ohjelmoija voi kirjoittaa itse metodeja joita sovellus kutsuu. Oikeastaan on hyvin poikkeuksellista jos ohjelmassa ei ole yhtään itse kirjoitettua metodia. Tästälähtien lähes jokainen kurssilla tehty ohjelma sisältääkin itsekirjoitettuja metodeja.
Ohjelmarunkoon metodit kirjoitetaan main:in aaltosulkeiden ulkopuolelle mutta kuitenkin "uloimmaisten" aaltosulkeiden sisäpuolelle, joko mainin ylä- tai alapuolelle
import java.util.Scanner; public class OhjelmaRunko { // omia metodeja tänne public static void main(String[] args) { Scanner lukija = new Scanner(System.in); // ohjelmakoodi } // omia metodeja tai tänne }
Luodaan metodi tervehdi
.
public static void tervehdi() { System.out.println("Terveiset metodimaailmasta!"); }
Ja asetetaan se metodeille kuuluvalle paikalle.
import java.util.Scanner; public class OhjelmaRunko { public static void main(String[] args) { Scanner lukija = new Scanner(System.in); // ohjelmakoodi } // omat metodit public static void tervehdi() { System.out.println("Terveiset metodimaailmasta!"); } }
Metodin määrittely sisältää kaksi osaa. Metodimäärittelyn ensimmäisellä rivillä on metodin nimi eli tervehdi. Nimen vasemmalla puolella tässä vaiheessa määreet public static void
. Metodin nimen sisältävän rivin alla on aaltosulkeilla erotettu koodilohko, jonka sisälle kirjoitetaan metodin koodi, eli ne komennot jotka metodia kutsuttaessa suoritetaan. Metodimme tervehdi
ei tee muuta kuin kirjoittaa rivillisen tekstiä ruudulle.
Itsekirjoitetun metodin kutsu on helppoa, kirjoitetaan metodin nimi ja perään sulut ja puolipiste. Seuraavassa main eli pääohjelma kutsuu metodia ensin kerran ja sen jälkeen useita kertoja.
import java.util.Scanner; public class OhjelmaRunko { public static void main(String[] args) { Scanner lukija = new Scanner(System.in); // ohjelmakoodi System.out.println("Kokeillaan pääsemmekö metodimaailmaan:"); tervehdi(); System.out.println("Näyttää siltä, kokeillaan vielä:"); tervehdi(); tervehdi(); tervehdi(); } // omat metodit public static void tervehdi() { System.out.println("Terveiset metodimaailmasta!"); } }
Ohjelman suoritus saa aikaan seuraavan tulosteen:
Kokeillaan pääsemmekö metodimaailmaan: Terveiset metodimaailmasta! Näyttää siltä, kokeillaan vielä: Terveiset metodimaailmasta! Terveiset metodimaailmasta! Terveiset metodimaailmasta!
Huomionarvoista tässä on koodin suoritusjärjestys. Koodin suoritus etenee siten, että pääohjelman -- eli main:in -- rivit suoritetaan ylhäältä alas yksi kerrallaan. Koodirivin ollessa metodikutsu, mennään suorittamaan metodin koodirivit, jonka jälkeen palataan josta metodin kutsu tapahtui. Tarkemmin ottaen metodikutsun jälkeiselle riville.
Jos ollaan tarkkoja on pääohjelma eli main itsekin metodi. Kun ohjelma käynnistyy, kutsuu käyttöjärjestelmä main:ia. Metodi main on siis ohjelman käynnistyspiste, jonka ylimmältä riviltä ohjelman suoritus lähtee liikkeelle. Ohjelman suoritus loppuu kun päädytään mainin loppuun.
Jatkossa kun esittelemme metodeja, emme erikseen mainitse että niiden täytyy sijaita omalla paikallaan. Metodia ei esimerkiksi voi määritellä toisen metodin sisällä.
Tee metodi tulostaTeksti
, joka tulostaa
tekstin "Alussa olivat suo, kuokka ja Java." sekä rivinvaihdon.
public static void main(String[] args) { tulostaTeksti(); } public static void tulostaTeksti() { // kirjoita koodia tähän }
Ohjelman tulostus:
Alussa olivat suo, kuokka ja Java.
Laajenna edellistä ohjelmaa siten, että pääohjelma kysyy käyttäjältä, montako kertaa teksti tulostetaan eli montako kertaa metodia kutsutaan.
public static void main(String[] args) { // kysy käyttäjältä, montako kertaa teksti tulostetaan // kutsu metodia tulostaTeksti while-komennon avulla useita kertoja } public static void tulostaTeksti() { // kirjoita koodia tähän }
Ohjelman tulostus:
Kuinka monta? 7 Alussa olivat suo, kuokka ja Java. Alussa olivat suo, kuokka ja Java. Alussa olivat suo, kuokka ja Java. Alussa olivat suo, kuokka ja Java. Alussa olivat suo, kuokka ja Java. Alussa olivat suo, kuokka ja Java. Alussa olivat suo, kuokka ja Java.
huom: tulosta kehote Kuinka monta? omalle rivilleen!
Metodista saa huomattavasti monikäyttöisemmän antamalla sille parametreja. Parametrit ovat muuttujia, jotka määritellään metodin ylimmällä rivillä metodin nimen jälkeen olevien sulkujen sisällä. Kun metodia kutsutaan, sen parametreille annetaan arvot kutsuvaiheessa.
Seuraavassa esimerkissä määritellään parametrillinen metodi tervehdi
, jolla on String-tyyppinen parametri nimi
.
public static void tervehdi(String nimi) { System.out.println("Hei " + nimi + ", terveiset metodimaailmasta!"); }
Kutsutaan metodia tervehdi
siten, että parametrin nimi
arvoksi asetetaan ensimmäisellä kutsulla Matti
ja toisella kutsulla Arto
.
public static void main(String[] args) { tervehdi("Matti"); tervehdi("Arto"); }
Hei Matti, terveiset metodimaailmasta! Hei Arto, terveiset metodimaailmasta!
Aivan kuten kutsuttaessa Javan valmista System.out.println()
-metodia, voi oman metodin kutsussa parametrina käyttää monimutkaisempaa ilmausta:
public static void main(String[] args) { String nimi1 = "Antti"; String nimi2 = "Mikkola"; tervehdi( nimi1 + " " + nimi2 ); int ika = 24; tervehdi("Juhana " + ika + " vuotta"); }
Hei Antti Mikkola, terveiset metodimaailmasta! Hei Juhana 24 vuotta, terveiset metodimaailmasta!
Molemmissa tapauksissa metodilla on edelleen vain 1 parametri. Parametrin arvo lasketaan ennen metodin kutsumista. Ensimmäisessä tapauksessa parametrin arvo saadaan merkkijonokatenaationa nimi1 + " " + nimi2
joka siis on arvoltaan Antti Mikkola ja jälkimmäisessä tapauksessa merkkijonokatenaatiosta "Juhana " + ika + " vuotta"
.
Metodille voidaan määritellä useita parametreja. Tällöin metodin kutsussa parametrit annetaan samassa järjestyksessä.
public static void tervehdi(String nimi, String mistaTerveiset) { System.out.println("Hei " + nimi + ", terveiset " + mistaTerveiset); }
String kuka = "Matti"; String terveiset = "Kyröjoelta"; tervehdi(kuka, terveiset); tervehdi(kuka, terveiset + " ja Kumpulasta");
Jälkimmäisessä tervehdi
-funktion kutsussa toinen parametri muodostetaan katenoimalla muuttujaan terveiset
teksti " ja Kumpulasta"
. Tämä suoritetaan ennen varsinaista funktion suoritusta.
Hei Matti, terveiset Kyröjoelta Hei Matti, terveiset Kyröjoelta ja Kumpulasta
main ei ole suinkaan ainoa joka voi kutsua metodeita. Metodit voivat kutsua myös toisiaan. Tehdään metodi tervehdiMontaKertaa
, joka tervehtii käyttäjää useasti metodin tervehdi
avulla:
public static void tervehdi(String nimi) { System.out.println("Hei " + nimi + ", terveiset metodimaailmasta!"); } public static void tervehdiMontaKertaa(String nimi, int kerrat) { int i = 0; while ( i < kerrat ) { tervehdi(nimi); i++; } } public static void main(String[] args) { tervehdiMontaKertaa("Antti", 3); System.out.println("ja"); tervehdiMontaKertaa("Martin", 2); }
Tulostuu:
Hei Antti, terveiset metodimaailmasta! Hei Antti, terveiset metodimaailmasta! Hei Antti, terveiset metodimaailmasta! ja Hei Martin, terveiset metodimaailmasta! Hei Martin, terveiset metodimaailmasta!
Screencast aiheesta:
Tee metodi tulostaTahtia
,
joka tulostaa annetun määrän tähtiä ja rivinvaihdon.
Tee metodi seuraavaan runkoon:
private static void tulostaTahtia(int maara) { // yhden tähden saat tulostettua komennolla // System.out.print("*"); // kutsu tulostuskomentoa n kertaa } public static void main(String[] args) { tulostaTahtia(5); tulostaTahtia(3); tulostaTahtia(9); }
Ohjelman tulostus:
***** *** *********
huom: moniosaisen tehtävät voi palauttaa palvelimelle (painamalla testausnapin oikealla puolella olevaa nappia) vaikka kaikki osat eivät olisikaan tehty. Palvelin valittelee tällöin tekemättömien osien testeistä, tehdyt osat palvelin kirjaa.
Tee metodi tulostaNelio(int sivunpituus)
joka tulostaa neliön käyttäen tulostaTahtia
-metodia. Siis esimerkiksi kutsu tulostaNelio(4)
tulostaa seuraavaa:
**** **** **** ****
Huom: tehtävässä ei riitä että tulostus näyttää oikealta, tulostaNelio-metodin sisällä neliön "rivien" tulostus tulee tehdä tulostaTahtia-metodia käyttäen.
Ohjelmaa tehdessäsi kannattaa varmistaa main:iin kirjoitetun testikoodin avulla että metodit toimivat vaaditulla tavalla.
Tee metodi tulostaSuorakulmio(int leveys, int korkeus)
joka tulostaa suorakulmion käyttäen tulostaTahtia
-metodia. Siis esimerkiksi kutsu tulostaSuorakulmio(17,3)
tulostaa seuraavaa:
***************** ***************** *****************
Tee metodi tulostaKolmio(int koko)
joka tulostaa kolmion käyttäen tulostaTahtia
-metodia. Siis esimerkiksi kutsu tulostaKolmio(4)
tulostaa seuraavaa:
* ** *** ****
Tee metodi tulostaTyhjaa(int koko)
joka tulostaa koko
kappaletta välilyöntejä. Metodi ei tulosta rivinvaihtoa.
Joudut myös joko kopioimaan edellisen tehtävän vastauksestasi metodin tulostaTahtia
tai toteuttamaan sen uudelleen tämän tehtävän tehtäväpohjaan.
Tee metodi tulostaKolmio(int koko)
joka tulostaa kolmion käyttäen tulostaTyhjaa
- ja tulostaTahtia
-metodeja. Siis esimerkiksi kutsu tulostaKolmio(4)
tulostaa seuraavaa:
* ** *** ****
Tee metodi jouluKuusi(int korkeus)
joka tulostaa joulukuusen. Joulukuusi koostuu annetun korkuisesta kolmiosta ja jalasta. Jalka on kaksi tähteä korkea ja kolme tähteä leveä ja se on keskellä kolmion pohjaa.
Kuusi tulee rakentaa käyttämällä tulostukseen metodeja tulostaTyhjaa
ja tulostaTahtia
Esimerkiksi kutsu jouluKuusi(4)
tulostaa seuraavaa:
* *** ***** ******* *** ***
Kutsu jouluKuusi(10)
tulostaa:
* *** ***** ******* ********* *********** ************* *************** ***************** ******************* *** ***
Huom: Korkeuksien jotka ovat alle 3 ei tarvitse toimia!
Tässä tehtävässä luodaan seuraavanlainen numerovisa:
Arvaa luku: 73 Luku on pienempi, tehtyjä arvauksia: 1 Arvaa luku: 22 Luku on suurempi, tehtyjä arvauksia: 2 Arvaa luku: 51 Luku on suurempi, tehtyjä arvauksia: 3 Arvaa luku: 62 Luku on suurempi, tehtyjä arvauksia: 4 Arvaa luku: 68 Luku on suurempi, tehtyjä arvauksia: 5 Arvaa luku: 71 Luku on pienempi, tehtyjä arvauksia: 6 Arvaa luku: 70 Onneksi olkoon, oikein arvattu!
Tehtävänannon mukana tulevassa ohjelmassa tulee mukana komento arvoLuku
, joka arpoo luvun suljetulta väliltä [0, 100] (0 ja 100 ovat myös mahdollisia). Toteuta ohjelma jossa arvataan arvottua lukua kerran. Ohjelman tulee tulostaa joko "Luku on pienempi", "Luku on suurempi" tai "Onneksi olkoon, oikein arvattu!" riippuen käyttäjän antamasta luvusta.
Arvaa luku: 12 Luku on suurempi
Arvaa luku: 66 Luku on pienempi
Arvaa luku: 42 Onneksi olkoon, oikein arvattu!
Lisää ohjelmaan toiminnallisuus jossa arvausta tehdään toistuvasti kunnes käyttäjä syöttää oikean numeron. Huomaa, että sinun tulee arpoa numero komentoa arvoLuku
ennen toistolauseketta. Miksi? Mitä tapahtuu, jos luku arvotaan toistolausekkeen sisällä?
Alla olevassa esimerkissä kutsu komentoon arvoLuku
palautti arvon 83.
Arvaa luku: 55 Luku on suurempi Arvaa luku: 85 Luku on pienempi Arvaa luku: 77 Luku on suurempi Arvaa luku: 81 Luku on suurempi Arvaa luku: 83 Onneksi olkoon, oikein arvattu!
Lisää ohjelmaan kokonaislukutyyppinen muuttuja, jonka avulla pidetään kirjaa tehtyjen arvausten määrästä. Tulosta arvausten määrä aina arvauksen yhteydessä.
Arvaa luku: 55 Luku on suurempi, tehtyjä arvauksia: 1 Arvaa luku: 85 Luku on pienempi, tehtyjä arvauksia: 2 Arvaa luku: 77 Luku on suurempi, tehtyjä arvauksia: 3 Arvaa luku: 81 Luku on suurempi, tehtyjä arvauksia: 4 Arvaa luku: 83 Onneksi olkoon, oikein arvattu!
Kaverisi ohjelmoi käyttöösi Hirsipuu-pelin, jolta puuttuu vielä käyttöliittymätoiminnallisuus. Hirsipuulla on seuraavat metodit:
Käytössäsi on palautusautomaatista tuleva ohjelmarunko, jossa on jo hieman toiminnallisuutta:
Scanner lukija = new Scanner(System.in); Hirsipuu hirsipuu = new Hirsipuu(); System.out.println("************"); System.out.println("* HIRSIPUU *"); System.out.println("************"); System.out.println(""); tulostaValikko(); System.out.println(""); // OHJELMOI TOTEUTUKSESI TÄNNE System.out.println("Kiitos pelistä!");
Rungon lisäksi käytössäsi on metodi tulostaValikko
:
public static void tulostaValikko() { System.out.println(" * valikko *"); System.out.println("lopeta - lopettaa pelin"); System.out.println("tilanne - tulostaa tarkemman tilanteen"); System.out.println("yksittäinen kirjain arvaa annettua kirjainta"); System.out.println("tyhjä rivi tulostaa tämän valikon"); }
Tehtävä toteutetaan pienissä osissa.
Luo ohjelmaan toistolauseke, joka toimii käyttöliittymän pohjana. Lue toistolausekkeen sisällä käyttäjältä komento. Jos komento on "lopeta", poistu toistolauseesta.
Käytä whilen ehtona komentoa hirsipuu.peliKaynnissa()
, eli toisto on muotoa:
while (hirsipuu.peliKaynnissa()) { String komento = lukija.nextLine(); // ... }
Ensi viikolla ymmärrämme mistä tässä hieman erikoiselta näyttävässä toiston lopetusehdossa on tarkkaan ottaen kysymys.
Tässä vaiheessa ohjelman tulisi toimia seuraavasti:
************ * HIRSIPUU * ************ * valikko * lopeta - lopettaa pelin tilanne - tulostaa tarkemman tilanteen yksittäinen kirjain arvaa annettua kirjainta tyhjä rivi tulostaa tämän valikon Anna komento: ei lopeta Anna komento: lopeta Kiitos pelistä!
Jos käyttäjä syöttää merkkijonon "tilanne", tulosta tilanne metodin hirsipuu.tulostaTilanne()
avulla.
************ * HIRSIPUU * ************ * valikko * lopeta - lopettaa pelin tilanne - tulostaa tarkemman tilanteen yksittäinen kirjain arvaa annettua kirjainta tyhjä rivi tulostaa tämän valikon Anna komento: tilanne Et ole vielä tehnyt yhtään arvausta. Käyttämättömät kirjaimet: abcdefghijklmnopqrstuvwxyzåäö Anna komento: lopeta Kiitos pelistä!
Jos käyttäjä kirjoittaa komentona yhden merkin pituisen merkkijonon, eli yksittäisen kirjaimen, käytä syötettä arvaamiseen. Arvaaminen tapahtuu metodilla hirsipuu.arvaa(komento)
. Arvauskomennolla on oma tulostustoiminnallisuus, jolla se tulostaa lisätietoa arvauksen onnistumisesta.
Vihje: komennon toteaminen yksittäiseksi kirjaimeksi onnistuu seuraavasti:
String komento = lukija.nextLine(); if(komento.length() == 1) { // komento on vain yksi kirjain, eli kyseessä on arvaus hirsipuu.arvaa(komento); }
... Anna komento: a Kirjainta a ei löytynyt sanasta. Anna komento: b Kirjainta b ei löytynyt sanasta. Anna komento: c Kirjain c löytyi sanasta! Anna komento: lopeta Kiitos pelistä!
Jos käyttäjä syöttää tyhjän merkkijonon, eli merkkijonon jonka pituus on nolla, kutsu metodia tulostaValikko
. Huomaa, että metodi tulostaValikko
ei liity Hirsipuu-peliin, vaan omaan ohjelmaasi.
Vihje: merkkijonon tyhjyyden tarkastaminen onnistuu seuraavasti:
String nalle = "puh"; if(nalle.isEmpty()) { System.out.println("Tyhjä merkkijono"); } else { System.out.println("Löytyipäs sieltä jotain!"); }
Jos käyttäjä ei ole syöttänyt komentoa lopeta
, kutsu toistolausekkeen lopussa hirsipuupelin komentoja hirsipuu.tulostaUkko()
ja hirsipuu.tulostaSana()
.
... Anna komento: a Kirjainta a ei löytynyt sanasta. ____ | | | | /|\ Arvattava sana: ???? Anna komento: m Kirjain m löytyi sanasta! ____ | | | | /|\ Arvattava sana: m??? Anna komento: lopeta Kiitos pelistä!
Edellisellä viikolla opettelimme kirjoittamaan omia metodeja. Metodien avulla voimme jäsennellä ohjelmaa pienemmiksi hyvin nimetyiksi. selkeän tehtävän omaaviksi loogisiksi kokonaisuuksiksi. Tämä sekä helpottaa ongelmanratkaisua että parantaa ohjelman luettavuutta niin ohjelmoijan kuin ohjelmaa myöhemmin mahdollisesti ylläpitävänkin osalta. Tästä lähtien oikeastaan jokainen tekemämme ohjelma sisältää metodeita.
Jatketaan edelleen metodien parissa.
Yritetään muuttaa metodin sisältä pääohjelman muuttujan arvoa.
// pääohjelma public static void main(String[] args) { int luku = 1; kasvataKolmella(); } // metodi public static void kasvataKolmella() { luku = luku + 3; }
Ohjelma ei kuitenkaan toimi, sillä metodi ei näe pääohjelman muuttujaa luku
.
Yleisemminkin voi todeta, että pääohjelman muuttujat eivät näy metodien sisään, ja metodin muuttujat eivät näy muille metodeille tai pääohjelmalle. Ainoa keino viedä metodille tietoa ulkopuolelta on parametrin avulla.
// 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); }
Yllä metodilla kasvataKolmella on parametri luku
. Parametri luku kopioidaan metodin käyttöön. 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
Parametrina annettu luku siis kopioitiin metodin käyttöön. Jotta saisimme luvun uuden arvon myös pääohjelmaan, 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() { ...
Arvon palauttavaa metodia määriteltäessä täytyy määritellä myös palautettavan arvon tyyppi. Paluuarvon tyyppi merkitään metodin nimen vasemmalle puolelle. Seuraavassa metodi joka palauttaa aina kokonaisluvun 10, metodin tyyppi on int
. Palautus tapahtuu komennolla return:
public static int palautetaanAinaKymppi() { return 10; }
Jotta metodin palauttamaa arvoa voisi käyttää, tulee paluuarvo ottaa talteen muuttujaan:
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 paluuarvo, on sen myös 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. Tämän jälkeen metodia käytetään laskemaan luvut 2 ja 7 yhteen. Metodikutsusta saatava paluuarvo asetetaan muuttujaan lukujenSumma
.
public static int summa(int eka, int toka) { return eka + toka; }
Metodin kutsu:
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; }
Kuten huomataan, metodin paluuarvoa ei tarvitse välttämättä sijoittaa muuttujaan, se voi olla osana tulostuslausetta aivan kuten mikä tahansa muukin int-arvo.
Huomaa, että metodin parametrien nimillä ja metodin kutsujan puolella määritellyillä muuttujan nimillä ei ole mitään tekemistä keskenään. Edellisessä esimerkissä sekä pääohjelman muuttujat että metodin parametrit olivat "sattumalta" nimetty samoin (eli eka
ja toka
). Seuraava toimisi aivan yhtä hyvin:
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
Seuraava metodi laskee syötteinään saamiensa lukujen keskiarvon. Metodi käyttää apumuuttujia summa
ja ka
. Metodin sisäisen muuttujan määrittely tapahtuu tutulla tavalla.
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:
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 43 metodia summa
!
Tee metodi seuraavaan runkoon:
public static int summa(int luku1, int luku2, int luku3, int luku4) { // kopioi koodi tehtävästä 43 } 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)!
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, jne.Pisteen vasemman puoleinen osa kertoo kenen metodia kutsutaan.
Javassa on erillinen char
-tietotyyppi kirjaimia varten. Yksittäiseen char
-muuttujaan voi tallentaa yhden kirjaimen. Merkkijonolta voidaan kysyä sen kirjaimia kirjaimen paikan perusteella käyttämällä charAt()
-metodia. Huomaa että laskeminen alkaa nollasta!
String kirja = "Kalavale"; char merkki = kirja.charAt(3); System.out.println("Kirjan neljäs kirjain on " + merkki); //tulostaa "a"
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)
. Esimerkiksi seuraava kaataa ohjelman: yritämme hakea kirjainta kohdasta jota ei ole olemassa.
char merkki = kirja.charAt(kirja.length());
Muuttujat, metodit ja ensi viikolla opittavat luokat kannattaa nimetä kuvaavasti. Usein käy, että valittu nimi on hieman epäkuvaava ja tulee tarve nimen muuttamiselle. NetBeans:issa tämä on todella helppoa. Maalaa huono nimi jostain kohtaa koodiasi hiirellä. Paina (yhtäaikaa) ctrl ja r ja kirjoita muuttujalle/metodille uusi nimi.
Tee ohjelma, joka kysyy käyttäjän nimen ja ilmoittaa, kuinka monta kirjainta siinä on.
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.
Tee ohjelma, joka kysyy käyttäjän nimen ja ilmoittaa nimen ensimmäisen kirjaimen.
Anna nimi: Pekka Ensimmäinen kirjain: P
Anna nimi: Katariina Ensimmäinen kirjain: K
Huom! 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.
Tee ohjelma, joka kysyy käyttäjän nimen ja ilmoittaa nimen viimeisen kirjaimen.
Anna nimi: Pekka Viimeinen kirjain: a
Anna nimi: Katariina Viimeinen kirjain: a
Huom! 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.
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 valitsema osajono merkkijonosta:
String kirja = "Kalavale"; System.out.println(kirja.substring(4)); //tulostaa "vale" System.out.println(kirja.substring(2,6)); //tulostaa "lava"
Koska substring
-metodin paluuarvo on String
-tyyppinen,
voidaan metodin paluuarvo ottaa talteen String-tyyppiseen muuttujaan kirja..
String kirja = "8 veljestä"; String loppuosa = kirja.substring(2); System.out.println("7 " + loppuosa); // tulostaa: 7 veljestä
String-luokan metodit tarjoavat myös mahdollisuuden etsiä tekstistä tiettyä sanaa.
Esimerkiksi sana "erkki" sisältyy tekstiin "merkki".
Metodi indexOf()
etsii parametrinaan annettua sanaa merkkijonosta.
Jos sana löytyi, se palauttaa sanan ensimmäisen kirjaimen indeksin, eli paikan (muista että paikkanumerointi alkaa nollasta!). Jos taas sanaa ei löytynyt merkkijonosta palautetaan arvo -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 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
Tee ohjelma, joka kysyy käyttäjältä kaksi sanaa.
Sitten 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: tulosta ohjelmassasi täsmälleen samassa muodossa kuin esimerkissä!
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.
String apu = ""; // ... // lisätään merkki apu-nimisen muuttujan perään apu = apu + merkki;
Ohjelman tulostus:
Anna merkkijono: esimerkki ikkremise
Merkkijonot poikkeavat luonteeltaan hieman esim. 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 );
Merkkijonot 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.
Merkkijonot ovat olioita eli "asioita joihin liittyy metodeja sekä arvo". Jatkossa tulemme näkemään hyvin paljon muitakin olioita kuin merkkijonoja.
Kuten edellisestä esimerkistä huomaamme, kutsutaan olion metodia lisäämällä olion nimen perään piste ja metodikutsu:
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, 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 "tyyppiä" sanotaan luokaksi. 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ä olisi aivan kelvoton ratkaisu ja sen huonoutta ei kannata oikeastaan edes perustella -- ajattele ylläoleva esimerkki vaikkapa sadalla tai tuhannella sanalla.
Java, kuten kaikki modernit ohjelmointikielet, tarjoaa erilaisia apuvälineitä joiden avulla on helppo säilyttää ohjelmassa monia olioita. Tutustumme nyt Javan ehkä 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<String>(); sanaLista.add("Ensimmäinen"); sanaLista.add("Toinen"); } }
Yllä olevan pääohjelman ensimmäinen rivi luo sanaLista
-nimisen merkkijonoja tallettavan arraylistin. Listamuuttujan tyypin nimi on ArrayList<String>
, eli merkkijonoja tallettava ArrayList. Itse lista luodaan sanomalla new ArrayList<String>();
.
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<String>(); opettajat.add("Antti"); opettajat.add("Arto"); opettajat.add("Pekka"); opettajat.add("Juhana"); opettajat.add("Martin"); opettajat.add("Matti"); 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("Arto"); if ( opettajat.contains("Arto") ) { System.out.println("Arto on opettajien listalla"); } else { System.out.println("Arto 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ä annetaan parametriksi tietty luku. Esim. jos sanotaan remove(3)
poistuu listalla neljäntenä oleva merkkijono.
Esimerkin lopussa kutsutaan metodia contains
jonka avulla kysytään listalta sisältääkö se parametrina annettavan merkkijonon. Jos sisältää, palauttaa metodi arvon true
.
Ohjelman tulostus:
opettajien lukumäärä 6 listalla ensimmäisena Antti listalla kolmantena Pekka Arto ei ole opettajien listalla
Huom! Metodit remove
ja contains
olettavat että listaan tallennetuilla olioilla on metodi equals
. Palaamme tähän myöhemmin kurssilla.
Seuraavassa esimerkissä lisätään listalle 4 nimeä ja tulostetaan listan sisältö:
public static void main(String[] args) { ArrayList<String> opettajat = new ArrayList<String>(); opettajat.add("Antti"); opettajat.add("Pekka"); opettajat.add("Juhana"); opettajat.add("Martin"); System.out.println( opettajat.get(0) ); System.out.println( opettajat.get(1) ); System.out.println( opettajat.get(2) ); System.out.println( opettajat.get(3) ); }
Ratkaisu on kuitenkin erittäin kömpelö. Entäs 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:
public static void main(String[] args) { ArrayList<String> opettajat = new ArrayList<String>(); opettajat.add("Antti"); opettajat.add("Pekka"); opettajat.add("Juhana"); opettajat.add("Martin"); opettajat.add("Matti"); 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<String>(); opettajat.add("Antti"); opettajat.add("Pekka"); opettajat.add("Juhana"); opettajat.add("Martin"); opettajat.add("Matti"); 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ä.
While-toistolauseen käyttö ja listan paikkojen "indeksointi" ei kuitenkaan ole yleensä järkevin tapa listan läpikäyntiin. Suositeltavampi on seuraavassa esiteltävä for-each -toistolauseke.
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 viimeviikolla 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 lasten leikkiä:
public static void main(String[] args) { ArrayList<String> opettajat = new ArrayList<String>(); 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ää, 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 tulee 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 sitä kannattaa ehdottomasti totutella käyttämään!
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<String>();
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<String>();
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:
public static void main(String[] args) { ArrayList<String> opettajat = new ArrayList<String>(); // ... 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
. 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<String>(); 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<String>(); ohjelmointikielet.add("Java"); ohjelmointikielet.add("Python"); ohjelmointikielet.add("Ruby"); ohjelmointikielet.add("C++"); ArrayList<String> maat = new ArrayList<String>(); 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<String>(); 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<String>(); 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.
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<String>(); 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.
ArrayList:eihin voi tallettaa minkä tahansa tyyppisiä arvoja. Jos talletetaan kokonaislukuja eli int
:ejä, tulee muistaa pari detaljia. int:ejä tallettava lista tulee määritellä 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<Integer>(); luvut.add(4); luvut.add(8); // yrittää poistaa luvun listan kohdasta 4, eli ei toimi odotetulla tavalla! luvut.remove(4); // tämä poistaa listalta luvun 4 luvut.remove( Integer.valueOf(4) );
Eli luvut.remove(4)
yrittää poistaa listalla kohdassa 4 olevan alkion. Listalla on vain 2 alkiota, joten komento aiheuttaa virheen. Jos halutaan poistaa luku 4, täytyy käyttää hieman monimutkaisempaa muotoa: luvut.remove( Integer.valueOf(4) );
Listalle voi tallettaa myös liukulukuja eli double
:ja ja merkkejä eli char
:eja. Tällöin listat luodaan seuraavasti:
ArrayList<Double> doublet = new ArrayList<Double>(); ArrayList<Character> merkit = new ArrayList<Character>();
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<Integer>(); 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<Integer>(); 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<Integer>(); 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 palauttaa saamansa kokonaislukuja sisältävän listan otosvarianssin.
Ohjeen varianssin laskemiseksi voit katsoa esimerkiksi Wikipediasta kohdasta populaatio- ja otosvarianssi.
Tee metodi käytäen apuna tehtävän 64 metodia keskiarvo
, 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<Integer>(); 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.)
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
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<Integer>(); 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<Integer>(); 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<Integer>(); 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.
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ä 56. 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!
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<String>(); nimet.add("Kyösti"); nimet.add("Risto"); nimet.add("Carl"); nimet.add("Urho"); //luodaan kopio nimet-listasta ArrayList<String> kopio = new ArrayList<String>(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<Integer>(); lista1.add(3); lista1.add(7); lista1.add(1); boolean eri = kaikkiEri(lista1); // muuttujan eri arvo true ArrayList<Integer> lista2 = new ArrayList<Integer>(); 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.
Luvussa 15 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<Integer>(); 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.
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<Integer>(); ArrayList<Integer> lista2= new ArrayList<Integer>(); 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.
Yksi maailman johtavista ohjelmistonkehittäjistä, Kent Beck, on lausunut mm. seuraavasti:
Otamme viimeistään nyt ensimmäisiä askelia Kent 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<Integer>(); 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(Integer.valueOf(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<Integer>(); 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(Integer.valueOf(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<Integer>(); 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(Integer.valueOf(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<Integer>(); 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(Integer.valueOf(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.
Pieni johdanto olio-ohjelmointiin ennen aloitusta.
Proseduraalisessa ohjelmoinnissa, eli tähän asti opiskelemassamme ohjelmointityylissä, ohjelma jäsennellään jakamalla se pienempiin osiin eli metodeihin. Metodi toimii ohjelman erillisenä osana, ja sitä voi kutsua mistä tahansa ohjelmasta. Metodia kutsuttaessa ohjelman suoritus siirtyy metodin alkuun, ja suorituksen päätyttyä palataan takaisin siihen kohtaan mistä metodia kutsuttiin.
Olio-ohjelmoinnissa, kuten proseduraalisessa ohjelmoinnissa, pyritään jakamaan ohjelma pieniin osiin. Olio-ohjelmoinnissa pienet osat ovat olioita. Jokaisella oliolla on oma yksittäinen vastuunsa eli se sisältää joukon yhteenkuuluvaa tietoa ja toiminnallisuutta. Olio-ohjelmat koostuvat useista olioista, joiden yhteinen toiminta määrittelee järjestelmän toiminnan.
Olemme käyttäneet jo monia Javan valmiita olioita. Esimerkiksi ArrayList
:it ovat olioita. Jokainen yksittäinen lista koostuu yhteenkuuluvasta tiedosta, eli olion tilasta. Listaolioihin liittyy toiminnallisuutta, eli metodt joilla olion tilaa voidaan muuttaa. Esimerkiksi seuraavassa ohjelmanpätkässä on kaksi ArrayList
-olioa kaupungit
ja maat
:
public static void main(String[] args) { ArrayList<String> kaupungit = new ArrayList<String>(); ArrayList<String> maat = new ArrayList<String>(); maat.add("Suomi"); maat.add("Saksa"); maat.add("Hollanti"); kaupungit.add("Berliini"); kaupungit.add("Nijmegen"); kaupungit.add("Turku"); kaupungit.add("Helsinki"); System.out.println("maita " + maat.size() ); System.out.println("kaupunkeja " + kaupungit.size() ); }
Sekä maat
-olio että kaupungit
-olio elää omaa elämäänsä. Molempien "tila" on toisten olioiden tilasta riippumaton. Esim. olion maat
tila koostuu listalla olevista merkkijonoista "Suomi", "Saksa" ja "Hollanti" ja todennäköisesti myös tiedosta kuinka monta maata listalla on.
Olioon liittyvää metodikutsua tehdessä (esimerkiksi maat.add("Suomi");
) pisteen vasemmalle puolelle tulee sen olion nimi, jolle metodia kutsutaan, oikealle metodin nimi. Kun kysytään monta merkkijonoa listalla maat
on, kutsu on muotoa maat.size()
eli kutsutaan maat
oliolle sen metodia size
. Metodin palauttama tulos riippuu olion maat
tilasta, eli muut oliot kuten kaupungit
eivät vaikuta metodin suoritukseen millään tavalla.
Olemme jo useasti käyttämään komentoa new
. Esimerkiksi listan (ArrayList
) ja lukijan (Scanner
) luominen on tapahtunut new
-komennolla. Syy on se, että kumpikin näistä on Luokkia, joista olio luodaan. Java-kielessä oliot luodaan aina new
-komennolla muutamaa poikkeusta lukuunottamatta.
Yksi näistä poikkeuksista ovat merkkijonot, joiden luomiseen ei aina tarvita new
:iä. Tuttu tapa merkkijonon luomiseen on oikeastaan Javan lyhennysmerkintä new
:in käytölle. Merkkijonon voi luoda myös new:illä kuten muutkin oliot:
String teksti = "tekstiä"; // lyhennysmerkintä merkkijono-olion luomiselle String toinenTeksti = new String("lisää tekstiä");
On myös tilanteita, missä Javan valmis kalusto kutsuu new
:iä piilossa ohjelmoijalta.
On selvää että kaikki oliot eivät ole keskenään samankaltaisia. Esim. ArrayList
-oliot poikkeavat ratkaisevasti String
-olioista. Kaikilla ArrayList
:eillä on samat metodit (add
, contains
, remove
, size
, ...) ja vastaavasi kaikilla String
-olioilla on samat metodit (substring
, length
, charAt
, ...). ArrayList
- ja String
-olioiden metodit eivät ole samat sillä ne ovat eri tyyppisiä olioita.
Tietyn olioryhmän tyyppiä kutsutaan luokaksi. ArrayList
on luokka, String
on luokka, Scanner
on luokka, jne... Oliot taas ovat luokasta tehtyjä ilmentymiä.
Luokan kaikilla olioilla on samat metodit, sekä samankaltainen tila. Esim. kaikkien ArrayList
-olioiden tila koostuu listalle tallennetuista alkioista. String
-olioiden tila taas koostuu merkkijonon muodostavista kirjaimista.
Luokka määrittelee minkälaisia luokan oliot ovat:
Luokka kuvaa siitä luotavien olioiden "rakennuspiirustukset".
Otetaan analogia tietokoneiden ulkopuoleisesta maailmasta. Rintamamiestalot lienevät kaikille suomalaisille tuttuja. Voidaan ajatella, että jossain on olemassa piirustukset jotka määrittelevät minkälainen rintamamiestalo on. Piirrustukset ovat luokka, eli ne määrittelevät luokasta luotavien olioiden luonteen:
Yksittäiset oliot eli rintamamiestalot on tehty samojen piirustusten perusteella, eli ne ovat saman luokan ilmentymiä. Yksittäisten olioiden tila eli ominaisuudet (esim. seinien väri, katon rakennusmateriaali ja väri, kivijalan väri, ovien rakennusmateriaali ja väri, ...) vaihtelevat. Seuraavassa yksi "rintamamiestalo-luokan olio":
Luokasta luodaan olio aina kutsumalla olion luovaa metodia eli konstruktoria komennon new
avulla. Esimerkiksi Scanner
-luokasta luodaan uusi ilmentymä eli olio kun kutsutaan new Scanner(..)
:
Scanner lukija = new Scanner(System.in);
Konstruktorit saavat parametreja kuten muutkin metodit.
Tehtäväpohjan mukana tulee valmis luokka Tili
.
Luokan Tili
olio esittää pankkitiliä, jolla on saldo
(eli jossa on jokin määrä rahaa). Tilejä käytetään näin:
Tili artonTili = new Tili("Arton tili",100.00); Tili artonSveitsilainenTili = new Tili("Arton tili Sveitsissä",1000000.00); System.out.println("Alkutilanne"); System.out.println(artonTili); System.out.println(artonSveitsilainenTili); artonTili.otto(20); System.out.println("Arton tilin saldo on nyt: "+artonTili.saldo()); artonSveitsilainenTili.pano(200); System.out.println("Arton toisen tilin saldo on nyt: "+artonSveitsilainenTili.saldo()); System.out.println("Lopputilanne"); System.out.println(artonTili); System.out.println(artonSveitsilainenTili);
Tee ohjelma, joka luo tilin jonka saldo on 100.0, panee tilille 20.0 ja tulostaa tilin. Huom! tee kaikki nämä operaatiot täsmälleen tässä järjestyksessä.
Tee ohjelma joka:
"Matin tili"
saldolla 1000"Oma tili"
saldolla 0Yllä siirsit rahaa tililtä toiselle. Tehdään seuraavaksi metodi joka tekee saman!
Toteuta ohjelmapohjaan metodi public static void tilisiirto(Tili
mista, Tili minne, double paljonko)
joka siirtää rahaa
tililtä toiselle. Sinun ei tarvitse tarkistaa
että mista
-tilin saldo riittää.
Tämän jälkeen tee main
-metodissasi seuraavaa:
"A"
saldolla 100.0"B"
saldolla 0.0"C"
saldolla 0.0Luokka määritellään jotain mielekästä kokonaisuutta varten. Usein "mielekäs kokonaisuus" kuvaa jotain reaalimaailman asiaa. Jos tietokoneohjelman pitää käsitellä henkilötietoja, voisi olla mielekästä määritellä erillinen luokka Henkilo
joka kokoaa yhteen henkilöön liittyvät metodit ja ominaisuudet.
Aloitetaan. Oletetaan että meillä on projektirunko jossa on tyhjä pääohjelma:
public class Main { public static void main(String[] args) { } }
Luomme nyt projektiimme eli ohjelmaamme uuden luokan. Tämä tapahtuu valitsemalla NetBeansissa vasemmalta projects-kohdasta hiiren oikealla napilla new ja java class. Avautuvaan dialogiin annetaan luokalle nimi.
Kuten muuttujien ja metodien nimien, myös luokan nimen on aina oltava mahdollisimman kuvaava. Joskus ohjelmoinnin edetessä luokka elää ja muuttaa muotoaan. Tällaisissa tilanteissa luokan voi nimetä uudelleen (ks. NetBeans-ohje).
Luodaan luokka nimeltä Henkilo
. Luokasta muodostuu oma tiedostonsa Henkilo.java
. Eli ohjelma koostuu nyt kahdesta tiedostosta, sillä pääohjelma on omassa tiedostossaan. Aluksi luokka on tyhjä:
public class Henkilo { }
Luokan tulee määritellä mitä metodeja ja ominaisuuksia luokan olioilla on. Päätetään, että jokaisella henkilöllä on nimi ja ikä. Nimi on luonnollista esittää merkkijonona, eli Stringinä, ja ikä taas kokonaislukuna. Lisätään nämä rakennuspiirustuksiimme:
public class Henkilo { private String nimi; private int ika; }
Määrittelimme yllä että kaikilla Henkilo
-luokan olioilla on nimi
ja ika
. Määritettely tapahtuu hiukan kuten normaalin muuttujan määrittely. Eteen on kuitenkin nyt laitettu avainsana private
. Tämä tarkoittaa sitä, että nimi ja ikä eivät näy suoraan olion ulkopuolelle vaan ovat "piilossa" olion sisällä. Olion sisälle piilottamista kutsutaan kapseloinniksi.
Luokan sisälle määriteltyjä muuttujia kutsutaan oliomuuttujiksi tai olion kentiksi tai olion attribuuteiksi. Rakkaalla lapsella on monta nimeä.
Eli olemme määritelleet rakennuspiirrustukset -- luokan -- henkilöoliolle. Kaikilla henkilöolioilla on muuttujat nimi
ja ika
. Henkilöiden "tila" koostuu niiden nimeen ja ikään asetetuista arvoista.
Luotavalle oliolle halutaan asettaa luontivaiheessa jokin alkutila. Itse määritellyn olion luominen tapahtuu hyvin samaan tapaan kuin Javan valmiiden olioiden kuten ArrayList
:ien luominen. Oliot siis luodaan new
-komennolla. Olion synnyttämisen yhteydessä olisikin kätevä pystyä antamaan arvo joillekin olioiden muuttujille. Esim. henkilön synnytyksessä olisi kätevää pystyä antamaan nimi jo syntymähetkellä:
public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka"); // ... }
Tämä onnistuu määrittelemällä olion synnyttävä metodi, eli konstruktori. Seuraavassa on määritelty Henkilo
-luokalle konstruktori, joka luo uuden Henkilo
-olion. Konstruktorissa luotavan henkilön iäksi asetetaan 0 ja nimeksi parametrina tuleva merkkijono:
public class Henkilo { private String nimi; private int ika; public Henkilo(String nimiAlussa) { this.ika = 0; this.nimi = nimiAlussa; } }
Konstruktori on siis nimeltään sama kuin luokan nimi. Yllä luokka (class) on Henkilo
, ja konstruktori public Henkilo(String nimiAlussa)
. Konstruktorille parametrina tuleva arvo tulee sulkuihin konstruktorin nimen perään. Konstruktorin voi ajatella olevan metodi, jonka Java suorittaa kun olio luodaan sanomalla new Henkilo("Pekka");
Aina kun jostain luokasta luodaan olio, kutsutaan kyseisen luokan konstruktoria.
Muutama huomio: konstruktorin sisällä on komento this.ika = 0
. Tässä asetetaan arvo 0 juuri tämän olion, eli "this"-olion sisäiselle muuttujalle ika
. Toinen komento this.nimi = nimiAlussa;
taas asettaa juuri tämän olion sisäiselle muuttujalle nimi
arvoksi parametrina annetun merkkijonon. Olion muuttujat ika
ja nimi
näkyvät konstruktorissa ja muuallakin olion sisällä automaattisesti. Niihin viitataan this
-etuliitteellä. Koska niissä on private-määre, niin olion ulkopuolelle ne eivät näy.
Vielä yksi huomio: jos ohjelmoija ei tee luokalle konstruktoria, tekee Java automaattisesti luokalle oletuskonstruktorin. Oletuskonstruktori on konstruktori joka ei tee mitään. Eli jos konstruktoria ei jostain syystä tarvita, ei sellaista tarvitse ohjelmoida.
Alkaa olla korkea aika päästä käyttämään Henkilo
-olioita. Osaamme luoda olion ja alustaa olion muuttujat. Järkevään toimintaan pystyäkseen olioilla on oltava myös metodeja. Tehdään Henkilo
-luokalle metodi jonka avulla olio tulostaa itsensä ruudulle:
public class Henkilo { private String nimi; private int ika; public Henkilo(String nimiAlussa) { this.ika = 0; this.nimi = nimiAlussa; } public void tulostaHenkilo() { System.out.println(this.nimi + ", ikä " + this.ika + " vuotta"); } }
Metodi siis kirjoitetaan luokan sisälle, metodin nimen eteen tulee public void
sillä metodin on tarkoitus näkyä ulkomaailmalle ja metodi ei palauta mitään. Huomaa, että sana static
ei nyt ilmene missään. Olioiden yhteydessä static
-määrettä ei käytetä. Selitämme ensi viikolla hieman tarkemmin mistä tässä on kysymys.
Metodin tulostaHenkilo
sisällä on yksi koodirivi joka käyttää hyvakseen oliomuuttujia nimi
ja ika
. Juuri tämän olion muuttujiin viitataan taas etuliitteellä this
. Kaikki olion muuttujat ovat siis näkyvillä ja käytettävissä metodin sisällä.
Luodaan pääohjelmassa kolme henkilöä ja pyydetään niitä tulostamaan itsensä:
public class Main { public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka"); Henkilo antti = new Henkilo("Antti"); Henkilo martin = new Henkilo("Martin"); pekka.tulostaHenkilo(); antti.tulostaHenkilo(); martin.tulostaHenkilo(); } }
Tulostuu:
Pekka, ikä 0 vuotta Antti, ikä 0 vuotta Martin, ikä 0 vuotta
Sama screencastina:
Luo luokka Tuote
joka esittää kaupan tuotetta jolla on hinta, lukumäärä ja nimi.
Uuden luokan saa lisättyä seuraavasti: Ruudun vasemmalla reunalla on projektilistaus. Paina projektin nimen 073.Tuote kohdalla hiiren oikeaa nappia. Valitse avautuvasta valikosta New ja Java Class. Anna luokan nimeksi (Class Name) Tuote
.
Luokalla tulee olla:
public Tuote(String nimiAlussa, double hintaAlussa, int maaraAlussa)
public void tulostaTuote()
joka tulostaa tuotteen tiedot tässä muodossa:
Banaani, hinta 1.1, 13 kpl
Lisätään aiemmin rakentamallemme Henkilölle metodi, joka kasvattaa henkilön ikää vuodella:
public class Henkilo { // ... public void vanhene() { this.ika = this.ika + 1; } }
Metodi siis kirjoitetaan tulostaHenkilo
-metodin tapaan luokan Henkilo
sisälle. Metodissa kasvatetaan oliomuuttujan ika
arvoa yhdellä.
Kutsutaan metodia ja katsotaan mitä tapahtuu:
public class Main { public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka"); Henkilo antti = new Henkilo("Antti"); pekka.tulostaHenkilo(); antti.tulostaHenkilo(); System.out.println(""); pekka.vanhene(); pekka.vanhene(); pekka.tulostaHenkilo(); antti.tulostaHenkilo(); } }
Ohjelman tulostus on seuraava:
Pekka, ikä 0 vuotta Antti, ikä 0 vuotta Pekka, ikä 2 vuotta Antti, ikä 0 vuotta
Eli "syntyessään" molemmat oliot ovat nollavuotiaita (konstruktorissa suoritetaan mm. rivi this.ika = 0;
). Olion pekka
metodia vanhene
kutsutaan kaksi kertaa. Kuten tulostus näyttää, tämä saa aikaan sen että Pekan ikä vanhenemisen jälkeen 2 vuotta. Kutsumalla metodia Pekkaa vastaavalle oliolle, toisen henkilöolion ikä ei muutu.
Jokaisella oliolla on siis oma sisäinen tilansa.
Aivan kuiten edellisellä viikolla käsittelemämme olioihin liittymättömät metodit, myös olioihin liittyvät metodit voivat palauttaa arvon. Lisätään Henkilölle metodi joka palauttaa henkilön iän:
public class Henkilo { // ... public int palautaIka() { return this.ika; } }
Eli koska kyseessä olioon liittyvä metodi, ei määrittelyssä ole sanaa static. Olioiden arvon palauttavia metodeja käytetään kuten mitä tahansa arvon palauttavia metodeja:
public class Main { public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka"); Henkilo antti = new Henkilo("Antti"); pekka.vanhene(); pekka.vanhene(); antti.vanhene(); System.out.println( "Pekan ikä: "+pekka.palautaIka() ); System.out.println( "Antin ikä: "+antti.palautaIka() ); int yht = pekka.palautaIka() + antti.palautaIka(); System.out.println( "Pekka ja Antti yhteensä "+yht+ " vuotta" ); } }
Ohjelman tulostus on seuraava:
Pekan ikä 2 Antin ikä 1 Pekka ja Antti yhteensä 3 vuotta
Luo luokka Kertoja
jolla on:
public Kertoja(int luku)
.public int kerro(int toinenLuku)
joka palauttaa sille annetun luvun toinenLuku
kerrottuna konstruktorille annetulla luvulla luku
.Esimerkki luokan käytöstä:
Kertoja kolmellaKertoja = new Kertoja(3); System.out.println("kolmellaKertoja.kerro(2): " + kolmellaKertoja.kerro(2)); Kertoja neljallaKertoja = new Kertoja(4); System.out.println("neljallaKertoja.kerro(2): " + neljallaKertoja.kerro(2)); System.out.println("kolmellaKertoja.kerro(1): " + kolmellaKertoja.kerro(1)); System.out.println("neljallaKertoja.kerro(1): " + neljallaKertoja.kerro(1));
Tulostus
kolmellaKertoja.kerro(2): 6 neljallaKertoja.kerro(2): 8 kolmellaKertoja.kerro(1): 3 neljallaKertoja.kerro(1): 4
Tehtäväpohjan mukana tulee osittain valmiiksi toteutettu luokka VahenevaLaskuri
:
public class VahenevaLaskuri { private int arvo; // oliomuuttuja joka muistaa laskurin arvon public VahenevaLaskuri(int arvoAlussa) { this.arvo = arvoAlussa; } public void tulostaArvo() { System.out.println("arvo: " + this.arvo); } public void vahene() { // kirjoita tänne metodin toteutus // laskurin arvon on siis tarkoitus vähentyä yhdellä } // ja tänne muut metodit }
Seuraavassa esimerkki miten pääohjelma käyttää vähenevää laskuria:
public class Paaohjelma { public static void main(String[] args) { VahenevaLaskuri laskuri = new VahenevaLaskuri(10); laskuri.tulostaArvo(); laskuri.vahene(); laskuri.tulostaArvo(); laskuri.vahene(); laskuri.tulostaArvo(); } }
Pitäisi tulostua:
arvo: 10 arvo: 9 arvo: 8
VahenevaLaskuri
-luokan konstruktorille annetaan parametrina alkuarvo. Esimerkin oliota laskuri
luodessa laskurille välitetään parametrina arvo 10
. Esimerkin laskuri
-olioon liittyvään oliomuuttujaan arvo
asetetaan siis aluksi arvo 10
. Laskurin arvon voi tulostaa metodilla tulostaArvo()
. Laskurilla tulee myös olla metodi vahene()
joka vähentää laskurin arvoa yhdellä.
Täydennä luokan runkoon metodin vahene()
toteutus sellaiseksi, että se vähentää kutsuttavan olion oliomuuttujan arvo
arvoa yhdellä. Kun olet toteuttanut metodin vahene()
, edellisen esimerkin pääohjelman tulee toimia esimerkkitulosteen mukaan.
Täydennä metodin vahene()
toteutus sellaiseksi, ettei laskurin arvo mene koskaan negatiiviseksi. Eli jos laskurin arvo on jo 0, ei vähennys sitä enää vähennä:
public class Paaohjelma { public static void main(String[] args) { VahenevaLaskuri laskuri = new VahenevaLaskuri(2); laskuri.tulostaArvo(); laskuri.vahene(); laskuri.tulostaArvo(); laskuri.vahene(); laskuri.tulostaArvo(); laskuri.vahene(); laskuri.tulostaArvo(); } }
Tulostuu:
arvo: 2 arvo: 1 arvo: 0 arvo: 0
Tee laskurille metodi public void nollaa()
joka nollaa laskurin arvon, esim:
public class Paaohjelma { public static void main(String[] args) { VahenevaLaskuri laskuri = new VahenevaLaskuri(100); laskuri.tulostaArvo(); laskuri.nollaa(); laskuri.tulostaArvo(); laskuri.vahene(); laskuri.tulostaArvo(); } }
Tulostuu:
arvo: 100 arvo: 0 arvo: 0
Tee laskurille metodi public void palautaAlkuarvo()
, joka palauttaa laskurille arvon joka sillä oli alussa:
public class Paaohjelma { public static void main(String[] args) { VahenevaLaskuri laskuri = new VahenevaLaskuri(100); laskuri.tulostaArvo(); laskuri.vahene(); laskuri.tulostaArvo(); laskuri.vahene(); laskuri.tulostaArvo(); laskuri.nollaa(); laskuri.tulostaArvo(); laskuri.palautaAlkuarvo(); laskuri.tulostaArvo(); } }
Tulostuu:
arvo: 100 arvo: 99 arvo: 98 arvo: 0 arvo: 100
Vihje jotta alkuarvon voi palauttaa, se täytyy "muistaa" toisen oliomuuttujan avulla! Joudut siis lisäämään ohjelmaan oliomuuttujan johon talletetaan laskurin alussa saama arvo.
Kumpulan kampuksella Helsingissä toimivaan Unicafe-nimiseen gourmet-ravintolaan tarvitaan uusi ruokalista. Keittiömestari tietää ohjelmoinnista, ja haluaa listan hallinnointiin tietokonejärjestelmän. Toteutetaan tässä tehtävässä järjestelmän sydän, luokka Ruokalista.
Tehtäväpohjan mukana tulee Main
-luokka, jossa voit testata ruokalistan toimintaa. Ruokalistan toteuttamista varten saat seuraavanlaisen tehtäväpohjan:
import java.util.ArrayList; public class Ruokalista { private ArrayList<String> ateriat; public Ruokalista() { this.ateriat = new ArrayList<String>(); } // toteuta tänne tarvittavat metodit }
Ruokalistaoliolla on siis oliomuuttujana ArrayList, jonka on tarkoitus tallentaa ruokalistalla olevien ruokalajien nimet.
Ruokalistan tulee tarjota metodit public void lisaaAteria(String ateria)
, public void tulostaAteriat()
, ja public void tyhjennaRuokalista()
.
Toteuta metodi public void lisaaAteria(String ateria)
, joka lisää uuden aterian ruokalistan ateriat
-listaan. Jos lisättävä ateria on jo listassa, sitä ei lisätä uudelleen.
Toteuta metodi public void tulostaAteriat()
, joka tulostaa ateriat. Esimerkiksi kolmen aterian lisäyksen jälkeen tulostuksen tulee olla seuraavanlainen.
ensimmäisenä lisätty ateria toisena lisätty ateria kolmantena lisätty ateria
Toteuta metodi public void tyhjennaRuokalista()
joka tyhjentää ruokalistan. ArrayList
-luokalla on metodi josta on tässä hyötyä. NetBeans osaa vihjata käytettävissä olevista metodeista kun kirjoitat olion nimen ja pisteen. Yritä kirjoittaa ateriat.
metodirungon sisällä ja katso mitä käy.
Jatketaan taas Henkilo
-luokan laajentamista. Luokan tämänhetkinen versio on seuraava:
public class Henkilo { private String nimi; private int ika; public Henkilo(String nimiAlussa) { this.ika = 0; this.nimi = nimiAlussa; } public void tulostaHenkilo() { System.out.println(this.nimi + ", ikä " + this.ika + " vuotta"); } public void vanhene() { this.ika = this.ika + 1; } }
Tehdään henkilölle metodi, jonka avulla voidaan selvittää onko henkilö täysi-ikäinen. Metodi palauttaa totuusarvon -- joko true
tai false
:
public class Henkilo { // ... public boolean taysiIkainen(){ if ( this.ika < 18 ) { return false; } return true; } /* huom. metodin voisi kirjoittaa lyhyemmin seuraavasti: public boolean taysiIkainen(){ return this.ika >= 18; } */ }
Ja testataan:
public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka"); Henkilo antti = new Henkilo("Antti"); int i = 0; while ( i < 30 ) { pekka.vanhene(); i++; } antti.vanhene(); System.out.println(""); if ( antti.taysiIkainen() ) { System.out.print("täysi-ikäinen: "); antti.tulostaHenkilo(); } else { System.out.print("alaikäinen: "); antti.tulostaHenkilo(); } if ( pekka.taysiIkainen() ) { System.out.print("täysi-ikäinen: "); pekka.tulostaHenkilo(); } else { System.out.print("alaikäinen: "); pekka.tulostaHenkilo(); } }
alaikäinen: Antti, ikä 1 vuotta täysi-ikäinen: Pekka, ikä 30 vuotta
Viritellään ratkaisua vielä hiukan. Nyt henkilön pystyy "tulostamaan" ainoastaan siten, että nimen lisäksi tulostuu ikä. On tilanteita, joissa haluamme tietoon pelkän olion nimen. Eli tehdään tarkoitusta varten oma metodi:
public class Henkilo { // ... public String getNimi() { return this.nimi; } }
Metodi getNimi
palauttaa oliomuuttujan nimi
kutsujalle. Metodin nimi on hieman erikoinen. Javassa on usein tapana nimetä oliomuuttujan palauttava metodi juuri näin, eli getMuuttujanNimi
. Tälläisiä metodeja kutsutaan usein "gettereiksi".
Muotoillaan pääohjelma käyttämään uutta "getteri"-metodia:
public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka"); Henkilo antti = new Henkilo("Antti"); int i = 0; while ( i < 30 ) { pekka.vanhene(); i++; } antti.vanhene(); System.out.println(""); if ( antti.taysiIkainen() ) { System.out.println( antti.getNimi() + " on täysi-ikäinen" ); } else { System.out.println( antti.getNimi() + " on alaikäinen" ); } if ( pekka.taysiIkainen() ) { System.out.println( pekka.getNimi() + " on täysi-ikäinen" ); } else { System.out.println( pekka.getNimi() + " on alaikäinen " ); } }
Tulostus alkaa olla jo aika siisti:
Antti on alaikäinen Pekka on täysi-ikäinen
Olemme syyllistyneet edellä osittain huonoon ohjelmointityyliin tekemällä metodin jonka avulla olio tulostetaan, eli metodin tulostaHenkilo
. Suositeltavampi tapa on määritellä oliolle metodi jonka palauttaa olion "merkkijonoesityksen". Merkkijonoesityksen palauttavan metodin nimi on Javassa aina toString
. Määritellään seuraavassa henkilölle tämä metodi:
public class Henkilo { // ... public String toString() { return this.nimi + ", ikä " + this.ika + " vuotta"; } }
Metodi toString
toimii kuten tulostaHenkilo
, mutta se ei itse tulosta mitään vaan palauttaa merkkijonoesityksen, jotta metodin kutsuja voi halutessaan suorittaa tulostamisen.
Metodia käytetään hieman yllättävällä tavalla:
public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka"); Henkilo antti = new Henkilo("Antti"); int i = 0; while ( i < 30 ) { pekka.vanhene(); i++; } antti.vanhene(); System.out.println( antti ); // sama kun System.out.println( antti.toString() ); System.out.println( pekka ); // sama kun System.out.println( pekka.toString() ); }
Periaatteena on, että System.out.println
-metodi pyytää olion merkkijonoesityksen ja tulostaa sen. Merkkijonoesityksen palauttavan toString
-metodin kutsua ei tarvitse kirjoittaa itse, sillä Java lisää sen automaattisesti. Ohjelmoijan kirjoittaessa:
System.out.println( antti );
Java täydentää suorituksen aikana kutsun muotoon:
System.out.println( antti.toString() );
Käy niin, että oliolta pyydetään sen merkkijonoesitys. Olion palauttama merkkijonoesitys tulostetaan normaaliin tapaan System.out.println
-komennolla.
Voimme nyt poistaa turhaksi käyneen tulostaOlio
-metodin.
Olioscreencastin toinen osa:
Helsingin Yliopiston opiskelijaruokaloissa eli Unicafeissa opiskelijat maksavat lounaansa käyttäen Lyyra-korttia.
Tässä tehtäväsäsarjassa tehdään luokka LyyraKortti
, jonka tarkoituksena on jäljitellä Lyyra-kortin käyttämistä Unicafessa.
Projektiin tulee kuulumaan kaksi kooditiedostoa:
Tehtäväpohjan eli projektin Viikko4_077.Lyyrakortti mukana tulee kooditiedosto Paaohjelma
jonka sisällä on main
-metodi.
Lisää projektiin uusi luokka nimeltä LyyraKortti
. Uuden luokan saa lisättyä seuraavasti: Ruudun vasemmalla reunalla on projektilistaus. Paina projektin nimen Viikko4_077.Lyyrakortti kohdalla hiiren oikeaa nappia. Valitse avautuvasta valikosta New ja Java Class. Anna luokan nimeksi (Class Name) LyyraKortti
.
Tee ensin LyyraKortti
-olion konstruktori, jolle annetaan kortin alkusaldo ja joka tallentaa sen olion sisäiseen muuttujaan. Tee sitten toString
-metodi, joka palauttaa kortin saldon muodossa "Kortilla on rahaa X euroa".
Seuraavassa on luokan LyyraKortti
runko:
public class LyyraKortti { private double saldo; public LyyraKortti(double alkusaldo) { // kirjoita koodia tähän } public String toString() { // kirjoita koodia tähän } }
Seuraava pääohjelma testaa luokkaa:
public class Paaohjelma { public static void main(String[] args) { LyyraKortti kortti = new LyyraKortti(50); System.out.println(kortti); } }
Ohjelman tulisi tuottaa seuraava tulostus:
Kortilla on rahaa 50.0 euroa
Täydennä LyyraKortti
-luokkaa seuraavilla metodeilla:
public void syoEdullisesti() { // kirjoita koodia tähän } public void syoMaukkaasti() { // kirjoita koodia tähän }
Metodin syoEdullisesti
tulisi vähentää kortin saldoa 2.50 eurolla ja metodin syoMaukkaasti
tulisi vähentää kortin saldoa 4.00 eurolla.
Seuraava pääohjelma testaa luokkaa:
public class Paaohjelma { public static void main(String[] args) { LyyraKortti kortti = new LyyraKortti(50); System.out.println(kortti); kortti.syoEdullisesti(); System.out.println(kortti); kortti.syoMaukkaasti(); kortti.syoEdullisesti(); System.out.println(kortti); } }
Ohjelman tulisi tuottaa seuraava tulostus:
Kortilla on rahaa 50.0 euroa Kortilla on rahaa 47.5 euroa Kortilla on rahaa 41.0 euroa
Mitä tapahtuu, jos kortilta loppuu raha kesken? Ei ole järkevää, että saldo muuttuu negatiiviseksi. Muuta metodeita syoEdullisesti
ja syoMaukkaasti
niin, että ne eivät vähennä saldoa, jos saldo menisi negatiiviseksi.
Seuraava pääohjelma testaa luokkaa:
public class Paaohjelma { public static void main(String[] args) { LyyraKortti kortti = new LyyraKortti(5); System.out.println(kortti); kortti.syoMaukkaasti(); System.out.println(kortti); kortti.syoMaukkaasti(); System.out.println(kortti); } }
Ohjelman tulisi tuottaa seuraava tulostus:
Kortilla on rahaa 5.0 euroa Kortilla on rahaa 1.0 euroa Kortilla on rahaa 1.0 euroa
Yllä toinen metodin syoMaukkaasti
kutsu ei vaikuttanut saldoon, koska saldo olisi mennyt negatiiviseksi.
Lisää LyyraKortti
-luokkaan seuraava metodi:
public void lataaRahaa(double rahamaara) { // kirjoita koodia tähän }
Metodin tarkoituksena on kasvattaa kortin saldoa parametrina annetulla rahamäärällä. Kuitenkin kortin saldo saa olla korkeintaan 150 euroa, joten jos ladattava rahamäärä ylittäisi sen, saldoksi tulisi tulla silti tasan 150 euroa.
Seuraava pääohjelma testaa luokkaa:
public class Paaohjelma { public static void main(String[] args) { LyyraKortti kortti = new LyyraKortti(10); System.out.println(kortti); kortti.lataaRahaa(15); System.out.println(kortti); kortti.lataaRahaa(10); System.out.println(kortti); kortti.lataaRahaa(200); System.out.println(kortti); } }
Ohjelman tulisi tuottaa seuraava tulostus:
Kortilla on rahaa 10.0 euroa Kortilla on rahaa 25.0 euroa Kortilla on rahaa 35.0 euroa Kortilla on rahaa 150.0 euroa
Muuta metodia lataaRahaa
vielä siten, että jos yritetään ladata negatiivinen rahamäärä, ei kortilla oleva arvo muutu.
Seuraava pääohjelma testaa luokkaa:
public class Paaohjelma { public static void main(String[] args) { LyyraKortti kortti = new LyyraKortti(10); System.out.println(kortti); kortti.lataaRahaa(-15); System.out.println(kortti); } }
Ohjelman tulisi tuottaa seuraava tulostus:
Kortilla on rahaa 10.0 euroa Kortilla on rahaa 10.0 euroa
Tee pääohjelma, joka sisältää seuraavan tapahtumasarjan:
Pääohjelman runko on seuraava:
public class Main { public static void main(String[] args) { LyyraKortti pekanKortti = new LyyraKortti(20); LyyraKortti matinKortti = new LyyraKortti(30); // kirjoita koodia tähän } }
Ohjelman tulisi tuottaa seuraava tulostus:
Pekka: Kortilla on rahaa 16.0 euroa Matti: Kortilla on rahaa 27.5 euroa Pekka: Kortilla on rahaa 36.0 euroa Matti: Kortilla on rahaa 23.5 euroa Pekka: Kortilla on rahaa 31.0 euroa Matti: Kortilla on rahaa 73.5 euroa
Jatketaan taas Henkilo
-luokan parissa. Päätetään että haluamme laskea henkilöiden painoindeksejä. Tätä varten teemme henkilölle metodit pituuden ja painon asettamista varten, sekä metodin joka laskee painoindeksin. Henkilön uudet ja muuttuneet osat seuraavassa:
public class Henkilo { private String nimi; private int ika; private int paino; private int pituus; public Henkilo(String nimiAlussa) { this.ika = 0; this.paino = 0; this.pituus = 0; this.nimi = nimiAlussa; } public void setPituus(int uusiPituus) { this.pituus = uusiPituus; } public void setPaino(int uusiPaino) { this.paino = uusiPaino; } public double painoIndeksi(){ double pituusPerSata = this.pituus / 100.0; return this.paino / ( pituusPerSata * pituusPerSata ); } // ... }
Eli henkilölle lisättiin oliomuuttujat pituus
ja paino
. Näille voi asettaa arvon metodeilla setPituus
ja setPaino
. Jälleen käytössä Javaan vakiintunut nimeämiskäytäntö, eli jos metodin tehtävänä on ainoastaan asettaa arvo oliomuuttujaan, on metodi tapana nimetä setMuuttujanNimi
:ksi. Arvon asettavia metodeja kutsutaan usein "settereiksi". Seuraavassa käytämme uusia metodeja:
public static void main(String[] args) { Henkilo matti = new Henkilo("Matti"); Henkilo juhana = new Henkilo("Juhana"); matti.setPituus(180); matti.setPaino(86); juhana.setPituus(175); juhana.setPaino(64); System.out.println( matti.getNimi() + ", painoindeksisi on " + matti.painoIndeksi() ); System.out.println( juhana.getNimi() + ", painoindeksisi on " + juhana.painoIndeksi() );
Tulostus:
Matti, painoindeksisi on 26.54320987654321 Juhana, painoindeksisi on 20.897959183673468
Edellä metodissa setPituus
asetetaan oliomuuttujaan pituus
parametrin uusiPituus
arvo:
public void setPituus(int uusiPituus) { this.pituus = uusiPituus; }
Parametrin nimi voisi olla myös sama kuin oliomuuttujan nimi, eli seuraava toimisi myös:
public void setPituus(int pituus) { this.pituus = pituus; }
Nyt metodissa pituus
tarkottaa nimenomaan pituus-nimistä parametria ja this.pituus
saman nimistä oliomuuttujaa. Esim. seuraava ei toimisi sillä koodi ei viittaa ollenkaan oliomuuttujaan pituus:
public void setPituus(int pituus) { // EI TOIMI ÄLÄ TEE NÄIN!!! pituus = pituus; }
Desimaalien määrä edellisessä tulostuksessa on hieman liioiteltu. Yksi tapa päästä määräämään tulostettavien desimaalien määrä on seuraava:
System.out.println( matti.getNimi() + ", painoindeksisi on " + String.format( "%.2f", matti.painoIndeksi() ) ); System.out.println( juhana.getNimi() + ", painoindeksisi on " + String.format( "%.2f", juhana.painoIndeksi() ) );
Eli jos luku
on liukuluku, voi siitä tehdä komennolla String.format( "%.2f", luku )
merkkijonon, jossa luku on otettu mukaan kahden desimaalin tarkkuudella. Pisteen ja f:n välissä oleva numero säätää mukaan tulevan desimaalien määrän.
Nyt tulostus on siistimpi:
Matti, painoindeksisi on 26,54 Juhana, painoindeksisi on 20,90
String.format
ei ehkä ole kaikkein monikäyttöisin tapa Javassa tulostuksen muotoiluun. Se kuitenkin lienee yksinkertaisin ja kelpaa meille hyvin nyt.
Tässä tehtävässä tehdään luokka YlhaaltaRajoitettuLaskuri
ja sovelletaan sitä kellon tekemiseen.
Tehdään luokka YlhaaltaRajoitettuLaskuri
. Luokan olioilla on seuraava toiminnallisuus:
seuraava
kasvattaa laskurin arvoa.
Mutta jos laskurin arvo ylittää ylärajan, sen arvoksi tulee 0.toString
palauttaa laskurin arvon merkkijonona.Tehtäväpohjassa on valmiina pääohjelmaa varten tiedosto Paaohjelma
. Aloita tekemällä luokka YlhaaltaRajoitettuLaskuri
vastaavasti kuin LyyraKortti-tehtävässä. Näin tehdään myös tulevissa tehtäväsarjoissa.
Luokan rungoksi tulee seuraava:
public class YlhaaltaRajoitettuLaskuri { private int arvo; private int ylaraja; public YlhaaltaRajoitettuLaskuri(int ylarajanAlkuarvo) { // kirjoita koodia tähän } public void seuraava() { // kirjoita koodia tähän } public String toString() { // kirjoita koodia tähän } }
Vihje: et voi palauttaa toStringissä suoraan kokonaislukutyyppisen oliomuuttujan laskuri
arvoa. Kokonaislukumuuttujasta arvo
saa merkkijonomuodon esim. lisäämällä sen eteen tyhjän merkkijonon eli kirjoittamalla "" + arvo
.
Seuraavassa on pääohjelma, joka käyttää laskuria:
public class Paaohjelma { public static void main(String[] args) { YlhaaltaRajoitettuLaskuri laskuri = new YlhaaltaRajoitettuLaskuri(4); System.out.println("arvo alussa: " + laskuri ); int i = 0; while ( i < 10) { laskuri.seuraava(); System.out.println("arvo: " + laskuri ); i++; } } }
Laskurille asetetaan konstruktorissa ylärajaksi 4, joten laskurin arvo on luku 0:n ja 4:n väliltä. Huomaa, miten metodi seuraava
vie laskurin arvoa eteenpäin, kunnes se pyörähtää 4:n jälkeen 0:aan:
Ohjelman tulostuksen tulisi olla seuraava:
arvo alussa: 0 arvo: 1 arvo: 2 arvo: 3 arvo: 4 arvo: 0 arvo: 1 arvo: 2 arvo: 3 arvo: 4 arvo: 0
Tee toString
-metodista sellainen, että se lisää arvon merkkijonoesitykseen etunollan, jos laskurin arvo on vähemmän kuin 10. Eli jos laskurin arvo on esim. 3, palautetaan merkkijono "03", jos arvo taas on esim. 12, palautetaan normaaliin tapaan merkkijono "12".
Muuta pääohjelma seuraavaan muotoon ja varmista, että tulos on haluttu.
public class Paaohjelma { public static void main(String[] args) { YlhaaltaRajoitettuLaskuri laskuri = new YlhaaltaRajoitettuLaskuri(14); System.out.println("arvo alussa: " + laskuri ); int i = 0; while ( i < 16){ laskuri.seuraava(); System.out.println("arvo: " + laskuri ); i++; } } }
arvo alussa: 00 arvo: 01 arvo: 02 arvo: 03 arvo: 04 arvo: 05 arvo: 06 arvo: 07 arvo: 08 arvo: 09 arvo: 10 arvo: 11 arvo: 12 arvo: 13 arvo: 14 arvo: 00 arvo: 01
Käyttämällä kahta laskuria voimme muodostaa kellon. Tuntimäärä on laskuri, jonka yläraja on 23, ja minuuttimäärä on laskuri jonka yläraja on 59. Kuten kaikki tietävät, kello toimii siten, että aina kun minuuttimäärä pyörähtää nollaan, tuntimäärä kasvaa yhdellä.
Tee ensin laskurille metodi arvo
, joka palauttaa laskurin arvon:
public int arvo() { // kirjoita koodia tähän }
Tee sitten kello täydentämällä seuraava pääohjelmarunko (kopioi tämä pääohjelmaksesi sekä täydennä tarvittavilta osin kommenttien ohjaamalla tavalla):
public class Paaohjelma { public static void main(String[] args) { YlhaaltaRajoitettuLaskuri minuutit = new YlhaaltaRajoitettuLaskuri(59); YlhaaltaRajoitettuLaskuri tunnit = new YlhaaltaRajoitettuLaskuri(23); int i = 0; while ( i < 121 ) { System.out.println( tunnit + ":" + minuutit); // tulostetaan nykyinen aika // minuuttimäärä kasvaa // jos minuuttimäärä menee nollaan, tuntimäärä kasvaa i++; } } }
Jos kellosi toimii oikein, sen tulostus näyttää suunnilleen seuraavalta:
00:00 00:01 ... 00:59 01:00 01:01 01:02 ... 01:59 02:00
Laajenna kelloasi myös sekuntiviisarilla. Tee lisäksi luokalle YlhaaltaRajoitettuLaskuri
metodi asetaArvo
, jolla laskurille pystyy asettamaan halutun arvon. Jos laskurille yritetään asettaa kelvoton arvo eli negatiivinen luku tai ylärajaa suurempi luku, ei laskurin arvo muutu.
Tämän metodin avulla voit muuttaa kellon ajan heti ohjelman alussa haluamaksesi.
Voit testata kellon toimintaa seuraavalla ohjelmalla
public class Paaohjelma { public static void main(String[] args) { Scanner lukija = new Scanner(System.in); YlhaaltaRajoitettuLaskuri sekunnit = new YlhaaltaRajoitettuLaskuri(59); YlhaaltaRajoitettuLaskuri minuutit = new YlhaaltaRajoitettuLaskuri(59); YlhaaltaRajoitettuLaskuri tunnit = new YlhaaltaRajoitettuLaskuri(23); System.out.print("sekunnit: "); int sek = // kysy sekuntien alkuarvo käyttäjältä System.out.print("minuutit: "); int min = // kysy minuuttien alkuarvo käyttäjältä System.out.print("tunnit: "); int tun = // kysy tuntien alkuarvo käyttäjältä sekunnit.asetaArvo(sek); minuutit.asetaArvo(min); tunnit.asetaArvo(tun); int i = 0; while ( i < 121 ) { // lisää edelliseen myös sekuntiviisari i++; } } }
Kokeile laittaa kellosi alkamaan ajasta 23:59:50 ja varmista, että vuorokauden vaihteessa kello toimii odotetusti!
Bonus-tehtävä: ikuisesti käyvä kello (tehtävää ei palauteta!)
Ennen kuin alat tekemään tätä tehtävää, palauta jo tekemäsi kello!
Muuta pääohjelmasi seuraavaan muotoon:
public class Paaohjelma { public static void main(String[] args) throws Exception { YlhaaltaRajoitettuLaskuri sekunnit = new YlhaaltaRajoitettuLaskuri(59); YlhaaltaRajoitettuLaskuri minuutit = new YlhaaltaRajoitettuLaskuri(59); YlhaaltaRajoitettuLaskuri tunnit = new YlhaaltaRajoitettuLaskuri(23); sekunnit.asetaArvo(50); minuutit.asetaArvo(59); tunnit.asetaArvo(23); while ( true ) { System.out.println( tunnit + ":" + minuutit + ":" + sekunnit ); Thread.sleep(1000); // lisää kellon aikaa sekunnilla eteenpäin } } }
Nyt kello käy ikuisesti ja kasvattaa arvoaan sekunnin välein. Sekunnin odotus tapahtuu komennolla Thread.sleep(1000);
, komennon parametri kertoo nukuttavan ajan millisekunteina. Jotta komento toimisi, pitää main:in esittelyriville tehdä pieni lisäys: public static void main(String[] args) throws Exception {
, eli tummennettuna oleva throws Exception
.
Saat ohjelman lopetettua painamalla NetBeans-konsolin (eli sen osan johon kello tulostaa arvonsa) vasemmalla laidalla olevasta punaisesta laatikosta.
Tärkeitä kommentteja liittyen olioiden käyttöön. Lue nämä ehdottomasti.
Olio-ohjelmoinnissa on kyse pitkälti käsitteiden eristämisestä omiksi kokonaisuuksikseen tai toisin ajatellen abstraktioiden muodostamisesta. Voisi ajatella, että on turha luoda oliota jonka sisällä on ainoastaan luku, eli että saman voisi tehdä suoraan int
-muuttujilla. Asia ei kuitenkaan ole näin. Jos kello koostuu pelkästään kolmesta int-muuttujasta joita kasvatellaan, muuttuu ohjelma lukijan kannalta epäselvemmäksi, koodista on vaikea "nähdä" mistä on kysymys. Aiemmin materiaalissa mainitsimme jo kokeneen ja kuuluisan ohjelmoijan Kent Beckin neuvon "Any fool can write code that a computer can understand. Good programmers write code that humans can understand", eli koska viisari on oikeastaan oma selkeä käsitteensä, on siitä ohjelman ymmärrettävyyden parantamiseksi hyvä tehdä oma luokka, eli YlhaaltaRajoitettuLaskuri
.
Käsitteen erottaminen omaksi luokaksi on monellakin tapaa hyvä idea. Ensinnäkin tiettyjä yksityiskohtia (esim. laskurin pyörähtäminen) saadaan piilotettua luokan sisään (eli abstrahoitua). Sen sijaan että kirjoitetaan if-lause ja sijoitusoperaatio, riittää, että laskurin käyttäjä kutsuu selkeästi nimettyä metodia seuraava()
. Aikaansaatu laskuri sopii kellon lisäksi ehkä muidenkin ohjelmien rakennuspalikaksi, eli selkeästä käsitteestä tehty luokka voi olla monikäyttöinen. Suuri etu saavutetaan myös sillä, että koska laskurin toteutuksen yksityiskohdat eivät näy laskurin käyttäjille, voidaan yksityiskohtia tarvittaessa muuttaa.
Totesimme että kello sisältää kolme viisaria, eli koostuu kolmesta käsitteestä. Oikeastaan kello on itsekin käsite ja teemme ensi viikolla luokan Kello, jotta voimme luoda selkeitä Kello-olioita. Kello tulee siis olemaan olio jonka toiminta perustuu "yksinkertaisimpiin" olioihin eli viisareihin. Tämä on juuri olio-ohjelmoinnin suuri idea: ohjelma rakennetaan pienistä selkeistä yhteistoiminnassa olevista olioista.
Nyt otamme varovaisia ensiaskelia oliomaailmassa. Kurssin lopussa oliot alkavat kuitenkin olla jo selkärangassa ja nyt ehkä käsittämättömältä tuntuva lausahdus, ohjelma rakennetaan pienistä selkeistä yhteistoiminnassa olevista olioista alkaa tuntua meistä ehkä järkeenkäyvältä ja itsestäänselvältä.
Olio voi kutsua myös omia metodeitaan. Jos esim. halutaan, että toString-metodin palauttama merkkijonoesitys kertoisi myös henkilön painoindeksin, kannattaa toString
:istä kutsua olion omaa metodia painoIndeksi
:
public String toString() { return this.nimi + ", ikä " + this.ika + " vuotta, painoindeksini on " + this.painoIndeksi(); }
Eli kun olio kutsuu omaa metodiaan, riittää etuliite this ja pelkkä metodin nimi. Vaihtoehtoinen tapa on tehdä oman metodin kutsu muodossa painoIndeksi()
jolloin ei korosteta, että kutsutaan "olion itsensä" metodia painoindeksi:
public String toString() { return this.nimi + ", ikä " + this.ika + " vuotta, painoindeksini on " + painoIndeksi(); }
Olioscreencastin kolmas osa:
Nyt on aika harjoitella lisää yksinkertaisten olioiden tekemistä käytännössä:
Tee luokka Lukutilasto
(tiedosto luomaasi luokkaa varten on tehtäväpohjassa valmiina), joka tuntee seuraavat toiminnot :
lisaaLuku
lisää uuden luvun tilastoon
haeLukujenMaara
kertoo lisättyjen lukujen määrän
Luokan ei tarvitse tallentaa mihinkään lisättyjä lukuja, vaan riittää muistaa niiden määrä. Metodin lisaaLuku
ei tässä vaiheessa tarvitse edes ottaa huomioon, mikä luku lisätään tilastoon, koska ainoa tallennettava asia on lukujen määrä.
Luokan runko on seuraava:
public class Lukutilasto { private int lukujenMaara; public Lukutilasto() { // alusta tässä muuttuja lukujenMaara } public void lisaaLuku(int luku) { // kirjoita koodia tähän } public int haeLukujenMaara() { // kirjoita koodia tähän } }
Seuraava ohjelma esittelee luokan käyttöä:
public class Paaohjelma { public static void main(String[] args) { Lukutilasto tilasto = new Lukutilasto(); tilasto.lisaaLuku(3); tilasto.lisaaLuku(5); tilasto.lisaaLuku(1); tilasto.lisaaLuku(2); System.out.println("Määrä: " + tilasto.haeLukujenMaara()); } }
Ohjelman tulostus on seuraava:
Määrä: 4
Laajenna luokkaa seuraavilla toiminnoilla:
summa
kertoo lisättyjen lukujen summan (tyhjän lukutilaston summa on 0)
keskiarvo
kertoo lisättyjen lukujen keskiarvon (tyhjän lukutilaston keskiarvo on 0)
Luokan runko on seuraava:
public class Lukutilasto { private int lukujenMaara; private int summa; public Lukutilasto() { // alusta tässä muuttujat maara ja summa } public void lisaaLuku(int luku) { // kirjoita koodia tähän } public int haeLukujenMaara() { // kirjoita koodia tähän } public int summa() { // kirjoita koodia tähän } public double keskiarvo() { // kirjoita koodia tähän } }
Seuraava ohjelma esittelee luokan käyttöä:
public class Main { public static void main(String[] args) { Lukutilasto tilasto = new Lukutilasto(); tilasto.lisaaLuku(3); tilasto.lisaaLuku(5); tilasto.lisaaLuku(1); tilasto.lisaaLuku(2); System.out.println("Määrä: " + tilasto.haeLukujenMaara()); System.out.println("Summa: " + tilasto.summa()); System.out.println("Keskiarvo: " + tilasto.keskiarvo()); } }
Ohjelman tulostus on seuraava:
Määrä: 4 Summa: 11 Keskiarvo: 2.75
Tee ohjelma, joka kysyy lukuja käyttäjältä, kunnes käyttäjä antaa luvun -1. Sitten ohjelma ilmoittaa lukujen summan.
Ohjelmassa tulee käyttää Lukutilasto
-olioa summan laskemiseen.
Anna lukuja: 4 2 5 4 -1 Summa: 15
Muuta edellistä ohjelmaa niin, että ohjelma laskee myös parillisten ja parittomien lukujen summaa.
HUOM: Määrittele ohjelmassa kolme oliota luokasta Lukutilasto
: ensimmäinen laskee kaikkien summan, toinen laskee parillisten lukujen summan ja kolmas laskee parittomien lukujen summan.
Jotta testi toimisi, on oliot luotava pääohjelmassa edellä mainitussa järjestyksessä (eli ensin kaikkien summan laskeva olio, toisena parillisten summan laskeva ja viimeisenä parittomien summan laskeva olio)!
Ohjelman tulee toimia seuraavasti:
Anna lukuja: 4 2 5 2 -1 Summa: 13 Parillisten summa: 8 Parittomien summa: 5
Ohjelmoidessa satunnaisuutta tarvitaan silloin tällöin. Satunnaisuutta -- esimerkiksi sään arvaamattomuutta tai tietokoneen tietokonepeleissä tekemien yllättäviä siirtoja -- voidaan useimmiten simuloida tietokoneelta pyydettävien satunnaislukujen avulla. Satunnaislukujen pyytäminen onnistuu käyttäen Javasta löytyvää Random
-luokkaa. Random-luokkaa voi käyttää seuraavalla tavalla.
import java.util.Random; public class Arvontaa { public static void main(String[] args) { Random arpoja = new Random(); // luodaan arpoja apuväline int i = 0; while (i < 10) { // Arvotaan ja tulostetaan jokaisella kierroksella satunnainen luku System.out.println(arpoja.nextInt(10)); i++; } } }
Yllä olevassa koodissa luodaan ensin Random
-luokan ilmentymä käyttäen avainsanaa new
-- samoin kuin muitakin olioita luodessa. Random-olio tarjoaa metodin nextInt
, jolle annetaan parametrina kokonaisluku. Metodi palauttaa satunnaisen kokonaisluvun väliltä 0..(annettu kokonaisluku - 1).
Ohjelman tulostus voisi olla vaikka seuraavanlainen:
2 2 4 3 4 5 6 0 7 8
Tarvitsemme liukulukuja esimerkiksi todennäköisyyslaskennan yhteydessä. Tietokoneella todennäköisyyksiä simuloidaan yleensä väliltä [0..1] olevilla luvuilla. Random-oliolta saa satunnaisia liukulukuja metodilla nextDouble
. Tarkastellaan seuraavia säämahdollisuuksia:
Luodaan edellä olevista arvioista sääennustaja.
import java.util.ArrayList; import java.util.Random; public class SaaEnnustaja { private Random random; public SaaEnnustaja() { this.random = new Random(); } public String ennustaSaa() { double todennakoisyys = this.random.nextDouble(); if (todennakoisyys <= 0.1) { return "Sataa räntää"; } else if (todennakoisyys <= 0.4) { // 0.1 + 0.3 return "Sataa lunta"; } else { // loput, 1.0 - 0.4 = 0.6 return "Aurinko paistaa"; } } public int ennustaLampotila() { return (int) ( 4 * this.random.nextGaussian() - 3 ); } }
Metodi ennustaLampotila
on monella tapaa mielenkiintoinen. Metodin sisällä tehtävä kutsu this.random.nextGaussian()
on tavallinen metodikutsu, jonka kaltaisia olemme nähneet aikaisemminkin. Kiinnostavaa tässä Random
-luokan ilmentymän tarjoamassa metodissa on se, että metodin palauttama luku on normaalijakautunut (jos et koe mielenkiintoa satunnaisuuden eri lajeihin se ei haittaa!).
public int ennustaLampotila() { return (int) ( 4 * this.random.nextGaussian() - 3 ); }
Edellisessä lausekkeessa kiinnostava on myös osa (int)
. Tämä kohta lausekkeessa muuttaa suluissa olevan liukuluvun kokonaisluvuksi. Vastaavalla menetelmällä voidaan muuttaa myös kokonaislukuja liukuluvuiksi kirjoittamalla (double) kokonaisluku
. Tätä kutsutaan eksplisiittiseksi tyyppimuunnokseksi.
Luodaan vielä pääohjelma josta luokkaa SaaEnnustaja
käytetään.
public class Ohjelma { public static void main(String[] args) { SaaEnnustaja ennustaja = new SaaEnnustaja(); // käytetään listaa apuvälineenä ArrayList<String> paivat = new ArrayList<String>(); Collections.addAll(paivat, "Ma", "Ti", "Ke", "To", "Pe", "La", "Su"); System.out.println("Seuraavan viikon sääennuste:"); for(String paiva : paivat) { String saaEnnuste = ennustaja.ennustaSaa(); int lampotilaEnnuste = ennustaja.ennustaLampotila(); System.out.println(paiva + ": " + saaEnnuste + " " + lampotilaEnnuste + " astetta."); } } }
Ohjelman tulostus voisi olla esimerkiksi seuraavanlainen:
Seuraavan viikon sääennuste: Ma: Sataa lunta 1 astetta. Ti: Sataa lunta 1 astetta. Ke: Aurinko paistaa -2 astetta. To: Aurinko paistaa 0 astetta. Pe: Sataa lunta -3 astetta. La: Sataa lunta -3 astetta. Su: Aurinko paistaa -5 astetta
Tehtäväpohjassa on luokka Noppa
, jolla on seuraavat toiminnot:
Noppa(int tahkojenMaara)
luo uuden noppa-olion annetulla nopan tahkojen (eri oman numeronsa sisältämien "puolien") määrälläheita
kertoo nopanheiton tuloksen (tulos riippuu tahkojen määrästä)Luokan runko on seuraava:
import java.util.Random; public class Noppa { private Random random = new Random(); private int tahkojenMaara; public Noppa(int tahkojenMaara) { // Alusta muuttuja tahkojenMaara tässä } public int heita() { // arvotaan luku väliltä 1-tahkojenMaara } }
Täydennä luokkaa Noppa
siten, että noppa palauttaa jokaisella heitolla arvotun luvun väliltä 1...tahkojen määrä
. Seuraavassa noppaa testaava pääohjelma:
public class Ohjelma { public static void main(String[] args) { Noppa noppa = new Noppa(6); int i = 0; while ( i < 10 ) { System.out.println( noppa.heita() ); i++; } } }
Tulostus voisi olla esimerkiksi seuraava:
1 6 3 5 3 3 2 2 6 1
Tehtävänäsi on täydentää luokkaa SalasananArpoja
, jossa on seuraavat toiminnot:
SalasananArpoja
luo uuden olion, joka käyttää annettua salasanan pituuttaluoSalasana
palauttaa uuden, merkeistä a-z muodostetun konstruktorin parametrin määräämän pituisen salasanan Luokan runko on seuraava:
import java.util.Random; public class SalasananArpoja { // Määrittele muuttuja tässä public SalasananArpoja(int pituus) { // Alusta muuttuja tässä } public String luoSalasana() { // Kirjoita tähän koodi, joka palauttaa uuden salasanan } }
Seuraavassa ohjelma joka käyttää SalasananArpoja-olioa:
public class Ohjelma { public static void main(String[] args) { SalasananArpoja arpoja = new SalasananArpoja(13); System.out.println("Salasana: " + arpoja.luoSalasana()); System.out.println("Salasana: " + arpoja.luoSalasana()); System.out.println("Salasana: " + arpoja.luoSalasana()); System.out.println("Salasana: " + arpoja.luoSalasana()); } }
Ohjelman tulostus voisi näyttää seuraavalta:
Salasana: mcllsoompezvs Salasana: urcxboisknkme Salasana: dzaccatonjcqu Salasana: bpqmedlbqaopq
Vihje1: näin muutat kokonaisluvun luku kirjaimeksi:
int luku = 17; char merkki = "abcdefghijklmnopqrstuvxyz".charAt(luku);
Vihje2: tehtävän 58 vihjeestä lienee tässäkin tehtävässä apua.
Tehtävänäsi on täydentää luokkaa LottoRivi
, joka arpoo viikon lottonumerot. Lottonumerot ovat väliltä 1–39 ja niitä arvotaan 7. Lottorivi koostuu siis 7:stä eri numerosta väliltä 1–39. Luokassa on seuraavat toiminnot:
LottoRivi
luo uuden LottoRivi-olion joka sisältää uudet, arvotut numerotnumerot
palauttaa tämän lottorivin lottonumerotarvoNumerot
arpoo riville uudet numerotsisaltaaNumeron
kertoo onko arvotuissa numeroissa annettu numeroLuokan runko on seuraava:
import java.util.ArrayList; import java.util.Random; public class LottoRivi { private ArrayList<Integer> numerot; public LottoRivi() { // Alustetaan lista numeroille this.numerot = new ArrayList<Integer>(); // Arvo numerot heti LottoRivin luomisen yhteydessä this.arvoNumerot(); } public ArrayList<Integer> numerot() { return this.numerot; } public void arvoNumerot() { // Kirjoita numeroiden arvonta tänne käyttämällä metodia sisaltaaNumeron() } public boolean sisaltaaNumeron(int numero) { // Testaa tässä onko numero jo arvottujen numeroiden joukossa } }
Tehtäväpohjan mukana tulee seuraava pääohjelma:
import java.util.ArrayList; public class Ohjelma { public static void main(String[] args) { LottoRivi lottoRivi = new LottoRivi(); ArrayList<Integer> lottonumerot = lottoRivi.numerot(); System.out.println("Lottonumerot:"); for (int numero : lottonumerot) { System.out.print(numero + " "); } System.out.println(""); } }
Ohjelman mahdollisia tulostuksia ovat seuraavat:
Lottonumerot: 3 5 10 14 15 27 37
Lottonumerot: 2 9 11 18 23 32 34
Huom! Sama numero saa esiintyä lottorivissä vain kerran.
Hirsipuu-pelistä pitävä kaverisi ohjelmoi hirsipuupelin joka näyttää seuraavalta.
Hänen koneestaan hajosi kovalevy, eikä hän ollut tehnyt varmuuskopiota. Hän on kuitenkin luvannut pelin lahjaksi pikkuveljelleen, ja aikataulukiireidensä takia tarvitsee apuasi pelin toteutukseen. Tässä tehtäväsarjassa toteutat kaverisi hirsipuu-pelille arvauslogiikan ja annetun sanan salaamisen.
Kaverisi on luonut pelille käyttöliittymän sekä valmiin rungon sovelluksen logiikkaan. Kun lataat projektin TMC:ltä, näet käyttöliittymä- ja testiluokkien lisäksi allaolevan ohjelmarungon.
public class HirsipuuLogiikka { private String sana; private String arvatutKirjaimet; private int virheidenLukumaara; public HirsipuuLogiikka(String sana) { this.sana = sana.toUpperCase(); this.arvatutKirjaimet = ""; this.virheidenLukumaara = 0; } public int virheidenLukumaara() { return this.virheidenLukumaara; } public String arvatutKirjaimet() { return this.arvatutKirjaimet; } public int virheitaHavioon() { return 12; } public void arvaaKirjain(String kirjain) { // Ohjelmoi tänne toiminnallisuus kirjaimen arvaamiseksi. // Arvattua kirjainta ei saa arvata uudestaan. Jos arvattava sana // ei sisällä kirjainta, virheiden lukumäärän tulee kasvaa. // Lisää arvattu kirjain arvattuihin kirjaimiin } public String salattuSana() { // Ohjelmoi tänne toiminnallisuus salatun sanan luomiseksi // ja antamiseksi. // Luo uusi salattu sana this.sana-merkkijonoa kirjain kirjaimelta // läpikäyden. Jos kirjain on arvatuissa kirjaimissa, voit lisätä sen // sellaisenaan. Jos ei, lisää uuteen sanaan merkki "_". // Muista varmistaa ettet ohjelmoi päättymätöntä toistolauseketta! // palauta lopuksi salattua sanaa kuvaava merkkijono. return ""; } }
Tässä tehtävässä sinun tulee lisätä toiminnallisuutta vain luokan HirsipuuLogiikka
metodeihin arvaaKirjain(String kirjain)
ja salattuSana()
.
Logiikan testaaminen
Tehtäväpohja sisältää kaksi testiluokkaa. Luokka Main
käynnistää kaverisi toteuttaman graafisen version pelistä. Luokkaa Testiohjelma
taas voit käyttää HirsipuuLogiikka
-luokan testaamiseen.
Muokkaa tässä tehtävässä vain metodin arvaaKirjain(String kirjain)
toiminnallisuutta.
Kun käyttäjä arvaa jonkin kirjaimen, kutsuu hirsipuun käyttöliittymä hirsipuulogiikan metodia arvaaKirjain
jonka tulee tarkistaa onko arvattava kirjain jo arvattujen kirjainten (joita hirsipuulogiikka säilyttää oliomuuttujassa this.arvatutKirjaimet
) joukossa. Jos kirjain on jo arvattujen kirjainten joukossa, metodin ei tule tehdä mitään. Jos arvattavaa kirjainta ei ole arvattu aiemmin, on tarkastettava onko se arvattavassa sanassa (jota hirsipuulogiikka säilyttää oliomuuttujassa this.sana
). Jos ei, virheiden lukumäärää (oliomuuttuja this.virheidenLukumaara
) pitää kasvattaa yhdellä. Arvattu kirjain pitää lopuksi lopuksi osaksi arvattuja kirjaimia, jos se ei ole jo siellä.
Esimerkki arvaaKirjain
-metodin toiminnasta:
HirsipuuLogiikka l = new HirsipuuLogiikka("kissa"); System.out.println("Arvataan: A, D, S, F, D"); l.arvaaKirjain("A"); // oikea l.arvaaKirjain("D"); // väärä l.arvaaKirjain("S"); // oikea l.arvaaKirjain("F"); // väärä l.arvaaKirjain("D"); // Tämän ei pitäisi kasvattaa virheiden lukumäärää koska D oli jo arvattu System.out.println("Arvatut kirjaimet: "+l.arvatutKirjaimet()); System.out.println("Virheiden lukumäärä: "+l.virheidenLukumaara());
Arvataan: A, D, S, F, D Arvatut kirjaimet: ADSF Virheiden lukumäärä: 2
Hirsipuun käyttöliittymä näyttää käyttäjälle salatun muodon arvattavana olevasta sanasta. Yllä olevassa kuvassa salattu sana on METO_I
, eli arvattavana olevasta sanasta näytetään vain arvatut kirjaimet, muut korvataan alaviivalla. Salatun sanan muodostaa hirsipuulogiikan metodi salattuSana()
jonka sisältöä tässä tehtävässä on tarkoitus muokata.
Salattu sana luodaan luomalla arvattavasta sanasta (this.sana
) uusi versio. Jokainen kirjain jota ei ole vielä arvattu tulee vaihtaa merkkiin "_". Jos kirjain on jo arvattu, eli se löytyy arvatuista kirjaimista, voidaan se lisätä sellaisenaan salattuun sanaan.
Avainsanoista while
, charAt
ja contains
lienee tästä hyötyä. Saat muutettua yksittäisen merkin merkkijonoksi seuraavasti:
char merkki = 'a'; String merkkijono = "" + merkki;
Esimerkki metodin toiminnasta:
HirsipuuLogiikka l = new HirsipuuLogiikka("kissa"); System.out.println("Sana on: "+l.salattuSana()); System.out.println("Arvataan: A, D, S, F, D"); l.arvaaKirjain("A"); l.arvaaKirjain("D"); l.arvaaKirjain("S"); l.arvaaKirjain("F"); l.arvaaKirjain("D"); System.out.println("Arvatut kirjaimet: "+l.arvatutKirjaimet()); System.out.println("Virheiden lukumäärä: "+l.virheidenLukumaara()); System.out.println("Sana on: "+l.salattuSana());
Sana on: _____ Arvataan: A, D, S, F, D Arvatut kirjaimet: ADSF Virheiden lukumäärä: 2 Sana on: __SSA
Testaa lopuksi sinun ja kaverisi yhteistyössä tekemää ohjelmaa Main
-luokan avulla! Voit muuttaa arvattavaa sanaa muuttamalla HirsipuuLogiikka-luokan konstruktorille parametrina annettavaa merkkijonoa:
HirsipuuLogiikka logiikka = new HirsipuuLogiikka("parametri"); HirsipuuIkkuna peliIkkuna = new HirsipuuIkkuna(logiikka); peliIkkuna.pelaa();
Peliä pelataan näppäimistöltä, pelin voi lopettaa painamalla peli-ikkunan vasemmassa yläkulmassa olevaa x-merkkiä.
Palataan jälleen henkilöitä käsittelevän luokan pariin. Luokka Henkilo
näyttää tällä hetkellä seuraavalta:
public class Henkilo { private String nimi; private int ika; private int pituus; private int paino; public Henkilo(String nimi) { this.nimi = nimi; this.ika = 0; this.paino = 0; this.pituus = 0; } public void tulostaHenkilo() { System.out.println(this.nimi + " olen " + this.ika + " vuotta vanha"); } public void vanhene() { this.ika++; } public boolean taysiIkainen(){ if ( this.ika < 18 ) { return false; } return true; } public double painoindeksi(){ double pituusMetreina = this.pituus/100.0; return this.paino / (pituusMetreina*pituusMetreina); } public String toString(){ return this.nimi + " olen " + this.ika + " vuotta vanha, painoindeksini on " + this.painoindeksi(); } public void setPituus(int pituus){ this.pituus = pituus; } public int getPituus(){ return this.pituus; } public int getPaino() { return this.paino; } public void setPaino(int paino) { this.paino = paino; } public String getNimi(){ return this.nimi; } }
Kaikki henkilöoliot ovat luontihetkellä 0-vuotiaita, sillä konstruktori asettaa uuden henkilön ika-oliomuuttujan arvoksi 0:
public Henkilo(String nimi) { this.nimi = nimi; this.ika = 0; this.paino = 0; this.pituus = 0; }
Haluaisimme myös luoda henkilöitä siten, että nimen lisäksi konstruktorin parametrina annettaisiin ikä. Tämä onnistuu helposti, sillä konstruktoreja voi olla useita. Tehdään vaihtoehtoinen konstruktori. Vanhaa konstruktoria ei tarvise poistaa.
public Henkilo(String nimi) { this.nimi = nimi; this.ika = 0; this.paino = 0; this.pituus = 0; } public Henkilo(String nimi, int ika) { this.nimi = nimi; this.ika = ika; this.paino = 0; this.pituus = 0; }
Nyt olioiden luonti onnistuu kahdella vaihtoehtoisella tavalla:
public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka", 24); Henkilo esko = new Henkilo("Esko"); System.out.println( pekka ); System.out.println( esko ); }
Pekka, ikä 24 vuotta Esko, ikä 0 vuotta
Tekniikkaa jossa luokalla on kaksi konstruktoria, kutsutaan konstruktorin kuormittamiseksi. Luokalla voi siis olla useita konstruktorjea, jotka poikkeavat toisistaanparametriensa määrältä tai tyypeiltä. Ei kuitenkaan ole mahdollista tehdä kahta erilaista konstruktoria joilla on täysin saman tyyppiset parametrit. Emme siis voi edellisten lisäksi lisätä konstruktoria public Henkilo(String nimi, int paino)
sillä Javan on mahdoton erottaa tätä kaksiparametrisesta konstruktorissa, jossa luku tarkoittaa ikää.
Mutta hetkinen, luvussa 21 todettiin että "copy-paste"-koodi ei ole hyvä idea. Kun tarkastellaan edellä tehtyjä kuormitettuja konstruktoreita, niissä on aika paljon samaa. Emme ole oikein tyytyväisiä tilanteeseen.
Konstruktoreista ylempi on oikeastaan alemman erikoistapaus. Entä jos ylempi konstruktori voisi "kutsua" alempaa konstruktoria? Tämä onnistuu, sillä konstruktorin sisältä voi kutsua toista konstruktoria juuri tähän olioon liittyvän this
-ilmauksen avulla!
Muutetaan ylempää konstruktoria siten, että se ei itse tee mitään vaan ainoastaan kutsuu alempaa konstruktoria ja pyytää sitä asettamaan iäksi 0:
public Henkilo(String nimi) { this(nimi, 0); // suorita tässä toisen konstruktorin koodi ja laita ika-parametrin arvoksi 0 } public Henkilo(String nimi, int ika) { this.nimi = nimi; this.ika = ika; this.paino = 0; this.pituus = 0; }
Oman konstruktorin kutsu this(nimi, 0);
saattaa vaikuttaa erikoiselta. Asiaa voi vaikka ajatella siten, että kutsun kohdalle tulee "copy-pastena" automaattisesti alemman konstruktorin koodi, siten että ika parametrin arvoksi tulee 0.
Konstruktorien tapaan myös metodeja voi kuormittaa, eli samannimisestä metodista voi olla useita versioita. Jälleen eri versioiden parametrien tyyppien on oltava erilaiset. Tehdään vanhene
-metodista toinen versio, joka mahdollistaa henkilön vanhentamisen parametrina olevalla vuosimäärällä:
public void vanhene() { this.ika = this.ika + 1; } public void vanhene(int vuodet) { this.ika = this.ika + vuodet; }
Seuraavassa "Pekka" syntyy 24-vuotiaana, vanhenee ensin vuoden ja sitten 10 vuotta:
public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka", 24); System.out.println( pekka ); pekka.vanhene(); System.out.println( pekka ); pekka.vanhene(10); System.out.println( pekka );
Tulostuu:
Pekka, ikä 24 vuotta Pekka, ikä 25 vuotta Pekka, ikä 35 vuotta
Henkilöllä on siis vanhene
-nimisiä metodeja 2 kappaletta. Se kumpi metodeista valitaan suoritettavaksi, riippuu metodikutsussa käytettyjen parametrien määrästä. Metodin vanhene
voi myös toteuttaa metodin vanhene(int vuodet)
avulla:
public void vanhene() { this.vanhene(1); } public void vanhene(int vuodet) { this.ika = this.ika + vuodet; }
Toteuta luokka Laskuri
, joka sisältää luvun, jota voi
vähentää ja suurentaa. Laskurissa on lisäksi
valinnainen tarkistus joka estää laskurin menemisen
miinukselle. Luokalla tulee olla seuraavat konstruktorit:
public Laskuri(int alkuarvo, boolean tarkistus)
asettaa laskurin alkuarvoksi parametrin alkuarvo
arvon. Tarkistus on päällä jos parametrin tarkista
arvoksi annettiin
true
.public Laskuri(int alkuarvo)
asettaa laskurin alkuarvoksi parametrin alkuarvo
arvon, tarkastus ei ole päällä.public Laskuri(boolean tarkista)
laskurin alkuarvoksi tulee 0. Tarkistus on päällä jos parametrin tarkista
arvoksi annettiin
true
.public Laskuri()
laskurin alkuarvoksi tulee 0 ja tarkastus ei ole päällä.ja seuraavat metodit:
public int arvo()
palauttaa laskurin tämänhetkisen arvonpublic void lisaa()
lisää laskurin arvoa yhdelläpublic void vahenna()
vähentää laskurin arvoa yhdellä,
mutta ei alle nollan jos tarkistus on päälläTee laskurin metodeista lisaa
ja vahenna
myös yksiparametriset versiot:
public void lisaa(int lisays)
lisää laskurin arvoa parametrina annetun luvun verran. Jos parametrin arvo on negatiivinen, ei laskurin arvo muutu.public void vahenna(int vahennys)
vähentää laskurin arvoa parametrina annetun luvun verran,
mutta ei alle nollan jos tarkistus on päällä. Jos parametrin arvo on negatiivinen, ei laskurin arvo muutu.Luvussa 20 mainittiin, että ArrayList
on "langan päässä". Myös oliot ovat langan päässä. Mitä tämä oikein tarkoittaa? Tarkastellaan seuraavaa esimerkkiä:
public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka", 24); System.out.println( pekka ); }
Kun suoritamme lauseen Henkilo pekka = new Henkilo("Pekka", 24);
syntyy olio. Olioon päästään käsiksi muuttujan pekka
avulla. Teknisesti ottaen olio ei ole muuttujan pekka
"sisällä" (eli lokerossa pekka) vaan pekka
viittaa syntyneeseen olioon. Toisin sanonen olio on pekka
-nimisestä muuttujasta lähtevän "langan päässä". Kuvana asiaa voisi havainnollistaa seuraavasti:
Lisätään ohjelmaan Henkilo
-tyyppinen muuttuja henkilo
ja annetaan sille alkuarvoksi pekka
. Mitä nyt tapahtuu?
public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka", 24); System.out.println( pekka ); Henkilo henkilo = pekka; henkilo.vanhene(25); System.out.println( pekka ); }
Tulostuu:
Pekka, ikä 24 vuotta Pekka, ikä 49 vuotta
Eli Pekka on alussa 24-vuotias, henkilo
-muuttujaan liittyvän langan päässä olevaa Henkilö-oliota vanhennetaan 25:llä vuodella ja sen seurauksena Pekka vanhenee! Mistä on kysymys?
Komento Henkilo henkilo = pekka;
saa aikaan sen, että henkilo
rupeaa viittaamaan samaan olioon kuin mihin pekka
viittaa. Eli ei synnykään kopiota oliosta, vaan molemmissa muuttujissa on langan päässä sama olio. Komennossa Henkilo henkilo = pekka;
syntyy kopio langasta. Kuvana (Huom: kuvassa p ja h tarkottavat pääohjelman muuttujia pekka ja henkilo. Kuvien muuttujanimiä on lyhennelty myös muutamassa seuraavassa kuvassa.):
Esimerkissä "vieras henkilö henkilo
ryöstää Pekan identiteetin". Seuraavassa esimerkkiä on jatkettu siten, että luodaan uusi olio ja pekka
alkaa viittaamaan uuteen olioon:
public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka", 24); System.out.println( pekka ); Henkilo henkilo = pekka; henkilo.vanhene(25); System.out.println( pekka ); pekka = new Henkilo("Pekka Mikkola", 24); System.out.println( pekka ); }
Tulostuu:
Pekka, ikä 24 vuotta Pekka, ikä 49 vuotta Pekka Mikkola, ikä 24 vuotta
Muuttuja pekka
viittaa siis ensin yhteen olioon, mutta rupeaa sitten viittaamaan toiseen olion.
Seuraavassa kuva tilanteesta viimeisen koodirivin jälkeen:
Jatketaan vielä esimerkkiä laittamalla henkilo
viittaamaan "ei mihinkään", eli null
:iin:
public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka", 24); System.out.println( pekka ); Henkilo henkilo = pekka; henkilo.vanhene(25); System.out.println( pekka ); pekka = new Henkilo("Pekka Mikkola", 24); System.out.println( pekka ); henkilo = null; System.out.println( henkilo ); }
Viimeisen koodirivin jälkeen näyttää seuraavalta:
Alempaan olioon ei nyt viittaa kukaan. Oliosta on tullut "roska". Javan roskienkerääjä käy siivoamassa aika ajoin roskaksi joutuneet oliot. Jos näin ei tehtäisi, jäisivät ne kuluttamaan turhaan koneen muistia ohjelman suorituksen loppuun asti.
Huomaamme, että viimeisellä rivillä yritetään vielä tulostaa "ei mitään" eli null
. Käy seuraavasti:
Pekka, ikä 24 vuotta Pekka, ikä 49 vuotta Pekka Mikkola, ikä 24 vuotta null
Mitä tapahtuu jos yritämme kutsua "ei minkään" metodia, esimerkiksi metodia painoIndeksi
:
public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka", 24); System.out.println( pekka ); Henkilo henkilo = null; System.out.println( henkilo.painoIndeksi() ); }
Tulos:
Pekka, ikä 24 vuotta Exception in thread "main" java.lang.NullPointerException at Main.main(Main.java:20) Java Result: 1
Eli käy huonosti. Tämän on ehkä ensimmäinen kerta elämässäsi kun näet tekstin NullPointerException. Voimme luvata, että tulet näkemään sen vielä uudelleen. NullPointerException on poikkeustila, joka syntyy kun null
-arvoisen olion metodeja yritetään kutsua.
Olemme nähneet että metodien parametrina voi olla esim. int, double, String
tai ArrayList. ArrayListit ja merkkijonot ovat olioita, joten kuten arvata saattaa, metodi voi saada
parametriksi minkä tahansa tyyppisen olion. Demonstroidaan tätä esimerkillä.
Painonvartijoihin hyväksytään jäseniksi henkilöitä, joiden painoindeksi ylittää jonkun annetun rajan. Kaikissa painonvartijayhdistyksissä raja ei ole sama. Tehdään painonvartijayhdistystä vastaava luokka. Olioa luotaessa konstruktorille annetaan parametriksi pienin painoindeksi, jolla yhdistyksen jäseneksi pääsee.
public class PainonvartijaYhdistys { private double alinPainoindeksi; public PainonvartijaYhdistys(double indeksiRaja) { this.alinPainoindeksi = indeksiRaja; } }
Tehdään sitten metodi, jonka avulla voidaan tarkastaa hyväksytäänkö tietty henkilö
yhdistyksen jäseneksi, eli onko henkilön painoindeksi tarpeeksi suuri.
Metodi palauttaa true
jos parametrina annettu henkilö hyväksytään, false
jos ei.
public class PainonvartijaYhdistys { // ... public boolean hyvaksytaanJaseneksi(Henkilo henkilo) { if ( henkilo.painoIndeksi() < this.alinPainoindeksi ) { return false; } return true; } }
Painonvartijayhdistys-olion metodi hyvaksytaanJaseneksi
saa siis parametriksi Henkilo
-olion (tarkemmin sanottuna "langan" henkilöön) ja kutsuu parametrina saamansa henkilön metodia painoIndeksi
.
Seuraavassa testipääohjelma jossa painonvartijayhdistyksen metodille annetaan ensin parametriksi henkilöolio matti
ja sen jälkeen henkilöolio juhana
:
public static void main(String[] args) { Henkilo matti = new Henkilo("Matti"); matti.setPaino(86); matti.setPituus(180); Henkilo juhana = new Henkilo("Juhana"); juhana.setPaino(64); juhana.setPituus(172); PainonvartijaYhdistys kumpulanPaino = new PainonvartijaYhdistys(25); if ( kumpulanPaino.hyvaksytaanJaseneksi(matti) ) { System.out.println( matti.getNimi() + " pääsee jäseneksi"); } else { System.out.println( matti.getNimi() + " ei pääse jäseneksi"); } if ( kumpulanPaino.hyvaksytaanJaseneksi(juhana) ) { System.out.println( juhana.getNimi() + " pääsee jäseneksi"); } else { System.out.println( juhana.getNimi() + " ei pääse jäseneksi"); } }
Ohjelma tulostaa:
Matti pääsee jäseneksi Juhana ei pääse jäseneksi
Mene luokan koodilohkon sisäpuolelle mutta kaikkien metodien ulkopuolelle ja paina yhtä aikaa ctrl ja välilyönti. Jos luokallasi on esim. oliomuuttuja saldo
, tarjoaa NetBeans mahdollisuuden generoida oliomuuttujalle getteri- ja setterimetodit sekä konstruktorin joka asettaa oliomuuttujalle alkuarvon.
HUOM: laitoksen tämä saadaan aikaan painamalla yhtä aikaa ctrl, alt ja välilyönti.
Tehtäväpohjassasi on valmiina jo tutuksi tullut luokka Henkilo
sekä runko luokalle Kasvatuslaitos
. Kasvatuslaitosoliot käsittelevät ihmisiä eri tavalla, esim. punnitsevat ja syöttävät ihmisiä. Rakennamme tässä tehtävässä kasvatuslaitoksen. Luokan Henkilö koodiin ei tehtävässä ole tarkoitus koskea!
Kasvatuslaitoksen luokkarungossa on valmiina runko metodille punnitse
:
public class Kasvatuslaitos { public int punnitse(Henkilo henkilo) { // palautetaan parametrina annetun henkilön paino return -1; } }
Metodi saa parametrina henkilön ja metodin on tarkoitus palauttaa kutsujalleen parametrina olevan henkilön paino. Paino selviää kutsumalla parametrina olevan henkilön henkilo
sopivaa metodia. Eli täydennä metodin koodi!
Seuraavassa on pääohjelma jossa kasvatuslaitos punnitsee kaksi henkilöä:
public static void main(String[] args) { // esimerkkipääohjelma tehtävän ensimmäiseen kohtaan Kasvatuslaitos haaganNeuvola = new Kasvatuslaitos(); Henkilo eero = new Henkilo("Eero", 1, 110, 7); Henkilo pekka = new Henkilo("Pekka", 33, 176, 85); System.out.println(eero.getNimi() + " paino: " + haaganNeuvola.punnitse(eero) + " kiloa"); System.out.println(pekka.getNimi() + " paino: " + haaganNeuvola.punnitse(pekka) + " kiloa"); }
Tulostuksen pitäisi olla seuraava:
Eero paino: 7 kiloa Pekka paino: 85 kiloa
Parametrina olevan olion tilaa on mahdollista muuttaa. Tee kasvatuslaitokselle metodi public void syota(Henkilo henkilo)
joka kasvattaa parametrina olevan henkilön painoa yhdellä.
Seuraavassa esimerkki, jossa henkilöt ensin punnitaan, ja tämän jälkeen neuvolassa syötetään eeroa kolme kertaa. Tämän jälkeen henkilöt taas punnitaan:
public static void main(String[] args) { Kasvatuslaitos haaganNeuvola = new Kasvatuslaitos(); Henkilo eero = new Henkilo("Eero", 1, 110, 7); Henkilo pekka = new Henkilo("Pekka", 33, 176, 85); System.out.println(eero.getNimi() + " paino: " + haaganNeuvola.punnitse(eero) + " kiloa"); System.out.println(pekka.getNimi() + " paino: " + haaganNeuvola.punnitse(pekka) + " kiloa"); haaganNeuvola.syota(eero); haaganNeuvola.syota(eero); haaganNeuvola.syota(eero); System.out.println(""); System.out.println(eero.getNimi() + " paino: " + haaganNeuvola.punnitse(eero) + " kiloa"); System.out.println(pekka.getNimi() + " paino: " + haaganNeuvola.punnitse(pekka) + " kiloa"); }
Tulostuksen pitäisi paljastaa että Eeron paino on noussut kolmella:
Eero paino: 7 kiloa Pekka paino: 85 kiloa Eero paino: 10 kiloa Pekka paino: 85 kiloa
Tee kasvatuslaitokselle metodi public int
punnitukset()
joka kertoo kuinka monta punnitusta
kasvatuslaitos on ylipäätään tehnyt. Testipääohjelma:
public static void main(String[] args) { // esimerkkipääohjelma tehtävän ensimmäiseen kohtaan Kasvatuslaitos haaganNeuvola = new Kasvatuslaitos(); Henkilo eero = new Henkilo("Eero", 1, 110, 7); Henkilo pekka = new Henkilo("Pekka", 33, 176, 85); System.out.println("punnituksia tehty "+haaganNeuvola.punnitukset()); haaganNeuvola.punnitse(eero); haaganNeuvola.punnitse(pekka); System.out.println("punnituksia tehty "+haaganNeuvola.punnitukset()); haaganNeuvola.punnitse(eero); haaganNeuvola.punnitse(eero); haaganNeuvola.punnitse(eero); haaganNeuvola.punnitse(eero); System.out.println("punnituksia tehty "+haaganNeuvola.punnitukset()); }
Tulostuu:
punnituksia tehty 0 punnituksia tehty 2 punnituksia tehty 6
Teimme viime viikolla luokan LyyraKortti. Kortilla oli metodit edullisesti ja maukkaasti syömistä sekä rahan lataamista varten.
Viime viikon tyylillä tehdyssä Lyyra-kortissa oli kuitenkin ongelma. Kortti tiesi lounaiden hinnan ja osasi sen ansiosta vähentää saldoa oikean määrän. Entä kun hinnat nousevat? Tai jos myyntivalikoimaan tulee uusia tuotteita? Hintojen muuttaminen tarkoittaisi, että kaikki jo käytössä olevat Lyyra-kortit pitäisi korvata uusilla, uudet hinnat tuntevilla korteilla.
Parempi ratkaisu on tehdä kortit "tyhmiksi", hinnoista ja myytävistä tuotteista tietämättömiksi pelkän saldon säilyttäjiksi. Kaikki äly kannattaakin laittaa erillisiin olioihin, kassapäätteisiin.
Toteutetaan ensin Lyyra-kortista "tyhmä" versio. Kortilla on ainoastaan metodit saldon kysymiseen, rahan lataamiseen ja rahan ottamiseen. Täydennä alla (ja tehtäväpohjassa) olevaan luokkaan metodin public boolean otaRahaa(double maara)
ohjeen mukaan:
public class LyyraKortti { private double saldo; public LyyraKortti(double saldo) { this.saldo = saldo; } public double saldo() { return this.saldo; } public void lataaRahaa(double lisays) { this.saldo += lisays; } public boolean otaRahaa(double maara){ // toteuta metodi siten että se ottaa kortilta rahaa vain jos saldo on vähintään maara // onnistuessaan metodi palauttaa true ja muuten false } }
Testipääohjelma:
public class Paaohjelma { public static void main(String[] args) { LyyraKortti pekanKortti = new LyyraKortti(10); System.out.println("rahaa " + pekanKortti.saldo() ); boolean onnistuiko = pekanKortti.otaRahaa(8); System.out.println("onnistuiko otto: " + onnistuiko ); System.out.println("rahaa " + pekanKortti.saldo() ); onnistuiko = pekanKortti.otaRahaa(4); System.out.println("onnistuiko otto: " + onnistuiko ); System.out.println("rahaa " + pekanKortti.saldo() ); } }
Tulostuksen kuuluisi olla seuraavanlainen
rahaa 10.0 onnistuiko otto: true rahaa 2.0 onnistuiko otto: false rahaa 2.0
Unicafessa asioidessa asiakas maksaa joko käteisellä tai Lyyra-kortilla. Myyjä käyttää kassapäätettä kortin velottamiseen ja käteismaksujen hoitamiseen. Tehdään ensin kassapäätteestä käteismaksuihin sopiva versio.
Kassapäätteen runko. Metodien kommentit kertovat halutun toiminnallisuuden:
public class Kassapaate { private double rahaa; // kassassa olevan käteisen määrä private int edulliset; // myytyjen edullisten lounaiden määrä private int maukkaat; // myytyjen maukkaiden lounaiden määrä public Kassapaate() { // kassassa on aluksi 1000 euroa rahaa } public double syoEdullisesti(double maksu) { // edullinen lounas maksaa 2.50 euroa. // kasvatetaan kassan rahamäärää edullisenlounaan hinnalla ja palautetaan vaihtorahat // jos parametrina annettu maksu ei ole riittävän suuri, ei lounasta myydä ja metodi palauttaa koko summan } public double syoMaukkaasti(double maksu) { // maukas lounas maksaa 4.00 euroa. // kasvatetaan kassan rahamäärää maukkaan lounaan hinnalla ja palautetaan vaihtorahat // jos parametrina annettu maksu ei ole riittävän suuri, ei lounasta myydä ja metodi palauttaa koko summan } public String toString() { return "kassassa rahaa "+rahaa+" edullisia lounaita myyty "+edulliset+" maukkaita lounaita myyty "+maukkaat; } }
Kassapäätteessä on aluksi rahaa 1000 euroa. Toteuta ylläolevan rungon metodit ohjeen ja alla olevan pääohjelman esimerkkitulosteen mukaan toimiviksi.
public class Paaohjelma { public static void main(String[] args) { Kassapaate unicafeExactum = new Kassapaate(); double vaihtorahaa = unicafeExactum.syoEdullisesti(10); System.out.println("vaihtorahaa jäi " + vaihtorahaa ); vaihtorahaa = unicafeExactum.syoEdullisesti(5); System.out.println("vaihtorahaa jäi " + vaihtorahaa ); vaihtorahaa = unicafeExactum.syoMaukkaasti(4); System.out.println("vaihtorahaa jäi " + vaihtorahaa ); System.out.println( unicafeExactum ); } }
vaihtorahaa jäi 7.5 vaihtorahaa jäi 2.5 vaihtorahaa jäi 0.0 kassassa rahaa 1009.0 edullisia lounaita myyty 2 maukkaita lounaita myyty 1
Laajennetaan kassapäätettä siten että myös kortilla voi maksaa. Teemme kassapäätteelle siis metodit joiden parametrina kassapääte saa lyyrakortin jolta se vähentää valitun lounaan hinnan. Seuraavassa uusien metodien rungot ja ohje niiden toteuttamiseksi:
public class Kassapaate { // ... public boolean syoEdullisesti(LyyraKortti kortti) { // edullinen lounas maksaa 2.50 euroa. // jos kortilla on tarpeeksi rahaa, vähennetään hinta kortilta ja palautetaan true // muuten palautetaan false } public boolean syoMaukkaasti(LyyraKortti kortti) { // edullinen lounas maksaa 4.00 euroa. // jos kortilla on tarpeeksi rahaa, vähennetään hinta kortilta ja palautetaan true // muuten palautetaan false } // ... }
Huom: kortilla maksaminen ei lisää kassapäätteessä olevan käteisen määrää.
Seuraavassa testipääohjelma ja haluttu tulostus:
public class Paaohjelma { public static void main(String[] args) { Kassapaate unicafeExactum = new Kassapaate(); double vaihtorahaa = unicafeExactum.syoEdullisesti(10); System.out.println("vaihtorahaa jäi " + vaihtorahaa ); LyyraKortti antinKortti = new LyyraKortti(7); boolean onnistuiko = unicafeExactum.syoMaukkaasti(antinKortti); System.out.println("riittikö raha: " + onnistuiko); onnistuiko = unicafeExactum.syoMaukkaasti(antinKortti); System.out.println("riittikö raha: " + onnistuiko); onnistuiko = unicafeExactum.syoEdullisesti(antinKortti); System.out.println("riittikö raha: " + onnistuiko); System.out.println( unicafeExactum ); } }
vaihtorahaa jäi 7.5 riittikö raha: true riittikö raha: false riittikö raha: true kassassa rahaa 1002.5 edullisia lounaita myyty 2 maukkaita lounaita myyty 1
Lisätään vielä kassapäätteelle metodi jonka avulla kortille voidaan ladata lisää rahaa. Muista, että rahan lataamisen yhteydessä ladattava summa viedään kassapäätteeseen. Metodin runko:
public void lataaRahaaKortille(LyyraKortti kortti, double summa) { // ... }
Testipääohjelma ja esimerkkisyöte:
public class Paaohjelma { public static void main(String[] args) { Kassapaate unicafeExactum = new Kassapaate(); System.out.println( unicafeExactum ); LyyraKortti antinKortti = new LyyraKortti(2); System.out.println("kortilla rahaa " + antinKortti.saldo() + " euroa"); boolean onnistuiko = unicafeExactum.syoMaukkaasti(antinKortti); System.out.println("riittikö raha: " + onnistuiko); unicafeExactum.lataaRahaaKortille(antinKortti, 100); onnistuiko = unicafeExactum.syoMaukkaasti(antinKortti); System.out.println("riittikö raha: " + onnistuiko); System.out.println("kortilla rahaa " + antinKortti.saldo() + " euroa"); System.out.println( unicafeExactum ); } }
kassassa rahaa 1000.0 edullisia lounaita myyty 0 maukkaita lounaita myyty 0 kortilla rahaa 2.0 euroa riittikö raha: false riittikö raha: true kortilla rahaa 98.0 euroa kassassa rahaa 1100.0 edullisia lounaita myyty 0 maukkaita lounaita myyty 1
Jatkamme edelleen luokan Henkilo
parissa. Kuten muistamme, henkilöt tietävät ikänsä:
public class Henkilo { private String nimi; private int ika; private int pituus; private int paino; // ... }
Haluamme vertailla kahden henkilön ikää. Vertailu voidaan hoitaa usealla tavalla. Henkilölle voitaisiin määritellä getterimetodi getIka
ja kahden henkilön iän vertailu tapauhtuisi tällöin seuraavasti:
Henkilo pekka = new Henkilo("Pekka"); Henkilo juhana = new Henkilo("Juhana") if ( pekka.getIka() > juhana.getIka() ) { System.out.println( pekka.getNimi() + " on vanhempi kuin " + juhana.getNimi() ); }
Opettelemme kuitenkin nyt hieman "oliohenkisemmän" tavan kahden henkilön ikävertailun tekemiseen.
Teemme Henkilö-luokalle metodin boolean vanhempiKuin(Henkilo verrattava)
jonka avulla tiettyä henkilö-olioa voi verrata parametrina annettuun henkilöön iän perusteella.
Metodia on tarkoitus käyttää seuraavaan tyyliin:
public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka", 24); Henkilo antti = new Henkilo("Antti", 22); if ( pekka.vanhempiKuin(antti) ) { // sama kun pekka.vanhempiKuin(antti)==true System.out.println( pekka.getNimi() + " on vanhempi kuin " + antti.getNimi() ); } else { System.out.println( pekka.getNimi() + " ei ole vanhempi kuin " + antti.getNimi() ); }
Tässä siis kysytään Pekalta onko hän Anttia vanhempi, Pekka vastaa true jos on ja false muuten. Käytännössä kutsutaan "Pekkaa" vastaavan olion johon pekka
viittaa metodia vanhempiKuin
, jolle annetaan parametriksi "Anttia" vastaavan olion viite antti
.
Ohjelman tulostaa:
Pekka on vanhempi kuin Antti
Metodi saa parametrikseen henkilöolion (tarkemmin sanottuna viitteen henkilöolioon, eli "langan päässä" olevan henkilön) ja vertaa omaa ikäänsä this.ika
verrattavaksi annetun henkilön ikään verrattava.ika
. Toteutus näyttää seuraavalta:
public class Henkilo { // ... public boolean vanhempiKuin(Henkilo verrattava) { if ( this.ika > verrattava.ika ) { return true; } return false; } }
Vaikka ika
onkin olion yksityinen (private
) oliomuuttuja,
pystymme lukemaan muuttujan arvon kirjoittamalla verrattava.ika
.
Tämä johtuu siitä, että private
-muuttujat ovat luettavissa kaikissa
metodeissa, jotka kyseinen luokka sisältää. Huomaa, että syntaksi (kirjoitusasu) vastaa
tässä jonkin olion metodin kutsumista. Toisin kuin metodia kutsuttaessa, viittaamme olion kenttään,
jolloin metodikutsun osoittavia sulkeita ei kirjoiteta.
Toinen esimerkki samasta teemasta. Tehdään luokka, jonka avulla voidaan esittää päiväyksiä.
Olion sisällä päiväys esitetään kolmella oliomuuttujalla. Tehdään myös metodi jolla voi vertailla onko päivämäärä aiemmin kuin parametrina annettu päivämäärä:
public class Paivays { private int paiva; private int kuukausi; private int vuosi; public Paivays(int paiva, int kuukausi, int vuosi) { this.paiva = paiva; this.kuukausi = kuukausi; this.vuosi = vuosi; } public String toString() { return this.paiva + "." + this.kuukausi + "." + this.vuosi; } public boolean aiemmin(Paivays verrattava) { // ensin verrataan vuosia if ( this.vuosi < verrattava.vuosi ) { return true; } // jos vuodet ovat samat, verrataan kuukausia if ( this.vuosi == verrattava.vuosi && this.kuukausi < verrattava.kuukausi ) { return true; } // vuodet ja kuukaudet samoja, verrataan päivää if ( this.vuosi == verrattava.vuosi && this.kuukausi == verrattava.kuukausi && this.paiva < verrattava.paiva ) { return true; } return false; } }
Käyttöesimerkki:
public static void main(String[] args) { Paivays p1 = new Paivays(14, 2, 2011); Paivays p2 = new Paivays(21, 2, 2011); Paivays p3 = new Paivays(1, 3, 2011); Paivays p4 = new Paivays(31, 12, 2010); System.out.println( p1 + " aiemmin kuin " + p2 + ": " + p1.aiemmin(p2)); System.out.println( p2 + " aiemmin kuin " + p1 + ": " + p2.aiemmin(p1)); System.out.println( p2 + " aiemmin kuin " + p3 + ": " + p2.aiemmin(p3)); System.out.println( p3 + " aiemmin kuin " + p2 + ": " + p3.aiemmin(p2)); System.out.println( p4 + " aiemmin kuin " + p1 + ": " + p4.aiemmin(p1)); System.out.println( p1 + " aiemmin kuin " + p4 + ": " + p1.aiemmin(p4)); }
14.2.2011 aiemmin kuin 21.2.2011: true 21.2.2011 aiemmin kuin 14.2.2011: false 21.2.2011 aiemmin kuin 1.3.2011: true 1.3.2011 aiemmin kuin 21.2.2011: false 31.12.2010 aiemmin kuin 14.2.2011: true 14.2.2011 aiemmin kuin 31.12.2010: false
Asuntovälitystoimiston tietojärjestelmässä myynnissä olevaa asuntoa kuvataan seuraavan luokan olioilla:
public class Asunto { private int huoneita; private int nelioita; private int neliohinta; public Asunto(int huoneita, int nelioita, int neliohinta){ this.huoneita = huoneita; this.nelioita = nelioita; this.neliohinta = neliohinta; } }
Tehtävänä on toteuttaa muutama metodi, joiden avulla myynnissä olevia asuntoja voidaan vertailla.
Tee metodi public boolean suurempi(Asunto verrattava)
joka palauttaa true jos asunto-olio, jolle metodia kutsutaan on suurempi kuin verrattavana oleva asunto-olio.
Esimerkki metodin toiminnasta:
Asunto eiraYksio = new Asunto(1, 16, 5500); Asunto kallioKaksio = new Asunto(2, 38, 4200); Asunto jakomakiKolmio = new Asunto(3, 78, 2500); System.out.println( eiraYksio.suurempi(kallioKaksio) ); // false System.out.println( jakomakiKolmio.suurempi(kallioKaksio) ); // true
Tee metodi public int hintaero(Asunto verrattava)
joka palauttaa asunto-olion jolle metodia kutsuttiin ja parametrina olevan asunto-olion hintaeron. Hintaero on asuntojen hintojen (=neliöhinta*neliöt) itseisarvo.
Esimerkki metodin toiminnasta:
Asunto eiraYksio = new Asunto(1, 16, 5500); Asunto kallioKaksio = new Asunto(2, 38, 4200); Asunto jakomakiKolmio = new Asunto(3, 78, 2500); System.out.println( eiraYksio.hintaero(kallioKaksio) ); // 71600 System.out.println( jakomakiKolmio.hintaero(kallioKaksio) ); // 35400
Tee metodi public boolean kalliimpi(Asunto verrattava)
joka palauttaa true jos asunto-olio, jolle metodia kutsutaan on kalliimpi kuin verrattavana oleva asunto-olio.
Esimerkki metodin toiminnasta:
Asunto eiraYksio = new Asunto(1, 16, 5500); Asunto kallioKaksio = new Asunto(2, 38, 4200); Asunto jakomakiKolmio = new Asunto(3, 78, 2500); System.out.println( eiraYksio.kalliimpi(kallioKaksio) ); // false System.out.println( jakomakiKolmio.kalliimpi(kallioKaksio) ); // true
Olemme käyttäneet ArrayList
:ejä jo monessa esimerkissä ja tehtävässä. ArrayList-olioon pystyy lisäämään esimerkiksi merkkijonoja ja listalla olevien merkkijonojen läpikäynti, etsiminen, poistaminen, järjestäminen ym. ovat vaivattomia toimenpiteitä.
ArrayList:eihin voidaan laittaa minkä tahansa tyyppisiä oliota. Luodaan seuraavassa henkilölista, eli tyyppiä
ArrayList<Henkilo>
oleva ArrayList ja laitetaan sinne muutama henkilöolio:
public static void main(String[] args) { ArrayList<Henkilo> opettajat = new ArrayList<Henkilo>(); // voidaan ensin ottaa henkilö muuttujaan Henkilo opettaja = new Henkilo("Juhana"); // ja lisätä se sitten listalle opettajat.add(opettaja); // tai voidaan myös luoda olio lisättäessä: opettajat.add( new Henkilo("Matti") ); opettajat.add( new Henkilo("Martin") ); System.out.println("opettajat vastasyntyneenä: "); for ( Henkilo hlo : opettajat ) { System.out.println( hlo ); } for ( Henkilo hlo : opettajat ) { hlo.vanhene( 30 ); } System.out.println("30 vuoden kuluttua: "); for ( Henkilo hlo : opettajat ) { System.out.println( hlo ); }
Ohjelman tulostus:
opettajat vastasyntyneenä: Juhana, ikä 0 vuotta Matti, ikä 0 vuotta Martin, ikä 0 vuotta 30 vuoden kuluttua: Juhana, ikä 30 vuotta Matti, ikä 30 vuotta Martin, ikä 30 vuotta
Tee luokka Opiskelija
,
johon tallennetaan seuraavat tiedot opiskelijasta:
String
)String
)Tee luokkaan seuraavat metodit:
haeNimi
, joka palauttaa opiskelijan nimen, esim. Pekka MikkolahaeOpiskelijanumero
, joka palauttaa opiskelijan opiskelijanumeron, esim. 013141590toString
, joka palauttaa merkkijonoesityksen opiskelijasta muodossa: Pekka Mikkola (013141590)Voit testata luokan toimintaa seuraavalla koodilla:
public class Main { public static void main(String[] args) { Opiskelija pekka = new Opiskelija("Pekka Mikkola", "013141590"); System.out.println("Nimi: " + pekka.haeNimi()); System.out.println("Opiskelijanumero: " + pekka.haeOpiskelijanumero()); System.out.println(pekka); } }
Ohjelman tulostuksen tulisi olla seuraava:
Nimi: Pekka Mikkola Opiskelijanumero: 013141590 Pekka Mikkola (013141590)
Tee edellisen tilalle uusi pääohjelma, joka kysyy alla olevan esimerkkitulostuksen tyyliin opiskelijoiden tietoja (ensin kysytään nimi ja sen jälkeen opiskelijanumero). Ohjelma luo jokaisesta opiskelijasta uuden olion ja tallentaa sen listaan. Kun käyttäjä antaa nimeksi tyhjän merkkijonon, ohjelma tulostaa listalla olevat opiskelijat.
Listan määrittelyn tulisi olla seuraava:
ArrayList<Opiskelija> lista = new ArrayList<Opiskelija>();
Seuraavassa on esimerkki ohjelman suorituksesta:
Nimi: Alfred Apina Opiskelijanumero: 017635727 Nimi: Bruno Banaani Opiskelijanumero: 011288989 Nimi: Cecilia Cembalo Opiskelijanumero: 013672548 Nimi: Alfred Apina (017635727) Bruno Banaani (011288989) Cecilia Cembalo (013672548)
Laajenna edellisen tehtävän opiskelijalistaa siten, että listan syöttämisen jälkeen pääohjelma kysyy hakusanan, jonka avulla voi hakea opiskelijat, joiden nimessä on annettu hakusana. Tehtävänäsi on siis toteuttaa hakutoiminto.
Vihje: Käy opiskelijat läpi silmukassa ja
tarkista String
-luokan contains
-metodilla,
onko hakusana opiskelijan nimessä.
Seuraavassa on esimerkki ohjelman suorituksesta:
Nimi: Saku Silmukka Opiskelijanumero: 015696234 Nimi: Cecilia Cembalo Opiskelijanumero: 013672548 Nimi: Taina Taulukko Opiskelijanumero: 014662803 Nimi: Saku Silmukka (015696234) Cecilia Cembalo (013672548) Taina Taulukko (014662803) Anna hakusana: ukk Tulokset: Saku Silmukka (015696234) Taina Taulukko (014662803)
Olioiden sisällä voi olla olioita, ei pelkästään merkkijonoja vaan myös itse määriteltyjä oliota. Jatketaan taas Henkilo
-luokan parissa ja lisätään henkilölle syntymäpäivä. Syntymäpäivä on luonnollista esittää aiemmin tehdyn Paivays
-olion avulla:
public class Henkilo { private String nimi; private int ika; private int paino; private int pituus; private Paivays syntymaPaiva; // ...
Tehdään henkilölle uusi konstruktori, joka mahdollistaa syntymäpäivän asettamisen:
public Henkilo(String nimi, int paiva, int kuukausi, int vuosi) { this.nimi = nimi; this.paino = 0; this.pituus = 0; this.syntymaPaiva = new Paivays(paiva, kuukausi, vuosi); }
Eli konstruktorin parametrina annetaan erikseen päiväyksen osat (päivä, kuukausi, vuosi), niistä
luodaan päiväysolio joka sijoitetaan oliomuuttujaan syntymaPaiva
.
Muokataan toString
siten, että iän sijaan se näyttää syntymäpäivän:
public String toString() { return this.nimi + ", syntynyt " + this.syntymaPaiva; }
Ja kokeillaan miten uusittu Henkilö-luokka toimii:
public static void main(String[] args) { Henkilo martin = new Henkilo("Martin", 24, 4, 1983); Henkilo juhana = new Henkilo("Juhana", 17, 9, 1985); System.out.println( martin ); System.out.println( juhana ); }
Tulostuu:
Martin, syntynyt 24.4.1983 Juhana, syntynyt 17.9.1985
Luvussa 24.4 todettiin, että oliot ovat "langan päässä". Kertaa nyt luku 24.4.
Henkilö-oliolla on oliomuuttujat nimi
joka on merkkijono-olio ja syntymaPaiva
joka on Päiväys-olio. Henkilön oliomuuttujat siis ovat molemmat olioita, eli teknisesti ottaen ne eivät sijaitse henkilö-olion sisällä vaan ovat "langan päässä", ts. henkilöllä on oliomuuttujissa tallessa viite niihin. Kuvana:
Pääohjelmalla on nyt siis langan päässä kaksi Henkilö-olioa. Henkilöllä on nimi ja syntymäpäivä. Koska molemmat ovat olioita, ovat ne henkilöllä langan päässä.
Syntymäpäivä vaikuttaa hyvältä laajennukselta Henkilö-luokkaan. Huomaamme kuitenkin, että oliomuuttuja ikä
uhkaa jäädä turhaksi ja lienee syytä poistaa se. Iän pystyy nimittäin tarvittaessa selvittämään helposti nykyisen päivämäärän ja syntymäpäivän perusteella. Javassa nykyinen päivä selviää esim. seuraavasti:
int paiva = Calendar.getInstance().get(Calendar.DATE); int kuukausi = Calendar.getInstance().get(Calendar.MONTH) + 1; // tammikuun numero 0 joten lisätään 1 int vuosi = Calendar.getInstance().get(Calendar.YEAR); System.out.println("tänään on " + paiva + "." + kuukausi + "." + vuosi );
Kun ikä poistetaan, täytyy vanhempiKuin
-metodi muuttaa toimimaan syntymäpäiviä
vertaamalla. Teemme muutoksen harjoitustehtävänä.
Edellisen viikon tehtävässä 78 tehtiin ensin luokka YlhaaltaRajoitettuLaskuri
ja rakennettiin laskurien avulla pääohjelmaan kello. Tehdään nyt myös itse kellosta olio. Luokan kello runko näyttää seuraavalta:
public class Kello { private YlhaaltaRajoitettuLaskuri tunnit; private YlhaaltaRajoitettuLaskuri minuutit; private YlhaaltaRajoitettuLaskuri sekunnit; public Kello(int tunnitAlussa, int minuutitAlussa, int sekunnitAlussa) { // luodaan kello joka asetetaan parametrina annettuun aikaan } public void etene(){ // kello etenee sekunnilla } public String toString() { // palauttaa kellon merkkijonoesityksen } }
Kopioi uuteen projektiin viime viikon tehtävän 78 YlhaaltaRajoitettuLaskuri
-luokka.
Toteuta luokan Kello
konstruktori ja puuttuvat
metodit. Voit testata kelloasi seuraavalla pääohjelmalla:
public class Main { public static void main(String[] args) { Kello kello = new Kello(23, 59, 50); int i = 0; while( i < 20) { System.out.println( kello ); kello.etene(); i++; } } }
Tulostuksen tulisi edetä seuraavasti:
23:59:50 23:59:51 23:59:52 23:59:53 23:59:54 23:59:55 23:59:56 23:59:57 23:59:58 23:59:59 00:00:00 00:00:01 ...
Laajennetaan PainonvartijaYhdistys
-oliota siten, että yhdistys tallettaa kaikki jäsenensä ArrayList
-olioon. Listalle tulee siis Henkilo
-olioita. Yhdistykselle annetaan laajennetussa versiossa konstruktorin parametrina nimi:
public class PainonvartijaYhdistys { private double alinPainoindeksi; private String nimi; private ArrayList<Henkilo> jasenet; public PainonvartijaYhdistys(String nimi, double alinPainoindeksi) { this.alinPainoindeksi = alinPainoindeksi; this.nimi = nimi; this.jasenet = new ArrayList<Henkilo>(); } //.. }
Tehdään metodi jolla henkilö liitetään yhdistykseen. Metodi ei liitä yhdistykseen kuin tarpeeksi suuren painoindeksi omaavat henkilöt. Tehdään myös toString jossa tulostetaan jäsenten nimet:
public class PainonvartijaYhdistys { // ... public boolean hyvaksytaanJaseneksi(Henkilo henkilo) { if ( henkilo.painoIndeksi() < this.alinPainoindeksi ) { return false; } return true; } public void lisaaJaseneksi(Henkilo henkilo) { if ( !hyvaksytaanJaseneksi(henkilo) ) { // sama kuin hyvaksytaanJaseneksi(henkilo) == false return; } this.jasenet.add(henkilo); } public String toString() { String jasenetMerkkijonona = ""; for ( Henkilo jasen : this.jasenet ) { jasenetMerkkijonona += " " + jasen.getNimi() + "\n"; } return "Painonvartijayhdistys " + this.nimi + " jäsenet: \n" + jasenetMerkkijonona; } }
Metodi lisaaJaseneksi
käyttää aiemmin tehtyä metodia hyvaksytaanJaseneksi
.
Kokeillaan laajentunutta painonvartijayhdistystä:
public static void main(String[] args) { PainonvartijaYhdistys painonVartija = new PainonvartijaYhdistys("Kumpulan paino", 25); Henkilo matti = new Henkilo("Matti"); matti.setPaino(86); matti.setPituus(180); painonVartija.lisaaJaseneksi(matti); Henkilo juhana = new Henkilo("Juhana"); juhana.setPaino(64); juhana.setPituus(172); painonVartija.lisaaJaseneksi(juhana); Henkilo harri = new Henkilo("Harri"); harri.setPaino(104); harri.setPituus(182); painonVartija.lisaaJaseneksi(harri); Henkilo petri = new Henkilo("Petri"); petri.setPaino(112); petri.setPituus(173); painonVartija.lisaaJaseneksi(petri); System.out.println( painonVartija ); }
Tulostuksesta huomaamme, että Juhanaa ei kelpuutettu jäseneksi:
Painonvartijayhdistys Kumpulan paino jäsenet: Matti Harri Petri
Joukkue
-luokkaTee luokka Joukkue
,
johon tallennetaan joukkueen nimi (String
).
Tee luokkaan seuraavat metodit:
haeNimi
, joka palauttaa joukkueen nimenSeuraava pääohjelma testaa luokan toimintaa:
public class Main { public static void main(String[] args) { Joukkue tapiiri = new Joukkue("FC Tapiiri"); System.out.println("Joukkue: " + tapiiri.haeNimi()); } }
Ohjelman tulostus on seuraava:
Joukkue: FC Tapiiri
Luo luokka Pelaaja
, johon tallennetaan pelaajan nimi ja tehtyjen maalien määrä. Tee luokkaan kaksi konstruktoria: yksi jolle annetaan vain pelaajan nimi, toinen jolle annetaan sekä pelaajan nimi että pelaajan tekemien maalien määrä. Lisää pelaajalle myös metodit:
haeNimi
, joka palauttaa pelaajan nimenmaalit
, joka palauttaa tehtyjen maalien määräntoString
, joka palauttaa pelaajan merkkijonoesityksenpublic class Main { public static void main(String[] args) { Joukkue tapiiri = new Joukkue("FC Tapiiri"); System.out.println("Joukkue: " + tapiiri.haeNimi()); Pelaaja matti = new Pelaaja("Matti"); System.out.println("Pelaaja: " + matti); Pelaaja pekka = new Pelaaja("Pekka", 39); System.out.println("Pelaaja: " + pekka); } }
Joukkue: FC Tapiiri Pelaaja: Matti, maaleja 0 Pelaaja: Pekka, maaleja 39
Lisää luokkaan Joukkue
seuraavat metodit:
lisaaPelaaja
, joka lisää pelaajan joukkueeseentulostaPelaajat
, joka tulostaa joukkueessa olevat pelaajatTallenna joukkueessa olevat pelaajat Joukkue
-luokan
sisäiseen ArrayList
-listaan.
Seuraava pääohjelma testaa luokan toimintaa:
public class Main { public static void main(String[] args) { Joukkue tapiiri = new Joukkue("FC Tapiiri"); Pelaaja matti = new Pelaaja("Matti"); Pelaaja pekka = new Pelaaja("Pekka", 39); tapiiri.lisaaPelaaja(matti); tapiiri.lisaaPelaaja(pekka); tapiiri.lisaaPelaaja(new Pelaaja("Mikael", 1)); //vaikutus on sama kuin edellisillä tapiiri.tulostaPelaajat(); } }
Ohjelman tulostuksen tulisi olla seuraava:
Matti, maaleja 0 Pekka, maaleja 39 Mikael, maaleja 1
Lisää luokkaan Joukkue
seuraavat metodit:
asetaMaksimikoko(int maksimikoko)
, joka asettaa joukkueen maksimikoon (eli maksimimäärän pelaajia)koko
, joka palauttaa pelaajien määrän (int
)Joukkueen suurin sallittu pelaajamäärä on oletusarvoisesti 16. Metodin asetaMaksimikoko
avulla tätä rajaa voi muuttaa. Muuta metodia lisaaPelaaja
niin, että se ei lisää pelaajaa joukkueeseen, jos sallittu pelaajamäärä ylittyisi.
HUOM: muista lisätä oletusarvoinen maksimikoko koodiisi sillä muuten arvoksi tulee 0. Tämä aiheuttaa edellisen kohdan testien hajoamisen, sillä testit luovat oletusmaksimikokoisia joukkueita ja jos joukkueen maksimikoko on 0, ei joukkueeseen voi lisätä yhtään pelaajaa.
Seuraava pääohjelma testaa luokan toimintaa:
public class Main { public static void main(String[] args) { Joukkue tapiiri = new Joukkue("FC Tapiiri"); tapiiri.asetaMaksimikoko(1); Pelaaja matti = new Pelaaja("Matti"); Pelaaja pekka = new Pelaaja("Pekka", 39); tapiiri.lisaaPelaaja(matti); tapiiri.lisaaPelaaja(pekka); tapiiri.lisaaPelaaja(new Pelaaja("Mikael", 1)); //vaikutus on sama kuin edellisillä System.out.println("Pelaajia yhteensä: " + tapiiri.koko()); } }
Pelaajia yhteensä: 1
Lisää luokkaan Joukkue
metodi:
maalit
, joka palauttaa joukkueen pelaajien tekemien maalien yhteismäärän.Seuraava pääohjelma testaa luokan toimintaa:
public class Main { public static void main(String[] args) { Joukkue tapiiri = new Joukkue("FC Tapiiri"); Pelaaja matti = new Pelaaja("Matti"); Pelaaja pekka = new Pelaaja("Pekka", 39); tapiiri.lisaaPelaaja(matti); tapiiri.lisaaPelaaja(pekka); tapiiri.lisaaPelaaja(new Pelaaja("Mikael", 1)); //vaikutus on sama kuin edellisillä System.out.println("Maaleja yhteensä: " + tapiiri.maalit()); } }
Maaleja yhteensä: 40
Olemme nähneet metodeja jotka palauttavat totuusarvoja, lukuja, listoja ja merkkijonoja. On helppoa arvata, että metodi voi palauttaa minkä tahansa tyyppisen olion. Tehdään painovartijayhdistykselle metodi, jolla saadaan tietoon yhdistyksen suurimman painoindeksin omaava henkilö.
public class PainonvartijaYhdistys { // ... public Henkilo suurinPainoindeksinen() { // jos jasenlista on tyhjä, palautetaan null-viite if ( this.jasenet.isEmpty() ) { return null; } Henkilo painavinTahanAsti = this.jasenet.get(0); for ( Henkilo henkilo : this.jasenet) { if ( henkilo.painoIndeksi() > painavinTahanAsti.painoIndeksi() ) { painavinTahanAsti = henkilo; } } return painavinTahanAsti; }
Logiikaltaan metodi toimii samaan tapaan kuin suurimman luvun etsiminen taulukosta. Käytössä on apumuuttuja painavinTahanAsti
joka laitetaan aluksi viittaamaan listan ensimmäiseen henkilöön. Sen jälkeen käydään lista läpi ja katsotaan tuleeko vastaan suuremman painoindeksin omaavia henkilöitä, jos tulee, niin otetaan viite talteen muuttujaan painavinTahanAsti
. Lopuksi palautetaan muuttujan arvo eli viite henkilöolioon.
Tehdään lisäys edelliseen pääohjelmaan. Pääohjelma ottaa vastaan metodin palauttaman viitteen muuttujaan painavin
.
public static void main(String[] args) { PainonvartijaYhdistys painonVartija = new PainonvartijaYhdistys("Kumpluan paino", 25); // .. Henkilo painavin = painonVartija.suurinPainoindeksinen(); System.out.print("suurin painoindeksinen jäsen: " + painavin.getNimi() ); System.out.println(" painoindeksi " + String.format( "%.2f", painavin.painoIndeksi() ) ); }
Tulostuu:
suurin painoindeksinen jäsen: Petri painoindeksi 37,42
Edellisessä esimerkissä metodi palautti yhden painonVartija-olion sisältämistä Henkilo-olioista. On myös mahdollista, että metodi palauttaa kokonaan uuden olion. Seuraavassa yksinkertainen laskuri, jolla on metodi kloonaa
, jonka avulla laskurista voidaan tehdä klooni, eli uusi laskurio-olio, jolla on luomishetkellä sama arvo kuin kloonattavalla laskurilla:
public Laskuri { private int arvo; public Laskuri(){ this(0); } public Laskuri(int alkuarvo){ this.arvo = alkuarvo; } public void kasvata(){ this.arvo++; } public String toString(){ return "arvo: "+arvo; } public Laskuri kloonaa(){ // luodaan uusi laskuriolio, joka saa alkuarvokseen kloonattavan laskurin arvon Laskuri klooni = new Laskuri(this.arvo); // palautetaan klooni kutsujalle return klooni; } }
Seuraavassa käyttöesimerkki:
Laskuri laskuri = new Laskuri(); laskuri.kasvata(); laskuri.kasvata(); System.out.println(laskuri); // tulostuu 2 Laskuri klooni = laskuri.kloonaa(); System.out.println(laskuri); // tulostuu 2 System.out.println(klooni); // tulostuu 2 laskuri.kasvata(); laskuri.kasvata(); laskuri.kasvata(); laskuri.kasvata(); System.out.println(laskuri); // tulostuu 6 System.out.println(klooni); // tulostuu 2 klooni.kasvata(); System.out.println(laskuri); // tulostuu 6 System.out.println(klooni); // tulostuu 3
Kloonattavan ja kloonin arvo on siis kloonauksen tapahduttua sama. Kyseessä on kuitenkin kaksi erillistä olioa, eli jatkossa kun toista laskureista kasvatetaan, ei kasvatus vaikuta toisen arvoon millään tavalla.
Tehtäväpohjan mukana tulee luvussa 24.7 esitelty luokka Paivays
, jossa päivämäärä talletetaan oliomuuttujien vuosi
, kuukausi
, ja paiva
avulla:
public class Paivays { private int paiva; private int kuukausi; private int vuosi; public Paivays(int paiva, int kuukausi, int vuosi) { this.paiva = paiva; this.kuukausi = kuukausi; this.vuosi = vuosi; } public String toString() { return this.paiva + "." + this.kuukausi + "." + this.vuosi; } public boolean aiemmin(Paivays verrattava) { // ensin verrataan vuosia if ( this.vuosi < verrattava.vuosi ) { return true; } // jos vuodet ovat samat, verrataan kuukausia if ( this.vuosi == verrattava.vuosi && this.kuukausi < verrattava.kuukausi ) { return true; } // vuodet ja kuukaudet samoja, verrataan päivää if ( this.vuosi == verrattava.vuosi && this.kuukausi == verrattava.kuukausi && this.paiva < verrattava.paiva ) { return true; } return false; } }
Tässä tehtäväsarjassa laajennetaan luokkaa.
Toteuta metodi public void etene()
, joka siirtää päiväystä yhdellä päivällä. Tässä tehtävässä oletetaan, että jokaisessa kuukaudessa on 30 päivää. Huom! Sinun tulee tietyissä tilanteissa muuttaa kuukauden ja vuoden arvoa.
Toteuta metodi public void etene(int montakoPaivaa)
, joka siirtää päiväystä annetun päivien määrän verran. Käytä apuna edellisessä tehtävässä toteutettua metodia etene()
.
Lisätään Paivays
-olioon mahdollisuus edistää aikaa. Tee oliolle metodi Paivays paivienPaasta(int paivia)
, joka luo uuden Paivays
-olion, jonka päiväys on annetun päivien lukumäärän verran suurempi kuin oliolla, jolle sitä kutsuttiin. Voit edelleen olettaa, että jokaisessa kuukaudessa on 30 päivää. Huomaa, että vanhan päiväysolion on pysyttävä muuttumattomana!
Koska metodissa on luotava uusi olio, tulee rungon olla suunnilleen seuraavanlainen:
public Paivays paivienPaasta(int paivia){ Paivays uusiPaivays = new Paivays( ... ); // tehdään jotain... return uusiPaivays; }
Ohessa on esimerkki metodin toiminnasta.
public static void main(String[] args) { Paivays pvm = new Paivays(25, 2, 2011); Paivays uusi_pvm = pvm.paivienPaasta(7); for (int i = 1; i <= 7; ++i) { System.out.println("Perjantai " + i + " viikon kuluttua on " + uusi_pvm); uusi_pvm = uusi_pvm.paivienPaasta(7); } System.out.println("Tämän viikon perjantai on " + pvm); System.out.println("Päivämäärä 790:n päivän päästä tämän viikon perjantaista on " + pvm.paivienPaasta(790)); }
Ohjelma tulostaa:
Perjantai 1 viikon kuluttua on 2.3.2011 Perjantai 2 viikon kuluttua on 9.3.2011 Perjantai 3 viikon kuluttua on 16.3.2011 Perjantai 4 viikon kuluttua on 23.3.2011 Perjantai 5 viikon kuluttua on 30.3.2011 Perjantai 6 viikon kuluttua on 7.4.2011 Perjantai 7 viikon kuluttua on 14.4.2011 Tämän viikon perjantai on 25.2.2011 Päivämäärä 790:n päivän päästä tämän viikon perjantaista on 5.5.2013
Huom! Sen sijaan, että muuttaisimme vanhan olion tilaa
palautamme uuden olion. Kuvitellaan, että Paivays
-luokalle on
olemassa metodi edista
, joka toimii vastaavasti kuin ohjelmoimamme
metodi, mutta se muuttaa vanhan olion tilaa.
Tällöin seuraava koodin pätkä tuottaisi ongelmia.
Paivays nyt = new Paivays(20,2,2011); Paivays viikonPaasta = nyt; viikonPaasta.edista(7); System.out.println("Nyt: " + nyt); System.out.println("Viikon päästä: " + viikonPaasta);
Ohjelman tulostus olisi seuraavanlainen:
Nyt 27.2.2011 Viikon päästä 27.2.2011
Tämä johtuu siitä, että tavallinen sijoitus kopioi ainoastaan viitteen olioon. Siis
itse asiassa ohjelman oliot nyt
ja viikonPaasta
viittavaat
yhteen ja samaan Paivays
-olioon.
Viikon teoria on esitelty, seuraavassa vielä joukko samoja teemoja käsitteleviä tehtäviä.
Jatketaan luokan Päiväys laajentamista. Tämä tehtävä ei riipu edellisestä tehtävästä, saat tehtäväpohjan mukana Paivays-luokan jossa ei ole edellisen tehtävän lisäyksiä.
Lisää päiväykselle metodi public int erotusVuosissa(Paivays verrattava)
, jonka avulla saadaan selville päiväyksen ja verrattavan päiväyksen ero vuosissa. Huomioi seuraavat:
Seuraava pääohjelma demonstroi metodin käyttöä:
public class Paaohjelma { public static void main(String[] args) { Paivays eka = new Paivays(24, 12, 2009); Paivays toka = new Paivays(1, 1, 2011); Paivays kolmas = new Paivays(25, 12, 2010); System.out.println( toka + " ja " + eka + " ero vuosissa: " + toka.erotusVuosissa(eka) ); System.out.println( kolmas + " ja " + eka + " ero vuosissa: " + kolmas.erotusVuosissa(eka) ); System.out.println( toka + " ja " + kolmas + " ero vuosissa: " + toka.erotusVuosissa(kolmas) ); } }
Tulos näyttää seuraavalta:
1.1.2011 ja 24.12.2009 ero vuosissa: 2 25.12.2010 ja 24.12.2009 ero vuosissa: 1 1.1.2011 ja 25.12.2010 ero vuosissa: 1
Vuosien laskenta ei edellisessä versiossa ollut vielä kovin tarkkaa. Esim. 1.1.2011 ja 25.12.2010 välillä ilmoitettiin olevan vuoden ero. Tarkennetaan metodin toiminta sellaiseksi, että se osaa laskea vuodet kunnolla. Laske erotukseen mukaan vain täydet vuodet. Eli vaikka päiväysten ero olisi 1 vuosi ja 364 päivää, ilmoittaa metodi eroksi vuoden.
Metodin tämänkin version tarvitsee toimia ainoastaan siten, että parametriksi annettava päivämäärä on aiempi kuin se päivämäärä jolle metodia kutsutaan.
Edellisen esimerkin tulos on nyt:
1.1.2011 ja 24.12.2009 ero vuosissa: 1 25.12.2010 ja 24.12.2009 ero vuosissa: 1 1.1.2011 ja 25.12.2010 ero vuosissa: 0
Laitetaan metodi toimimaan samoin riippumatta onko parametrina annettava päiväys myöhempi vai aiempi kuin päiväys mille metodia kutsutaan. Esimerkkipääohjelma:
public class Paaohjelma { public static void main(String[] args) { Paivays eka = new Paivays(24, 12, 2009); Paivays toka = new Paivays(1, 1, 2011); Paivays kolmas = new Paivays(25, 12, 2010); System.out.println( eka + " ja " + toka + " ero vuosissa: " + toka.erotusVuosissa(eka) ); System.out.println( toka + " ja " + eka + " ero vuosissa: " + eka.erotusVuosissa(toka) ); System.out.println( eka + " ja " + kolmas + " ero vuosissa: " + kolmas.erotusVuosissa(eka) ); System.out.println( kolmas + " ja " + eka + " ero vuosissa: " + eka.erotusVuosissa(kolmas) ); System.out.println( kolmas + " ja " + toka + " ero vuosissa: " + toka.erotusVuosissa(kolmas) ); System.out.println( toka + " ja " + kolmas + " ero vuosissa: " + kolmas.erotusVuosissa(toka) ); } }
24.12.2009 ja 1.1.2011 ero vuosissa: 1 1.1.2011 ja 24.12.2009 ero vuosissa: 1 24.12.2009 ja 25.12.2010 ero vuosissa: 1 25.12.2010 ja 24.12.2009 ero vuosissa: 1 1.1.2011 ja 25.12.2010 ero vuosissa: 0 25.12.2010 ja 1.1.2011 ero vuosissa: 0
Luvussa 24.9. lisättiin henkilölle oliomuuttujaksi syntymäpäivän kertova Paivays-olio. Samalla todettiin, että oliomuuttuja ika
kannattaa poistaa sillä iän pystyy laskemaan päiväyksen ja syntymäpäivän avulla.
Toteuta metodi ika
joka palauttaa henkilön iän.
Huom: edellisessä tehtävässä lisättiin luokalle Paivays
metodi public int erotusVuosissa(Paivays verrattava)
. Kannattaa kopioida metodi tässä tehtävässä olevaan luokkaan, se helpottaa tehtävän tekemistä oleellisesti!
import java.util.Calendar; public class Henkilo { private String nimi; private Paivays syntymaPaiva; public Henkilo(String nimi, int pp, int kk, int vv) { this.nimi = nimi; this.syntymaPaiva = new Paivays(pp, kk, vv); } public int ika() { // laske henkilön ikä syntymäpäivän ja tämän päivän perusteella // tämä päivä saadaan selville seuraavasti // Calendar.getInstance().get(Calendar.DATE); // Calendar.getInstance().get(Calendar.MONTH) + 1; // tammikuun numero on 0 joten lisätään 1 // Calendar.getInstance().get(Calendar.YEAR); } public String getNimi() { return this.nimi; } public String toString() { return this.nimi +", syntynyt "+ this.syntymaPaiva; } }
Voit testata Henkilöä seuraavalla pääohjelmalla. Lisää myös itsesi ohjelmaan ja varmista että ikäsi tulostuu oikein.
public class Main { public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka", 15, 2, 1993); Henkilo antti = new Henkilo("Antti", 1, 3, 1955); System.out.println( antti.getNimi() + " ikä " + antti.ika() + " vuotta"); System.out.println( pekka.getNimi() + " ikä " + pekka.ika() + " vuotta"); } }
Tulostus:
Antti ikä 57 vuotta Pekka ikä 19 vuotta
Tee henkilölle metodi jonka avulla se vertaa ikäänsä parametrina annettuun henkilöön. Jos henkilö on vanhempi eli syntynyt aiemmin, palauttaa metodi true ja muuten false.
public class Henkilo { // ... public boolean vanhempiKuin(Henkilo verrattava) { // vertaa henklöiden ikiä käyttäen henkilöiden syntymäpäivää } }
Ja testaa laajennettua Henkilö-luokkaa esim. seuraavasti:
public class Main { public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka", 15, 2, 1983); Henkilo martin = new Henkilo("Martin", 1, 3, 1983); System.out.println( martin.getNimi() + " vanhempi kuin " + pekka.getNimi() + ": "+ martin.vanhempiKuin(pekka) ); System.out.println( pekka.getNimi() + " vanhempi kuin " + martin.getNimi() + ": "+ pekka.vanhempiKuin(martin) ); } }
Tulostus:
Martin vanhempi kuin Pekka: false Pekka vanhempi kuin Martin: true
Tee Henkilo-luokalle kaksi uutta konstruktoria:
public Henkilo(String nimi, Paivays syntymapaiva)
- jossa konstruktori käyttää annettua Paivays-oliota syntymäpäivänäpublic Henkilo(String nimi)
- jossa konstruktori määrittää syntymäpäiväksi tämänhetkisen päivänTestaa uusia konstruktoreja esim. seuraavasti:
public class Main { public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka", new Paivays(15, 2, 1983)); Henkilo sepe = new Henkilo("Sepe"); System.out.println( pekka ); System.out.println( sepe ); } } a
Esimerkkitulostus:
Pekka, syntynyt 15.2.1983 Sepe, syntynyt 9.2.2012
Huom: jälkimmäinen rivi riippuu päivämäärästä, jolloin koodi ajetaan!
Aloitetaan viikko muutamalla viime viikon tärkeimpiä teemoja kertaavalla tehtävällä. Kertaa tarvittaessa luku 24.10 ennen tehtävää 94 ja luvut 24.6 ja 24.12 ennen tehtävää 95.
MUISTUTUS kun lisäät ohjelmaasi ArrayList:in, Scanner:in tai Random:in ei Java tunnista luokkaa ellet "importoi" sitä lisäämällä ohjelmatiedoston alkuun:
import java.util.ArrayList; // importoi ArrayListin import java.util.*; // importoi kaikki java.util:sissa olevat työkalut, mm. ArrayListin, Scannerin ja Randomin
Tehtävässä tehdään yksinkertainen puhelinmuistio.
Tee ensin luokka Henkilo
Luokan pitää toimia seuraavan esimerkin osoittamalla tavalla:
public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka Mikkola", "040-123123"); System.out.println( pekka.haeNimi() ); System.out.println( pekka.haeNumero() ); System.out.println( pekka ); pekka.vaihdaNumeroa("050-333444"); System.out.println( pekka ); }
Tulostuu:
Pekka Mikkola 040-123123 Pekka Mikkola puh: 040-123123 Pekka Mikkola puh: 050-333444
Tee siis luokalle
public String toString()
, joka palauttaa henkilön merkkijonoesityksen (yo. esimerkin tapaan muotoiltuna)public String haeNimi()
, joka palauttaa nimenpublic String haeNumero()
, joka palauttaa puhelinnumeronpublic void vaihdaNumeroa(String uusiNumero)
, joka muuttaa henkilön puhelinnumeroaTee luokka Puhelinmuistio
joka tallettaa sisällään olevaan ArrayListiin Henkilo
-olioita. Tässä vaiheessa luokalle tehdään seuraavat metodit:
public void lisaa(String nimi, String numero)
luo Henkilo
-olion ja lisää sen puhelinmuistion ArrayListiin.public void tulostaKaikki()
, tulostaa puhelinmuistion sisällönEsimerkki muistion toiminnasta:
public static void main(String[] args) { Puhelinmuistio muistio = new Puhelinmuistio(); muistio.lisaa("Pekka Mikkola", "040-123123"); muistio.lisaa("Antti Laaksonen", "045-456123"); muistio.lisaa("Juhana Laurinharju", "050-222333"); muistio.tulostaKaikki();
Ohjelman tulostus oikein toteutetuilla luokilla on:
Pekka Mikkola puh: 040-123123 Antti Laaksonen puh: 045-456123 Juhana Laurinharju puh: 050-222333
Tehdään puhelinmuistiolle metodi
public String haeNumero(String nimi)
, joka palauttaa parametrina annetun henkilön numeron. Jos henkilö ei ole muistiossa, palautetaan merkkijono "numero ei tiedossa". Esimerkki metodin toiminnasta:
public static void main(String[] args) { Puhelinmuistio muistio = new Puhelinmuistio(); muistio.lisaa("Pekka Mikkola", "040-123123"); muistio.lisaa("Antti Laaksonen", "045-456123"); muistio.lisaa("Juhana Laurinharju", "050-222333"); String numero = muistio.haeNumero("Pekka Mikkola"); System.out.println( numero ); numero = muistio.haeNumero("Martti Tienari"); System.out.println( numero );
Tulostuu:
040-123123 numero ei tiedossa
Lyyrakortti-tehtävässä käytimme rahamäärän tallettamiseen double-tyyppistä oliomuuttujaa. Todellisissa sovelluksissa näin ei kannata tehdä, sillä kuten jo olemme nähneet, doubleilla laskenta ei ole tarkkaa. Onkin järkevämpää toteuttaa rahamäärän käsittely oman luokkansa avulla. Seuraavassa on luokan runko:
public class Raha { private final int euroa; private final int senttia; public Raha(int euroa, int senttia) { this.euroa = euroa; this.senttia = senttia; } public int eurot(){ return euroa; } public int sentit(){ return senttia; } public String toString() { String nolla = ""; if (senttia <= 10) { nolla = "0"; } return euroa + "." + nolla + senttia + "e"; } }
Määrittelyssä pistää silmään oliomuuttujien määrittelyn yhteydessä käytetty sana final
, tällä saadaan aikaan se, että oliomuuttujien arvoa ei pystytä muuttamaan sen jälkeen kun ne on konstruktorissa asetettu. Raha-luokan oliot ovatkin muuttumattomia eli immutaabeleita, eli jos halutaan esim. kasvattaa rahamäärää, on luotava uusi olio, joka kuvaa kasvatettua rahasummaa.
Luomme seuraavassa muutaman operaation rahojen käsittelyyn.
Tee ensin metodi public Raha plus(Raha lisattava)
, joka palauttaa uuden raha-olion, joka on arvoltaan yhtä suuri kuin se olio jolle metodia kutsuttiin ja parametrina oleva olio yhteensä.
Metodin runko on seuraavanlainen:
public Raha plus(Raha lisattava) { Raha uusi = new Raha(...); // ludaan uusi Raha-olio jolla on oikea arvo // palautetaan uusi Raha-olio return uusi; }
Seuraavassa esimerkkejä metodin toiminnasta
Raha a = new Raha(10,0); Raha b = new Raha(5,0); Raha c = a.plus(b); System.out.println(a); // 10.00e System.out.println(b); // 5.00e System.out.println(c); // 15.00e a = a.plus(c); // HUOM: tässä syntyy uusi Raha-olio, joka laitataan "a:n langan päähän" // vanha a:n langan päässä ollut 10 euroa häviää ja Javan roskien kerääjä korjaa sen pois System.out.println(a); // 25.00e System.out.println(b); // 5.00e System.out.println(c); // 15.00e
Tee metodi public boolean vahemman(Raha verrattava)
, joka palauttaa true jos raha-olio jolle metodia kutsutaan on arvoltaan pienempi kuin raha-olio, joka on metodin parametrina.
Raha a = new Raha(10,0); Raha b = new Raha(3,0); Raha c = new Raha(5,0); System.out.println(a.vahemman(b)); // false System.out.println(b.vahemman(c)); // true
Tee metodi public Raha miinus(Raha vahennettava)
, joka palauttaa uuden raha-olion, joka arvoksi tulee sen olion jolle metodia kutsuttiin ja parametrina olevan olion arvojen erotus. Jos erotus olisi negatiivinen, tulee luotavan raha-olion arvoksi 0.
Seuraavassa esimerkkejä metodin toiminnasta
Raha a = new Raha(10,0); Raha b = new Raha(3,50); Raha c = a.miinus(b); System.out.println(a); // 10.00e System.out.println(b); // 3.50e System.out.println(c); // 6.50e c = c.miinus(a); // HUOM: tässä syntyy uusi Raha-olio, joka laitataan "c:n langan päähän" // vanha c:n langan päässä ollut 6.5 euroa häviää ja Javan roskien kerääjä korjaa sen pois System.out.println(a); // 10.00e System.out.println(b); // 3.50e System.out.println(c); // 0.00e
Javan String-oliot ovat Raha-luokan olioiden tyyliin muuttumattomia, eli immutaabelena. Jos esim. merkkijonon perään katenoidaan eli liitetään +-operaatiolla uusi merkkijono, ei alkuperäistä merkkijonoa pidennetä vaan syntyy uusi merkkijono-olio:
String jono = "koe"; jono + "häntä"; System.out.println( jono ); // koe
Merkkijonoa ei siis voi muuttaa, mutta voimme ottaa katenoimalla syntyvän uuden merkkijonon talteen vanhaan muuttujaan:
String jono = "koe"; jono = jono + "häntä"; // tai jono += "häntä"; System.out.println( jono ); // koehäntä
Nyt siis muuttuja jono
viittaa uuteen merkkijono-olioon, joka luotiin yhdistämällä muuttujan aiemmin viittaama merkkijono "koe" ja merkkijono "häntä". Merkkijono-olioon "koe" ei enää viitata.
Olemme käyttäneet kurssin aikana lukemattomia kertoja ArrayList:ejä erilaisten olioiden säilömiseen. ArrayList on helppokäyttöinen sillä se tarjoaa paljon valmiita työvälineitä jotka helpottavat ohjelmoijan elämää: muun muassa automaattisen listan kasvatuksen, jonka ansioista listalta ei lopu tila kesken (elle lista kasva niin suureksi että se käyttää loppuun ohjelmalle varatun muistimäärän).
ArrayListin helppokäyttöisyydesta huolimatta ohjelmissa on joskus tarvetta ArrayListin esi-isälle eli taulukolle.
Taulukko on olio, joka voidaan käsittää eräänlaisena järjestettynä lokerikkona arvoille. Taulukon pituus tai koko on lokerikon paikkojen lukumäärä, eli kuinka monta arvoa taulukkoon voi laittaa. Taulukon arvoja kutsutaan taulukon alkioiksi. ArrayLististä poiketen taulukon kokoa (eli sen alkioiden määrää) ei voi muuttaa, taulukon kasvattaminen vaatii siis aina uuden taulukon luomista ja vanhassa olevien alkioiden kopiointia uuteen.
Taulukon voi luoda kahdella eri tavalla. Tutustutaan ensin tapaan jossa taulukolle annetaan sisältö luomisen yhteydessä. Kolmen alkion kokonaislukutyyppinen taulukko määritellään seuraavasti:
int[] luvut = {100, 1, 42};
Taulukko-olion tyyppi merkitään int[]
, joka tarkoittaa taulukkoa, jonka alkiot ovat tyyppiä int
. Taulukko-olion nimi on esimerkissä luvut
ja se sisältää kolme lukua {100, 1, 42}
. Taulukko alustetaan lohkolla, jossa taulukkoon asetettavat arvot on esitelty pilkulla eriteltyinä.
Taulukon arvot voivat olla mitä tahansa aikaisemmin nähtyjä muuttujatyyppejä. Alla on esitelty ensin merkkijonoja sisältävä taulukko, jonka jälkeen esitellään liukulukuja sisältävä taulukko.
String[] merkkijonotaulukko = {"Matti P.", "Matti V."}; double[] liukulukutaulukko = {1.20, 3.14, 100.0, 0.6666666667};
Taulukon alkioihin viitataan indeksillä, joka on kokonaisluku. Indeksi kertoo alkion paikan taulukossa. Taulukon ensimmäinen alkio on paikassa nolla, seuraava kohdassa yksi ja niin edelleen. Taulukon tietyssä indeksissä olevaa arvoa tutkittaessa indeksi annetaan taulukko-olion nimen perään hakasulkeiden sisällä.
// indeksi 0 1 2 3 4 5 6 7 int[] luvut = {100, 1, 42, 23, 1, 1, 3200, 3201}; System.out.println(luvut[0]); // tulostaa luvun taulukon indeksistä 0, eli luvun 100 System.out.println(luvut[2]); // tulostaa luvun taulukon indeksistä 2, eli luvun 42
Yllä olevan taulukon koko (eli pituus) on 8.
Huomaat todennäköisesti että ArrayListin metodi get
käyttäytyy hyvin samalla tavalla kuin taulukon tietystä indeksistä haku. Taulukon kohdalla vain syntaksi, eli merkintätapa, on erilainen.
Yksittäisen arvon asettaminen taulukon tiettyyn paikkaan tapahtuu kuten arvon asetus tavalliseen muuttujaan, mutta taulukkoon asetettaessa paikka eli indeksi tulee kertoa. Indeksi kerrotaan hakasulkeiden sisällä.
int[] luvut = {100,1,42}; luvut[0] = 1; // asetetaan luku 1 indeksiin 0 luvut[1] = 101; // asetetaan luku 101 indeksiin 1 // luvut-taulukko on nyt {1,101,42}
Jos indeksillä osoitetaan taulukon ohi, eli alkioon jota ei ole olemassa, niin saadaan virheilmoitus ArrayIndexOutOfBoundsException, joka kertoo että indeksiä johon osoitimme ei ole olemassa. Taulukon ohi, eli indeksiin joka on pienempi kuin 0 tai suurempi tai yhtäsuuri kuin taulukon koko ei siis saa viitata.
Huomaamme, että taulukko on selvästi sukua ArrayList:ille. Aivan kuten listoilla, myös taulukossa alkiot ovat tietyssä paikassa!
Taulukko-olion koon saa selville kirjoittamalla koodiin taulukko.length
, huomaa että ilmauksessa ei tule käyttää sulkuja eli taulukko.length()
ei toimi!
Taulukon alkioiden läpikäynti on helppo toteuttaa while
-komennon avulla:
int[] luvut = {1, 8, 10, 3, 5}; int i = 0; while (i < luvut.length ) { System.out.println(luvut[i]); i++; }
Esimerkissä käydään muuttujan i
avulla läpi indeksit 0, 1, 2, 3, ja 4, ja tulostetaan taulukon kussakin indeksissä olevan muuttujan arvo. Ensin siis tulostuu luvut[0]
, sitten luvut[1]
jne. Muuttujan i
kasvatus loppuu kun koko taulukko on käyty läpi, eli kun sen arvo on yhtäsuuri kuin taulukon pituus.
Taulukon läpikäynnissä ei ole aina todellista tarvetta taulukon indeksien luetteluun, vaan ainoa kiinnostava asia ovat taulukon arvot. Tällöin voidaan käyttää aiemmin tutuksi tullutta for-each-rakennetta arvojen läpikäyntiin. Nyt toistolauseen rungossa annetaan vain muuttujan nimi, johon kukin taulukon arvo asetetaan vuorollaan, ja taulukon nimi kaksoispisteellä erotettuna.
int[] luvut = {1,8,10,3,5}; for (int luku : luvut) { System.out.println(luku); }
String[] nimet = {"Juhana L.", "Matti P.", "Matti L.", "Pekka M."}; for (String nimi : nimet) { System.out.println(nimi); }
Huom: for-each-tyylisellä läpikäynnillä taulukon alkioihin ei voi asettaa arvoja! Seuraavaksi nähtävällä for-lauseen toisella muodolla sekin onnistuu.
Olemme toistaiseksi käyttäneet toistolauseissa whileä tai for-lauseen ns. "for-each"-muotoa. Toistolauseesta for on olemassa myös toinen muoto, joka on kätevä erityisesti taulukoiden käsittelyn yhteydessä. Seuraavassa tulostetaan for-toistolauseen avulla luvut 0, 1 ja 2:
for (int i = 0; i < 3; i++ ) { System.out.println(i); }
Esimerkin for toimii täsmälleen samalla tavalla kuin alla oleva while:
int i = 0; // toistossa käytettävän muuttujan alustus while ( i < 3 ) { // toistoehto System.out.println(i); i++; // toistossa käytettävän muuttujan päivitys }
for-komento, kuten yllä esitelty for (int i = 0; i < 3; i++ )
sisältää kolme osaa: looppimuuttujien alustus; toistoehto; looppimuuttujien päivitys:
i
lauseella int i=0
. Ensimmäinen osa suoritetaan vain kerran, juuri for:in suorituksen alussa.i < 3
. Toistoehdon voimassaolo tarkastetaan ennen jokaista for:in toistokertaa. Toistoehto toimii täsmälleen samoin kuin while:n toistoehto.i++
suoritetaan aina kertaalleen forin koodilohkon suorituksen jälkeen.for on hieman while:ä selkeämpi tapa toteuttaa toistoja joissa toistojen määrä perustuu esim. laskurin kasvatukseen. Taulukkojen läpikäynnissä tilanne on yleensä juuri tämä. Seuraavassa tulostetaan taulukon luvut
sisältö for:illa
int[] luvut = {1, 3, 5, 9, 17, 31, 57, 105}; for(int i = 3; i < 7; i++) { System.out.println(luvut[i]); }
Forilla voidaan aloittaa läpikäynti luonnollisesti muualtakin kuin nollasta ja läpikäynti voi edetä "ylhäältä alas". Esimerkiksi taulukon paikoissa 6, 5, 4, ja 3 olevat alkiot voidaan tulostaa seuraavasti:
int[] luvut = {1, 3, 5, 9, 17, 31, 57, 105}; for(int i = 6; i>2 ; i--) { System.out.println(luvut[i]); }
Kaikkien taulukon alkioiden läpikäynti for:in avulla onnistuu seuraavasti:
int[] luvut = {1, 8, 10, 3, 5}; for (int i = 0; i < luvut.length; i++ ) { System.out.println(luvut[i]); }
Huomaa, että toistoehdossa i < luvut.length
verrataan looppimuuttujan arvoa taulukolta kysyttyyn pituuteen. Ehtoa ei kannata missään tapauksessa "kovakoodata" tyyliin i < 5
sillä yleensä taulukon pituudesta ei ole etukäteen varmuutta.
Taulukkoja voidaan käyttää metodin parametrina aivan kuten muitakin olioita. Huomaa, että kuten kaikkien olioiden tapauksessa, metodi saa parametrina viitteen taulukkoon, eli kaikki metodissa tapahtuvat taulukon sisältöön vaikuttavat muutokset näkyvät myös pääohjelmassa.
public static void listaaAlkiot(int[] kokonaislukuTaulukko) { System.out.println("taulukon alkiot ovat: "); for( int luku : kokonaislukuTaulukko) { System.out.print(luku + " "); } System.out.println(""); } public static void main(String[] args) { int[] luvut = { 1, 2, 3, 4, 5 }; listaaAlkiot(luvut); }
Kuten jo tiedämme, parametrin nimi metodin sisällä voi olla aivan vapaasti valittu, nimen ei tarvitse missään tapauksessa olla sama kuin kutsuvassa. Edellä taulukkoa kutsutaan metodin sisällä nimellä kokonaislukuTaulukko
, metodin kutsuja taas näkee saman taulukon luvut
-nimisenä.
Tee metodi public static int laskeTaulukonLukujenSumma(int[] taulukko)
, joka palauttaa taulukossa olevien lukujen summan.
Ohjelman runko on seuraava:
public class Main { public static void main(String[] args) { // Tässä voit testata metodia int[] taulukko = {5, 1, 3, 4, 2}; System.out.println(laskeTaulukonLukujenSumma(taulukko)); } public static int laskeTaulukonLukujenSumma(int[] taulukko) { // Kirjoita koodia tänne return 0; } }
Ohjelman tulostus on seuraava:
15
Huom: tämän tehtävän metodi samoin kuin muutamien seuraavien tehtävien taulukkoa käsittelevät metodit ovat viikkojen 2 ja 3 tapaan static
eli staattisia metodeja. Karkeasti ottaen tämä johtuu siitä, että metodi ei liity mihinkään olioon, vaan saa kaiken datan jota se käyttää eli tässä tapauksessa taulukon, parametrina. Luvussa 28 palaamme tarkemmin aiheeseen staattiset vs. olioihin liittyvät metodit.
Tee metodi public static void tulostaTyylikkaasti(int[] taulukko)
, joka tulostaa taulukossa olevat luvut tyylikkäästi. Lukujen väliin tulee pilkku ja välilyönti. Viimeisen luvun jälkeen ei pilkkua tule.
Ohjelman runko on seuraava:
public class Main { public static void main(String[] args) { // Tässä voit testata metodia int[] taulukko = {5, 1, 3, 4, 2}; tulostaTyylikkaasti(taulukko); } public static void tulostaTyylikkaasti(int[] taulukko) { // Kirjoita koodia tänne } }
Ohjelman tulostus on seuraava:
5, 1, 3, 4, 2
Jos taulukon koko ei ohjelmassa ole aina sama, eli se riippuu esim. käyttäjän syötteestä, ei äsken esitelty taulukon luontitapa kelpaa. Taulukko on mahdollista luoda myös siten, että sen koko määritellään muuttujan avulla:
int alkioita = 99; int[] taulukko = new int[alkioita];
Yllä luodaan int-tyyppinen taulukko, jossa on 99 paikkaa. Tällä vaihtoehtoisella tavalla taulukon luominen tapahtuu siis kuten muidenkin olioiden luominen, eli new
-komennolla. Komentoa new
seuraa taulukon sisältämien muuttujien tyyppi, ja hakasuluissa taulukon koko.
int alkioita = 99; int[] taulukko = new int[alkioita]; //luodaan muuttujan alkioita sisältämän arvon kokoinen taulukko if(taulukko.length == alkioita) { System.out.println("Taulukon pituus on " + alkioita); } else { System.out.println("Jotain epätodellista tapahtui. Taulukon pituus on eri kuin " + alkioita); }
Seuraavassa esimerkissä on ohjelma, joka kysyy käyttäjältä lukujen määrän ja joukon lukuja. Tämän jälkeen ohjelma tulostaa luvut uudestaan samassa järjestyksessä. Käyttäjän antamat luvut tallennetaan taulukkoon.
System.out.print("Kuinka monta lukua? "); int lukuja = Integer.parseInt(lukija.nextLine()); int[] luvut = new int[lukuja]; System.out.println("Anna luvut:"); for(int i = 0; i < lukuja; i++) { luvut[i] = Integer.parseInt(lukija.nextLine()); } System.out.println("Luvut uudestaan:"); for(int i = 0; i < lukuja; i++) { System.out.println(luvut[i]); }
Eräs ohjelman suorituskerta voisi olla seuraavanlainen:
Kuinka monta lukua? 4 Anna luvut: 4 8 2 1 Luvut uudestaan: 4 8 2 1
Koska metodit voivat palauttaa olioita, voivat ne palauttaa myös taulukkoja. Eräs merkkijonotaulukon palauttava metodi on seuraavannäköinen -- huomaa että taulukkoihin voi aivan hyvin siis laittaa myös olioita.
public static String[] annaMerkkijonoTaulukko() { String[] opet = new String[3]; opet[0] = "Bonus"; opet[1] = "Ihq"; opet[2] = "Lennon"; return opet; } public static void main(String[] args){ String[] opettajat = annaMerkkijonoTaulukko(); for ( String opettaja : opettajat) System.out.println( opettaja ); }
Tee metodi public static int[] kopioi(int[] taulukko)
joka luo kopion parametrina saadusta taulukosta. Vihje: koska metodin on luotava taulukosta kopio, tulee metodin sisällä luoda uusi taulukko ja kopioida vanhan taulukon sisältö uudelle taulukolle alkio alkiolta.
Seuraavassa esimerkki metodin käytöstä (koodissa myös Arrays-luokan tarjoama kätevä apuväline taulukon sisällön tulostamiseen):
public static void main(String[] args) { int[] alkuperainen = {1, 2, 3, 4}; int[] kopio = kopioi(alkuperainen); // muutetaan kopioa kopio[0] = 99; // tulostetaan molemmat System.out.println( "alkup: " + Arrays.toString(alkuperainen)); System.out.println( "kopio: " + Arrays.toString(kopio)); }
Kuten tulostuksesta huomaa, ei kopioon tehty muutos vaikuta alkuperäiseen:
alkup: [1, 2, 3, 4] kopio: [99, 2, 3, 4]
Tee metodi public static int[] kaanna(int[] taulukko)
joka luo käänteisessä järjestyksessä olevan kopion parametrinaan saamastaan taulukosta.
Eli jos parametrina on taulukko jossa esim. luvut 5, 6, 7 palauttaa metodi uuden taulukon jonka sisältönä luvut 7, 6, 5. Parametrina oleva taulukko ei saa muuttua.
Seuraavassa esimerkki metodin käytöstä:
public static void main(String[] args) { int[] alkuperainen = {1, 2, 3, 4}; int[] kaannetty = kaanna(alkuperainen); // tulostetaan molemmat System.out.println( "alkup: " +Arrays.toString(alkuperainen)); System.out.println( "käännetty: " +Arrays.toString(kaannetty)); }
Tulostuksesta pitäisi selvitä, että alkuperäinen taulukko on muuttumaton:
alkup: [1, 2, 3, 4] käännetty: [4, 3, 2, 1]
Aaltosululla {
alkavaa ja aaltosululla }
koodia sisältävää aluetta siis sanotaan lohkoksi. Kuten on jo nähty, lohkoja käytetään muun muassa ehto- ja toistolauseiden alueen rajaamisessa. Tärkeä lohkon ominaisuus on se, että lohkossa määritellyt muuttujat ovat voimassa vain lohkon sisällä.
Seuraavassa esimerkissä määritellään ehtolausekkeeseen liittyvän lohkon sisällä tekstimuuttuja lohkonSisallaMaariteltyTeksti
, joka on olemassa vain lohkon sisällä. Lohkossa esitellyn muuttujan tulostus lohkon ulkopuolella ei toimi!
int luku = 5; if( luku == 5 ){ String lohkonSisallaMaariteltyTeksti = "Jea!"; } System.out.println(lohkonSisallaMaariteltyTeksti); // ei toimi!
Lohkossa voidaan toki käyttää ja muuttaa sen ulkopuolella määriteltyjä muuttujia.
int luku = 5; if( luku == 5 ) { luku = 6; } System.out.println(luku); // tulostaa luvun 6
Lohkon sisällä voi olla mitä tahansa koodia. Esimerkiksi for-toistolauseen määrittelemän lohkon sisällä voi olla toinen for-toistolauseke tai vaikkapa while-toistolauseke. Tarkastellaan seuraavaa ohjelmaa:
for(int i = 0; i < 3; i++) { System.out.print(i + ": "); for(int j = 0; j < 3; j++) { System.out.print(j + " "); } System.out.println(); }
Ohjelman tulostus on seuraava:
0: 0 1 2 1: 0 1 2 2: 0 1 2
Eli mitä ohjelmassa tapahtuukaan? Jos ajatellaan pelkkää ulommaista for:ia, on toiminnallisuus helppo ymmärtää:
for(int i = 0; i < 3; i++) { System.out.print(i + ": "); // sisemmäinen for System.out.println(); }
Eli ensin i=0
ja tulostuu 0:
ja rivinvaihto. Tämän jälkeen i kasvaa ja tulostuu ykkönen, jne., eli ulompi for saa aikaan seuraavan:
0: 1: 2:
Myös sisempi for on helppo ymmärtää erillään. Se saa aina aikaan tulosteen 0 1 2
. Kun yhdistämme nämä kaksi, huomaamme, että sisin for suorittaa tulosteensa aina juuri ennen uloimman for:in tulostamaa rivinvaihtoa.
Tutkitaan seuraavaa muunnosta edelliseen esimerkkiin:
for(int i = 0; i < 3; i++) { System.out.print(i + ": "); for(int j = 0; j <= i; j++) { System.out.print(j + " "); } System.out.println(); }
Sisemmän for:in toistojen määrä riippuukin nyt ulomman for:in muuttujan i
arvosta. Eli kun i=0
, tulostaa sisin looppi luvun 0, kun i=1
, tulostaa sisempi toistolauseke 0 1, jne. Koko ohjelman tulostus on seuraava:
0: 0 1: 0 1 2: 0 1 2
Seuraava ohjelma tulostaa lukujen 1..10 kertotaulun.
for(int i = 1; i <= 10; i++) { for(int j = 1; j <= 10; j++) { System.out.print(i * j + " "); } System.out.println(); }
Tulostus näyttää seuraavalta:
1 2 3 4 5 6 7 8 9 10 2 4 6 8 10 12 14 16 18 20 3 6 9 12 15 18 21 24 27 30 4 8 12 16 20 24 28 32 36 40 5 10 15 20 25 30 35 40 45 50 6 12 18 24 30 36 42 48 54 60 7 14 21 28 35 42 49 56 63 70 8 16 24 32 40 48 56 64 72 80 9 18 27 36 45 54 63 72 81 90 10 20 30 40 50 60 70 80 90 100
Ylimmällä rivillä luvun 1 kertotaulu. Alussa i=1
ja sisimmän loopin muuttuja j
saa arvot 1...10. Jokaisella i, j
arvoparilla tulostetaan niiden tulo. Eli alussa i=1, j=1
, sitten i=1, j=2
, ..., i=1, j=10
seuraavaksi i=2, j=1
, jne.
Kertotaulu-ohjelman voi toki pilkkoa pienempiin osiin. Voimme määritellä metodit public void tulostaKertotaulunRivi(int kerroin, int montakokertaa)
ja public void tulostaKertotaulu(int mihinAsti)
, jolloin ohjelman rakenne olisi esimerkiksi seuraavanlainen seuraavanlainen:
public class Kertotaulu { public void tulosta(int mihinAsti) { for(int i = 1; i <= mihinAsti; i++) { tulostaKertotaulunRivi(i, mihinAsti); System.out.println(); } } public void tulostaKertotaulunRivi(int kerroin, int montakokertaa) { for(int i = 1; i <= montakokertaa; i++) { System.out.print(i * kerroin + " "); } } }
Nyt kutsu new Kertotaulu().tulosta(5);
tulostaa allaolevan kertotaulun.
1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 5 10 15 20 25
Kirjoita metodi public static void tulostaTaulukkoTahtina(int[] taulukko)
, joka tulostaa jokaista taulukossa olevaa lukua vastaavan pituisen rivin tähtiä.
Ohjelman runko on seuraava:
public class Main { public static void main(String[] args) { // Tässä voit testata metodia int[] taulukko = {5, 1, 3, 4, 2}; tulostaTaulukkoTahtina(taulukko); } public static void tulostaTaulukkoTahtina(int[] taulukko) { // Kirjoita tulostuskoodi tänne } }
Edellisen esimerkin mukaisella syötteellä ohjelman tulostus on seuraava:
***** * *** **** **
Eli koska taulukon nollannessa paikassa on luku 5, tulee ensimmäiselle riville 5 tähteä. Seuraavalla 1 tähti jne.
Luodaan ohjelma tähtitaivaan tulostamiseen. Tähtitaivaan tähtien määrä kerrotaan tiheyden avulla. Esimerkiksi jos tähtitaivaan tiheys on 0.2
, on noin 10% tähtitaivaasta peitettynä tähdillä. Pääset harjoittelemaan siis myös satunnaisuuslukujen käyttöä.
Käytä tähtien tulostamiseen *
-merkkiä. Alla on esimerkki lopullisen Tahtitaivas
-luokan käytöstä ja käyttöä vastaavasti tulostuksesta.
Tahtitaivas tahtitaivas = new Tahtitaivas(0.1, 39, 10); tahtitaivas.tulosta(); System.out.println("Tähtiä: " + tahtitaivas.tahtiaViimeTulostuksessa()); System.out.println(""); tahtitaivas = new Tahtitaivas(0.2, 15, 6); tahtitaivas.tulosta(); System.out.println("Tähtiä: " + tahtitaivas.tahtiaViimeTulostuksessa());
* * * * * * * ** * * * * * * * * * * * * * * * * * * * ** * * * * * * Tähtiä: 36 * * * * * * * * * * * * * * * * * ** ** * Tähtiä: 22
Huom! tehtävissä kannattaa käyttää for
-lauseketta. Vaikka edellinen luku puhuukin sisäkkäisistä toistolauseista, tässä tehtävässä "sisempi" toisto piilotetaan metodin sisälle.
Luo luokka Tahtitaivas
, jolla on kolme oliomuuttujaa: tiheys (double
), leveys (int
), ja korkeus (int
). Luo luokalle myös kolme konstruktoria:
public Tahtitaivas(double tiheys)
Luo tähtitaivas-olion, jolla on parametrina annettu tiheys, leveys saa arvon 20
, korkeus saa arvon 10
.public Tahtitaivas(int leveys, int korkeus)
Luo tähtitaivas-olion, jolla on parametrina annetut leveys ja korkeus, tiheys saa arvon 0.1
.public Tahtitaivas(double tiheys, int leveys, int korkeus)
Luo tähtitaivas-olion, jolla on parametrina annetut tiheys, leveys ja korkeus.Lisää luokalle Tahtitaivas
metodi tulostaRivi
, joka tulostaa yhden rivin. Rivin leveyden määrää oliomuuttuja leveys
. Oliomuuttuja tiheys
kertoo todennäköisyyden tähdelle. Arvo jokaisen merkin kohdalla tulostetaanko tähti vai ei Random
-luokan nextDouble
-metodin avulla.
Testaa ohjelmaasi, esimerkkinä seuraava kutsu ja esimerkkitulostus.
Tahtitaivas tahtitaivas = new Tahtitaivas(0.1); tahtitaivas.tulostaRivi();
* * *
Luo Tahtitaivas
-luokalle metodi tulosta
, joka tulostaa koko tähtitaivaan. Käytä tässä hyödyksesi aiempaa tulostaRivi
-metodia.
Tahtitaivas tahtitaivas = new Tahtitaivas(8, 4); tahtitaivas.tulosta();
* * *
Lisää Tahtitaivas
-luokalle oliomuuttuja tahtiaViimeTulostuksessa (int
) ja metodi tahtiaViimeTulostuksessa()
, joka palauttaa viime tulostuksessa tulostuneiden tähtien lukumäärän. Toteuta ohjelmaasi tähtien laskeminen.
Tahtitaivas tahtitaivas = new Tahtitaivas(8, 4); tahtitaivas.tulosta(); System.out.println("Tähtiä: " + tahtitaivas.tahtiaViimeTulostuksessa()); System.out.println(""); tahtitaivas.tulosta(); System.out.println("Tähtiä: " + tahtitaivas.tahtiaViimeTulostuksessa());
* Tähtiä: 1 * * * Tähtiä: 3
Kun aloimme olioden käytön, materiaalissa oli neuvo jättää sana static pois olioiden metodien määrittelystä. Viikkoon 3 asti taas kaikissa metodeissa esiintyi määre static. Mistä on kysymys?
Seuraavassa esimerkissä on metodi nollaaTaulukko
joka toimii nimensä mukaisesti eli asettaa nollan parametrina saamansa taulukon kaikkien lokeroiden arvoksi.
public class Ohjelma { public static void nollaaTaulukko(int[] taulukko) { for ( int i=0; i < taulukko.length; i++ ) taulukko[i] = 0; } public static void main(String[] args) { int[] luvut = { 1, 2, 3, 4, 5 }; for ( int luku : luvut ) { System.out.print( luku + " " ); // tulostuu 1, 2, 3, 4, 5 } System.out.println(); nollaaTaulukko(luvut); for ( int luku : luvut ) { System.out.print( luku + " " ); // tulostuu 0, 0, 0, 0, 0 } } }
Huomaamme, että metodin määrittelyssä on nyt määre static
. Syynä on se, että metodi ei liity mihinkään olioon vaan kyseessä on luokkametodi. Luokkametodeja kutsutaan usein myös staattisiksi metodeiksi. Toisin kuin olioiden metodit (joilla ei ole määrettä static), staattiseen metodiin ei liity mitään olioa. Staattinen metodi ei siis voi viitata this
-määreellä olioon itseensä toisin kuin oliometodit.
Staattiselle metodille voi toki antaa olion parametrina. Staattinen metodi ei voi käsitellä mitään muita lukuja, merkkijonoja, taulukoita tai oliota kuin niitä, jotka on sille parametrina annettu. Toisin sanoin, staattisen metodin kutsuja antaa metodille aina käsiteltävät arvot ja oliot.
Koska staattinen metodi ei liity mihinkään olioon, ei sitä kutsuta oliometodien tapaan olionNimi.metodinNimi()
, vaan ylläolevan esimerkin tapaan käytetään pelkkää staattisen metodin nimeä.
Jos staattisen metodin koodi on eri luokan sisällä kuin sitä kutsuva metodi, tapahtuu kutsu muodossa LuokanNimi.staattisenMetodinNimi()
. Edellinen esimerkki alla muutettuna siten, että pääohjelma ja metodi ovat omissa luokissaan (eli eri tiedostoissa):
public class Ohjelma { public static void main(String[] args) { int[] luvut = { 1, 2, 3, 4, 5 }; for ( int luku : luvut ) { System.out.print( luku + " " ); // tulostuu 1, 2, 3, 4, 5 } System.out.println(); TaulukonKasittely.nollaaTaulukko(luvut); for ( int luku : luvut ) { System.out.print( luku + " " ); // tulostuu 0, 0, 0, 0, 0 } } }
public class TaulukonKasittely { public static void nollaaTaulukko(int[] taulukko) { for ( int i=0; i < taulukko.length; i++ ) { taulukko[i] = 0; } } }
Toisen luokan sisällä määriteltyä staattista metodia kutsutaan nyt muodossa TaulukonKasittely.nollaaTaulukko(parametri);
.
Kaikki olion tilaa käsittelevät metodit tulee määritellä normaaleina oliometodeina. Esim. edellisillä viikoilla määrittelemiemme luokkien Henkilo, Paivays, Kello, Joukkue, ...
kaikki metodit tulee määritellä normaaleina oliometodeina eli ei static:eina.
Palataan vielä luokkaan Henkilo
. Seuraavassa on osa luokan määritelmästä. Kaikkiin oliomuuttujiin viitataan this
-määreen avulla sillä korostamme, että metodeissa käsitellään olion "sisällä" olevia oliomuuttujia.
public class Henkilo { private String nimi; private int ika; public Henkilo(String nimi) { this.ika = 0; this.nimi = nimi; } public boolean taysiIkainen(){ if ( this.ika < 18 ) { return false; } return true; } public void vanhene() { this.ika++; } public String getNimi() { return this.nimi; } }
Koska metodit käsittelevät olioa, ei niitä voi määrittää static:eiksi eli "olioon kuulumattomiksi". Jos näin yritetään tehdä, ei metodi toimi:
public class Henkilo { //... public static void vanhene() { this.ika++; } }
Seurauksena on virheilmoitus non static variable ika can not be referenced from static context, joka tarkoittaa että staattinen metodi ei pysty käsittelemään oliomuuttujaa.
Eli milloin staattista metodia sitten kannattaa käyttää? Tarkastellaan luvusta 23 tuttua henkilöolioita käsittelevää esimerkkiä:
public class Ohjelma { public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka"); Henkilo antti = new Henkilo("Antti"); Henkilo juhana = new Henkilo("Juhana"); for ( int i=0; i < 30; i++ ) { pekka.vanhene(); juhana.vanhene(); } antti.vanhene(); if ( antti.taysiIkainen() ) { System.out.println( antti.getNimi() + " on täysi-ikäinen" ); } else { System.out.println( antti.getNimi() + " on alaikäinen" ); } if ( pekka.taysiIkainen() ) { System.out.println( pekka.getNimi() + " on täysi-ikäinen" ); } else { System.out.println( pekka.getNimi() + " on alaikäinen " ); } if ( juhana.taysiIkainen() ) { System.out.println( juhana.getNimi() + " on täysi-ikäinen" ); } else { System.out.println( juhana.getNimi() + " on alaikäinen " ); } } }
Huomaamme, että henkilöiden täysi-ikäisyyden ilmottamiseen liittyy koodinpätkä joka on copy-pastettu peräkkäin kolme kertaa. Todella rumaa!
Henkilön täysi-ikäisyyden ilmoittaminen on mainio kohde staattiselle metodille. Kirjoitetaan ohjelma uudelleen metodia hyödyntäen:
public class Main { public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka"); Henkilo antti = new Henkilo("Antti"); Henkilo juhana = new Henkilo("Juhana"); for ( int i=0; i < 30; i++ ) { pekka.vanhene(); juhana.vanhene(); } antti.vanhene(); ilmoitaTaysiIkaisyys(antti); ilmoitaTaysiIkaisyys(pekka); ilmoitaTaysiIkaisyys(juhana); } private static void ilmoitaTaysiIkaisyys(Henkilo henkilo) { if ( henkilo.taysiIkainen() ) { System.out.println(henkilo.getNimi() + " on täysi-ikäinen"); } else { System.out.println(henkilo.getNimi() + " on alaikäinen"); } } }
Metodi ilmoitaTaysiIkaisyys
on määritelty staattiseksi, eli se ei liity mihinkään olioon,
mutta metodi saa parametrikseen henkilöolion. Metodia ei ole nyt määritelty
Henkilö-luokan sisälle sillä vaikka se käsittelee parametrinaan saamaan henkilöolioa, se on juuri
kirjoitetun pääohjelman apumetodi, jonka avulla main on saatu kirjoitettua selkeämmin.
Kumpulan tiedekirjasto tarvitsee uuden järjestelmän kirjojen hallintaan. Tässä tehtävässä toteutetaan prototyyppi, jossa toteutetaan kirjan haku nimen, julkaisijan tai julkaisuvuoden perusteella.
Rakennetaan järjestelmä osista, ensin toteutetaan oleelliset luokat eli Kirja
ja Kirjasto
. Luokka Kirja
sisältää kirjaan liittyvät tiedot, luokka Kirjasto
tarjoaa erilaisia hakutoiminnallisuuksia kirjoihin liittyen.
Luodaan ensiksi luokka Kirja. Kirjalla on oliomuuttujina nimike
, eli kirjan nimi, julkaisija
, eli kirjan julkaisija, ja julkaisuvuosi
eli vuosi jolloin kirja on julkaistu. Kaksi ensimmäistä muuttujaa on merkkijonotyyppisiä, viimeisin on kokonaisluku. Oletamme tässä että kirjalla on aina vain yksi kirjoittaja.
Toteuta luokka Kirja
. Kirjalla tulee olla myös konstruktori public Kirja(String nimike, String julkaisija, int julkaisuvuosi)
sekä metodit public String nimike()
, public String julkaisija()
, public int julkaisuvuosi()
ja public String toString()
. Arvannet mitä metodien tulee tehdä, alla esimerkki.
Testaa luokan toimintaa:
Kirja cheese = new Kirja("Cheese Problems Solved", "Woodhead Publishing", 2007); System.out.println(cheese.nimike()); System.out.println(cheese.julkaisija()); System.out.println(cheese.julkaisuvuosi()); System.out.println(cheese);
Cheese Problems Solved Woodhead Publishing 2007 Cheese Problems Solved, Woodhead Publishing, 2007
Kirjaston tehtävä on antaa käyttäjälle mahdollisuus kirjojen lisäämiseen ja niiden hakemiseen. Luo luokka Kirjasto
, jolla on konstruktori public Kirjasto()
ja metodit public void lisaaKirja(Kirja uusiKirja)
ja public void tulostaKirjat()
Kirjasto kirjasto = new Kirjasto(); Kirja cheese = new Kirja("Cheese Problems Solved", "Woodhead Publishing", 2007); kirjasto.lisaaKirja(cheese); Kirja nhl = new Kirja("NHL Hockey", "Stanley Kupp", 1952); kirjasto.lisaaKirja(nhl); kirjasto.lisaaKirja(new Kirja("Battle Axes", "Tom A. Hawk", 1851)); kirjasto.tulostaKirjat();
Cheese Problems Solved, Woodhead Publishing, 2007 NHL Hockey, Stanley Kupp, 1952 Battle Axes, Tom A. Hawk, 1851
Kirjastosta tulee pystyä etsimään kirjoja nimikkeiden ja julkaisijoiden perusteella. Lisää kirjastolle metodit public ArrayList<Kirja> haeKirjaNimikkeella(String nimike)
, public ArrayList<Kirja> haeKirjaJulkaisijalla(String julkaisija)
ja public ArrayList<Kirja> haeKirjaJulkaisuvuodella(int julkaisuvuosi)
. Metodit palauttavat listan kirjoista, joissa on haluttu nimike, julkaisija tai julkaisuvuosi.
Huom: joudut siis tehdä metodin jonka paluuarvona on ArrayList. Tämä onnustuu seuraavaa metodirunkoa hyödyntäen:
public class Kirjasto { // ... public ArrayList<Kirja> haeKirjaNimikkeella(String nimike) { ArrayList<Kirja> loydetyt = new ArrayList<Kirja>(); // käy läpi kaikki kirjat ja lisää ne joilla haetun kaltainen nimike listalle loydetyt return loydetyt; }
Huom! Kun haet teet hakua merkkijonon avulla, älä tee tarkkaa hakua (metodi equals
) vaan käytä String
-luokan metodia contains
. Huomaat todennäköisesti myös että sinulla on ns. copy-paste -koodia Kirjasto
-luokan koodissa. Keksitkö tavan päästä siitä eroon?
Kirjasto kirjasto = new Kirjasto(); kirjasto.lisaaKirja(new Kirja("Cheese Problems Solved", "Woodhead Publishing", 2007)); kirjasto.lisaaKirja(new Kirja("The Stinky Cheese Man and Other Fairly Stupid Tales", "Penguin Group", 1992)); kirjasto.lisaaKirja(new Kirja("NHL Hockey", "Stanley Kupp", 1952)); kirjasto.lisaaKirja(new Kirja("Battle Axes", "Tom A. Hawk", 1851)); ArrayList<Kirja> hakutulos = kirjasto.haeKirjaNimikkeella("Cheese"); for (Kirja kirja: hakutulos) { System.out.println(kirja); } System.out.println("---"); for (Kirja kirja: kirjasto.haeKirjaJulkaisijalla("Penguin Group ")) { System.out.println(kirja); } System.out.println("---"); for (Kirja kirja: kirjasto.haeKirjaJulkaisuvuodella(1851)) { System.out.println(kirja); }
Cheese Problems Solved, Woodhead Publishing, 2007 The Stinky Cheese Man and Other Fairly Stupid Tales, Penguin Group, 1992 --- --- Battle Axes, Tom A. Hawk, 1851
Hakutoiminnallisuutemme on jo hyvä, mutta se ei ymmärrä isojen ja pienten kirjainten eroa. Yllä olleessa esimerkissä haku nimikkeellä "cheese"
ei olisi tuottanut yhtäkään tulosta. Myös toinen esimerkki, jossa oli ylimääräisiä välilyöntejä, ei näyttänyt haluttua tulosta. Haluamme että nimikkeiden ja julkaisijoiden nimillä haettaessa ei välitetä merkkien koosta, ja että käyttäjä voi syöttää ylimääräisiä välilyöntejä kirjan nimen alkuun tai loppuun (meidän ei tarvitse välittää sanojen välillä olevista tyhjistä!). Toteutetaan pieni apukirjasto StringUtils
merkkijonojen vertailuun.
Luo luokka StringUtils
, ja lisää sille staattinen metodi public static boolean sisaltaa(String sana, String haettava)
, joka tarkistaa sisältääkö merkkijono sana
merkkijonon haettava
. Jos jommankumman merkkijonon arvo on null, metodin tulee palauttaa arvo false
. Metodin tarjoaman vertailun tulee olla välittämättä merkin koosta.
Lisää metodille sisaltaa
myös toiminnallisuus, joka poistaa merkkijonojen sana
ja haettava
alusta ja lopusta ylimääräiset välilyönnit. Käytä tähän String
-luokan metodia trim
, esim. trimmattu = trimmattava.trim()
Vinkki! String
-luokan metodista toUpperCase()
on hyötyä kun haluat verrata ovatko kaksi merkkijonoa samat -- riippumatta niiden alkuperäisestä merkkikoosta.
Kun olet saanut metodin valmiiksi, käytä sitä Kirjasto
-luokassa. Alla esimerkki:
if(StringUtils.sisaltaa(kirja.nimike(), haettuNimike)) { // kirja löytyi! }
Kirjasto kirjasto = new Kirjasto(); kirjasto.lisaaKirja(new Kirja("Cheese Problems Solved", "Woodhead Publishing", 2007)); kirjasto.lisaaKirja(new Kirja("The Stinky Cheese Man and Other Fairly Stupid Tales", "Penguin Group", 1992)); kirjasto.lisaaKirja(new Kirja("NHL Hockey", "Stanley Kupp", 1952)); kirjasto.lisaaKirja(new Kirja("Battle Axes", "Tom A. Hawk", 1851)); for (Kirja kirja: kirjasto.haeKirjaNimikkeella("CHEESE")) { System.out.println(kirja); } System.out.println("---"); for (Kirja kirja: kirjasto.haeKirjaJulkaisijalla("PENGUIN ")) { System.out.println(kirja); }
Cheese Problems Solved, Woodhead Publishing, 2007 The Stinky Cheese Man and Other Fairly Stupid Tales, Penguin Group, 1992 --- The Stinky Cheese Man and Other Fairly Stupid Tales, Penguin Group, 1992
Tehtävä vastaa kolmea yksiosaista tehtävää.
HUOM: Ohjelmassa saa käyttää vain yhtä Scanner-olioa, eli vain kertaalleen saa sanoa new Scanner
. Jos tarvitset Scanneria monessa kohtaa, voit välittää sen muualle parametrina seuraavaan tyyliin:
public static void main(String[] args) { Scanner lukija = new Scanner(System.in); // ... teeJotain(lukija); } public static void teeJotain(Scanner lukija) { String rivi = lukija.nextLine(); // ... }
Tai jos toinen olio tarvitsee Scanneria, voi sen välittää konstruktoriparametrina ja tallettaa oliomuuttujaan jolloin Scanner on olion kaikkien metodien käytössä.
Ohjelman syöte on joukko kokonaislukuja, jotka kuvaavat opiskelijoiden kokeesta saamia pistemääriä. Käyttäjä syöttää yhden pistemäärän per rivi. Kun syöte on -1, lopettaa ohjelma pistemäärien kyselyn.
Pisteiden syöttö toimii seuraavasti:
Syötä koepisteet, -1 lopettaa: 34 41 53 36 55 27 43 40 -1
Pisteiden syöttämisen jälkeen ohjelma tulostaa kurssin arvosanajakauman ja hyväksymisprosentin seuraavassa muodossa:
Arvosanajakauma: 5: ** 4: 3: *** 2: * 1: * 0: * Hyväksymisprosentti: 87.5
Arvosananajakauma muodostetaan seuraavasti:
Hyväksyttyjä ovat muut paitsi arvosanan 0 saaneet. Hyväksyttyjä edellä on siis 7 osallistujaa 8:sta. Hyväksymisprosentti lasketaan kaavalla 100*hyväksytyt/osallistujat.
HUOM: Ohjelmassa saa käyttää vain yhtä Scanner-olioa, eli vain kertaalleen saa sanoa new Scanner
. Jos tarvitset Scanneria monessa kohtaa, voit välittää sen muualle parametrina.
Tehtävä vastaa kolmea yksiosaista tehtävää.
Tässä tehtävässä suunnittelet ja toteutat tietokannan lintubongareille. Tietokanta sisältää lintuja, joista jokaisella on nimi (merkkijono) ja latinankielinen nimi (merkkijono). Tämän lisäksi tietokanta laskee kunkin linnun havaintokertoja.
Ohjelmasi täytyy toteuttaa seuraavat komennot:
Lisaa
- lisää linnun (huom: komennon nimessä ei ä-kirjainta!)Havainto
- lisää havainnonTilasto
- tulostaa kaikki linnutNayta
- tulostaa yhden linnun (huom: komennon nimessä ei ä-kirjainta!)Lopeta
- lopettaa ohjelmanLisäksi virheelliset syötteet pitää käsitellä. (Ks. Simo
alla). Tässä vielä esimerkki ohjelman toiminnasta:
? Lisaa Nimi: Korppi Latinankielinen nimi: Corvus Corvus ? Lisaa Nimi: Haukka Latinankielinen nimi: Dorkus Dorkus ? Havainto Mikä havaittu? Haukka ? Havainto Mikä havaittu? Simo Ei ole lintu! ? Havainto Mikä havaittu? Haukka ? Tilasto Haukka (Dorkus Dorkus): 2 havaintoa Korppi (Corvus Corvus): 0 havaintoa ? Nayta Mikä? Haukka Haukka (Dorkus Dorkus): 2 havaintoa ? Lopeta
Huom! Ohjelmasi rakenne on täysin vapaa. Testaamme vain että Tietokanta
luokan main
-metodi toimii kuten tässä on kuvailtu. Yritä miettiä millaisista luokista ja olioista olisi hyötyä ohjelman toteuttamisessa!
Ohjelmien muuttuessa monimutkaisemmiksi, tulee virheiden löytämisestäkin koko ajan haastavampaa. NetBeansiin integroitu debuggeri voi olla avuksi virheiden löytämisessä. Seuraavalla screencastilla esitellään debuggerin käyttöä. Screencast esittelee myös miten projekteja voidaan luoda, avata ja sulkea sekä miten ohjelmia voidaan suorittaa NetBeansin ulkopuolella.
Palaamme jälleen taulukkojen pariin.
Kuten olemme nähneet, Javassa on valmiina paljon kaikenlaista hyödyllistä. Esimerkiksi ArrayListin käsittelyyn löytyi useita hyödyllisiä apumetodeja luokasta Collections. Taulukoille löytyy vastaavia apuvälineitä luokasta Arrays
. Taulukon saa järjestettyä komennolla Arrays.sort(taulukko)
.
Huom: Komennon käyttäminen vaatii, että ohjelmatiedoston yläosassa lukee seuraava määrittely:
import java.util.Arrays;
Jos unohdat import
-rivin, NetBeans tarjoaa apua sen kirjoittamiseen. Kokeile klikata punaisella alleviivatun koodirivin vasempaan laitaan ilmestyvää lampun kuvaa.
Seuraava ohjelma luo taulukon ja järjestää taulukossa olevat luvut Arrays.sort -komennon avulla.
int[] luvut = {-3, -111, 7, 42}; Arrays.sort(luvut); for(int luku: luvut) { System.out.println(luku); }
-111 -3 7 42
Taulukon järjestäminen on helppoa Javan valmiin kaluston avulla. Ohjelmoijan yleissivistykseen kuuluu kuitenkin ainakin yhden järjestämisalgoritmin (eli tavan järjestää taulukko) tuntemus. Tutustutaan yhteen "klassiseen" järjestämisalgoritmiin, valintajärjestämiseen. Tutustuminen tapahtuu harjoitustehtävien avulla.
Huom: tässä tehtävässä on tarkoitus järjestää taulukko itse. Et saa käyttää Arrays.sort()-metodia tai ArrayListejä apunasi!
Tee metodi pienin
, joka palauttaa taulukon pienimmän luvun.
Metodin runko on seuraava:
public static int pienin(int[] taulukko) { // kirjoita koodia tähän }
HUOM: parametrina olevaa taulukkoa ei saa muuttaa!
Seuraava koodi esittelee metodin toimintaa:
int[] luvut = {6, 5, 8, 7, 11}; System.out.println("Pienin: " + pienin(luvut));
Pienin: 5
Tee metodi pienimmanIndeksi
, joka palauttaa taulukon pienimmän luvun indeksin (eli luvun kohdan taulukossa).
Metodin runko on seuraava:
public static int pienimmanIndeksi(int[] taulukko) { // kirjoita koodia tähän }
HUOM: parametrina olevaa taulukkoa ei saa muuttaa!
Seuraava koodi esittelee metodin toimintaa:
// indeksit: 0 1 2 3 4 int[] luvut = {6, 5, 8, 7, 11}; System.out.println("Pienimmän indeksi: " + pienimmanIndeksi(luvut));
Pienimmän indeksi: 1
Taulukon pienin luku on 2, ja sen indeksi eli sijaintipaikka taulukossa on 1. Muistathan, että taulukon numerointi alkaa 0:sta.
Tee metodi pienimmanIndeksiAlkaen
, joka toimii samalla tavalla kuin edellisen tehtävän metodi mutta ottaa huomioon vain taulukon loppuosan jostain indeksistä alkaen. Metodille annetaan taulukon lisäksi aloitusindeksi, josta lähtien pienintä lukua etsitään.
Metodin runko on seuraava:
public static int pienimmanIndeksiAlkaen(int[] taulukko, int aloitusIndeksi) { // kirjoita koodia tähän }
HUOM: parametrina olevaa taulukkoa ei saa muuttaa!
Seuraava koodi esittelee metodin toimintaa:
// indeksit: 0 1 2 3 4 int[] luvut = {-1, 6, 9, 8, 12}; System.out.println(pienimmanIndeksiAlkaen(luvut, 1)); System.out.println(pienimmanIndeksiAlkaen(luvut, 2)); System.out.println(pienimmanIndeksiAlkaen(luvut, 4));
1 3 4
Esimerkissä ensimmäinen metodikutsu etsii pienimmän luvun indeksin aloittaen indeksistä 1. Indeksistä 1 alkaen pienin luku on 6, ja sen indeksi on 1. Vastaavasti toinen metodikutsu etsii pienimmän luvun indeksiä indeksistä 2 aloittaen. Tällöin pienin luku on 8, ja sen indeksi on 3. Viimeinen kutsu etsii pienimmän luvun indeksiä taulukon viimeisestä indeksistä eli kohdasta 4 aloittaen. Tällöinen muita paikkoja ei ole, joten pienin on paikassa 4.
Tee metodi vaihda
, jolle annetaan taulukko ja kaksi sen indeksiä. Metodi vaihtaa indekseissä olevat luvut keskenään.
Metodin runko on seuraava:
public static void vaihda(int[] taulukko, int indeksi1, int indeksi2) { // kirjoita koodia tähän }
Seuraavassa estellään metodin toimintaa. Taulukon tulostamisessa käytetään apuna taulukon merkkijonoksi muotoilevaa Arrays.toString-metodia:
int[] luvut = {3, 2, 5, 4, 8}; System.out.println( Arrays.toString(luvut) ); vaihda(luvut, 1, 0); System.out.println( Arrays.toString(luvut) ); vaihda(luvut, 0, 3); System.out.println( Arrays.toString(luvut) );
[3, 2, 5, 4, 8] [2, 3, 5, 4, 8] [4, 3, 5, 2, 8]
Nyt koossa on joukko hyödyllisiä metodeja, joiden avulla voimme toteuttaa järjestämisalgoritmin nimeltä vaihtojärjestäminen.
Vaihtojärjestämisen idea on seuraava:
Toisin sanoen:
Toteuta metodi jarjesta
, joka perustuu yllä olevaan ideaan. Metodissa on syytä olla silmukka, joka käy läpi taulukon indeksejä. Metodeista pieninIndeksiAlkaen
ja vaihda
on varmasti hyötyä. Tulosta myös taulukon sisältö ennen järjestämistä ja jokaisen kierroksen jälkeen, jotta voit varmistaa algoritmin toimivan oikein.
Metodin runko on seuraava:
public static void jarjesta(int[] taulukko) { }
Testaa metodin toimintaa ainakin seuraavalla esimerkillä:
int[] luvut = {8, 3, 7, 9, 1, 2, 4}; jarjesta(luvut);
Ohjelman tulosteen tulisi olla seuraavanlainen. Huomaa että sinun tulee tulostaa taulukon sisältö jokaisen vaihtamisen jälkeen!
[8, 3, 7, 9, 1, 2, 4] [1, 3, 7, 9, 8, 2, 4] [1, 2, 7, 9, 8, 3, 4] [1, 2, 3, 9, 8, 7, 4] [1, 2, 3, 4, 8, 7, 9] [1, 2, 3, 4, 7, 8, 9] [1, 2, 3, 4, 7, 8, 9]
Huomaat, miten taulukko tulee pikkuhiljaa järjestykseen alkaen alusta ja edeten loppua kohti.
Järjestämisen lisäksi toinen hyvin tyypillinen ongelma johon ohjelmoija törmää, on tietyn arvon etsiminen taulukosta. Olemme aiemmin jo toteuttaneet metodeja, jotka etsivät arvoja listoista ja taulukoista. Taulukkojen tapauksessa lukuja ja merkkijonoja voi etsiä seuraavasti:
public static boolean onkoTaulukossa(int[] taulukko, int etsittava) { for ( int luku : taulukko ) { if ( luku == etsittava ) { return true; } } return false; } public static boolean onkoSanaTaulukossa(String[] taulukko, String etsittava) { for ( String sana: taulukko ) { if ( sana.equals(etsittava) ) { return true; } } return false; }
Tämänkaltainen toteutus on paras mihin olemme tähän asti pystyneet. Metodin huono puoli on se, että jos taulukossa on hyvin suuri määrä lukuja, kuluu etsintään paljon aikaa. Metodi käy läpi pahimmassa tapauksessa jokaisen taulukossa olevan alkion. Tämä tarkoittaa sitä, että taulukon, jossa on esimerkiksi 16777216 alkiota, läpikäynti vaatii 16777216 alkion tutkiskelua.
Toisaalta, jos taulukossa olevat luvut ovat suuruusjärjestyksessä, voidaan etsiminen tehdä huomattavasti tehokkaammin soveltamalla tekniikkaa nimeltään binäärihaku. Tutkitaan binäärihaun ideaa seuraavan taulukon kautta:
// indeksit 0 1 2 3 4 5 6 7 8 9 10 // luvut -7 -3 3 7 11 15 17 21 24 28 30
Oletetaan että haluamme löytää luvun 17. Hyödynnetään tietoa siitä että taulukon arvot ovat järjestyksessä sen sijaan, että kävisimme taulukon lukuja läpi taulukon alusta lähtien. Tutkitaan taulukon keskimmäistä alkiota. Taulukon puolessa välissä olevan alkion indeksi on isoin indeksi 10 jaettuna kahdella eli 5. Keskimmäinen alkio on merkattu seuraavaan tähdellä:
* // indeksit 0 1 2 3 4 5 6 7 8 9 10 // luvut -7 -3 3 7 11 15 17 21 24 28 30
Puolessa välissä on luku 15, joka ei ollut hakemamme luku. Etsimme lukua 17, joten koska taulukon alkiot ovat suuruusjärjestyksessä, ei etsitty luku voi missään tapauksessa olla luvun 15 vasemmalla puolella. Voimme siis päätellä että kaikki indeksit, jotka ovat pienempiä tai yhtäsuuria kuin 5, eivät missään nimessä sisällä hakemaamme arvoa.
Alue, jolta etsimme haettavaa lukua voidaan nyt rajata lukuihin, jotka sijaitsevat indeksin 5 oikealla puolella, eli indekseihin välillä [6, 10] (6, 7, 8, 9, 10). Seuraavassa on merkitty harmaalla se osa taulukkoa jossa etsitty ei voi olla:
// indeksit 0 1 2 3 4 5 6 7 8 9 10 // luvut -7 -3 3 7 11 15 17 21 24 28 30
Tutkitaan seuraavaksi jäljellä olevan etsintäalueen, eli indeksien 6-10 keskimmäistä indeksiä. Keskimmäinen indeksi löytyy ottamalla etsintäalueen pienimmän ja suurimman indeksin summa ja jakamalla se kahdella, eli (6+10)/2 = 16/2 = 8. Indeksi 8 on merkitty alle tähdellä.
* // indeksit 0 1 2 3 4 5 6 7 8 9 10 // luvut -7 -3 3 7 11 15 17 21 24 28 30
Indeksissä 8 oleva luku on 24, joka ei ollut hakemamme luku. Koska luvut taulukossa ovat suuruusjärjestyksessä, ei etsittävä luku voi missään nimessä olla luvun 24 oikealla puolella. Voimme siis päätellä että kaikki indeksit, jotka ovat suurempia tai yhtäsuuria kuin 8, eivät missään nimessä sisällä hakemaamme arvoa. Etsintäalue rajautuu taas, harmaat alueet on käsitelty:
// indeksit 0 1 2 3 4 5 6 7 8 9 10 // luvut -7 -3 3 7 11 15 17 21 24 28 30
Etsintä jatkuu. Tutkitaan jäljellä olevan etsintäalueen, eli indeksien 6-7, keskimmäistä indeksiä. Keskimmäinen indeksi löytyy taas ottamalla etsintäalueen pienimmän ja suurimman indeksin summa ja jakamalla se kahdella, eli (6+7)/2 = 6,5, joka pyöristyy alaspäin luvuksi 6. Kohta on merkitty alle tähdellä.
* // indeksit 0 1 2 3 4 5 6 7 8 9 10 // luvut -7 -3 3 7 11 15 17 21 24 28 30
Indeksissä 6 on luku 17, joka on sama kuin hakemamme luku. Voimme lopettaa haun ja ilmoittaa että etsitty luku on taulukossa. Jos luku ei olisi ollut taulukossa -- esimerkiksi jos haettava luku olisi ollut 16, etsintäalue olisi jäänyt lopulta tyhjäksi.
* // indeksit 0 1 2 3 4 5 6 7 8 9 10 // luvut -7 -3 3 7 11 15 17 21 24 28 30
Jotta binäärihaun idea tulee sinulle tutuksi, simuloi kynällä ja paperilla miten binäärihaku toimii kun taulukkona on alla oleva taulukko ja haet ensin lukua 33, sitten lukua 1.
// indeksit 0 1 2 3 4 5 6 7 8 9 10 11 12 13 // luvut -5 -2 3 5 8 11 14 20 22 26 29 33 38 41
Binäärihaun avulla haemme alkiota puolittamalla aina tutkittavan alueen kahteen osaan. Tämä mahdollistaa hyvin tehokkaan hakemisen. Esimerkiksi taulukko, jossa on 16 alkiota, voidaan jakaa kahteen osaan korkeintaan 4 kertaa, eli 16 -> 8 -> 4 -> 2 -> 1. Toisaalta, taulukko, jossa on 16777216 alkiota, voidaan jakaa kahteen osaan korkeintaan 24 kertaa. Tämä tarkoittaa sitä, että binäärihaun avulla 16777216-alkioisessa taulukossa tarvitsee tarkastella korkeintaan 24 alkiota haetun alkion löytymiseksi.
Binäärihaun tehokkuutta voi tutkia logaritmien avulla. Kaksikantainen logaritmi (log2
) luvusta 16777216 on 24 -- voimme siis laskea kaksikantaisen logaritmin avulla kuinka monta kertaa jonkun luvun voi puolittaa. Vastaavasti luvun 4294967296 kaksikantainen logaritmi, (log2 4294967296
) on 32. Tämä tarkoittaa että 4294967296 eri arvoa sisältävästä järjestyksessä olevasta taulukosta hakeminen vaatisi korkeintaan 32 eri alkion tarkastamista. Tehokkuus on oleellinen osa tietojenkäsittelytiedettä. Esimerkiksi tietojenkäsittelytieteen ensimmäisen vuoden kurssi Tietorakenteet keskittyy tehokkaiden tietorakenteiden toteuttamiseen.
Tehdään tässä tehtävässä tekoäly, joka arvaa pelaajan ajatteleman luvun. Tekoäly olettaa, että luku on välillä alaraja...yläraja. Pelin käynnistäjä antaa nämä rajat pelin toteuttavalle metodille parametrina. Tekoäly kysyy käyttäjältä kysymyksiä muotoa "Onko lukusi suurempi kuin X?" ja päättelee oikean luvun käyttäjän vastausten perusteella.
Tekoäly pitää kirjaa hakualueesta muuttujien alaraja ja yläraja avulla. Tekoäly kysyy aina, onko käyttäjän luku suurempi kuin näiden lukujen keskiarvo, jolloin vastauksen perusteella hakualue aina puolittuu. Lopulta alaraja ja yläraja ovat samat, ja käyttäjän ajattelema luku on paljastunut.
Seuraavassa esimerkissä käyttäjä valitsee luvun 44:
Ajattele jotain lukua väliltä 1...100. Lupaan pystyä arvaamaan ajattelemasi luvun 7 kysymyksellä. Esitän sinulle seuraavaksi sarjan kysymyksiä. Vastaa niihin rehellisesti. Onko lukusi suurempi kuin 50? (k/e) e Onko lukusi suurempi kuin 25? (k/e) k Onko lukusi suurempi kuin 38? (k/e) k Onko lukusi suurempi kuin 44? (k/e) e Onko lukusi suurempi kuin 41? (k/e) k Onko lukusi suurempi kuin 43? (k/e) k Valitsemasi luku on 44.
Yllä olevassa esimerkissä mahdollinen lukualue on aluksi 1...100. Kun käyttäjä kertoo, että luku ei ole yli 50, mahdollinen lukualue on 1...50. Kun käyttäjä kertoo, että luku on yli 25, mahdollinen lukualue on 26...50. Samanlainen päättely jatkuu, kunnes saavutaan lukuun 44.
Puolitus- eli binäärihaun mukaisesti tässä puolitetaan mahdollinen hakualue jokaisella kysymyksellä, jolloin kysymyksiä tarvitaan vähän. Jopa lukuvälillä 1..1000000 pitäisi kulua korkeintaan 20 kysymystä.
Pelin toteuttavan luokan Arvauspeli
runko on seuraavanlainen:
public class Arvauspeli { private Scanner lukija; public Arvauspeli() { this.lukija = new Scanner(System.in); } public void pelaa(int alaraja, int ylaraja) { tulostaOhjeet(ylaraja, alaraja); // Kirjoita pelin koodi tänne } // tee metodit onkoSuurempiKuin ja keskiarvo tänne public void tulostaOhjeet(int ylaraja, int alaraja) { int kysymyksiaKorkeintaan = kuinkaMontaKertaaVoiJakaaKahteen(ylaraja - alaraja); System.out.println("Ajattele jotain lukua väliltä " + alaraja + "..." + ylaraja + "."); System.out.println("Lupaan pystyä arvaamaan ajattelemasi luvun " + kysymyksiaKorkeintaan + " kysymyksellä."); System.out.println(""); System.out.println("Esitän sinulle seuraavaksi sarjan kysymyksiä. Vastaa niihin rehellisesti."); System.out.println(""); } // apumetodi public int kuinkaMontaKertaaVoiJakaaKahteen(int luku) { // luodaan kaksikantainen logaritmi annetusta luvusta, logaritmeista // löytyy lisää tietoa mm. osoitteessa // http://www02.oph.fi/etalukio/pitka_matematiikka/kurssi8/maa8_teoria7.html // Alla vaihdamme kantalukua alkuperäisestä kaksikantaisiin logaritmeihin! return (int) (Math.log(luku) / Math.log(2)) + 1; } }
Peli käynnistetään seuraavasti:
// luodaan peli-olio Arvauspeli peli = new Arvauspeli(); // pelataan pari kierrosta peli.pelaa(1,10); // arvattava luku nyt välillä 1-10 peli.pelaa(10,99); // arvattava luku nyt välillä 10-99
Toteutetaan tämä tehtävä askeleittain.
Toteuta luokalle Arvauspeli
metodi public boolean onkoSuurempiKuin(int luku)
, joka esittää käyttäjälle kysymyksen:
"Onko lukusi suurempi kuin annettu luku? (k/e)"
Metodi palauttaa arvon true
jos käyttäjä vastaa "k", muulloin false
.
Huom: metodin tulee lukea käyttäjän syöte luokan Arvauspeli oliomuuttujassa this.lukija
olevaa lukijaa hyväksikäyttäen. Metodissa ei saa luoda uutta lukijaa!
Testaa metodisi toimintaa
Arvauspeli peli = new Arvauspeli(); System.out.println(peli.onkoSuurempiKuin(32)); System.out.println(peli.onkoSuurempiKuin(99));
Onko lukusi suurempi kuin 32? (k/e) k true Onko lukusi suurempi kuin 99? (k/e) e false
Toteuta luokalle Arvauspeli
metodi public int keskiarvo(int ekaLuku, int tokaLuku)
, joka laskee annettujen lukujen keskiarvon. Huomaa että Java pyöristää liukuluvut luvut automaattisesti alaspäin, tämä on meidän tapauksessamme täysin toivottavaa.
Arvauspeli peli = new Arvauspeli(); System.out.println(peli.keskiarvo(3, 4)); System.out.println(peli.keskiarvo(6, 12));
3 9
Kirjoita varsinainen arvauslogiikka luokan Arvauspeli
metodin public void pelaa(int alaraja, int ylaraja)
runkoon. Tarvitset ainakin toistolauseen, sekä kyselyn jossa kysyt onko käyttäjän ajattelema luku suurempi kuin ala- ja ylärajan keskiarvo.
Käytä edellisten kohtien metodeja kyselyyn ja keskiarvon selvittämiseen.
Muokkaa toistolauseessa ala- tai ylärajaa käyttäjän vastauksesta riippuen.
Jatka toistolausekkeen toistoa kunnes alaraja ja yläraja ovat samat! Voit testata peliä myös pienemmillä ala- ja ylärajan arvoilla:
Ajattele jotain lukua väliltä 1...4. Lupaan pystyä arvaamaan ajattelemasi luvun 2 kysymyksellä. Esitän sinulle seuraavaksi sarjan kysymyksiä. Vastaa niihin rehellisesti. Onko lukusi suurempi kuin 2? (k/e) k Onko lukusi suurempi kuin 3? (k/e) k Ajattelemasi luku on 4.
Testiautomaatista tulevassa rungossa on pohja binäärihaun toteutukselle. Luokka BinaariHaku
sisältää metodin public static boolean hae(int[] taulukko, int etsittavaLuku)
, jonka tehtävänä on selvittää binäärihakua käyttäen, onko parametrina annettu luku parametrina annetussa järjestyksessä olevassa taulukossa.
Metodi hae
ei kuitenkaan toimi vielä. Viimeistele metodin toteutus oikeaksi binäärihauksi.
Ohjelman testausta varten on erillinen pääohjelma luokassa Main
, jonka runko on seuraava:
import java.util.Arrays; import java.util.Scanner; public class Main { public static void main(String[] args) { // Tässä voit testata binäärihakua int[] taulukko = { -3, 2, 3, 4, 7, 8, 12 }; Scanner lukija = new Scanner(System.in); System.out.print("Taulukon luvut: " + Arrays.toString(taulukko)); System.out.println(); System.out.print("Anna haettava luku: "); String etsittavaLuku = lukija.nextLine(); System.out.println(); boolean tulos = BinaariHaku.hae(taulukko, Integer.parseInt(etsittavaLuku)); // Tulosta tässä binäärihaun tulos } }
Ohjelman suoritus näyttää seuraavalta:
Taulukon luvut: [-3, 2, 3, 4, 7, 8, 12] Anna haettava luku: 8 Luku 8 on taulukossa
Taulukon luvut: [-3, 2, 3, 4, 7, 8, 12] Anna haettava luku: 99 Luku 99 ei ole taulukossa
Jos tarvetta on, taulukkoon voi luonnollisesti laittaa minkä tahansa tyyppisen olion. Seuraavassa esimerkki taulukosta johon talletetaan Henkilo-olioita:
public static void main(String[] args) { Henkilo[] henkilot = new Henkilo[3]; henkilot[0] = new Henkilo("Pekka"); henkilot[1] = new Henkilo("Antti"); henkilot[2] = new Henkilo("Juhana"); for ( int i=0; i < 30; i++ ) { henkilot[0].vanhene(); henkilot[1].vanhene(); henkilot[2].vanhene(); } for ( Henkilo henkilo : henkilot ) { ilmoitaTaysiIkaisyys(henkilo); } }
Alussa luodaan taulukko johon mahtuu 3 henkilöolioa. Laitetaan Pekka lokeroon 0, Antti lokeroon 1 ja Juhana lokeroon 2. Vanhennetaan kaikkia 30 vuotta ja tarkastetaan kaikkien täysi-ikäisyys edellisen luvun metodia hyödyntäen.
Sama esimerkki ArrayListien avulla:
public static void main(String[] args) { ArrayList<Henkilo> henkilot = new ArrayList<Henkilo>(); henkilot.add( new Henkilo("Pekka") ); henkilot.add( new Henkilo("Antti") ); henkilot.add( new Henkilo("Juhana") ); for ( int i=0; i < 30; i++ ) { for ( Henkilo henkilo : henkilot ) { henkilo.vanhene(); } // tai henkilot.get(0).vanhene(); // henkilot.get(1).vanhene(); // ... } for ( Henkilo henkilo : henkilot ) { ilmoitaTaysiIkaisyys(henkilo); } }
Useimmissa sovellustilanteissa taulukon sijaan kannattaa käyttää ArrayListiä. Voi kuitenkin olla joitain tilanteita joissa taulukko riittää ja on hieman yksinkertaisempi käyttää.
Viikko koostuu aina seitsemästä päivästä. Viikko-olio olisikin mielekästä koostaa tasan seitsemästä päiväoliosta. Koska päiva-olioita on aina täsmälleen 7, sopii taulukko tilanteeseen hyvin:
public class Paiva { private String nimi; // ... } public class Viikko { private Paiva[] paivat; public Viikko(){ paivat = new Paiva[7]; paivat[0] = new Paiva("Maanantai"); paivat[1] = new Paiva("Tiistai"); // ... } }
Olemme saaneet paljon arvokasta palautetta TMC:n kautta. Näin kurssin viimeisenä kysymyksenä haluaisimme koko kurssin sisältöä koskevan palautteen. Anna palaute täyttämällä täältä löytyvälomake. Palaute on anonyymi.
Jotta saat merkatuksi tämän tehtävän, aja tehtävän TMC-testit ja lähetä tehtävä palvelimelle.
Käydään vielä kurssin lopuksi läpi esimerkki jossa tehdään ohjelma seuraavaan tilanteeseen jossa käyttäjältä kysellään sanoja, kunnes käyttäjä antaa saman sanan uudestaan. Tehdään oletus, että käyttäjä antaa korkeintaan 1000 sanaa.
Anna sana: porkkana Anna sana: selleri Anna sana: nauris Anna sana: lanttu Anna sana: selleri Annoit saman sanan uudestaan!
Osin ongelmien syy saattoi olla, että oli vaikea päättää miten lähestyä tehtävää, eli miten jäsentää ongelmaa ja mistä aloittaa.
Ongelma koostuu oikeastaan kahdesta "aliongelmasta". Ensimmäinen on sanojen toistuva lukeminen käyttäjältä kunnes tietty ehto toteutuu. Tämä voitaisiin hahmotella seuraavaan tapaan ohjelmarungoksi:
public static void main(String[] args) { while ( true ) { String sana = lukija.nextLine(); if ( "pitää lopettaa" ) { break; } } System.out.println("Annoit saman sanan uudestaan"); }
Sanojen kysely pitää lopettaa siinä vaiheessa kun on syötetty jokin jo aiemmin syötetty sana. Päätetään tehdä metodi, joka huomaa että sana on jo syötetty. Vielä ei tiedetä miten metodi kannattaisi tehdä, joten tehdään siitä vasta runko:
public static void main(String[] args) { while ( true ) { String sana = lukija.nextLine(); if ( onJoSyotetty( sana ) ) { break; } } System.out.println("Annoit saman sanan uudestaan"); } public static boolean onJoSyotetty(String sana) { // tänne jotain }
Ohjelmaa on hyvä testata koko ajan, joten tehdään metodista kokeiluversio:
public static boolean onJoSyotetty(String sana) { if ( sana.equals("loppu") ) { return true; } return false; }
Eli toisto jatkuu nyt niin kauan kunnes syötteenä on sana loppu:
Anna sana: porkkana Anna sana: selleri Anna sana: nauris Anna sana: lanttu Anna sana: loppu Annoit saman sanan uudestaan!
Ohjelma ei siis toimi vielä kokonaisuudessaan, mutta ensimmäinen osaongelma eli ohjelman pysäyttäminen kunnes tietty ehto toteutuu on saatu toimimaan.
Toinen osaongelma on aiemmin syötettyjen sanojen muistaminen. Taulukko sopii tietysti mainiosti tähän tarkoitukseen. Tarvitaan myös muuttuja joka muistaa kuinka monta sanaa on syötetty:
public static void main(String[] args) { String[] aiemmatSanat = new String[1000]; int sanojaSyotetty = 0; // ... }
Kun uusi sana syötetään, on se lisättävä syötettyjen sanojen joukkoon. Tämä tapahtuu lisäämällä main:in while-silmukkaan taulukkoa ja sanojen laskuria päivittävät rivit:
while ( true ) { String sana = lukija.nextLine(); if ( onJoSyotetty( sana, aiemmatSanat, sanojaSyotetty ) ) { break; } // lisätään uusi sana aiempien sanojen taulukkoon aiemmatSanat[sanojaSyotetty] = sana; sanojaSyotetty++; }
Jälleen kannattaa testata, että ohjelma toimii edelleen. Voi olla hyödyksi esim. lisätä ohjelman loppuun testitulostus, joka varmistaa että syötetyt sanat todella menivät taulukkoon:
while ( true ) { // ... // lisätään uusi sana aiempien sanojen taulukkoon aiemmatSanat[sanojaSyotetty] = sana; sanojaSyotetty++; } // testitulostus joka varmistaa että kaikki toimii edelleen for ( int i=0; i < sanojaSyotetty; i++ ) { System.out.println( aiemmatSanat[sanojaSyotetty] ); }
Muokataan vielä äsken tekemämme metodi onJoSyotetty
tutkimaan onko kysytty sana jo syötettyjen joukossa, eli taulukon käytössä olevassa alkuosassa:
public static boolean onJoSyotetty(String sana, String[] aiemmatSanat, int sanojaSyotetty) { for ( int i=0; i < sanojaSyotetty; i++ ) { if ( sana.equals( aiemmatSanat[i] ) ) return true; } return false; } public static void main(String[] args) { String[] aiemmatSanat = new String[1000]; int sanojaSyotetty = 0; while ( true ) { String sana = lukija.nextLine(); if ( onJoSyotetty( sana, aiemmatSanat, sanojaSyotetty ) ) break; aiemmatSanat[sanojaSyotetty] = sana; sanojaSyotetty++; } System.out.println("Annoit saman sanan uudestaan"); }
Nyt koodi on valmis ja alkaa olla kohtuullisen luettava. Emme kuitenkaan ole koodiin täysin tyytyväisiä. Valitettavasti emme vielä osaa tarpeeksi Javaa tehdäksemme "lopullista" siistiä versiota. Ensi viikolla kuitenkin otamme ratkaisevan askeleen jolla saamme koodin siistiksi.
Eli ohjelmoidessasi, seuraa aina näitä neuvoja:Hyvät ja kokeneet ohjelmoijat noudattavat näitä käytänteitä sen takia että ohjelmointi olisi helpompaa, ja että ohjelmien lukeminen, ylläpitäminen ja muokkaaminen olisi helpompaa.
Rakensimme äsken askel askeleelta ratkaisun seuraavaan tehtävään:
Tee ohjelma, joka kysyy käyttäjältä sanoja, kunnes käyttäjä antaa saman sanan uudestaan. Voit olettaa, että käyttäjä antaa korkeintaan 1000 sanaa.
Anna sana: porkkana Anna sana: selleri Anna sana: nauris Anna sana: lanttu Anna sana: selleri Annoit saman sanan uudestaan!
Päädyimme ratkaisuun
public static boolean onJoSyotetty(String sana, String[] aiemmatSanat, int sanojaSyotetty) { for ( int i=0; i < sanojaSyotetty; i++ ) { if ( sana.equals( aiemmatSanat[i] ) ) return true; } return false; } public static void main(String[] args) { String[] aiemmatSanat = new String[1000]; int sanojaSyotetty = 0; while ( true ) { String sana = lukija.nextLine(); if ( onJoSyotetty( sana, aiemmatSanat, sanojaSyotetty ) ) break; aiemmatSanat[sanojaSyotetty] = sana; sanojaSyotetty++; } System.out.println("Annoit saman sanan uudestaan"); }
Totesimme, että vaikka ratkaisu alkaa olla ymmärrettävyydeltään ihan kohtuullinen, jokin siinä edelleen jäi vaivaamaan.
Pääohjelman käyttämät apumuuttujat, eli taulukko aiemmatSanat
ja kokonaisluku sanojaSyotetty
ovat todella ikäviä "matalan tason" detaljeja pääohjelman kannalta. Pääohjelman kannaltahan on oleellista, että muistetaan niiden sanojen joukko jotka on nähty jo aiemmin. Sanojen joukko, tämähän on selkeä erillinen "käsite", tai abstraktio. Tälläiset selkeät käsitteet ovat potentiaalisia olioita ja huomattuaan koodissaan "käsitteen" viisas ohjelmoija eristää sen luokaksi.
Eli päätämme tehdä luokan Sanajoukko
. Haluaisimme, että pääohjelma näyttää seuraavalta:
public static void main(String[] args) { Sanajoukko aiemmatSanat = new Sanajoukko(); while (true) { String sana = lukija.nextLine(); if ( aiemmatSanat.sisaltaa(sana) ) { break; } aiemmatSanat.lisaa(sana); } System.out.println("Annoit saman sanan uudestaan"); }
Pääohjelman, eli sanajoukon käyttäjän kannalta Sanajoukko olisi hyvä jos sillä olisi metodit boolean sisaltaa(String sana)
jolla tarkastetaan sisältyykö annettu sana jo sanajoukkoon ja void lisaa(String sana)
jolla annettu sana lisätään joukkoon.
Huomaamme, että näin kirjoitettuna pääohjelman luettavuus on huomattavasti parempi kuin taulukkoa käyttäen. Pääohjelma on lähes suomen kieltä!
Luokan Sanajoukko
runko näyttää seuraavanlaiselta:
public class Sanajoukko { // sopivia oliomuuttujia public Sanajoukko() { // ... } public boolean sisaltaa(String sana) { // ... } public void lisaa(String sana) { // ... } }
Voimme toteuttaa sanajoukon siirtämällä aiemman ratkaisumme taulukon sanajoukon oliomuuttujaksi:
public class Sanajoukko { private String[] joukonSanat; private int sanojaSyotetty; public Sanajoukko() { joukonSanat = new String[1000]; sanojaSyotetty = 0; } // ... }
Oliomuuttujien alustus tapahtuu tuttuun tapaan konstruktorissa.
Uuden sanan lisääminen on helppoa. Koska sanojaSyotetty
muistaa monta sanaa taulukossa jo on, ja taulukon indeksit alkavat nollasta, tulee uusi sana juuri tähän paikkaan. Muuttujan arvo pitää muistaa vielä kasvattaa:
public class Sanajoukko { private String[] joukonSanat; private int sanojaSyotetty; public Sanajoukko() { joukonSanat = new String[1000]; sanojaSyotetty = 0; } public void lisaa(String sana) { joukonSanat[ sanojaSyotetty ] = sana; sanojaSyotetty++; } // ... }
Ja vielä metodi, jolla tarkistetaan onko sana jo joukossa. Eli käydään sanat muistavan taulukon alkuosaa läpi syötettyjen sanojen verran ja palautetaan true jos etsitty sana löytyy:
public class Sanajoukko { private String[] joukonSanat; private int sanojaSyotetty; public Sanajoukko() { joukonSanat = new String[1000]; sanojaSyotetty = 0; } public boolean sisaltaa(String sana) { for ( int i=0; i < sanojaSyotetty; i++ ) { if ( joukonSanat[i].equals(sana) ) return true; } return true; } public void lisaa(String sana) { joukonSanat[ sanojaSyotetty ] = sana; sanojaSyotetty++; } }
Ratkaisu on nyt varsin elegantti. Erillinen käsite on saatu erotettua ja pääohjelma näyttää siistiltä. Kaikki "likaiset yksityiskohdat" (taulukko ja lukumäärä sanoista) on saatu siivottua eli kapseloitua olion sisälle.
Yhtäkkiä mieleemme palaa toissa viikolla esitelty ArrayList. Sanojen muistaminenhan voidaan toteuttaa helposti ArrayList:illa! Päätämme korvata luokan Sanajoukko
sisällä olevan taulukon listalla:
import java.util.ArrayList; public class Sanajoukko { private ArrayList<String> joukonSanat; public Sanajoukko() { joukonSanat = new ArrayList<String> (); } public boolean sisaltaa(String sana) { return joukonSanat.contains(sana); } public void lisaa(String sana) { joukonSanat.add(sana); }
Näin päädytään ratkaisuun jossa Sanajoukko
on ainoastaan ArrayList:in "wräpperi". Onko tässä järkeä? Kenties. Voimme nimittäin halutessamme tehdä Sanajoukolle muitakin muutoksia. Ennen pitkään saatamme esim. huomata, että sanajoukko pitää tallettaa tiedostoon. Jos tekisimme nämä muutokset Sanajoukon koodiin, ei pääohjelmaa tarvitsisi muuttaa mitenkään.
Voi olla, että jatkossa ohjelmaa halutaan laajentaa siten, että Sanajoukko
-luokan olisi osattava uusia asiota. Jos esim. pääohjelmassa haluttaisiin tietää kuinka moni syötetyistä sanoista oli palindromi, voidaan sanajoukkoa laajentaa metodilla palindromeja
:
public static void main(String[] args) { Sanajoukko aiemmatSanat = new Sanajoukko(); while (true) { String sana = lukija.nextLine(); if ( aiemmatSanat.sisaltaa(sana) ) { break; } aiemmatSanat.lisaa(sana); } System.out.println("Annoit saman sanan uudestaan"); System.out.println("Sanoistasi " + aiemmatSanat.palindromeja() + "oli palindromeja"); }
Pääohjelma säilyy siistinä, viimeisellä rivillä kutsutaan uutta hyvin nimettyä metodia, palindromien laskeminen jää Sanajoukko
-olion huoleksi. Metodin toteutus voisi olla seuraavanlainen:
public class Sanajoukko { private ArrayList<String> joukonSanat; // ... public int palindromeja() { int palindromit = 0; for ( String sana : joukonSanat ) { if ( onPalindromi(sana) ) palindromit++; } return palindromit; } private boolean onPalindromi(String sana){ int loppu = sana.length()-1; for ( int i=0; i %lt; sana.length()/2; i++ ){ if ( sana.charAt(i) != sana.charAt(loppu-i) ) return false; } return true; }
Eli metodi palindromeja
käy läpi kaikki joukon sanat ja apumetodin onPalindromi
avulla laskee paindromien määrän.
Koska apumetodi onPalindromi
on tarkoitettu ainoastaan olion sisäiseen käyttöön, on sen näkyvyysmääreeksi määritelty private
. Olion käyttäjän siis ei ole mahdollista kutsua metodia, metodia voi kutsua ainoastaan olion sisältä.
Jos sanojen talletukseen olisi käytetty suoraan ArrayList:iä, olisi palindromien laskeminen vaatinut pääohjelman koodiin suurempia muutoksia kuin Sanajoukko-luokan käyttö. Joskus on toki tilanteita, joissa kannattaa käyttää suoraan valmista kalustoa eikä oman luokan määrittelylle ole tarvetta. Kuitenkin jos koodin luettavuus alkaa kärsimään, on selkeistä käsitteistä syytä tehdä omat luokkansa. Kuten näimme tämä parhaassa tapauksessa mahdollistaa ohjelman laajennettavuutta ja muokattavuutta.
Loppuivatko tehtävät kesken? Ei hätää: saatavilla on lisätehtäväsarja Ohpesokkelo. Siinä tehdään graafinen labyrinttipeli!
Tee yksinkertainen arvauspeli. Pelin toiminta alkaa sillä, että käyttäjältä kysytään arvattavat luvut:
Kuinka monta lukua? 3 Anna luvut: 5 2 11 Kiitos. Siirrytään arvausvaiheeseen.
Kun luvut on syötetty, tulostetaan 40 rivinvaihtoa kutsumalla komentoa System.out.println("") 40 kertaa.
Tämän jälkeen käyttäjälle (joka on kenties eri ihminen kun lukujen syöttäjä) annetaan 3 arvausta. Arvausten jälkeen ilmoitetaan, kuinka monta arvauksista oli oikein.
Arvaa mitä lukuja taulukossa on. Saat kolme arvausta. 4 99 2 Kiitos arvauksista. 1 oikein.
Tee peli käyttäen kahden edellisen tehtävän metodeja. Pääohjelma muotoutuu seuraavaan tapaan:
public static void main(String[] args) { int [] luvut = kysyLuvut(); // tulosta tyhjää int oikeatArvaukset = 0; // kolme arvausta for ( int i=0; i<3; i++ ) { // lue arvaus // tarkasta metodin onkoLukuTaulukossa avulla menikö arvaus oikein ja päivitä statistiikka } // tulosta oikeiden vastausten määrä }
Laajennetaan edellistä tehtävää niin, että pelaajalla on rajaton määrä arvauksia. Kun käyttäjä antaa syötteenä -99, loppuu peli ja käyttäjälle ilmoitetaan arvausten lukumäärä ja oikeiden arvausten lukumäärä. Peli etenee seuraavasti:
Arvaa mitä lukuja taulukossa on. Kun kyllästyt, syötä -99. 4 7 2 100 11 -99 Arvauksia oli 5, joista oikein 2.
Laajennetaan edellistä tehtävää siten, että ohjelma kysyy pelin jälkeen, haluaako käyttäjä pelata uudestaan. Lopuksi ohjelma ilmoittaa, kuinka monta peliä pelattiin yhteensä.
Arvaa mitä lukuja taulukossa on. Kun kyllästyt, syötä -99. 4 7 2 100 11 -99 Arvauksia oli 5, joista oikein 2. Uusi peli (k/e)? k Arvaa mitä lukuja taulukossa on. Kun kyllästyt, syötä -99. -1 5 2 -99 Arvauksia oli 3, joista oikein 0. Uusi peli (k/e)? e Pelaajia tänään 2.
Tässä tehtäväsarjassa tehdään tekoäly kivi, paperi, sakset -peliin.
Tee luokka KPSTekoaly
,
josta tulee tekoäly kivi, paperi, sakset -peliin.
Luokkaan tulee metodi teeSiirto
,
joka palauttaa tekoälyn tekemän siirron.
Merkkijonot "k"
, "p"
ja "s"
vastaavat siirtoja kivi, paperi ja sakset.
Tee tekoälystä ensin sellainen, että sen siirto on aina kivi.
Luokan runko on seuraava:
import java.util.*; public class KPSTekoaly { public String teeSiirto() { // kirjoita koodia tähän } }
Pääluokka näyttää seuraavalta:
import java.util.Scanner; public class Main { public static void main(String[] args) { static Scanner lukija = new Scanner(System.in); KPSTekoaly tekoaly = new KPSTekoaly(); System.out.print("Kuinka monta kierrosta? "); int kierrokset = Integer.parseInt(lukija.nextLine()); for (int i = 0; i < kierrokset; i++) { System.out.print("Anna siirto (k, p tai s): "); String pelaajanSiirto = lukija.nextLine(); String tekoalynSiirto = tekoaly.teeSiirto(); System.out.println("Pelaajan siirto: " + pelaajanSiirto); System.out.println("Tekoälyn siirto: " + tekoalynSiirto); } } }
Peli voi edetä esimerkiksi seuraavasti:
Kuinka monta kierrosta? 3 Anna siirto (k, p tai s): s Pelaajan siirto: s Tekoälyn siirto: k Anna siirto (k, p tai s): p Pelaajan siirto: p Tekoälyn siirto: k Anna siirto (k, p tai s): k Pelaajan siirto: k Tekoälyn siirto: k
Lisää projektiin vielä yksi luokka KPSTuomari
,
jonka runko on seuraava:
public class KPSTuomari { public void maaritaVoittaja(String pelaajanSiirto, String tekoalynSiirto) { // kirjoita koodia tähän } }
Metodin maaritaVoittaja
tarkoituksena
on selvittää pelaajan ja tekoälyn siirtojen perusteella,
kumpi on voittaja vai tuliko tasapeli.
Toteuta metodi niin, että se tulostaa tiedon voittajasta.
Kivi, paperi, sakset -pelissä kivi voittaa sakset, sakset voittavat paperin ja paperi voittaa kiven.
Pääluokka on nyt seuraava:
import java.util.Scanner; public class Main { private static Scanner lukija = new Scanner(System.in); public static void main(String[] args) { KPSTekoaly tekoaly = new KPSTekoaly(); KPSTuomari tuomari = new KPSTuomari(); System.out.print("Kuinka monta kierrosta? "); int kierrokset = Integer.parseInt(lukija.nextLine()); for (int i = 0; i < kierrokset; i++) { System.out.print("Anna siirto (k, p tai s): "); String pelaajanSiirto = lukija.nextLine(); String tekoalynSiirto = tekoaly.teeSiirto(); System.out.println("Tekoälyn siirto: " + tekoalynSiirto); tuomari.maaritaVoittaja(pelaajanSiirto, tekoalynSiirto); } } }
Peli voi edetä esimerkiksi seuraavasti:
Kuinka monta kierrosta? 3 Anna siirto (k, p tai s): s Tekoälyn siirto: k Tekoäly voitti! Anna siirto (k, p tai s): p Tekoälyn siirto: k Pelaaja voitti! Anna siirto (k, p tai s): k Tekoälyn siirto: k Tuli tasapeli!
Nyt on aika tehdä tekoälystä parempi, koska ei ole järkevää, että tekoäly valitsee aina kiven.
Tee tekoälystä satunnainen: se valitsee yhtä todennäköisesti kiven, saksen ja paperin.
Laajenna tuomariluokkaa niin,
että se pitää kirjaa pelaajan ja tekoälyn voitoista.
Lisää luokkaan myös metodi tulostaTilasto
,
joka tulostaa tilaston voitoista.
Lisää tämän metodin kutsu pääohjelman loppuun.
Kuinka monta kierrosta? 5 Anna siirto (k, p tai s): s Tekoälyn siirto: s Tuli tasapeli! Anna siirto (k, p tai s): p Tekoälyn siirto: s Tekoäly voitti! Anna siirto (k, p tai s): k Tekoälyn siirto: k Tuli tasapeli! Anna siirto (k, p tai s): s Tekoälyn siirto: p Pelaaja voitti! Anna siirto (k, p tai s): s Tekoälyn siirto: k Tekoäly voitti! Tilasto: Pelaaja: 1 voittoa Tekoäly: 2 voittoa
Lisää tekoälyyn muisti: tekoäly ottaa siirtonsa valinnassa huomioon, mitä käyttäjä on valinnut aiemmin eri tilanteissa. Päätä itse, millä tavoin tarkalleen tekoäly hyödyntää muistia.
Lisää tekoälyyn metodi kirjaaSiirto
,
jota kutsutaan käyttäjän siirron jälkeen.
Sen avulla tekoäly saa tietoonsa,
mitä siirtoja käyttäjä on tehnyt.
Pääluokkaan tehdään seuraava muutos:
... String pelaajanSiirto = lukija.nextLine(); String tekoalynSiirto = tekoaly.teeSiirto(); tekoaly.kirjaaSiirto(pelaajanSiirto); System.out.println("Tekoälyn siirto: " + tekoalynSiirto); ...
Jos tekoäly käyttää muistiaan hyvin, se voi sopeutua esimerkiksi tilanteeseen, jossa käyttäjä valitsee aina saman siirron:
Kuinka monta kierrosta? 5 Anna siirto (k, p tai s): s Tekoälyn siirto: p Pelaaja voitti! Anna siirto (k, p tai s): s Tekoälyn siirto: k Tekoäly voitti! Anna siirto (k, p tai s): s Tekoälyn siirto: k Tekoäly voitti! Anna siirto (k, p tai s): s Tekoälyn siirto: k Tekoäly voitti! Anna siirto (k, p tai s): s Tekoälyn siirto: k Tekoäly voitti! Tilasto Pelaaja: 1 voittoa Tekoäly: 4 voittoa
Tutkitaan vielä olioiden lisäämistä taulukkoon:
public static void main(String[] args) { Henkilo[] henkilot = new Henkilo[3]; henkilot[0] = new Henkilo("Pekka"); henkilot[1] = new Henkilo("Antti"); henkilot[2] = new Henkilo("Juhana"); for ( int i=0; i < 30; i++ ) { henkilot[0].vanhene(); henkilot[1].vanhene(); henkilot[2].vanhene(); } for ( Henkilo henkilo : henkilot ) ilmoitaTaysiIkaisyys(henkilo); }
Alussa luodaan taulukko johon mahtuu 3 henkilöolioa. Laitetaan Pekka lokeroon 0, Antti lokeroon 1 ja Juhana lokeroon 2. Vanhennetaan kaikkia 30 vuotta ja tarkastetaan kaikkien täysi-ikäisyys edellisen luvun metodia hyödyntäen.
Sama esimerkki ArrayListien avulla:
public static void main(String[] args) { ArrayList<Henkilo> henkilot = new ArrayList<Henkilo>(); henkilot.add( new Henkilo("Pekka") ); henkilot.add( new Henkilo("Antti") ); henkilot.add( new Henkilo("Juhana") ); for ( int i=0; i < 30; i++ ) { for ( Henkilo henkilo : henkilot ) { henkilo.vanhene(); } // tai henkilot.get(0).vanhene(); // henkilot.get(1).vanhene(); // ... } for ( Henkilo henkilo : henkilot ) { ilmoitaTaysiIkaisyys(henkilo); } }
Usein tahdotaan myös tehdä olioista loogisesti riippumattomista apuri-metodeista staattisia. Tästä sopiva esimerkki olisi luvun 24 Sanajoukko
-esimerkissä esiintynyt onPalindromi
-metodi. Kuten koodista nähdään, ei metodissa viitata lainkaan oliomuuttujiin.
public class Sanajoukko { /* Muu luokan määrittely */ private static boolean onPalindromi(String sana){ int loppu = sana.length()-1; for ( int i=0; i < sana.length()/2; i++ ){ if ( sana.charAt(i) != sana.charAt(loppu-i) ) return false; } return true; } }
HUOM: tehtävä on osoittautunut melko haastavaksi ollakseen viikon alkupään tehtävä. Jos et heti ymmärrä mitä tehtävässä ajetaan takaa, hyppää tehtävän yli ja palaa tähän siinä vaiheessa kun olet tehnyt viikon muut tehtävät.
Tehtäväpohja sisältää Kurt Koodarin tekemän tekstinkäsittelyohjelman. Ohjelma on kuitenkin koodattu yhteen metodiin! Tehtävänäsi on refaktoroida eli siistiä Kurtin ohjelma edellisen tehtävän mallin mukaan.
Luo jokaiselle komennolle (paitsi lopeta) oma metodeihinsa. Komentoa vastaava metodi saa parametrinaan lukijan (jotta se voi kysyä käyttäjältä asioita) ja listan rivejä (jotta se voi muokata niitä). Toteuta siis metodit:
public static void tulosta(Scanner lukija, ArrayList<String> rivit)
public static void reset(Scanner lukija, ArrayList<String> rivit)
public static void poista(Scanner lukija, ArrayList<String> rivit)
public static void lisaa(Scanner lukija, ArrayList<String> rivit)
public static void laske(Scanner lukija, ArrayList<String> rivit)
Tämän jälkeen muuta main-metodi käyttämään lisäämiäsi metodeja komentojen toteuttamiseen. Uusi main-metodi siis vain kysyy komennon nimen ja sen jälkeen kutsuu oikeaa metodia ylläolevalta listalta. Ohjelman toiminnan pitää pysyä samanlaisena siistimisestä huolimatta!
Edellisen askeleen jälkeen koodissa on kuitenkin vielä toistoa
jäljellä: rivinumeroitten lukeminen. Lisää siis ohjelmaan
metodi public static int kysyRivinumero(String kysymys,
Scanner lukija)
. Tämä metodi tulostaa kysymyksen ja lukee
rivinumeron. Palautusarvo on -1 jos käyttäjä syötti tyhjän
rivin.
vihje: voit ottaa toteutuksesi pohjaksi seuraavan metodirungon:
public static int kysyRivinumero(String kysymys, Scanner lukija) { System.out.println(kysymys); String rivi = // lue käytäjältä merkkijono // jos merkkijono oli tyhjä, palautetaan -1; if (rivi.isEmpty()) { return -1; } // muuten muuta rivi kokonaisluvuksi Integer.parseInt:illä ja palauta se return ... }
kysyRivinumero
-metodia metodista poista
Metodi poista
lukee käyttäjältä rivinumeron. Muuta
siis poista
-metodi käyttämään edellisessä askeleessa
toteuttamaasi kysyRivinumero
-metodia
kysyRivinumero
-metodia metodista lisaa
Myös metodi lisaa
lukee käyttäjältä rivinumeron.
Käytä rivinumeron lukemiseen kysyRivinumero
-metodia
Tässä tehtävässä lasketaan kahden luvun pienin yhteinen jaettava näiden lukujen suurimman yhteisen tekijän avulla.
Pienin yhteinen jaettava on pienin kokonaisluku, joka on tasan jaollinen kyseessä olevilla luvuilla. Se voidaan laskea helposti, jos tiedetään kyseessä olevien lukujen suurin yhteinen tekijä. Suurin yhteinen tekijä taas tarkoittaa suurinta sellaista lukua, joka jakaa kyseessä olevat luvut niin, että lopputulos on kokonaisluku (eli lukujen jakojäännös on nolla).
Esimerkiksi lukujen 32 ja 6 suurin yhteinen tekijä on 2, koska 32 / 2 = 16 ja 6 / 2 = 3, eikä löydy suurempaa lukua kuin 2, jolla molemmat luvut, 32 ja 6, voitaisiin jakaa siten, että tuloksena olisi kokonaisluku.
Lukujen 32 ja 6 pienin yhteinen jaettava on 96, koska 96 / 32 = 3 ja 96 / 6 = 16, eikä löydy pienempää lukua kuin 96, joka voitaisiin jakaa molemilla luvuilla, 32 ja 6, siten, että tuloksena olisi kokonaisluku.
Luo luokkaan PieninYhteinenJaettava
metodi public static int suurinYhteinenTekija(int luku1, int luku2)
, joka laskee parametreina annettujen lukujen suurimman yhteisen tekijän ja palauttaa vastaukseksi saadun luvun.
Eräs tapa laskea suurin yhteinen tekijä on Eukleideen algoritmi, joka on yksinkertainen toteuttaa ohjelmakoodissa.
Eukleideen algoritmi toimii seuraavasti, kun lukuina ovat a
ja b
(muuttujaa c
käytetään vain apuna):
b
on 0
, niin algoritmi päättyy ja a
on suurin yhteinen tekijä.c = a
a = b
b = c % b
(%
-operaatio tarkoittaa jakojäännöstä eli moduloa)Esimerkiksi lukujen 32 ja 20 suurin yhteinen tekijä löytyy seuraavasti:
a = 32, b = 20
b != 0
eli algoritmi jatkuu.c = 32
a = 20
b = 12
b != 0
eli algoritmi jatkuu.c = 20
a = 12
b = 8
b != 0
eli algoritmi jatkuu.c = 12
a = 8
b = 4
b != 0
eli algoritmi jatkuu.c = 8
a = 4
b = 0
b = 0
eli suurin yhteinen tekijä on 4
.Vinkki: Tarvitset siis silmukkaa algoritmin toteuttamisessa.
Luo luokkaan PieninYhteinenJaettava
metodi public static int pieninYhteinenJaettava(int luku1, int luku2)
, joka laskee parametreina annettujen lukujen pienimmän yhteisen tekijän käyttämällä apuna lukujen suurinta yhteistä tekijää.
Pienin yhteinen jaettava voidaan laskea helposti seuraavalla kaavalla luvuille a
ja b
, kun syt
laskee lukujen suurimman yhteisen tekijän:
(a * b) / syt(a, b)
Esimerkiksi:
a = 32 b = 6 syt(32, 6) = 2 (32 * 6) / syt(32, 6) = 192 / 2 = 96
Huom: Metodin pieninYhteinenJaettava
on siis kutsuttava edellisessä kohdassa tehtyä metodia suurinYhteinenTekija
.
Tehtäväpohjan mukana tulee kaksi luokkaa, Pankki
ja Tili
, sekä pääohjelmaluokka Ohjelma
. Luokalla Tili
on:
public Tili(String tilinNimi)
luo annetun nimisen tilin.public double nosta(double paljonko)
yrittää nostaa tililtä annetun summan. Palauttaa nostetun rahamäärän, joka voi olla annettua pienempi kuin pyydetty jos tilillä olevat rahat eivät riitä.public void pane(double paljonko)
lisää tilille annetun summan.public String tilinNimi()
palauttaa tilin nimen.public double tilinSaldo()
palauttaa tilin saldon.Luokalla pankki on:
Pankki(String pankinNimi)
luo annetun nimisen pankin.void lisaaTili(Tili tili)
lisää pankkiin annetun tilin. On virhe yrittää lisätä saman niminen tili kaksi kertaa.Tili haeTili(String nimi)
hakee annetun nimisen tilin pankista. On virhe yrittää hakea tiliä jota ei ole ensin lisätty pankkiin.void tilisiirto(String mista, String minne, double paljonko)
suorittaa siirron pankin sisällä annetun nimisten
tilien välillä.double pankinSaldo()
palauttaa pankissa olevien tilien saldojen summan.Tehtäväsi on luoda pääohjelmaan nimeltä Ohjelma
ohjelma joka
Pankki megaPankki = new Pankki("MegaPankki")
MegaPankki: 45.0 HassuPankki: 70.0
Huom! suorita ohjelmassasi vain kuvaillut toimenpiteet.
ArrayList voi toimia myös metodin paluuarvona. Seuraavassa esimerkkinä metodi joka luo listan, lisää sille parametrinaan saamansa kolme kokonaislukua ja palauttaa lopuksi listan. Huomaa miten pääohjelma tallettaa metodin palauttaman listan muuttujaan jonka tyyppi on sama kuin metodin palauttamaan listan tyyppi:
public class Main { public static ArrayList<Integer> laitaLuvutListalle(int luku1, int luku2, int luku3){ ArrayList<Integer> lista = new ArrayList<Integer>(); lista.add(luku1); lista.add(luku2); lista.add(luku3); return lista; } public static void main(String[] args) { ArrayList<Integer> luvut = laitaLuvutListalle(3, 5, 2); for (int luku : luvut) { System.out.println( luku ); } } }
Tee metodi pituudet
,
joka palauttaa ArrayList
issä saamiensa
merkkijonojen pituudet, ja palauttaa
ne ArrayList
inä samassa järjestyksessä.
public static ArrayList<Integer> pituudet(ArrayList<String> lista) { // kirjoita koodia tähän } public static void main(String[] args) { ArrayList<String> lista = new ArrayList<String>(); lista.add("Hei"); lista.add("Moi"); lista.add("Benvenuto!"); lista.add("badger badger badger badger"); ArrayList<Integer> pituudet = pituudet(lista); System.out.println("Pituudet: " + pituudet); }
Ohjelman tulostus:
Pituudet: [3, 3, 10, 27]
Tehtäväpohjan mukana tulee luokka Wikipedia
, josta luotu olio yhdistää internetin yli (englanninkieliseen) wikipediaan ja kyselee sieltä tietoja. Luokalla on metodi ArrayList<String> getLinks(String title)
joka hakee kaikkien niiden sivujen nimet, joille annetulta sivulta on linkki. Esimerkiksi seuraava ohjelmanpätkä:
Wikipedia wiki = new Wikipedia(); ArrayList<String> linkit = wiki.getLinks("Helsinki"); Collections.sort(linkit); Collections.reverse(linkit); System.out.println("1: " + linkit.get(0)); System.out.println("2: " + linkit.get(1)); System.out.println("3: " + linkit.get(2));
Tulostaa (tällä hetkellä, wikipedia muuttuu jatkuvasti):
1: Pasila 2: Parliament of Finland 3: Parliament House, Helsinki
Toteuta ohjelma, joka ensin kysyy käyttäjältä wikipediasivun nimen ja tämän jälkeen hakee kyseisen sivun linkit ja tulostaa niiden määrän. Tämän jälkeen käyttäjä voi suorittaa kyselyitä linkkilistaan. Ohjelma päättyy kun käyttäjä syöttää tyhjän rivin. Esimerkki:
Anna wikipediasivun nimi: Helsinki Sivulta Helsinki on 130 linkkiä. Linkkikysely: Pasila Sivulta Helsinki on linkki sivulle Pasila Linkkikysely: Barack Obama Sivulta Helsinki ei ole linkkiä sivulle Barack Obama Linkkikysely:
Toteuta neljä tulostaLaatikko
-metodia:
public static void tulostaLaatikko(String teksti, char merkki)
tulostaa annetun tekstin ympäröitynä annetulla merkillä. Esimerkiksi tulostaLaatikko("festivål",'%')
tulostaa:
%%%%%%%%%%%% % festivål % %%%%%%%%%%%%
public static void tulostaLaatikko(String teksti)
tulostaa annetun tekstin ympäröitynä merkillä #
. Siis tulostaLaatikko("uliuliuliuli")
tulostaa:
################ # uliuliuliuli # ################
public static void tulostaLaatikko(char merkki)
tulostaa tekstin Hello world!
ympäröitynä annetulla merkillä. Siis tulostaLaatikko('K')
tulostaa:
KKKKKKKKKKKKKKKK K Hello world! K KKKKKKKKKKKKKKKK
public static void tulostaLaatikko()
tulostaa tekstin Hello world!
ympäröitynä merkillä #
.HUOM: Toteuta kolme viimeistä metodia niin, että ne vain kutsuvat metodia tulostaLaatikko(String teksti, char merkki)
oikeilla arvoilla.
Muistutus! Javassa yksittäistä merkkiä merkitään näin: 'K'
. Huomaa ero yhden pituiseen merkkijonoon "K"
!
Muistutus! Tässä tehtävässä tehdään jälleen static
-metodeja, kuten esim. viikolla 3. Sanan static
tarkka merkitys kerrotaan ensi viikolla, lyhyesti sanottuna kyse on metodeista, jotka eivät liity mihinkään olioon vaan saavat kaiken käsittelemänsä tiedon parametrina.
Tee metodi public static boolean onkoSanaTaulukossa(String[] taulukko)
, joka palauttaa totuusarvon sen mukaan onko käyttäjän antama sana taulukossa. Kirjoita lisäksi main
-metodiin ohjelma, joka testaa onkoSanaTaulukossa
-metodia allaolevan esimerkkitulostuksen mukaisesti kertoen onko sana taulukossa vai ei.
Ohjelman runko on seuraava:
import java.util.Scanner; public class Main { public static void main(String[] args) { // Tässä voit testata metodia String[] taulukko = {"apina", "banaani", "cembalo"}; Scanner lukija = new Scanner(System.in); // Kirjoita tähän koodi, joka kysyy sanan ja kertoo oliko sana taulukossa vai ei // Muista käyttää metodia onkoSanaTaulukossa()! } public static boolean onkoSanaTaulukossa(String[] taulukko, String sana) { // Tarkista täällä onko sana taulukossa ja palauta tulos return false; } }
Esimerkkitulostuksia:
Anna sana: apina Sana on taulukossa.
Anna sana: siili Sanaa ei ole taulukossa.
Muista, mitä erityistä merkkijonojen vertailussa on!
Tee metodi public static int etsiTaulukonPieninLuku(int[] taulukko)
, joka palauttaa taulukon pienimmän luvun.
Ohjelman runko on seuraava:
public class Main { public static void main(String[] args) { // Tässä voit testata metodia int[] taulukko = {5, 1, 3, 4, 2}; System.out.println(etsiTaulukonPieninLuku(taulukko)); } public static int etsiTaulukonPieninLuku(int[] taulukko) { // Kirjoita pienimmän luvun etsivä koodi tänne return 0; } }
Ohjelman tulostus on seuraava:
1
Tee ohjelma, joka kysyy käyttäjältä lukujen määrän ja joukon lukuja. Tämän jälkeen ohjelma tulostaa luvut järjestyksessä. Ohjelman tulee toimia seuraavasti:
Kuinka monta lukua? 4 Anna luvut: 5 2 8 1 Luvut järjestyksessä: 1 2 5 8
Tässä tehtävässä luodaan korttipakka, jonka kehitystä jatketaan seuraavilla viikoilla. Tehtävässä luodaan luokka Kortti
, josta luodaan jokaista eri pelikorttia varten luodaan oma ilmentymä. Tehtävässä luodaan myös luokat Korttipakka
ja Kasi
jotka sisältävät Kortti
-olioita. Korttipakasta taas voidaan nostaa kortteja joko yksitellen tai kädellinen.
Tehtävässä toteutetaan ohjelma kolmessa askeleessa. Luokat ja toteutettavien metodien rungot ovat mukana tehtäväpohjassa.
Kortti-olio tallentaa arvonsa nollasta alkavilla luvuilla - arvo on vähintään nolla (ässä) ja enintään 12 (kuningas). Kortin maa on 0 (risti), 1 (ruutu), 2 (hertta) tai 3 (pata). Sinun ei tarvitse vielä tässä tehtävässä varmistaa, että kortteja luodaan vain näille arvoalueille sopivilla arvoilla.
Toteuta seuraavat metodit:
Kortti(int arvo, int maa)
luo uuden olion ja samalla asettaa parametrina annetun arvon kortin arvoksi ja parametrina annetun maan kortin maaksi.getArvo
palauttaa kortin arvongetMaa
palauttaa kortin maantoString
palauttaa kortin merkkijonoesityksen (katso esimerkki alta)Tulostuksen helpottamiseksi Kortti-luokalla on myös valmiiksi toteutetut ArrayListit maidenNimet
maita ja arvojenNimet
kortin arvoja varten. maidenNimet.get(maa)
on maan selkokielinen merkkijonoesitys ("Risti", "Ruutu" jne...). arvojenNimet.get(arvo)
on arvon korttipakkaan sopiva lyhenne (A, J, Q, K normaalien lukumerkintöjen lisäksi).
toString
-metodin esimerkkitulostuksia:
Risti 4 Pata K Pata 5 Hertta Q Risti 6 Risti A Hertta 9 Ruutu 8 Ruutu 7 Ruutu J
Kasi-olio esittää korttikättä, eli kokoelmaa korteista, jotka ovat peräisin samasta korttipakasta. Käsi voi olla myös tyhjä, jolloin siinä ei ole kortteja.
Toteuta seuraavat luokan Kasi
metodit
public Kasi(ArrayList<Kortti> kortit)
luo uuden Käsi-olion, joka sisältää konstruktorin parametrina saadun listan korteista.suurinKortti
palauttaa viitteen arvolta suurimpaan korttiin. Huomaa, että samanarvoisista korteista suurempi määräytyy maan perusteella (suurimmasta pienimpään: pata, hertta, ruutu, risti). Arvojärjestyksessä pienin kortti on ässä (arvoltaan 0) ja suurin on kuningas (arvoltaan 12).pieninKortti
palauttaa viitteen arvolta pienimpään korttiin. Huomaa, että myös kortin maa vaikuttaa kortin arvoon ja maalle valmiiksi määriteltyä numeroarvoa voi käyttää maiden vertailuun.arvojenSumma
palauttaa Kasi-olion sisältämien Korttien kokonaisarvon. Toteuta metodi niin, että se laskee jokaisen kortin arvon yhdellä suuremmaksi kuin mitä se Kortti-olion mielestä on -- näin summa saadaan vastaamaan pelikorttien todellisia arvoja nollasta alkavien arvojen sijaan.getKortit
palauttaa käden sisältämät kortit ArrayListissä.Korttipakka-olio esittää tavallista korttipakkaa, joka koostuu 52 kortista. Täysi korttipakka sisältää jokaisen neljän maan (pata, risti, ruutu ja hertta) kaikki 13 korttia ässästä kuninkaaseen. Kortin poistamista korttipakasta kutsutaan nostamiseksi.
Toteuta seuraavat luokan Korttipakka
metodit
Korttipakka
luo ja lisää kaikki 52 korttia korttipakkaan. Korttipakkaan tulee päätyä kaikki maa- ja arvoyhdistelmät. Kun luot Kortteja pakkaan, voit antaa maa
-parametriksi Kortti.MAA_RISTI
, Kortti.MAA_RUUTU
, Kortti.MAA_HERTTA
tai Kortti.MAA_PATA
. nostaPaallimmainenKortti
poistaa pakassa ensimmäisenä olevan kortin ja palauttaa viitteen siihen. Metodi palauttaa null
-viitteen, jos kortteja ei enää ole.nostaKasi(int korttienMaara)
nostaa pakasta parametrina annetun määrän verran kortteja, luo niistä uuden Kasi-olion ja palauttaa sen. Metodi palauttaa vajaan käden, jos kortteja ei ole tarpeeksi, tai tyhjän käden, jos pakassa ei ole jäljellä kortteja lainkaan.korttienMaara
palauttaa korttipakan sisältämien korttien määrän. getKortit
palauttaa korttipakan sisältämät kortit ArrayListissä. sekoita
sekoittaa korttipakan kortit. (vinkki: Collections
-luokasta löytyy tähän näppärä metodi) Huom: Korttipakasta kortteja nostavat metodit voivat aiheuttaa helposti poikkeustilanteita, jos kortit loppuvat kesken. Tee metodien toteutukset huolellisesti, ettei näin pääse käymään.
Tämä tehtävä toteutetaan kolmessa osassa.
Toteuta seuraavat metodit luokkaan Vieras
Vieras(String nimi, int vierasID)
joka asettaa parametrinä saamansa Stringin olion 'muistiin', samoin kuin saamansa vierasID:n.getNimi()
palauttaa vieraan nimengetVierasID()
palauttaa vieraan vierasID:ntoString()
palauttaa vieraan nimen ja vierasID:n. Kts. alla oleva esimerkki tulosteVieras
-luokan esimerkki toString metodin tulosteita
Pekka: 14 Timo: 15 Jaakko: 16
Toteuta seuraavat metodit luokkaan Huone
Huone()
joka oletuksena asettaa huoneen kapasiteetiksi 2Huone(int kapasiteetti)
joka asettaa huoneen kapasiteetiksi parametrinä saadun kapasiteetinlisaaVieras(Vieras vieras)
asettaa vieraat luokan oliomuuttujana olevaan Vieras
taulukkoon ja huolehtii siitä, että huoneeseen ei lisätä enempää ihmisiä kun mitä huoneeseen mahtuu. Lisäksi Vierasta ei lisätä Huoneeseen jos Vieras on jo siellä. Metodi palauttaa true
mikäli lisääminen onnistui, muulloin false
. huoneenKoko()
joka palauttaa huoneen kapasiteetinvieraitaNyt()
joka palauttaa huoneessa olevien henkilöiden määränannaVieras(int VierasID)
joka palauttaa viitteen Vieras
-luokan olioon, jonka vierasID
on sama kuin parametrina saatu vierasID. Jos vierasta ei löydy palautetaan null
. Huomaa että tätä metodia käytettäessä tulee huomioida, että on mahdollista, että metodi palauttaa null
olioviitteen sijaantoString()
palauttaa huoneessa olevat henkilöt, kts alla oleva esimerkkitulosteHuone luokan toString()
metodin esimerkkitulostuksia, alla viiden huoneen tulostukset, joista viimeinen on tyhjä:
Aino: 2, Timo: 3 Timo: 4, Snorri: 5 Markku: 6, Noris: 7 Timo: 124, Saku: 114, Teppo: 29 Huone tyhjä
toteuta seuraavat metodit luokkaan Hotelli
Hotelli()
joka alustaa huoneiden säilömisessä käytetyn ArrayList<Huone>
lisaaHuoneita(int maara)
joka lisää maara
:n verran oletuskokoisia huoneita hotelliin ja ne lisätään hotellin huoneet
ArrayListiin. Käytä tässä luokan Huone
parametritonta konstruktoria lisaaHuoneita(int maara, int koko)
joka lisää maara
:n verran koko
:n kokoisia huoneita hotelliin, ja ne lisätään hotellin huoneet
ArrayListiinluoUusiVieras(String nimi)
luo uuden vieraan ja asettaa sille nimeksi parametrinä saadun nimen, sekä uniikin vierasID:n. Hotelli siis pitää kirjaa vierasID:istä ja osaa aina antaa uuden uniikin vierasID:n. Hotellin ensimmäinen vieras saa ID:n 1 seuraava ID:n 2, jne...hotellissaVieraita()
joka palauttaa hotellisa olevien vieraiden kokonaismääränannaSopivaHuone(int koko)
joka palauttaa sopivan huoneen, joka on koko parametrin kokoinen ja on tyhjä. Mikäli sopivaa tyhjää huonetta ei löydy, palautetaan null
etsiVieras(int vierasID)
joka käy läpi huonelistan ja tarkistaa jokaiselta huoneelta, onko kyseisellä vierasID
llä olevaa henkilöä. Mikäli henkilöä ei löydy palautetaan null. (Varaudu tähän).toString()
joka palauttaa siistityn tulostuksen, josta käy ilmi kaikkien hotellivieraiden nimet ja vierasIDt. Hyödynnä tässä Huone
ja Vieras
-luokkien toString metodeja. Esimerkkitulostus löytyy alempaa.Hotellilla on valmiiksi toteutettuna metodi getHuoneet()
joka palauttaa viitteen huoneet
-ArrayListiin.
Hotelli
-luokan toString metodin esimerkkitulostus:
kalle: 1, Simo: 2 kalle: 3, Timo: 4 kalle: 5, Snorri: 6 kalle: 7, Noris: 8 kalle: 9, Malloc: 10 kalle: 11, Rase: 12 kalle: 13, Dogo: 14 Huone tyhjä Huone tyhjä Huone tyhjä Huone tyhjä Huone tyhjä Huone tyhjä Huone tyhjä Huone tyhjä