Tässä kohtaa tulee tehdä syksyn Ohjelmoinnin perusteiden ensimmäinen konekoe. Ensimmäisen konekokeen tekemiseen on kokeen aloitushetkestä yksi päivä (24 tuntia). Ensimmäisessä konekokeessa on kaksi ohjelmointitehtävää, joissa käsitellään ja sovelletaan kurssin ensimmäisen kahden osan asioita. Ensimmäinen konekoe vastaa kymmentä kurssipistettä Ohjelmoinnin perusteet -kurssista. Ensimmäisestä konekokeesta ei ole pakkoa saada yhtäkään pistettä; kurssia voi jatkaa riippumatta miten konekokeessa pärjää.
Konekokeen saa käyttöönsä seuraavasti:
-
Aseta TMC:n asetuksissa palvelimen osoitteeksi
https://tmc.mooc.fi/org/hy/courses/250
ja valitse kurssiksihy-ohpe-s17-konekoe1
- Lataa tehtävä KonekoeOhjeet
- Muuta tehtävää KonekoeOhjeet siten, että siinä toteutettava ohjelma tulostaa tekstin "aloita" ja palauta tehtävä
- TMC tarkastaa tehtävän ja ehdottaa hetken päästä uusien tehtävien lataamista. Jos tehtävien lataamista ei ehdoteta vähään aikaan, voit valita TMC -> Download / update exercises
- TMC ehdottaa kahta tehtävää ladattavaksi. Lataa tehtävät "KonekoeTehtava1" ja "KonekoeTehtava2"
Nyt konekokeen tehtävät on ladattu ja voit alkaa tekemään tehtäviä.
- Ensimmäisen tehtävän tehtävänanto on tehtäväpohjan KonekoeTehtava1 kansiossa "Source Packages" -> default package nimellä "tehtava1-kuvaus.txt".
- Toisen tehtävän tehtävänanto on tehtäväpohjan KonekoeTehtava2 kansiossa "Source Packages" -> default package nimellä "tehtava2-kuvaus.txt".
Palauta tehtäviä sitä mukaa kun etenet niissä. Tehtävän saa palauttaa niin monta kertaa kuin haluaa annetun aikarajan sisällä.
Konekoe tulee aloittaa viimeistään keskiviikkona 27.9. Todellisuudessa se kannattaa tehdä kuitenkin aiemmin, sillä kolmannessa ja sitä seuraavissa osissa siirrymme hiljalleen olio-ohjelmoinnin harjoitteluun, jota ensimmäisessä konekokeessa ei tarvita. Jos et ole varma käytössä olevan tietokoneesi toiminnasta, konekoe kannattaa tehdä Kumpulan pajoissa (BK107, B221) olevilla koneilla.
Muistathan, että konekoe on yksilösuoritus. Et saa pyytää, saada tai antaa apua konekokeen tekemiseen. Älä myöskään jaa koetehtäviä tai mitään muuta kokeeseen liittyvää materiaalia muille.
Konekokeen tulokset julkaistaan (arviolta) 2.10.
Kun olet tehnyt konekokeen, kurssille pääsee takaisin valitsemalla TMC:n asetuksista palvelimen osoitteeksi https://tmc.mooc.fi/org/hy/courses/243
sekä kurssiksi kurssin hy-ohpe-s17
.
Tuntee loogiset operaatiot ja ja tai. Osaa luoda metodeja, jotka palauttavat arvon. Ymmärtää miten ohjelman suoritus etenee metodikutsun yhteydessä. Ymmärtää muuttujien näkyvyyden ja olemassaolon. Tuntee käsitteen merkkijono ja osaa käsitellä merkkijonoja (erit. vertailu). Tuntee käsitteet olio, konstruktori ja olion metodit. Luo ainakin yhden oman luokan. Luo olioita tekemistään luokista. Ymmärtää static-määreellä ja ilman static-määrettä olevien metodien eron.
Loogiset operaatiot
Materiaalin esimerkeissä ja tehtävissä käytetyt ehto- ja toistolauseet ovat tähän mennessä käyttäneet yksinkertaisia lausekkeita, joilla on tarkasteltu ehtolauseeseen ja toistolauseeseen liittyvän lähdekoodin suorittamista. Esim.
if (lauseke) {
System.out.println("Suoritetaan jos lausekkeen arvo on true");
}
while (lauseke) {
System.out.println("Suoritetaan jos lausekkeen arvo on true");
}
int luku = 2;
if (luku % 2 == 0) {
System.out.println("Luku on parillinen");
}
while (luku > 0) {
System.out.println("Pienennetään..");
luku--;
System.out.println("Luku nyt " + luku);
}
Luku on parillinen Pienennetään Luku nyt 1 Pienennetään Luku nyt 0
Ehtolauseen lauseke voi koostua myös useammasta osasta, joissa käytetään loogisia operaatioita ja &&
ja tai ||
. Kaksi ehtoa sisältävä lauseke, jossa on ja-operaatio on totta jos ja vain jos kummatkin ehdoista ovat tosia. Toisaalta, kaksi ehtoa sisältävä lauseke, jossa on tai-operaatio on totta jos jompi kumpi tai molemmat ehdoista ovat tosia.
Seuraavassa yhdistetään &&
:lla eli ja-operaatiolla kaksi yksittäistä ehtoa. Koodilla tarkistetaan, onko muuttujassa oleva luku suurempi kuin 4 ja pienempi kuin 11, eli siis välillä 5-10:
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! :)
Seuraavassa annetaan ||
:n eli tai-operaation avulla kaksi vaihtoehtoa, onko luku pienempi kuin 0 tai suurempi kuin 100. Ehto toteutuu jos luku täyttää jomman kumman ehdon:
System.out.println("Onkohan luku pienempi kuin 0 tai suurempi kuin 100");
int luku = 145;
if (luku < 0 || luku > 100) {
System.out.println("On! :)");
} else {
System.out.println("Ei ollut :(")
}
Onkohan luku pienempi kuin 0 tai suurempi kuin 100 On! :)
- Looginen operaatio ja:
lauseke1 && lauseke2
lausekkeen arvo on tosi kun molemmat ehdoista ovat tosia - Looginen operaatio tai:
lauseke1 || lauseke2
lausekkeen arvo on tosi jos jompi kumpi tai molemmat ehdoista tosia
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!
Ongelman ratkaiseminen osa kerrallaan
Tutustutaan klassiseen ohjelmointiongelmaan:
'Kirjoita ohjelma, joka kysyy käyttäjältä lukua yhden ja sadan väliltä ja tulostaa luvun. Jos luku on kolmella jaollinen, luvun sijaan tulostetaan "Fizz". Jos luku on viidellä jaollinen, luvun sijaan tulostetaan "Buzz". Jos luku on sekä kolmellä että viidellä jaollinen, luvun sijaan tulostetaan "FizzBuzz"'.
Ohjelmoija lähtee ratkaisemaan tehtävää lukemalla ongelmakuvauksen, ja luomalla ohjelmakoodia ongelmakuvausta seuraten. Koska ohjelman suoritusehdot esitellään ongelmassa annetussa järjestyksessä, muodostuu ohjelman rakenne järjestyksen perusteella. Ohjelman rakenne muodostuu seuraavien askelten perusteella:
- Tee ohjelma, joka lukee luvun käyttäjältä ja tulostaa sen.
- Jos luku on jaollinen kolmella, tulosta luvun sijaan merkkijono "Fizz".
- Jos luku on jaollinen viidellä, tulosta luvun sijaan merkkijono "Buzz".
- Jos luku on jaollinen kolmella ja viidellä, tulosta luvun sijan merkkijono "FizzBuzz".
Jos-tyyppiset ehdot on helppo toteuttaa if - else if - else
-valintakäskyjen avulla. Alla oleva koodi on toteutettu ylläolevien askelten perusteella, mutta se ei kuitenkaan toimi oikein. Kokeile!
Scanner lukija = new Scanner(System.in);
int luku = Integer.parseInt(lukija.nextLine());
if (luku % 3 == 0) {
System.out.println("Fizz");
} else if (luku % 5 == 0) {
System.out.println("Buzz");
} else if (luku % 3 == 0 && luku % 5 == 0) {
System.out.println("FizzBuzz");
} else {
System.out.println(luku);
}
Valintakäskyjen suoritusjärjestyksestä
Edellisessä lähestymistavassa ongelmana on se, että valintakäskyjen läpikäynti lopetetaan ensimmäiseen ehtoon, jonka arvo on totta. Esimerkiksi luvulla 15 tulostetaan merkkijono "Fizz", sillä luku on kolmella jaollinen (15 % 3 == 0).
Yksi lähestymistapa ylläolevan ajatusketjun kehittämiseen on ensin etsiä vaativin ehto ja toteuttaa se. Tämän jälkeen toteutettaisiin muut ehdot. Yllä olevassa esimerkissä ehto "jos luku on jaollinen kolmella ja viidellä" vaatii kahden tapauksen toteutumista. Nyt ajatusketju olisi muotoa.
- Tee ohjelma, joka lukee luvun käyttäjältä ja tulostaa sen.
- Jos luku on jaollinen kolmella ja viidellä, tulosta luvun sijan merkkijono "FizzBuzz".
- Jos luku on jaollinen kolmella, tulosta luvun sijaan merkkijono "Fizz".
- Jos luku on jaollinen viidellä, tulosta luvun sijaan merkkijono "Buzz".
Nyt ongelmakin tuntuu ratkeavan.
Scanner lukija = new Scanner(System.in);
int luku = Integer.parseInt(lukija.nextLine());
if (luku % 3 == 0 && luku % 5 == 0) {
System.out.println("FizzBuzz");
} else if (luku % 3 == 0) {
System.out.println("Fizz");
} else if (luku % 5 == 0) {
System.out.println("Buzz");
} else {
System.out.println(luku);
}
Tehdään ensimmäisessä osassa nähty tehtävä Karkausvuosi tässä kohtaa uudestaan. Mieti miten saisit hyödynnettyä edellä nähtyjä loogisia operaatioita sekä ajatusta siitä, että ehdoista kannattaa etsiä vaativin ensin.
Karkausvuoden määritelmä on seuraava: 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 lukee käyttäjältä vuosiluvun, ja 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.
Metodit jatkuvat
Metodien avulla ohjelmia voi pilkkoa pienemmiksi osakokonaisuuksiksi. Jos metodit nimetään kuvaavasti, ohjelmoija pystyy seuraamaan sovelluksen toimintaa metodien nimien perusteella. Tämä helpottaa ohjelman ymmärrettävyyttä niin alkuperäisen ohjelmoijan kuin ohjelmaa myöhemmin mahdollisesti ylläpitävän ohjelmoijan tai ohjelmoijien osalta. Jatketaan metodeihin tutustumista.
Metodit ja muuttujien näkyvyys
Edellisessä osassa käsitellyt muuttujien olemassaoloon liittyvät säännöt pätevät myös metodien yhteydessä. Muuttujaa ei ole olemassa ennen sen esittelyä, ja muuttuja on olemassa vain niiden aaltosulkujen sisällä kuin missä se on esitelty. Metodien yhteydessä tämä tarkoittaa sitä, että metodeilla on pääsy vain niihin muuttujiin, jotka ovat määritelty metodien sisällä, tai jotka metodi saa parametrina. Alla oleva esimerkki konkretisoi tilanteen, missä kasvataKolme
-metodin sisällä yritetään muuttaa main
-metodissa määritellyn luku
-muuttujan arvoa.
// pääohjelma
public static void main(String[] args) {
int luku = 1;
kasvataKolmella();
}
// metodi
public static void kasvataKolmella() {
luku = luku + 3;
}
Yllä oleva ohjelma ei toimi, sillä metodi kasvataKolmella
ei näe pääohjelman muuttujaa luku
. Tarkemmin ottaen, metodi kasvataKolmella
ei edes tiedä mistä muuttujasta luku
on kyse, sillä muuttujaa ei ole määritelty metodissa kasvataKolmella
tai sen parametreissa.
Yleisemmin voi todeta, että pääohjelman muuttujat eivät näy metodien sisälle, ja metodin muuttujat eivät näy muille metodeille tai pääohjelmalle. Ainoa keino viedä metodille tietoa metodin ulkopuolelta on parametrin avulla.
Metodille annettavat muuttujat kopioituvat
Muistellaan vielä edellisestä osasta erittäin oleellista metodien toimintaan liittyvää ominaisuutta: parametrien arvot kopioituvat. Käytännössä metodien parametrit ovat erillisiä muiden metodien muuttujista (tai parametreista), vaikka niillä olisikin sama luku. Kun metodikutsun yhteydessä metodille annetaan muuttuja, muuttujan arvo kopioituu kutsuttavan metodin metodimäärittelyssä olevan parametrimuuttujan arvoksi. Kahdessa eri metodissa olevat muuttujat ovat siis erillisiä toisistaan.
Tarkastellaan seuraavaa esimerkkiä, missä pääohjelmassa määritellään muuttuja luku
. Muuttuja luku annetaan parametrina metodille kasvataKolmella
.
// pääohjelma
public static void main(String[] args) {
int luku = 1;
System.out.println("Pääohjelman muuttujan luku arvo: " + luku);
kasvataKolmella(luku);
System.out.println("Pääohjelman muuttujan luku arvo: " + luku);
}
// metodi
public static void kasvataKolmella(int luku) {
System.out.println("Metodin parametrin luku arvo: " + luku);
luku = luku + 3;
System.out.println("Metodin parametrin luku arvo: " + luku);
}
Ohjelman suoritus aiheuttaa seuraavanlaisen tulostuksen.
Pääohjelman muuttujan luku arvo: 1 Metodin parametrin luku arvo: 1 Metodin parametrin luku arvo: 4 Pääohjelman muuttujan luku arvo: 1
Kun metodin sisällä kasvatetaan muuttujan luku
arvoa kolmella, se onnistuu. Tämä ei kuitenkaan näy pääohjelmassa olevassa muuttujassa luku
. Pääohjelmassa oleva muuttuja luku
on eri kuin metodissa oleva muuttuja luku
.
Parametri luku
kopioidaan metodin käyttöön, eli metodia kasvataKolmella
varten luodaan oma muuttuja nimeltä luku
, johon pääohjelmassa olevan muuttujan luku
arvo kopioidaan metodikutsun yhteydessä. Metodissa kasvataKolmella
oleva muuttuja luku
on olemassa vain metodin suorituksen ajan, eikä sillä ole yhteyttä pääohjelman samannimiseen muuttujaan.
Metodin voi palauttaa arvon
Metodin määrittelyssä kerrotaan palauttaako metodi arvon. Jos metodi palauttaa arvon, tulee metodimäärittelyn yhteydessä kertoa palautettavan arvon tyyppi. Muulloin määrittelyssä käytetään avainsanaa void. Tähän mennessä tekemämme metodit eivät ole palauttaneet arvoa, ja on määritelty avainsanaa void käyttäen.
public static void kasvataKolmella() {
...
}
Avainsana void
tarkoittaa että metodi ei palauta mitään. Jos haluamme, että metodi palauttaa arvon, tulee avainsanan void
paikalle asettaa palautettavan muuttujan tyyppi. Seuraavassa esimerkissä on määritelty metodi palautetaanAinaKymppi
, joka palauttaa kokonaislukutyyppisen (int
) muuttujan (tässä arvon 10). Konkreettinen arvon palautus tapahtuu aina komennolla return
, jota seuraa palautettava arvo (tai muuttujan nimi, jonka arvo palautetaan).
public static int palautetaanAinaKymppi() {
return 10;
}
Yllä määritelty metodi palauttaa sitä kutsuttaessa int
-tyyppisen arvon 10
. Jotta metodin palauttamaa arvoa voisi käyttää, tulee se ottaa talteen muuttujaan. Tämä tapahtuu samalla tavalla kuin normaali muuttujan arvon asetus, eli yhtäsuuruusmerkillä.
public static void main(String[] args) {
int luku = palautetaanAinaKymppi();
System.out.println("metodi palautti luvun " + luku);
}
Metodin paluuarvo sijoitetaan int
-tyyppiseen muuttujaan aivan kuin mikä tahansa muukin int-arvo. Paluuarvo voi toimia myös osana mitä tahansa lauseketta.
double luku = 4 * palautetaanAinaKymppi() + (palautetaanAinaKymppi() / 2) - 8;
System.out.println("laskutoimituksen tulos " + luku);
Kaikki muuttujatyypit, mitä olemme tähän mennessä nähneet, voidaan palauttaa metodista.
Palautettavan arvon tyyppi | Esimerkki |
---|---|
Metodi ei palauta arvoa |
|
Metodi palauttaa int -tyyppisen muuttujan |
|
Metodi palauttaa double -tyyppisen muuttujan |
|
Jos metodille määritellään paluuarvon tyyppi, on sen pakko palauttaa arvo. Esimerkiksi seuraava metodi on virheellinen.
public static int virheellinenMetodi() {
System.out.println("Väitän palauttavani kokonaisluvun, mutten palauta sitä.");
}
Ylläolevassa metodissa tulee olla komento return
, jota seuraa palautettavan arvon tyyppi.
Kun metodin suorituksessa päädytään komentoon return
, metodin suoritus päättyy ja metodista palautetaan arvo sitä kutsuneelle metodille.
Ohjelmointikielen kääntäjä sekä käyttämämme ohjelmointiympäristö tietää, että return-komentoa seuraavia lähdekoodirivejä ei koskaan suoriteta. Jos ohjelmoija lisää lähdekoodia return-komennon jälkeen paikkaan, mitä ei metodin suorituksessa voida koskaan saavuttaa, ohjelmointiympäristö antaa virheviesti.
Seuraavanlainen metodi on virheellinen ohjelmointiympäristön näkökulmasta.
public static int virheellinenMetodi() {
return 10;
System.out.println("Väitän palauttavani kokonaisluvun, mutten palauta sitä.");
}
Seuraava metodi taas toimii, sillä -- vaikka return-komennon alla on lähdekoodia -- jokainen metodin lause on mahdollista saavuttaa.
public static int virheellinenMetodi(int parametri) {
if (parametri > 10) {
return 10;
}
System.out.println("Parametrina saatu luku on kymmenen tai pienempi.");
return parametri;
}
Metodi voi palauttaa lausekkeen evaluoinnin tuloksen
Palautettavan arvon ei tarvitse olla täysin ennalta määritelty, vaan se voidaan laskea. Metodista arvon palauttavalle return-komennolle voidaan antaa myös lauseke, joka evaluoituu ennen kuin arvo palautetaan.
Seuraavassa esimerkissä määritellään metodi summa, joka laskee kahden muuttujan arvon yhteen ja palauttaa summan. Yhteen laskettavien muuttujien eka
ja toka
arvot saadaan metodin parametrina.
public static int summa(int eka, int toka) {
return eka + toka;
}
Kun metodin suorituksessa päädytään lauseeseen return eka + toka;
, evaluoidaan lauseke eka + toka
, jonka arvo lopulta palautetaan.
Metodin kutsutaan seuraavasti. Alla metodia käytetään laskemaan luvut 2 ja 7 yhteen. Metodikutsusta saatava paluuarvo asetetaan muuttujaan lukujenSumma
:
int lukujenSumma = summa(2, 7);
// lukujenSumma on nyt 9
Laajennetaan edellistä esimerkkiä siten, että käyttäjä syöttää luvut.
public static void main(String[] args) {
Scanner lukija = new Scanner(System.in);
System.out.print("Anna ensimmäinen luku: ");
int eka = Integer.parseInt(lukija.nextLine());
System.out.print("Anna toinen luku: ");
int toka = Integer.parseInt(lukija.nextLine());
System.out.print("Luvut ovat yhteensä: " + summa(eka, toka));
}
public static int summa(int eka, int toka) {
return eka + toka;
}
Yllä olevassa esimerkissä metodin paluuarvoa ei aseteta muuttujaan, vaan sitä käytetään suoraan osana tulostusta. Tällöin tulostuskomennon suoritus tapahtuu siten, että tietokone selvittää ensin tulostettavan merkkijonon "Luvut ovat yhteensä: " + summa(eka, toka)
. Ensin tietokone etsii muuttujat eka
ja toka
, ja kopioi niiden arvot metodin summa
parametrien arvoiksi. Tämän jälkeen metodissa lasketaan parametrien arvot yhteen, jonka jälkeen summa palauttaa arvon. Tämä arvo asetetaan metodikutsun summa
paikalle, jonka jälkeen summa liitetään merkkijonon "Luvut ovat yhteensä: "
jatkeeksi.
Koska metodille annettavat arvot kopioidaan metodin parametreihin, metodin parametrien nimillä ja metodin kutsujan puolella määritellyillä muuttujien nimillä ei ole oikeastaan mitään tekemistä keskenään. Edellisessä esimerkissä sekä pääohjelman muuttujat että metodin parametrit olivat "sattumalta" nimetty samoin (eli eka
ja toka
). Seuraava toimii täysin samalla tavalla vaikka muuttujat ovatkin eri nimisiä:
public static void main(String[] args) {
Scanner lukija = new Scanner(System.in);
System.out.print("Anna ensimmäinen luku: ");
int luku1 = Integer.parseInt(lukija.nextLine());
System.out.print("Anna toinen luku: ");
int luku2 = Integer.parseInt(lukija.nextLine());
System.out.print("Luvut ovat yhteensä: " + summa(luku1, luku2));
}
public static int summa(int eka, int toka) {
return eka + toka;
}
Nyt pääohjelman muuttujan luku1
arvo kopioituu metodin parametrin eka
arvoksi ja pääohjelman muuttujan luku2
arvo kopioituu metodin parametrin toka
arvoksi.
Seuraavassa esimerkissä metodia summa kutsutaan kokonaisluvuilla, jotka saadaan summa
-metodin paluuarvoina.
int eka = 3;
int toka = 2;
int monenLuvunSumma = summa(summa(1, 2), summa(eka, toka));
// 1) suoritetaan sisemmät metodit:
// summa(1, 2) = 3 ja summa(eka, toka) = 5
// 2) suoritetaan ulompi metodi:
// summa(3, 5) = 8
// 3) muuttujan monenLuvunSumma arvoksi siis tulee 8
Metodin omat muuttujat
Muuttujien määrittely metodissa tapahtuu tutulla tavalla. Seuraava metodi laskee parametrina saamiensa lukujen keskiarvon. Keskiarvon laskemisessa käytetään apumuuttujia summa
ja ka
.
public static double keskiarvo(int luku1, int luku2, int luku3) {
int summa = luku1 + luku2 + luku3;
double ka = summa / 3.0;
return ka;
}
Metodin kutsu voi tapahtua esim seuraavasti
public static void main(String[] args) {
Scanner lukija = new Scanner(System.in);
System.out.print("Anna ensimmäinen luku: ");
int eka = Integer.parseInt(lukija.nextLine());
System.out.print("Anna toinen luku: ");
int toka = Integer.parseInt(lukija.nextLine());
System.out.print("ja kolmas luku: ");
int kolmas = Integer.parseInt(lukija.nextLine());
double keskiarvonTulos = keskiarvo(eka, toka, kolmas);
System.out.print("Lukujen keskiarvo: " + keskiarvonTulos);
}
Huomaa että metodin sisäiset muuttujat summa
ja ka
eivät näy pääohjelmaan. Tyypillinen ohjelmoinnin harjoittelussa eteen tuleva virhe on yrittää käyttää metodia seuraavasti.
public static void main(String[] args) {
// 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 edellä tehtyä metodia summa
!
Tee metodi seuraavaan runkoon:
public static int summa(int luku1, int luku2, int luku3, int luku4) {
// kopioi koodi tehtävästä 50
}
public static double keskiarvo(int luku1, int luku2, int luku3, int luku4) {
// kirjoita koodia tähän
// laske alkioiden summa kutsumalla metodia summa
}
public static void main(String[] args) {
double vastaus = keskiarvo(4, 3, 6, 1);
System.out.println("Keskiarvo: " + vastaus);
}
Ohjelman tulostus:
Keskiarvo: 3.5
Muistathan miten kokonaisluku (int) muutetaan desimaaliluvuksi (double)!
Mitä metodia kutsuttaessa tapahtuu -- kutsupino
Kun metodia kutsutaan, kutsuva metodi jää odottamaan kutsutun metodin suorittamista. Tätä voidaan visualisoida kutsupinon avulla. Tarkastellaan seuraavaa ohjelmaa:
public static void main(String[] args) {
System.out.println("Hei maailma!");
tulostaLuku();
System.out.println("Hei hei maailma!");
}
public static void tulostaLuku() {
System.out.println("Luku");
}
Kun ohjelma käynnistetään, suoritus alkaa main
-metodin ensimmäiseltä riviltä. Tällä rivillä olevalla komennolla tulostetaan teksti "Heippa maailma!"
. Ohjelman kutsupino näyttää seuraavalta:
main
Kun tulostuskomento on suoritettu, siirrytään seuraavaan komentoon, missä kutsutaan metodiatulostaLuku
. Metodin tulostaLuku
kutsuminen siirtää ohjelman suorituksen metodin tulostaLuku
alkuun, jolloin main
-metodi jää odottamaan metodin tulostaLuku
suorituksen loppumista. Metodin tulostaLuku
sisällä ollessa kutsupino näyttää seuraavalta.
tulostaLuku main
Kun metodi tulostaLuku
on suoritettu, palataan kutsupinossa metodia tulostaLuku
yhtä alempana olevaan metodiin, eli metodiin main
. Kutsupinosta poistetaan tulostaLuku
, ja jatketaan main
-metodin suoritusta tulostaLuku
-metodikutsun jälkeiseltä riviltä. Kutsupino on nyt seuraavanlainen:
main
Kutsupino ja metodin parametrit
Tarkastellaan kutsupinoa tilanteessa, missä metodille on määritelty parametreja.
public static void main(String[] args) {
int alku = 1;
int loppu = 5;
tulostaTahtia(alku, loppu);
}
public static void tulostaTahtia(int alku, int loppu) {
while (alku < loppu) {
System.out.print("*");
alku++;
}
}
Ohjelman suoritus alkaa main
-metodin ensimmäiseltä riviltä, jota seuraavilla riveillä luodaan muuttujat alku
ja loppu
, sekä asetetaan niihin arvot. Ohjelman tilanne ennen metodin tulostaTahtia
-kutsumista:
main alku = 1 loppu = 5
Kun metodia tulostaTahtia
kutsutaan, main
-metodi jää odottamaan. Metodikutsun yhteydessä metodille tulostaTahtia
luodaan uudet muuttujat alku
ja loppu
, joihin asetetaan parametreina annetut arvot. Nämä kopioidaan main
-metodin muuttujista alku
ja loppu
. Metodin tulostaTahtia
suorituksen ensimmäisellä rivillä ohjelman tilanne on seuraavanlainen.
tulostaTahtia alku = 1 loppu = 5 main alku = 1 loppu = 5
Kun toistolauseessa suoritetaan komento alku++
, muuttuu tällä hetkellä suoritettavaan metodiin liittyvän alku
-muuttujan arvo.
tulostaTahtia alku = 2 loppu = 5 main alku = 1 loppu = 5
Metodin main
muuttujien arvot eivät siis muutu. Metodin tulostaTahtia
suoritus jatkuisi tämän jälkeen jokusen hetken. Kun metodin tulostaTahtia
suoritus loppuu, palataan takaisin main
-metodin suoritukseen.
main alku = 1 loppu = 5
Tarkastellaan samaa ohjelman suorituksen askeleittaisella visualisoinnilla. Visualisointiin käyttämämme sovellus kasvattaa kutsupinoa alaspäin -- oikealla laidalla ylin on aina main-metodi, jonka alle tulee kutsuttavat metodit.
Kutsupino ja arvon palauttaminen metodista
Tarkastellaan vielä esimerkkiä, missä metodi palauttaa arvon. Ohjelman main
-metodi kutsuu erillistä kaynnista
-metodia, jossa luodaan kaksi muuttujaa, kutsutaan summa
-metodia, ja tulostetaan summa
-metodin palauttama arvo.
public static void main(String[] args) {
kaynnista();
}
public static void kaynnista() {
int eka = 5;
int toka = 6;
int summa = summa(eka, toka);
System.out.println("Summa: " + summa);
}
public static int summa(int luku1, int luku2) {
return luku1 + luku2;
}
Metodin kaynnista
suorituksen alussa kutsupino näyttää seuraavalta, sillä siihen päädyttiin main
-metodista. Metodilla main
ei tässä esimerkissä ole omia muuttujia:
kaynnista main
Kun kaynnista
-metodissa on luotu muuttujat eka
ja toka
, eli sen ensimmäiset kaksi riviä on suoritettu, on tilanne seuraava:
kaynnista eka = 5 toka = 6 main
Komento int summa = summa(eka, toka);
luo metodiin kaynnista
muuttujan summa
, ja kutsuu metodia summa
. Metodi kaynnista
jää odottamaan. Koska metodissa summa
on määritelty parametrit luku1
ja luku2
, luodaan ne heti metodin suorituksen alussa, ja niihin kopioidaan parametrina annettujen muuttujien arvot.
summa luku1 = 5 luku2 = 6 kaynnista eka = 5 toka = 6 summa // ei arvoa main
Metodin summa
suoritus laskee muuttujien luku1
ja luku2
arvot yhteen. Komento return
palauttaa lukujen summan kutsupinossa yhtä alemmalle metodille, eli metodille kaynnista
. Palautettu arvo asetetaan muuttujan summa
arvoksi.
kaynnista eka = 5 toka = 6 summa = 11 main
Tämän jälkeen suoritetaan tulostuskomento, jonka jälkeen palataan main
-metodiin. Kun suoritus on main
-metodin lopussa, ohjelman suoritus loppuu.
Merkkijonot
Tutustutaan seuraavaksi uuteen muuttujatyyppiin eli merkkijonoihin (String
). Merkkijonomuuttuja määritellään kertomalla sen tyyppi (String) sekä nimi. Tätä seuraa muuttujan arvo, joka on hipsujen sisällä olevaa tekstiä. Alla luodaan merkkijonomuuttuja taikasana
, joka sisältää arvon "abrakadabra"
.
String taikasana = "abrakadabra";
Merkkijonomuuttujan antaminen tulostuskomennolle (tai oikeastaan mille tahansa metodille) parametrina onnistuu tutulla tavalla. Alla määritellään merkkijono, joka tulostetaan.
String taikasana = "abrakadabra";
System.out.println(taikasana);
abrakadabra
Merkkijonojen lukeminen ja tulostaminen
Merkkijonon lukeminen onnistuu tutun Scanner-apuvälineen tarjoamalla nextLine-metodilla. Alla oleva ohjelma lukee käyttäjän nimen ja tulostaa sen seuraavalla rivillä (esimerkissä käyttäjän syöttämä teksti on merkitty punaisella):
Scanner lukija = new Scanner(System.in);
System.out.print("Mikä on nimesi? ");
String nimi = lukija.nextLine(); // Luetaan käyttäjältä rivi tekstiä ja asetetaan se muuttujaan nimi
System.out.println(nimi);
Mikä on nimesi? Venla
Venla
Merkkijonoja voi myös yhdistellä. Jos operaatiota +
sovelletaan kahden merkkijonon välille, syntyy uusi merkkijono, jossa kaksi merkkijonoa on yhdistetty. Huomaa nokkela välilyönnin käyttö lauseen "muuttujien" osana!
String tervehdys = "Hei ";
String nimi = "Lilja";
String hyvastely = " ja näkemiin!";
String lause = tervehdys + nimi + hyvastely;
System.out.println(lause);
Hei Lilja ja näkemiin!
Jos toinen operaation +
kohteista on merkkijono, muutetaan myös toinen operaation kohteista merkkijonoksi. Alla olevassa esimerkissä kokonaisluku 2
on muutettu merkkijonoksi "2", ja siihen on yhdistetty merkkijono.
String teksti = "tuossa on kokonaisluku";
System.out.println(teksti + " --> " + 2);
System.out.println(2 + " <-- " + teksti);
tuossa on kokonaisluku --> 2 2 <-- tuossa on kokonaisluku
Aiemmin tutuksi tulleet laskusäännöt sekä sulkeiden noudattaminen pätee myös merkkijonoja käsiteltäessä.
String teksti = " oho!";
System.out.println("Neljä: " + (2 + 2) + teksti);
System.out.println("Mutta! kaksikymmentäkaksi: " + 2 + 2 + teksti);
Neljä: 4 oho! Mutta! kaksikymmentäkaksi: 22 oho!
Seuraavassa on vielä käyttäjää tervehtivä ohjelma pääohjelmarungon kanssa. Ohjelman nimi on Tervehdys.
import java.util.Scanner;
public class Tervehdys {
public static void main(String[] args) {
Scanner lukija = new Scanner(System.in);
System.out.print("Kenelle sanotaan hei: ");
String nimi = lukija.nextLine();
System.out.println("Hei " + nimi);
}
}
Kun yllä oleva ohjelma ajetaan, pääset kirjoittamaan syötteen. NetBeansin tulostusvälilehti näyttää ajetun ohjelman jälkeen seuraavalta (käyttäjä syöttää nimen "Venla").
Tee ohjelma joka lukee käyttäjältä merkkijonon ja tulostaa merkkijonon kolmesti peräkkäin.
Mikä tulostetaan? kukka kukkakukkakukka
Esimerkissä punainen väri tarkoittaa käyttäjän kirjoittamaa tekstiä. Tätä käytäntöä noudatetaan jatkossa esimerkeissä.
Käyttäjän kanssa keskustelevan ohjelman runko:
import java.util.Scanner;
public class OhjelmanNimi {
public static void main(String[] args) {
Scanner lukija = new Scanner(System.in);
// koodi tähän
}
}
Merkkijonon lukeminen:
String merkkijono = lukija.nextLine();
Kokonaisluvun lukeminen:
int kokonaisluku = Integer.parseInt(lukija.nextLine());
Merkkijonojen vertailu ja equals
Merkkijonoja ei voi vertailla yhtäsuuri kuin (==) operaatiolla. Merkkijonojen vertailuun käytetään erillistä equals
-komentoa, joka liittyy aina verrattavaan merkkijonoon.
String teksti = "kurssi";
if (teksti.equals("marsipaani")) {
System.out.println("Teksti-muuttujassa on teksti marsipaani.");
} else {
System.out.println("Teksti-muuttujassa ei ole tekstiä marsipaani.");
}
Komento equals
liitetään aina siihen verrattavaan tekstimuuttujaan, "tekstimuuttuja piste equals teksti". Tekstimuuttujaa voidaan myös verrata toiseen tekstimuuttujaan.
String teksti = "kurssi";
String toinenTeksti = "pursi";
if (teksti.equals(toinenTeksti)) {
System.out.println("Samat tekstit!");
} else {
System.out.println("Eri tekstit!");
}
Merkkijonoja vertailtaessa on syytä varmistaa että verrattavalla tekstimuuttujalla on arvo. Jos muuttujalla ei ole arvoa, ohjelma tuottaa virheen NullPointerException, joka tarkoittaa ettei muuttujan arvoa ole asetettu tai se on tyhjä (null).
Seuraavassa käännetään !
:n eli negaatio-operaation avulla ehdon arvo päinvastaiseksi:
System.out.println("Eihän merkkijono ole 'maito'");
String merkkijono = "piimä";
if (!(merkkijono.equals("maito"))) { // tosi jos ehto merkkijono.equals("maito") on epätosi
System.out.println("ei ollut!");
} else {
System.out.println("oli");
}
ei ollut!
Negaatio-operaatio, eli !ehto
, kääntää siis totuusarvon ympäri.
int eka = 1;
int toka = 3;
boolean onkoSuurempi = eka > toka;
if (!onkoSuurempi) {
System.out.println("1 ei ole suurempi kuin 3");
}
1 ei ole suurempi kuin 3
Tee ohjelma, joka pyytää käyttäjää kirjoittamaan merkkijonon. Jos käyttäjä kirjoittaa merkkijonon "totta", tulostetaan merkkijono "Oikein meni!", muulloin tulostetaan merkkijono "Koitappa uudelleen!".
Kirjoita merkkijono: totta Oikein meni!
Kirjoita merkkijono: tottapa Koitappa uudelleen!
Tee ohjelma, joka tunnistaa seuraavat käyttäjät:
tunnus | salasana |
---|---|
aleksi | tappara |
elina | kissa |
Ohjelma näyttää käyttäjälle henkilökohtaisen viestin tai ilmoittaa, jos tunnus tai salasana on väärin.
Anna tunnus: aleksi Anna salasana: tappara Olet kirjautunut järjestelmään
Anna tunnus: elina Anna salasana: kissa Olet kirjautunut järjestelmään
Anna tunnus: aleksi Anna salasana: jokerit Virheellinen tunnus tai salasana!
HUOM: muista, että merkkijonoja ei voi vertailla ==-operaatiolla!
HUOM: Todellisuudessa kirjautumistoiminnallisuutta ei tule toteuttaa, eikä yleensä toteutetakkaan näin. Kirjautumistoiminnallisuuden toteuttamiseen tutustutaan mm. tietokantojen perusteet -kurssilla.
Tässä tehtävässä luodaan ohjelma joka kyselee käyttäjältä salasanaa. Jos salasana menee oikein, nähdään salainen viesti.
Anna salasana: nauris Väärin! Anna salasana: lanttu Väärin! Anna salasana: porkkana Oikein! Salaisuus on: znvavbfgv grugl!
Toteutetaan ohjelma kolmessa askeleessa.
Salasanan kysyminen
Ohjelmarunkoon on määritelty muuttuja String salasana
, jolle on asetettu arvoksi porkkana
-- älä muuta tätä salasanaa. Toteuta lisätoiminnallisuus, jossa ohjelma kysyy käyttäjältä salasanaa ja vertailee sitä muuttujassa salasana
olevaan arvoon. Muista mitä erityistä merkkijonojen vertailussa on!
Anna salasana: nauris Väärin!
Anna salasana: porkkana Oikein!
Anna salasana: bataatti Väärin!
Salasanan kysyminen kunnes käyttäjä vastaa oikein
Muokkaa ohjelmaa siten, että se kysyy salasanaa kunnes käyttäjä syöttää oikean salasanan. Toteuta salasanan jatkuva kysyminen while (true) { ... }
-toistolausekkeen avulla. Toistolausekkeesta pääsee pois, jos ja vain jos käyttäjän syöttämä salasana on sama kuin muuttujassa salasana
oleva arvo.
Anna salasana: nauris Väärin! Anna salasana: lanttu Väärin! Anna salasana: porkkana Oikein!
Salainen viesti
Lisää ohjelmaan oma salainen viestisi joka näytetään kun käyttäjä kirjoittaa salasanan oikein. Se voi olla mitä tahansa!
Anna salasana: nauris Väärin! Anna salasana: lanttu Väärin! Anna salasana: porkkana Oikein! Salaisuus on: znvavbfgv grugl!
Ylläoleva salaisuus on salattu käyttäen Rot13-algoritmia.
Merkkijonoilta voi kysyä niiden pituutta kirjoittamalla merkkijonon perään .length()
eli kutsumalla merkkijonolle sen pituuden kertovaa metodia.
String banaani = "banaani";
String kurkku = "kurkku";
String yhdessa = banaani + kurkku;
System.out.println("Banaanin pituus on " + banaani.length());
System.out.println("Kurkku pituus on " + kurkku.length());
System.out.println("Sanan " + yhdessa + " pituus on " + yhdessa.length());
Edellä kutsutaan metodia length()
kolmelle eri merkkijonolle. Kutsu banaani.length()
kutsuu nimenomaan merkkijonon banaani
pituuden kertovaa metodia, kun taas kurkku.length()
on merkkijonon kurkku
pituuden kertovan metodin kutsu. Pisteen vasemman puoleinen osa kertoo kenen metodia kutsutaan.
Tee ohjelma, joka kysyy käyttäjän nimen ja ilmoittaa, kuinka monta kirjainta siinä on. Toteuta merkkijonon pituuden selvittäminen erilliseen metodiin public static int laskeKirjaimet(String merkkijono)
.
Anna nimi: Pekka Kirjainmäärä: 5
Anna nimi: Katariina Kirjainmäärä: 9
Huom! Rakenna ohjelmasi niin että laitat pituuden laskemisen omaan metodiinsa: public static int laskeKirjaimet(String merkkijono)
. Testit testaavat sekä metodia laskeKirjaimet
että koko ohjelman toimintaa.
Muita merkkijonojen metodeja
Merkkijonosta halutaan usein lukea jokin tietty osa. Tämä onnistuu mekkkijonojen eli String-luokan metodilla substring
. Metodia substring
voidaan käyttää kahdella tavalla: yksiparametrisenä palauttamaan merkkijonon loppuosa tai kaksiparametrisena palauttamaan parametrien määrittelemä osajono merkkijonosta:
String kirja = "Kalavale";
System.out.println(kirja.substring(4));
System.out.println(kirja.substring(2, 6));
vale lava
Koska substring
-metodin paluuarvo on String
-tyyppinen, voidaan metodin paluuarvo ottaa talteen String-tyyppiseen muuttujaan loppuosa.
String kirja = "8 veljestä";
String loppuosa = kirja.substring(2);
System.out.println("7 " + loppuosa); // tulostaa: 7 veljestä
7 veljestä
Tee ohjelma, joka tulostaa sanan alkuosan. Ohjelma kysyy käyttäjältä sanan ja alkuosan pituuden. Käytä ohjelmassa metodia substring
.
Anna sana: esimerkki Alkuosan pituus: 4 Tulos: esim
Anna sana: esimerkki Alkuosan pituus: 7 Tulos: esimerk
Tee ohjelma, joka tulostaa sanan loppuosan. Ohjelma kysyy käyttäjältä sanan ja loppuosan pituuden. Käytä ohjelmassa merkkijonon metodia substring
.
Anna sana: esimerkki Loppuosan pituus: 4 Tulos: rkki
Anna sana: esimerkki Loppuosan pituus: 7 Tulos: imerkki
String-luokan metodit tarjoavat myös mahdollisuuden etsiä tekstistä tiettyä sanaa. Esimerkiksi sana "erkki" sisältyy tekstiin "merkki". Metodi indexOf()
etsii sille parametrina annettua sanaa merkkijonosta. Jos sana löytyy, metodi indexOf()
palauttaa sanan ensimmäisen kirjaimen indeksin, eli paikan (muista että paikkanumerointi alkaa nollasta!). Jos taas sanaa ei merkkijonosta löydy, metodi palauttaa arvon -1.
String sana = "merkkijono";
int indeksi = sana.indexOf("erkki"); //indeksin arvoksi tulee 1
System.out.println(sana.substring(indeksi)); //tulostetaan "erkkijono"
indeksi = sana.indexOf("jono"); //indeksin arvoksi tulee 6
System.out.println(sana.substring(indeksi)); //tulostetaan "jono"
indeksi = sana.indexOf("kirja"); //sana "kirja" ei sisälly sanaan "merkkijono"
System.out.println(indeksi); // tulostetaan -1
System.out.println(sana.substring(indeksi)); // virhe!
Tee ohjelma, joka kysyy käyttäjältä kaksi sanaa. Tämän jälkeen ohjelma kertoo onko toinen sana ensimmäisen sanan osana. Käytä ohjelmassa merkkijonon metodia indexOf
.
Anna 1. sana: suppilovahvero Anna 2. sana: ilo Sana 'ilo' on sanan 'suppilovahvero' osana.
Anna 1. sana: suppilovahvero Anna 2. sana: suru Sana 'suru' ei ole sanan 'suppilovahvero' osana.
Huom: toteuta ohjelmasi tulostus täsmälleen samassa muodossa kuin esimerkin tulostus!
Merkkijonon... metodit?
Merkkijonot poikkeavat luonteeltaan hieman esimerkiksi kokonaisluvuista. Kokonaisluvut ovat "pelkkiä arvoja", niiden avulla voi laskea ja niitä voi tulostella ruudulle:
int x = 1;
int y = 3;
y = 3 * x + 2;
System.out.println("y:n arvo nyt: " + y);
y:n arvo nyt: 5
Merkkijonot taas ovat hieman "älykkäämpiä" ja tietävät esimerkiksi pituutensa:
String sana1 = "Ohjelmointi";
String sana2 = "Java";
System.out.println("merkkijonon " + sana1 + " pituus: " + sana1.length());
System.out.println("merkkijonon " + sana2 + " pituus: " + sana2.length());
Tulostuu:
merkkijonon Ohjelmointi pituus on 11 merkkijonon Java pituus on 4
Pituus saadaan selville kutsumalla merkkijonon metodia length()
. Merkkijonoilla on joukko muitakin metodeja. Kokonaisluvuilla eli int
:eillä ei ole metodeja ollenkaan, ne eivät itsessään "osaa" mitään. Mistä tässä oikein on kyse?
Olioihin liittyy sekä metodeja että arvoja
Merkkijonot ovat olioita, joihin liittyy sekä merkkijonon teksti että metodeja, joilla tekstiä voi käsitellä. Kun puhumme olioista, tarkoitamme tietynlaista muuttujaa. Jatkossa tulemme näkemään hyvin paljon muitakin olioita kuin merkkijonoja.
Oliomuuttujan metodia kutsutaan lisäämällä muuttujan nimen perään piste ja metodin nimi. Näiden lisäksi tulee sulut sekä mahdolliset parametrit:
String sana1 = "Ohjelmointi";
String sana2 = "Java";
sana1.length(); // kutsutaan merkkijono-olion sana1 metodia length()
sana2.length(); // kutsutaan merkkijono-olion sana2 metodia length()
Metodikutsu kohdistuu nimenomaan sihen olioon, mille metodia kutsutaan. Yllä kutsumme ensin sana1
-nimisen merkkijonon length()
-metodia, sitten merkkijonon sana2
metodia length()
.
Vanha tuttumme lukija
on myös olio:
Scanner lukija = new Scanner(System.in);
Lukijat ja merkkijonot ovat molemmat oliota, mutta ne ovat kuitenkin varsin erilaisia. Lukijoilla on mm. metodi nextLine()
jota merkkijonoilla ei ole. Javassa oliot "synnytetään" eli luodaan melkein aina komennolla new
, merkkijonot muodostavat tässä suhteessa poikkeuksen! -- Merkkijonoja voi luoda kahdella tavalla:
String banaani = new String("Banaani");
String porkkana = "porkkana";
Kumpikin ylläolevista riveistä luo uuden merkkijono-olion. Merkkijonojen luonnissa new
-komentoa käytetään hyvin harvoin.
Olion "tyypistä" puhuttaessa puhutaan usein luokista. Merkkijonojen luokka on String
, lukijoiden luokka taas on Scanner
. Opimme jatkossa luokista ja olioista paljon lisää.
Olio-ohjelmointi
Olio-ohjelmoinnissa on kyse ratkaistavassa ongelmassa esiintyvien käsitteiden eristämisestä omiksi kokonaisuuksikseen sekä näiden kokonaisuuksien käyttämistä ongelman ratkaisemisessa. Kun ongelmaan liittyvät käsitteet on tunnistettu, niistä voidaan myös keskustella. Toisin ajatellen muodostamme ratkaistavasta ongelmasta abstraktioita, joiden avulla ongelmaa on helpompi käsitellä.
Kun ongelmasta tunnistetaan käsitteitä, voidaan niitä vastaavia rakenteita luoda myös ohjelmaan. Näitä rakenteita ja niistä luotavia yksittäisiä ilmentymiä eli olioita käytetään ongelman ratkaisemisessa. Nyt ehkä käsittämättömältä tuntuva lausahdus ohjelma rakennetaan pienistä selkeistä yhteistoiminnassa olevista olioista alkaa hiljalleen kurssin edetessä tuntua järkeenkäyvältä ja jopa itsestäänselvältä.
Luokka ja Olio
Olemme käyttäneet jo luokkia ja olioita, joita Java tarjoaa. Luokka määrittelee olioiden ominaisuudet eli niihin liittyvät tiedot eli oliomuuttujat, jotka määrittelevät yksittäisen olion sisäisen tilan, ja niiden tarjoamat komennot eli metodit. Olio luodaan luokkaan kirjoitetun määrittelyn perusteella. Samasta luokasta voidaan luoda useampia olioita, joilla jokaisella on eri tila eli jokaisella on omat oliomuuttujien arvot. Jokaisella oliolla on myös metodit, jotka olion luomiseen käytetyssä luokassa on määritelty.
Metodi on luokkaan kirjoitettu lähdekoodista koostuva kokonaisuus, jolle on annettu nimi, ja jota voidaan kutsua. Metodi liittyy aina tiettyyn luokkaan, ja sitä käytetään usein luokasta tehdyn olion sisäisen tilan muokkaamiseen.
Esimerkiksi Scanner
on Javan tarjoama luokka, josta luotuja olioita olemme hyödyntäneet ohjelmissamme. Alla ohjelmassa luodaan Scanner-olio nimeltä lukija
, jota käytetään kokonaislukumuuttujien lukemiseen.
// luodaan Scanner-luokasta olio, jonka nimeksi tulee lukija
Scanner lukija = new Scanner(System.in);
while (true) {
int luku = Integer.parseInt(lukija.nextLine());
if (luku == 0) {
break;
}
System.out.println("Luettu " + luku);
}
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(..)
. Konstruktorit saavat parametreja kuten muutkin metodit.
Luokka kuvaa siitä luotavien olioiden "rakennuspiirustukset". Otetaan analogia tietokoneiden ulkopuoleisesta maailmasta. Rintamamiestalot lienevät monille tuttuja. Voidaan ajatella, että jossain on olemassa piirustukset jotka määrittelevät minkälainen rintamamiestalo on. Piirrustukset ovat luokka, eli ne määrittelevät minkälaisia olioita luokasta voidaan luoda:
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":
Osoitteessa https://emo-2014.herokuapp.com o visuaalinen johdanto olio-ohjelmointiin. Johdannon läpikäynti vie korkeintaan 15 minuuttia -- katso kyseinen johdanto nyt.
Johdanto sijaitsee pilvipalvelussa, jonka käynnistyminen voi viedä hetken. Johdannon katsominen ja siihen liittyviin kysymyksiin vastaaminen lasketaan yhdeksi tehtäväksi.
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ä.
Tässäkin tehtävässä on käytössä edellisessä tehtävässä mukana ollut luokka Tili
.
Tee ohjelma joka:
- Luo tilin nimeltä
"Matin tili"
saldolla 1000 - Luo tilin nimeltä
"Oma tili"
saldolla 0 - Nostaa matin tililtä 100.0
- Panee omalle tilille 100.0
- Tulostaa molemmat tilit
Luokan luominen
Luokka määrittelee minkälaisia luokasta luotavat oliot ovat:
- mitä metodeita olioilla on
- minkälainen olioiden tila on tai toisinsanoen mitä muuttujia olioilla on
Tutustutaan seuraavaksi oman luokan luomiseen sekä luokkaan liittyvien oliomuuttujien määrittelyyn.
Luokka määritellään kuvaamaan jotain mielekästä kokonaisuutta. 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) {
}
}
Uuden luokan luominen NetBeansissa tapahtuu valitsemalla 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. Usein ohjelmoinnin edetessä luokka elää ja muuttaa muotoaan, joten on myös mahdollista että luokka nimetään uudelleen.
Luokkien, muuttujien ja metodien nimissä ei tyypillisesti käytetä ääkkösiä. Vältä niiden käyttöä myös tässä.
Luodaan luokka nimeltä Henkilo
. Luokkaa varten luodaan erillinen nimeltä Henkilo.java
. Ohjelmamme koostuu nyt siis kahdesta erillisestä tiedostosta, sillä myös pääohjelma on omassa tiedostossaan. Aluksi Henkilo.java -tiedosto sisältää luokan määrittelyn public class Henkilo sekä luokan sisällön rajaavat aaltosulut.
public class Henkilo {
}
Luokkaa kuvaamaan voi piirtää myös luokkakaavion, jonka merkintätekniikkaan tutustutaan tässä samalla. Henkilo-niminen luokka, jossa ei ole mitään sisällä näyttää seuraavalta:
Luokka määrittelee mitä toiminnallisuuksia ja ominaisuuksia luokasta luotavilla olioilla on. Päätetään, että jokaisella henkilöoliolla 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ä jokaisella Henkilo
-luokasta luotavalla oliolla on nimi
ja ika
. Luokan sisälle määriteltyjä muuttujia kutsutaan oliomuuttujiksi tai olion kentiksi tai olion attribuuteiksi. Muitakin nimiä tuntuu löytyvän.
Oliomuuttujat kirjoitetaan luokan määrittelyä "public class Henkilo {" seuraaville riveille. Jokaisen muuttujan eteen asetetaan avainsana private. Avainsana private tarkoittaa sitä, että muuttujat ovat "piilossa" olion sisällä. Tätä kutsutaan kapseloinniksi.
Luokkaakaaviossa luokkaan liittyvät muuttujat määritellään muodossa "muuttujanNimi: muuttujanTyyppi". Miinusmerkki ennen muuttujan nimeä kertoo, että muuttuja on kapseloitu (sillä on avainsana private).
Olemme nyt määritelleet rakennuspiirustukset -- luokan -- henkilöoliolle. Jokaisella uudella henkilöolioilla on muuttujat nimi
ja ika
, joissa voi olla oliokohtainen arvo. Henkilöiden "tila" koostuu niiden nimeen ja ikään asetetuista arvoista.
Uuden luokan saa lisättyä NetBeansissa seuraavasti: Ruudun vasemmalla reunalla on projektilistaus (Projects). Paina projektin nimen kohdalla hiiren oikeaa nappia. Valitse avautuvasta valikosta New ja Java Class. Anna luokan nimeksi (Class Name) Koira
.
Tässä tehtävässä harjoittelet luokan luomista. Luo tehtäväpohjaan luokka nimeltä Koira
ja lisää sille oliomuuttujat private String nimi
, private String rotu
ja private int ika
. Luokkakaaviona luokka näyttää seuraavalta:
Luokalla ei vielä oikeastaan tee mitään, mutta tämän askeleen harjoittelusta on hyötyä myöhempää ajatellen.
Konstruktorin määrittely
Konstruktoria käytetään olion luomiseen luokasta.
Luotavalle oliolle halutaan asettaa alkutila. Itse määritellyn olion luominen tapahtuu hyvin samaan tapaan kuin Javan valmiiden olioiden kuten ArrayList
:ien luominen. Oliot luodaan new
-komennolla. Olion luomisen yhteydessä on kätevää pystyä antamaan arvot luotavan olion muuttujille. Esimerkiksi uutta henkilö-oliota luotaessa olisi kätevää pystyä antamaan oliolle nimi:
public static void main(String[] args) {
Henkilo ada = new Henkilo("Ada");
// ...
}
Tämä onnistuu määrittelemällä olion luova metodi eli konstruktori. Konstruktori määritellään oliomuuttujien jälkeen. Seuraavassa esimerkissä Henkilo-luokalle on määritelty konstruktori, jota voidaan käyttää uuden Henkilo-olion luomiseen. Konstruktori asettaa luotavan olion iäksi 0 ja nimeksi konstruktorin parametrina annettavan merkkijonon:
public class Henkilo {
private String nimi;
private int ika;
public Henkilo(String nimiAlussa) {
this.ika = 0;
this.nimi = nimiAlussa;
}
}
Konstruktorin nimi on aina sama kuin luokan nimi. Yllä luokka (class) on Henkilo, joten konstruktorin nimeksi tulee Henkilo. Konstruktorille annetaan lisäksi parametrina luotavan henkilööolion nimi. Parametri asetetaan sulkuihin konstruktorin nimen perään. Parametreja mahdollisesti sisältävien sulkujen jälkeen tulee aaltosulut, joiden sisälle määritellään lähdekoodi, jonka ohjelma suorittaa konstruktorikutsun (esim. new Henkilo("Ada")
) yhteydessä.
Oliot luodaan aina konstruktorin avulla.
Muutama huomio: konstruktorin sisällä on lauseke this.ika = 0
. Lausekkeessa asetetaan juuri luotavan olion (eli "tämän" olion) oliomuuttujan ika arvoksi 0. Toinen lauseke this.nimi = nimiAlussa;
taas asettaa juuri tämän olion sisäiselle muuttujalle nimi
arvoksi parametrina annetun merkkijonon.
Koska oliomuuttujat on määritelty konstruktorin aaltosulkujen ulkopuolella, voi niitä käyttää myös konstruktorin sisällä.
Nyt luokkakaavioon on merkitty luokan nimen ja muuttujien lisäksi myös konstruktori. Konstruktori saa public näkyvyysmääreen takia eteen plussan, jonka lisäksi siitä merkitään sen nimi ja parametrin tyypit (tässä + Henkilo(String)
).
Vielä yksi huomio: jos ohjelmoija ei tee luokalle konstruktoria, tekee Java automaattisesti luokalle oletuskonstruktorin. Oletuskonstruktori on konstruktori joka ei tee mitään. Jos konstruktoria ei jostain syystä tarvita, ei sellaista tarvitse ohjelmoida.
Uuden luokan saa lisättyä seuraavasti: Ruudun vasemmalla reunalla on projektilistaus. Paina projektin nimen kohdalla hiiren oikeaa nappia. Valitse avautuvasta valikosta New ja Java Class. Jos haluat että luokan nimi on Luokka, aseta luokan nimeksi (Class Name) Luokka
.
Luo luokka nimeltä Luokka
. Luokalla on oliomuuttujina private String koodi
, esimerkiksi "B221", ja private int istumapaikat
. Luo tämän jälkeen konstruktori public Luokka(String luokanKoodi, int istumapaikkojenMaara)
, minkä avulla oliomuuttujiin asetetaan arvot.
Tälläkään luokalla ei vielä oikeastaan tee mitään, mutta seuraavassa tehtävässä luokastamme tehdyllä oliolla voi jo tulostella :).
Metodien määrittely
Alkaa olla korkea aika päästä käyttämään Henkilo-luokasta luotuja olioita. Osaamme luoda olion ja alustaa olion muuttujat. Järkevään toimintaan pystyäkseen olioilla on oltava myös metodeja. Metodi on luokkaan kirjoitettu lähdekoodista koostuva kokonaisuus, jolle on annettu nimi, ja jota voidaan kutsua. Metodi liittyy aina tiettyyn luokkaan, ja sitä käytetään usein luokasta tehdyn olion sisäisen tilan muokkaamiseen.
Tehdään luokalle Henkilo metodi, jota käytetään olion tietojen tulostamiseen.
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 kirjoitetaan luokan sisälle konstruktorin alapuolelle. Metodin nimen eteen tulee public void
sillä metodin on tarkoitus näkyä ulkomaailmalle ("public") ja metodi ei palauta arvoa ("void").
Aiemmin toteuttamissamme metodeissa on ollut käytössä määre static
. Määre static
viittaa siihen, että metodi ei liity olioon ja sen avulla ei voi käsitellä oliolle määriteltyjä muuttujia.
Metodeistamme puuttuu jatkossa määre static
jos ne käsittelevät olioiden tietoa. Jos taas metodit eivät käsittele olioihin liittyvää tietoa, niissä voidaan käyttää määrettä static.
Luokkakaavioon on merkitty luokan nimen, oliomuuttujien ja konstruktorin lisäksi nyt myös metodi tulostaHenkilo
. Koska metodilla on public-määre, tulee sille alkuun plus, jota seuraa metodin nimi. Metodille ei ole määritelty parametreja, joten ei myöskään piirretä metodin sulkujen sisälle. Metodille merkitään myös tieto siitä, että se ei palauta arvoa, tässä "void".
Metodin tulostaHenkilo
sisällä on yksi koodirivi joka käyttää hyvakseen oliomuuttujia nimi
ja ika
-- luokkakaavio ei kerro sisäisestä toteutuksesta. Olion sisäisiin muuttujiin viitataan 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 ada = new Henkilo("Ada");
Henkilo antti = new Henkilo("Antti");
Henkilo martin = new Henkilo("Martin");
ada.tulostaHenkilo();
antti.tulostaHenkilo();
martin.tulostaHenkilo();
}
}
Tulostuu:
Ada, ikä 0 vuotta Antti, ikä 0 vuotta Martin, ikä 0 vuotta
Sama screencastina:
Luo luokka nimeltä Pilli
. Pillillä on oliomuuttujina private String aani
. Luo tämän jälkeen konstruktori public Pilli(String pillinAani)
, minkä avulla luodaan uusi pilli, jolle annetaan ääni.
Lisää pillille vielä metodi public void soi()
, joka tulostaa pillin äänen.
Pillin tulee toimia seuraavasti.
Pilli sorsapilli = new Pilli("Kvaak");
Pilli kukkopilli = new Pilli("Peef");
sorsapilli.soi();
kukkopilli.soi();
sorsapilli.soi();
Kvaak Peef Kvaak
Luo luokka nimeltä Ovi
. Ovella ei ole oliomuuttujia. Luo sille parametriton konstruktori (tai käytä oletuskonstruktoria). Luo tämän jälkeen ovelle metodi public void koputa()
, jota kutsuttaessa tulostuu viesti "Who's there?".
Oven tulee toimia seuraavasti.
Ovi alexander = new Ovi();
alexander.koputa();
alexander.koputa();
Who's there? Who's there?
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 kohdalla hiiren oikeaa nappia. Valitse avautuvasta valikosta New ja Java Class. Anna luokan nimeksi (Class Name) Tuote
.
Luokalla tulee olla:
- Konstruktori
public Tuote(String nimiAlussa, double hintaAlussa, int maaraAlussa)
- Metodi
public void tulostaTuote()
joka tulostaa tuotteen tiedot tässä muodossa:Banaani, hinta 1.1, 13 kpl
Piirrä myös luokkaan liittyvä luokkakaavio itsellesi!
Oliomuuttujan arvon muuttaminen metodissa
Lisätään aiemmin rakentamallemme Henkilo-luokalle metodi, joka kasvattaa henkilön ikää vuodella:
public class Henkilo {
// ...
public void vanhene() {
this.ika = this.ika + 1;
}
}
Metodi kirjoitetaan tulostaHenkilo
-metodin tapaan luokan Henkilo
sisälle. Metodissa kasvatetaan oliomuuttujan ika
arvoa yhdellä.
Myös luokkakaavio päivittyy.
Kutsutaan metodia ja katsotaan mitä tapahtuu:
public class Main {
public static void main(String[] args) {
Henkilo ada = new Henkilo("Ada");
Henkilo antti = new Henkilo("Antti");
ada.tulostaHenkilo();
antti.tulostaHenkilo();
System.out.println("");
ada.vanhene();
ada.vanhene();
ada.tulostaHenkilo();
antti.tulostaHenkilo();
}
}
Ohjelman tulostus on seuraava:
Ada, ikä 0 vuotta Antti, ikä 0 vuotta Ada, ikä 2 vuotta Antti, ikä 0 vuotta
Eli "syntyessään" molemmat oliot ovat nollavuotiaita (konstruktorissa suoritetaan mm. rivi this.ika = 0;
). Olion ada
metodia vanhene
kutsutaan kaksi kertaa. Kuten tulostus näyttää, tämä saa aikaan sen että Adan ikä on vanhenemisen jälkeen 2 vuotta. Kutsumalla metodia Adaa vastaavalle oliolle, toisen henkilöolion ikä ei muutu, sillä jokaiselle luokasta luotavalle oliolle luodaan myös omat oliomuuttujat.
Tässä tehtävässä on useampi osa. Jokainen osa vastaa yhtä tehtäväpistettä.
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ä.
Metodin vahene() toteutus
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.
Laskurin arvo ei saa olla negatiivinen
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ä. Ehtolause auttaa tässä.
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
Laskurin arvon nollaus
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
Laskurin arvon palautus
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.
Luo luokka Velka
, jolla on double-tyyppiset oliomuuttujat saldo
ja korkokerroin
. Saldo ja korkokerroin annetaan konstruktorin parametrina public Velka(double saldoAlussa, double korkokerroinAlussa)
.
Luo luokalle myös metodit public void tulostaSaldo()
sekä public void odotaVuosi()
. Metodi tulostaSaldo tulostaa tämän hetkisen saldon, ja metodi odotaVuosi kasvattaa velan määrää.
Velan määrän kasvattaminen tapahtuu kertomalla saldo korkokertoimella.
Luokan tulee toimia seuraavasti:
public class Paaohjelma {
public static void main(String[] args) {
Velka asuntolaina = new Velka(120000.0, 1.01);
asuntolaina.tulostaSaldo();
asuntolaina.odotaVuosi();
asuntolaina.tulostaSaldo();
int vuosia = 0;
while(vuosia < 20) {
asuntolaina.odotaVuosi();
vuosia++;
}
asuntolaina.tulostaSaldo();
}
}
Ylläolevassa esimerkissä havainnollistetaan asuntolainan kehitystä prosentin korolla.
Tulostus:
120000.0 121200.0 147887.0328416936
Kun saat ohjelman toimimaan, tarkastele edelläolevaa esimerkkiä myös 1990-luvun alkupuolen laman korkokertoimilla. Tällöin korko oli jopa 15-20 prosenttia -- muuta yllä olevan esimerkin korkokertoimeksi 1.20
ja katso miten käy.
Arvon palauttaminen metodista
Metodi voi palauttaa arvon. Tähän mennessä olioihin luomamme metodit eivät palauttaneet mitään. Tämä on merkitty kirjoittamalla metodin määrittelyyn avainsana void.
public class Ovi {
public void koputa() {
...
}
}
Avainsana void tarkoittaa että metodi ei palauta arvoa.
Jos haluamme, että metodi palauttaa arvon, tulee avainsanan void
paikalle asettaa palautettavan muuttujan tyyppi. Seuraavassa esimerkissä näkyvälle luokalle Opettaja on määritelty metodi arvostele
, joka palauttaa aina kokonaislukutyyppisen (int
) muuttujan (tässä arvo 10). Arvon palauttaminen tapahtuu aina komennolla return:
public class Opettaja {
public int arvostele() {
return 10;
}
}
Ylläoleva metodi siis palauttaa sitä kutsuttaessa int
-tyyppisen arvon 10
. Jotta metodin palauttamaa arvoa voisi käyttää, tulee se ottaa talteen muuttujaan. Tämä tapahtuu samalla tavalla kuin normaali muuttujan arvon asetus, eli yhtäsuuruusmerkillä:
public static void main(String[] args) {
Opettaja opettaja = new Opettaja();
int arvostelu = opettaja.arvostele();
System.out.println("Arvosanaksi tuli " + arvostelu);
}
Metodin paluuarvo sijoitetaan int
-tyyppiseen muuttujaan aivan kuin mikä tahansa muukin int-arvo. Paluuarvo voi toimia myös osana mitä tahansa lauseketta:
public static void main(String[] args) {
Opettaja opettaja = new Opettaja();
double keskiarvo = (opettaja.arvostele() + opettaja.arvostele()) / 2.0;
System.out.println("Arvostelujen keskiarvo " + keskiarvo);
}
Kaikki tähän mennessä näkemämme muuttujatyypit voidaan myös palauttaa metodista. Yhteenveto:
- Metodilla, joka ei palauta mitään, on
void
-määre palautettavan muuttujan tyyppinä.public void metodiJokaEiPalautaMitaan() { // metodin runko }
- Metodilla, joka palauttaa kokonaislukutyyppisen muuttujan, on
int
-määre palautettavan muuttujan tyyppinä.public int metodiJokaPalauttaaKokonaisLuvun() { // metodin runko, tarvitsee return-komennon }
- Metodilla, joka palauttaa merkkijonotyyppisen muuttujan, on
String
-määre palautettavan muuttujan tyyppinä.public String metodiJokaPalauttaaTekstin() { // metodin runko, tarvitsee return-komennon }
- Metodilla, joka palauttaa liukulukutyyppisen muuttujan, on
double
-määre palautettavan muuttujan tyyppinä.public double metodiJokaPalauttaaLiukuluvun() { // metodin runko, tarvitsee return-komennon }
Lisätään Henkilölle metodi joka palauttaa henkilön iän:
public class Henkilo {
// ...
public int palautaIka() {
return this.ika;
}
}
Luokka kokonaisuudessaan:
Havainnollistetaan metodin toimintaa:
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");
}
}
Pekan ikä 2 Antin ikä 1 Pekka ja Antti yhteensä 3 vuotta
Seuraa materiaalin tähänastista esimerkkiä ja luo luokka Henkilo. Henkilön tulee sisältää seuraavan luokkakaavion määrittelemät ominaisuudet edellä mainittujen esimerkkien mukaisesti.
Henkilo pekka = new Henkilo("Pekka");
Henkilo antti = new Henkilo("Antti");
antti.tulostaHenkilo();
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");
Antti, ikä 0 vuotta Pekan ikä 2 Antin ikä 1 Pekka ja Antti yhteensä 3 vuotta
Luo luokka nimeltä Musiikkikappale
. Musiikkikappaleella on oliomuuttujat nimi
(merkkijono) ja pituus
sekunteina (kokonaisluku). Molemmat asetetaan konstruktorissa public Musiikkikappale(String kappaleenNimi, int kappaleenPituus)
. Lisää oliolle myös metodit public String nimi()
, joka palauttaa kappaleen nimen, ja public int pituus()
, joka palauttaa kappaleen pituuden.
Luokan tulee toimia seuraavasti.
Musiikkikappale garden = new Musiikkikappale("In The Garden", 10910);
System.out.println("Kappaleen " + garden.nimi() + " pituus on " + garden.pituus() + " sekuntia.");
Kappaleen In The Garden pituus on 10910 sekuntia.
Henkilo-luokka laajenee
Tehdään seuraavaksi 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".
Luokka kokonaisuudessaan:
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
Luo luokka Elokuva, jolla on oliomuuttujat nimi
(String) ja ikaraja
(int). Tee luokalle konstruktori public Elokuva(String elokuvanNimi, int elokuvanIkaraja)
sekä metodit public String nimi()
ja public int ikaraja()
. Ensimmäinen palauttaa elokuvan nimen ja toinen elokuvan ikärajan.
Esimerkki luokan toiminnasta.
Elokuva chipmunks = new Elokuva("Alvin and the Chipmunks: The Squeakquel", 0);
Scanner lukija = new Scanner(System.in);
System.out.println("Minkä ikäinen olet?");
int ika = Integer.parseInt(lukija.nextLine());
System.out.println();
if (ika >= chipmunks.ikaraja()) {
System.out.println("Saat katsoa elokuvan " + chipmunks.nimi());
} else {
System.out.println("Et saa katsoa elokuvaa " + chipmunks.nimi());
}
Minkä ikäinen olet? 7 Saat katsoa elokuvan Alvin and the Chipmunks: The Squeakquel
Olion merkkijonoesitys ja toString-metodi
Olemme syyllistyneet 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, jota 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 tulostaHenkilo
-metodin.
Olioscreencastin toinen osa:
Tehtäväpohjassa on määriteltynä luokka Agentti, jolla on etunimi ja sukunimi. Luokalle on määritelty metodi tulosta
, joka luo seuraavanlaisen merkkijonoesityksen.
Agentti bond = new Agentti("James", "Bond");
bond.tulosta();
My name is Bond, James Bond
Poista luokan metodi tulosta
ja luo luokalle metodi public String toString()
, joka palauttaa edellämainitun merkkijonoesityksen.
Luokan tulee toimia jatkossa seuraavasti.
Agentti bond = new Agentti("James", "Bond");
bond.toString(); // ei tulosta mitään
System.out.println(bond);
Agentti ionic = new Agentti("Ionic", "Bond");
System.out.println(ionic);
My name is Bond, James Bond My name is Bond, Ionic Bond
Helsingin Yliopiston opiskelijaruokaloissa eli Unicafeissa opiskelijat maksavat lounaansa käyttäen maksukorttia. Lopullinen Maksukortti tulee näyttämään luokkakaaviona seuraavalta:
Tässä tehtäväsäsarjassa tehdään luokka Maksukortti
, jonka tarkoituksena on jäljitellä Unicafeissa tapahtuvaa maksutoimintaa.
Luokan runko
Projektiin tulee kuulumaan kaksi kooditiedostoa:
Tehtäväpohjan mukana tulee kooditiedosto Paaohjelma
jonka sisällä on main
-metodi.
Lisää projektiin uusi luokka nimeltä Maksukortti
. Uuden luokan saa lisättyä seuraavasti: Ruudun vasemmalla reunalla on projektilistaus. Paina projektin nimen kohdalla hiiren oikeaa nappia. Valitse avautuvasta valikosta New ja Java Class. Anna luokan nimeksi (Class Name) Maksukortti
.
Tee ensin Maksukortti
-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 Maksukortti
runko:
public class Maksukortti {
private double saldo;
public Maksukortti(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) {
Maksukortti kortti = new Maksukortti(50);
System.out.println(kortti);
}
}
Ohjelman tulisi tuottaa seuraava tulostus:
Kortilla on rahaa 50.0 euroa
Kortilla maksaminen
Täydennä Maksukortti
-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.60 eurolla ja metodin syoMaukkaasti
tulisi vähentää kortin saldoa 4.60 eurolla.
Seuraava pääohjelma testaa luokkaa:
public class Paaohjelma {
public static void main(String[] args) {
Maksukortti kortti = new Maksukortti(50);
System.out.println(kortti);
kortti.syoEdullisesti();
System.out.println(kortti);
kortti.syoMaukkaasti();
kortti.syoEdullisesti();
System.out.println(kortti);
}
}
Ohjelman tulisi tuottaa kutakuinkin seuraava tulostus:
Kortilla on rahaa 50.0 euroa Kortilla on rahaa 47.4 euroa Kortilla on rahaa 40.199999999999996 euroa
Ei-negatiivinen saldo
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) {
Maksukortti kortti = new Maksukortti(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 0.40000000000000036 Kortilla on rahaa 0.40000000000000036
Yllä toinen metodin syoMaukkaasti
kutsu ei vaikuttanut saldoon, koska saldo olisi mennyt negatiiviseksi.
Kortin lataaminen
Lisää Maksukortti
-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) {
Maksukortti kortti = new Maksukortti(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
Kortin lataus negatiivisella arvolla
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) {
Maksukortti kortti = new Maksukortti(10);
System.out.println("Pekka: " + kortti);
kortti.lataaRahaa(-15);
System.out.println("Pekka: " + kortti);
}
}
Ohjelman tulisi tuottaa seuraava tulostus:
Pekka: Kortilla on rahaa 10.0 euroa Pekka: Kortilla on rahaa 10.0 euroa
Monta korttia
Tee pääohjelma, joka sisältää seuraavan tapahtumasarjan:
- Luo Pekan kortti. Kortin alkusaldo on 20 euroa
- Luo Matin kortti. Kortin alkusaldo on 30 euroa
- Pekka syö maukkaasti
- Matti syö edullisesti
- Korttien arvot tulostetaan (molemmat omalle rivilleen, rivin alkuun kortin omistajan nimi)
- Pekka lataa rahaa 20 euroa
- Matti syö maukkaasti
- Korttien arvot tulostetaan (molemmat omalle rivilleen, rivin alkuun kortin omistajan nimi)
- Pekka syö edullisesti
- Pekka syö edullisesti
- Matti lataa rahaa 50 euroa
- Korttien arvot tulostetaan (molemmat omalle rivilleen, rivin alkuun kortin omistajan nimi)
Pääohjelman runko on seuraava:
public class Main {
public static void main(String[] args) {
Maksukortti pekanKortti = new Maksukortti(20);
Maksukortti matinKortti = new Maksukortti(30);
// kirjoita koodia tähän
}
}
Ohjelman tulisi tuottaa seuraava tulostus:
Pekka: Kortilla on rahaa 15.4 euroa Matti: Kortilla on rahaa 27.4 euroa Pekka: Kortilla on rahaa 35.4 euroa Matti: Kortilla on rahaa 22.799999999999997 euroa Pekka: Kortilla on rahaa 30.199999999999996 euroa Matti: Kortilla on rahaa 72.8 euroa
Huomasit todennäköisesti, että osassa luvuista ilmenee pyöristysvirheitä. Esimerkiksi edellisessä tehtävässä Pekan saldo 30.7 saattaa tulostua muodossa 30.700000000000003
. Tämä liittyy siihen, että liukuluvut kuten double
tallennetaan oikeasti binäärimuodossa, eli nollina ja ykkösinä vain rajattua määrää lukuja käyttäen.
Koska liukulukuja on ääretön määrä (keksitkö miksi? kuinka monta liuku- tai desimaalilukua mahtuu vaikkapa lukujen 5 ja 6 väliin?), ei kaikkia liukulukuja yksinkertaisesti voi esittää rajatulla määrällä nollia ja ykkösiä. Tietokone joutuu siis rajoittamaan tallennustarkkuutta.
Normaalisti esimerkiksi tilien saldot tallennetaan kokonaislukuina siten, että arvo 1 vastaa esimerkiksi yhtä senttiä.
Metodin parametrit
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
Parametrilla ja oliomuuttujalla sama nimi!
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 -- koodi käytännössä asettaa parametrina saadulle pituus
-muuttujalle siinä jo olevan arvon:
public void setPituus(int pituus) {
// ÄLÄ TEE NÄIN!!!
pituus = pituus;
}
public void setPituus(int pituus) {
// VAAN NÄIN!!!
this.pituus = pituus;
}
Luo luokka Kertoja
jolla on:
- Konstruktori
public Kertoja(int luku)
. - Metodi
public int kerro(int toinenLuku)
joka palauttaa sille annetun luvuntoinenLuku
kerrottuna konstruktorille annetulla luvullaluku
.
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
Oman metodin kutsu
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:
Lukujen määrä
Tee luokka Lukutilasto
(tiedosto luomaasi luokkaa varten on tehtäväpohjassa valmiina), joka tuntee seuraavat toiminnot:
- metodi
lisaaLuku
lisää uuden luvun tilastoon - metodi
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
Summa ja keskiarvo
Laajenna luokkaa seuraavilla toiminnoilla:
- metodi
summa
kertoo lisättyjen lukujen summan (tyhjän lukutilaston summa on 0) - metodi
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
Summa käyttäjältä
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.
HUOM: älä muuta Lukutilasto-luokkaa millään tavalla!
Anna lukuja: 4 2 5 4 -1 Summa: 15
Monta summaa
Muuta edellistä ohjelmaa niin, että ohjelma laskee myös parillisten ja parittomien lukujen summaa.
HUOM: Määrittele ohjelmassa kolme Lukutilasto-olioa ja laske ensimmäisen avulla kaikkien lukujen summa, toisen avulla parillisten lukujen summa ja kolmannen avulla parittomien lukujen summa.
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)!
HUOM: älä muuta Lukutilasto-luokaa millään tavalla!
Ohjelman tulee toimia seuraavasti:
Anna lukuja: 4 2 5 2 -1 Summa: 13 Parillisten summa: 8 Parittomien summa: 5
Mistä olio-ohjelmoinnissa oikein on kyse: katsaus taaksepäin
Olio-ohjelmoinnissa on kyse pitkälti käsitteiden eristämisestä omiksi kokonaisuuksikseen tai toisin ajatellen abstraktioiden muodostamisesta. Voisi ajatella, että on turhaa luoda oliota jonka sisällä on ainoastaan luku, sillä saman voisi tehdä suoraan int
-muuttujilla. Asia ei kuitenkaan ole aina 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. Eräs kuuluisa ohjelmoija on sanonut "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". Koska viisari on oma selkeä käsitteensä, kannattaa ohjelman ymmärrettävyyden parantamiseksi siitä tehdä oma luokka.
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. Seuraavassa osiossa teemme myös 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.
Lausahdus ohjelma rakennetaan pienistä selkeistä yhteistoiminnassa olevista olioista tulee toistumaan kurssilla.