Tehtävät
Kurssi ensimmäinen koe

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 kurssiksi hy-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.

Kolmannen osan tavoitteet

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:

  1. Tee ohjelma, joka lukee luvun käyttäjältä ja tulostaa sen.
  2. Jos luku on jaollinen kolmella, tulosta luvun sijaan merkkijono "Fizz".
  3. Jos luku on jaollinen viidellä, tulosta luvun sijaan merkkijono "Buzz".
  4. 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.

  1. Tee ohjelma, joka lukee luvun käyttäjältä ja tulostaa sen.
  2. Jos luku on jaollinen kolmella ja viidellä, tulosta luvun sijan merkkijono "FizzBuzz".
  3. Jos luku on jaollinen kolmella, tulosta luvun sijaan merkkijono "Fizz".
  4. 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
public static void metodiJokaEiPalautaMitaan() {
    // metodin runko
}
Metodi palauttaa int-tyyppisen muuttujan
public static int metodiJokaPalauttaaKokonaisLuvun() {
    // metodin runko, tarvitsee return-komennon
}
Metodi palauttaa double-tyyppisen muuttujan
public static double metodiJokaPalauttaaLiukuluvun() {
    // metodin runko, tarvitsee return-komennon
}

Jos metodille määritellään paluuarvon tyyppi, on sen pakko palauttaa arvo. Esimerkiksi seuraava metodi on virheellinen.

public static 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.

Komento return lopettaa metodin suorituksen

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ä.

Merkkijonojen ja lukujen lukeminen

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.

Luokan ja olion suhde

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":

Visuaalinen johdanto oliohin

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:

  1. Luo tilin nimeltä "Matin tili" saldolla 1000
  2. Luo tilin nimeltä "Oma tili" saldolla 0
  3. Nostaa matin tililtä 100.0
  4. Panee omalle tilille 100.0
  5. 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

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").

Oliot ja määre static

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
Pyöristysvirheet

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

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.

Sisällysluettelo