Sisällysluettelo

Tehtävät

Kysely: Itsetunto

Tutkimus: Itsetunto

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

Tiedustelemme tällä lomakkeella itseänne koskevia piirteitä. Valitkaa jokaisesta kohdasta se vaihtoehto, joka parhaiten vastaa käsitystänne tällä hetkellä. Jos olet täysin samaa mieltä, valitse 1, jos olet lähes samaa mieltä, valitse 2, jos olet hieman eri mieltä, valitse 3, ja jos olet täysin eri mieltä, valitse 4.

 

 
Täysin samaa mieltä.
1
2
3
4
Täysin eri mieltä.

 

Kysely / tutkimus: Tehtävänvaihtotesti 2

Tässä osallistut taas kokeellisessa psykologiassa käytettyyn tehtävänvaihtotestiin, missä tutkitaan aivojen mukautumista kahden tai useamman samanaikaisen tehtävän suorittamiseen. Tavoitteenamme on selvittää vaikuttaako ohjelmointi aivojen kykyyn hoitaa useampia samanaikaisia tehtäviä. Tämä testi tehdään useamman kerran kurssin aikana.

Käytössä olevassa testiohjelmassa on neljä osaa. Ensimmäisessä osassa mitataan reaktioaikaa, toisessa osassa parittoman (odd) ja parillisen (even) luvun tunnistamista, kolmannessa osassa konsonantin (consonant) ja vokaalin (vowel) tunnistamista, ja viimeisessä osassa yhdistetään osat kaksi ja kolme. Näet jokaisen osion jälkeen edellisen osion tulokset.

Testin tulokset tulevat vain tutkimuskäyttöön, eikä mahdollisesti raportoitavista tuloksista voida yksilöidä yksittäisiä vastaajia. Tehtävän suorittamiseen menee noin 10-15 minuuttia.

Kun olet vastannut allaolevaan lomakkeeseen, testi aukeaa uudessa ikkunassa. Varmistathan, että selaimesi sallii uusien ikkunoiden avaamisen tästä ohjelmointimateriaalista!

Tehtävänvaihtotestin taustakysely

23 Satunnaisuus

Tietokoneohjelmissa satunnaisuutta käytetään esimerkiksi salausalgoritmeissa, koneoppimisessa sekä tietokonepelien ennustettavuuden vähentämisessä. Satunnaisuutta mallinnetaan käytännössä satunnaislukujen avulla, joiden luomiseen Java valmiin Random-luokan. Random-luokasta voi tehdä olion jota voi käyttää seuraavalla tavalla.

import java.util.Random;

public class Arvontaa {
    public static void main(String[] args) {
        Random arpoja = new Random(); // luodaan arpoja apuväline
        int i = 0;

        while (i < 10) {
            // Arvotaan ja tulostetaan jokaisella kierroksella satunnainen luku
            System.out.println(arpoja.nextInt(10));
            i++;
        }
    }
}

Yllä olevassa koodissa luodaan ensin Random-luokasta olio käyttäen avainsanaa new -- samoin kuin muitakin olioita luodessa. Random-olio tarjoaa metodin nextInt, jolle annetaan parametrina kokonaisluku. Metodi palauttaa satunnaisen kokonaisluvun väliltä 0..(annettu kokonaisluku - 1).

Ohjelman tulostus voisi olla vaikka seuraavanlainen:

2
2
4
3
4
5
6
0
7
8

Satunnaisia kokonaislukuja voidaan käyttää esimerkiksi nopanheittojen mallintamiseen.

Tehtävä 92: Nopan heittäminen

Tehtäväpohjassa on luokka Noppa, jonka runko on seuraava:

import java.util.Random;

public class Noppa {
    private Random random;
    private int tahkojenMaara;

    public Noppa(int tahkojenMaara) {
        this.random = new Random();
        // Alusta oliomuuttuja tahkojenMaara tässä 
    }

    public int heita() {
        // arvo täällä luku jonka tulee olla yhdestä tahkojen määrään
        // ([1-tahkojenMaara]) ja palauta se
    }
}

Muokkaa luokkaa siten, että sen konstruktoriNoppa(int tahkojenMaara) luo uuden noppa-olion annetulla nopan tahkojen (eri oman numeronsa sisältämien "puolien") määrällä. Muokkaa myös metodia heita siten, että se antaa satunnaisen nopanheiton tuloksen, jonka arvon tulee olla väliltä 1...tahkojen määrä.

Seuraavassa noppaa testaava pääohjelma:

public class Ohjelma {
    public static void main(String[] args) {
        Noppa noppa = new Noppa(6);

        int i = 0;
        while (i < 10) {
            System.out.println(noppa.heita());
            i++;
        }
    }
}

Tulostus voisi olla esimerkiksi seuraava:

1
6
3
5
3
3
2
2
6
1

Random-luokasta tehdyn olion kautta päästään käsiksi myös satunnaisiin liukulukuihin, joita käytetään muunmuassa todennäköisyyslaskennan yhteydessä; tietokoneilla todennäköisyyksiä simuloidaan yleensä väliltä [0..1] olevilla luvuilla.

Random-oliolta satunnaisia liukulukuja saa metodilla nextDouble. Tarkastellaan seuraavia säämahdollisuuksia:

  • Sataa räntää todennäköisyydellä 0.1 (10%)
  • Sataa lunta todennäköisyydellä 0.3 (30%)
  • Aurinko paistaa todennäköisyydellä 0.6 (60%)

Luodaan edellä olevista arvioista sääennustaja.

import java.util.ArrayList;
import java.util.Random;

public class SaaEnnustaja {
    private Random random;

    public SaaEnnustaja() {
        this.random = new Random();
    }

    public String ennustaSaa() {
        double todennakoisyys = this.random.nextDouble();

        if (todennakoisyys <= 0.1) {
            return "Sataa räntää";
        } else if (todennakoisyys <= 0.4) { // 0.1 + 0.3
            return "Sataa lunta";
        } else { // loput, 1.0 - 0.4 = 0.6
            return "Aurinko paistaa";
        }
    }

    public int ennustaLampotila() {
        return (int) (4 * this.random.nextGaussian() - 3);
    }
}

Metodi ennustaLampotila on monella tapaa mielenkiintoinen. Metodin sisällä tehtävä kutsu this.random.nextGaussian() on tavallinen metodikutsu, jonka kaltaisia olemme nähneet aikaisemminkin. Kiinnostavaa tässä Random-luokan ilmentymän tarjoamassa metodissa on se, että metodin palauttama luku on normaalijakautunut (jos et koe mielenkiintoa satunnaisuuden eri lajeihin se ei haittaa!).

public int ennustaLampotila() {
    return (int) (4 * this.random.nextGaussian() - 3);
}

Edellisessä lausekkeessa kiinnostava on myös osa (int). Tämä kohta lausekkeessa muuttaa suluissa olevan liukuluvun kokonaisluvuksi. Vastaavalla menetelmällä voidaan muuttaa myös kokonaislukuja liukuluvuiksi kirjoittamalla (double) kokonaisluku. Tätä kutsutaan eksplisiittiseksi tyyppimuunnokseksi.

Luodaan vielä pääohjelma josta luokkaa SaaEnnustaja käytetään.

public class Ohjelma {

    public static void main(String[] args) {
        SaaEnnustaja ennustaja = new SaaEnnustaja();

        // käytetään listaa apuvälineenä
        ArrayList<String> paivat = new ArrayList<>();
        Collections.addAll(paivat, "Ma", "Ti", "Ke", "To", "Pe", "La", "Su");

        System.out.println("Seuraavan viikon sääennuste:");
        for(String paiva : paivat) {
            String saaEnnuste = ennustaja.ennustaSaa();
            int lampotilaEnnuste = ennustaja.ennustaLampotila();

            System.out.println(paiva + ": " + saaEnnuste + " " + lampotilaEnnuste + " astetta.");
        }
    }
}

Ohjelman tulostus voisi olla esimerkiksi seuraavanlainen:

Seuraavan viikon sääennuste:
Ma: Sataa lunta 1 astetta.
Ti: Sataa lunta 1 astetta.
Ke: Aurinko paistaa -2 astetta.
To: Aurinko paistaa 0 astetta.
Pe: Sataa lunta -3 astetta.
La: Sataa lunta -3 astetta.
Su: Aurinko paistaa -5 astetta

Tehtävä 93: Salasanan arpoja

Tehtävänäsi on täydentää luokkaa SalasananArpoja, jossa on seuraavat toiminnot:

  • konstruktori SalasananArpoja luo uuden olion, joka käyttää annettua salasanan pituutta
  • metodi luoSalasana palauttaa uuden, merkeistä a-z muodostetun konstruktorin parametrin määräämän pituisen salasanan

Luokan runko on seuraava:

import java.util.Random;

public class SalasananArpoja {
    // Määrittele muuttuja tässä

    public SalasananArpoja(int salasananPituus) {
        // Alusta muuttuja tässä
    }

    public String luoSalasana() {
        // Kirjoita tähän koodi, joka palauttaa uuden salasanan
    }
}

Seuraavassa ohjelma joka käyttää SalasananArpoja-olioa:

public class Ohjelma {
    public static void main(String[] args) {
        SalasananArpoja arpoja = new SalasananArpoja(13);
        System.out.println("Salasana: " + arpoja.luoSalasana());
        System.out.println("Salasana: " + arpoja.luoSalasana());
        System.out.println("Salasana: " + arpoja.luoSalasana());
        System.out.println("Salasana: " + arpoja.luoSalasana());
    }
}

Ohjelman tulostus voisi näyttää seuraavalta:

Salasana: mcllsoompezvs
Salasana: urcxboisknkme
Salasana: dzaccatonjcqu
Salasana: bpqmedlbqaopq

Vihje1: näin muutat kokonaisluvun luku kirjaimeksi:

int luku = 17;
char merkki = "abcdefghijklmnopqrstuvwxyz".charAt(luku);

Tehtävä 94: Lottoarvonta

Tehtävänäsi on täydentää luokkaa LottoRivi, joka arpoo viikon lottonumerot. Lottonumerot ovat väliltä 1–39 ja niitä arvotaan 7. Lottorivi koostuu siis 7:stä eri numerosta väliltä 1–39. Luokassa on seuraavat toiminnot:

  • konstruktori LottoRivi luo uuden LottoRivi-olion joka sisältää uudet, arvotut numerot
  • metodi numerot palauttaa tämän lottorivin lottonumerot
  • metodi sisaltaaNumeron kertoo onko arvotuissa numeroissa annettu numero
  • metodi arvoNumerot arpoo riville uudet numerot

Luokan runko on seuraava:

import java.util.ArrayList;
import java.util.Random;

public class LottoRivi {
    private ArrayList<Integer> numerot;

    public LottoRivi() {
        // Arvo numerot heti LottoRivin luomisen yhteydessä
        this.arvoNumerot();
    }

    public ArrayList<Integer> numerot() {
        return this.numerot;
    }

    public boolean sisaltaaNumeron(int numero) {
        // Testaa tässä onko numero jo arvottujen numeroiden joukossa
    }

    public void arvoNumerot() {
        // Alustetaan lista numeroille
        this.numerot = new ArrayList<>();
        // Kirjoita numeroiden arvonta tänne käyttämällä metodia sisaltaaNumeron()
    }
}

Tehtäväpohjan mukana tulee seuraava pääohjelma:

import java.util.ArrayList;

public class Ohjelma {
    public static void main(String[] args) {
        LottoRivi lottoRivi = new LottoRivi();
        ArrayList<Integer> lottonumerot = lottoRivi.numerot();

        System.out.println("Lottonumerot:");
        for (int numero : lottonumerot) {
            System.out.print(numero + " ");
        }
        System.out.println("");
    }
}

Ohjelman mahdollisia tulostuksia ovat seuraavat:

Lottonumerot:
3 5 10 14 15 27 37
Lottonumerot:
2 9 11 18 23 32 34

Huom! Sama numero saa esiintyä lottorivissä vain kerran.

24 Lisää luokista ja olioista

24.1 Useita konstruktoreja

Palataan jälleen henkilöitä käsittelevän luokan pariin. Luokka Henkilo näyttää tällä hetkellä seuraavalta:

public class Henkilo {

    private String nimi;
    private int ika;
    private int pituus;
    private int paino;

    public Henkilo(String nimi) {
        this.nimi = nimi;
        this.ika = 0;
        this.paino = 0;
        this.pituus = 0;
    }

    public void tulostaHenkilo() {
        System.out.println(this.nimi + " olen " + this.ika + " vuotta vanha");
    }

    public void vanhene() {
        this.ika++;
    }

    public boolean taysiIkainen() {
        if (this.ika < 18) {
            return false;
        }

        return true;
    }

    public double painoindeksi() {
        double pituusMetreina = this.pituus / 100.0;

        return this.paino / (pituusMetreina * pituusMetreina);
    }

    public String toString() {
        return this.nimi + " olen " + this.ika + " vuotta vanha, painoindeksini on " + this.painoindeksi();
    }

    public void setPituus(int pituus) {
        this.pituus = pituus;
    }

    public int getPituus() {
        return this.pituus;
    }

    public int getPaino() {
        return this.paino;
    }

    public void setPaino(int paino) {
        this.paino = paino;
    }

    public String getNimi() {
        return this.nimi;
    }
}

Kaikki henkilöoliot ovat luontihetkellä 0-vuotiaita, sillä konstruktori asettaa uuden henkilön ika-oliomuuttujan arvoksi 0:

public Henkilo(String nimi) {
    this.nimi = nimi;
    this.ika = 0;
    this.paino = 0;
    this.pituus = 0;
}

Haluaisimme luoda henkilöitä myös siten, että konstruktorin parametrina annettaisiin ikä nimen lisäksi. Tämä onnistuu, sillä konstruktoreja voi olla useita. Tehdään vaihtoehtoinen konstruktori. Vanhaa konstruktoria ei tarvise poistaa.

public Henkilo(String nimi) {
    this.nimi = nimi;
    this.ika = 0;
    this.paino = 0;
    this.pituus = 0;
}

public Henkilo(String nimi, int ika) {
    this.nimi = nimi;
    this.ika = ika;
    this.paino = 0;
    this.pituus = 0;
}

Nyt olioiden luonti onnistuu kahdella vaihtoehtoisella tavalla:

public static void main(String[] args) {
    Henkilo pekka = new Henkilo("Pekka", 24);
    Henkilo esko = new Henkilo("Esko");

    System.out.println(pekka);
    System.out.println(esko);
}
Pekka, ikä 24 vuotta
Esko, ikä 0 vuotta

Tekniikkaa jossa luokalla on kaksi konstruktoria, kutsutaan konstruktorin kuormittamiseksi. Luokalla voi siis olla useita konstruktoreja, jotka poikkeavat toisistaanparametriensa määrältä tai tyypeiltä. Ei kuitenkaan ole mahdollista tehdä kahta erilaista konstruktoria joilla on täysin saman tyyppiset parametrit. Emme siis voi edellisten lisäksi lisätä konstruktoria public Henkilo(String nimi, int paino) sillä Javan on mahdoton erottaa tätä kaksiparametrisesta konstruktorissa, jossa luku tarkoittaa ikää.

24.2 Oman konstruktorin kutsuminen

Mutta hetkinen, aiemmin todettiin että "copy-paste"-koodi ei ole hyvä idea. Kun tarkastellaan edellä tehtyjä kuormitettuja konstruktoreita, niissä on aika paljon samaa. Emme ole oikein tyytyväisiä tilanteeseen.

Konstruktoreista ylempi on oikeastaan alemman erikoistapaus. Entä jos ylempi konstruktori voisi "kutsua" alempaa konstruktoria?

Tämä onnistuu, sillä konstruktorin sisältä voi kutsua toista konstruktoria juuri tähän olioon liittyvän this-ilmauksen avulla!

Muutetaan ylempää konstruktoria siten, että se ei itse tee mitään vaan ainoastaan kutsuu alempaa konstruktoria ja pyytää sitä asettamaan iäksi 0:

public Henkilo(String nimi) {
    this(nimi, 0);  // suorita tässä toisen konstruktorin koodi ja laita ika-parametrin arvoksi 0
}

public Henkilo(String nimi, int ika) {
    this.nimi = nimi;
    this.ika = ika;
    this.paino = 0;
    this.pituus = 0;
}

Oman konstruktorin kutsu this(nimi, 0); saattaa vaikuttaa erikoiselta. Asiaa voi vaikka ajatella siten, että kutsun kohdalle tulee "copy-pastena" automaattisesti alemman konstruktorin koodi, siten että ika parametrin arvoksi tulee 0. Huom! Jos konstruktorista kutsutaan toista konstruktoria, tulee konstruktorin kutsun tulee olla ensimmäinen toiminto konstruktorin sisällä.

Olioiden luonti onnistuu aivan kuten edellisessä esimerkissä:

public static void main(String[] args) {
    Henkilo pekka = new Henkilo("Pekka", 24);
    Henkilo esko = new Henkilo("Esko");

    System.out.println(pekka);
    System.out.println(esko);
}
Pekka, ikä 24 vuotta
Esko, ikä 0 vuotta

24.3 Metodin kuormittaminen

Konstruktorien tapaan myös metodeja voi kuormittaa, eli samannimisestä metodista voi olla useita versioita. Jälleen eri versioiden parametrien tyyppien on oltava erilaiset. Tehdään vanhene-metodista toinen versio, joka mahdollistaa henkilön vanhentamisen parametrina olevalla vuosimäärällä:

public void vanhene() {
    this.ika = this.ika + 1;
}

public void vanhene(int vuodet) {
    this.ika = this.ika + vuodet;
}

Seuraavassa "Pekka" syntyy 24-vuotiaana, vanhenee ensin vuoden ja sitten 10 vuotta:

public static void main(String[] args) {
    Henkilo pekka = new Henkilo("Pekka", 24);
    System.out.println(pekka);

    pekka.vanhene();
    System.out.println(pekka);

    pekka.vanhene(10);
    System.out.println(pekka);
}

Tulostuu:

Pekka, ikä 24 vuotta
Pekka, ikä 25 vuotta
Pekka, ikä 35 vuotta

Henkilöllä on nyt siis käytännössä kaksi kappaletta vanhene-nimisiä metodeja. Se kumpi metodeista valitaan suoritettavaksi, riippuu metodikutsussa käytettyjen parametrien määrästä.

Ohjelmaa voi muokata myös niin, että parametriton metodi vanhene toteutetaan metodin vanhene(int vuodet) avulla:

public void vanhene() {
    this.vanhene(1);
}

public void vanhene(int vuodet) {
    this.ika = this.ika + vuodet;
}

Tehtävä 95: Kuormitettu laskuri

95.1 Monta konstruktoria

Toteuta luokka Laskuri, joka sisältää luvun, jota voi vähentää ja suurentaa. Laskurissa on lisäksi valinnainen tarkistus joka estää laskurin menemisen miinukselle. Luokalla tulee olla seuraavat konstruktorit:

  • public Laskuri(int alkuarvo, boolean tarkistus) asettaa laskurin alkuarvoksi parametrin alkuarvo arvon. Tarkistus on päällä jos parametrin tarkistus arvoksi annettiin true.
  • public Laskuri(int alkuarvo) asettaa laskurin alkuarvoksi parametrin alkuarvo arvon, tarkastus ei ole päällä.
  • public Laskuri(boolean tarkistus) laskurin alkuarvoksi tulee 0. Tarkistus on päällä jos parametrin tarkistus arvoksi annettiin true.
  • public Laskuri() laskurin alkuarvoksi tulee 0 ja tarkastus ei ole päällä.

ja seuraavat metodit:

  • public int arvo() palauttaa laskurin tämänhetkisen arvon
  • public void lisaa() lisää laskurin arvoa yhdellä
  • public void vahenna() vähentää laskurin arvoa yhdellä, mutta ei alle nollan jos tarkistus on päällä

95.2 Vaihtoehtoiset metodit

Tee laskurin metodeista lisaa ja vahenna myös yksiparametriset versiot:

  • public void lisaa(int lisays) lisää laskurin arvoa parametrina annetun luvun verran. Jos parametrin arvo on negatiivinen, ei laskurin arvo muutu.
  • public void vahenna(int vahennys) vähentää laskurin arvoa parametrina annetun luvun verran, mutta ei alle nollan jos tarkistus on päällä. Jos parametrin arvo on negatiivinen, ei laskurin arvo muutu.

24.4 Olio on langan päässä

Aiemmin mainittiin, että ArrayList on "langan päässä". Myös oliot ovat langan päässä. Mitä tämä oikein tarkoittaa? Tarkastellaan seuraavaa esimerkkiä:

public static void main(String[] args) {
    Henkilo pekka = new Henkilo("Pekka", 24);

    System.out.println(pekka);
}

Kun suoritamme lauseen Henkilo pekka = new Henkilo("Pekka", 24); syntyy olio. Olioon päästään käsiksi muuttujan pekka avulla. Teknisesti ottaen olio ei ole muuttujan pekka "sisällä" (eli lokerossa pekka) vaan pekka viittaa syntyneeseen olioon. Toisin sanonen olio on pekka-nimisestä muuttujasta lähtevän "langan päässä". Kuvana asiaa voisi havainnollistaa seuraavasti:

Lisätään ohjelmaan Henkilo-tyyppinen muuttuja henkilo ja annetaan sille alkuarvoksi pekka. Mitä nyt tapahtuu?

public static void main(String[] args) {
    Henkilo pekka = new Henkilo("Pekka", 24);

    System.out.println(pekka);

    Henkilo henkilo = pekka;
    henkilo.vanhene(25);

    System.out.println(pekka);
}

Tulostuu:

Pekka, ikä 24 vuotta
Pekka, ikä 49 vuotta

Eli Pekka on alussa 24-vuotias, henkilo-muuttujaan liittyvän langan päässä olevaa Henkilö-oliota vanhennetaan 25:llä vuodella ja sen seurauksena Pekka vanhenee! Mistä on kysymys?

Komento Henkilo henkilo = pekka; saa aikaan sen, että henkilo rupeaa viittaamaan samaan olioon kuin mihin pekka viittaa. Eli ei synnykään kopiota oliosta, vaan molemmissa muuttujissa on langan päässä sama olio. Komennossa Henkilo henkilo = pekka; syntyy kopio langasta. Kuvana (Huom: kuvassa p ja h tarkottavat pääohjelman muuttujia pekka ja henkilo. Kuvien muuttujanimiä on lyhennelty myös muutamassa seuraavassa kuvassa.):

Esimerkissä "vieras henkilö henkilo ryöstää Pekan identiteetin". Seuraavassa esimerkkiä on jatkettu siten, että luodaan uusi olio ja pekka alkaa viittaamaan uuteen olioon:

public static void main(String[] args) {
    Henkilo pekka = new Henkilo("Pekka", 24);

    System.out.println(pekka);

    Henkilo henkilo = pekka;
    henkilo.vanhene(25);

    System.out.println(pekka);

    pekka = new Henkilo("Pekka Mikkola", 24);
    System.out.println(pekka);
}

Tulostuu:

Pekka, ikä 24 vuotta
Pekka, ikä 49 vuotta
Pekka Mikkola, ikä 24 vuotta

Muuttuja pekka viittaa siis ensin yhteen olioon, mutta rupeaa sitten viittaamaan toiseen olioon. Seuraavassa kuva tilanteesta viimeisen koodirivin jälkeen:

Jatketaan vielä esimerkkiä laittamalla henkilo viittaamaan "ei mihinkään", eli null:iin:

public static void main(String[] args) {
    Henkilo pekka = new Henkilo("Pekka", 24);

    System.out.println(pekka);

    Henkilo henkilo = pekka;
    henkilo.vanhene(25);

    System.out.println(pekka);

    pekka = new Henkilo("Pekka Mikkola", 24);
    System.out.println(pekka);

    henkilo = null;
    System.out.println(henkilo);
}

Viimeisen koodirivin jälkeen näyttää seuraavalta:

Alempaan olioon ei nyt viittaa kukaan. Oliosta on tullut "roska". Javan roskienkerääjä käy siivoamassa aika ajoin roskaksi joutuneet oliot. Jos näin ei tehtäisi, jäisivät ne kuluttamaan turhaan koneen muistia ohjelman suorituksen loppuun asti.

Huomaamme, että viimeisellä rivillä yritetään vielä tulostaa "ei mitään" eli null. Käy seuraavasti:

Pekka, ikä 24 vuotta
Pekka, ikä 49 vuotta
Pekka Mikkola, ikä 24 vuotta
null

Mitä tapahtuu jos yritämme kutsua "ei minkään" metodia, esimerkiksi metodia painoIndeksi:

public static void main(String[] args) {
    Henkilo pekka = new Henkilo("Pekka", 24);

    System.out.println(pekka);

    Henkilo henkilo = null;
    System.out.println(henkilo.painoIndeksi());
}

Tulos:

Pekka, ikä 24 vuotta
Exception in thread "main" java.lang.NullPointerException
at Main.main(Main.java:20)
Java Result: 1

Eli käy huonosti. Tämän on ehkä ensimmäinen kerta elämässäsi kun näet tekstin NullPointerException. Voimme luvata, että tulet näkemään sen vielä uudelleen. NullPointerException on poikkeustila, joka syntyy kun null-arvoisen olion metodeja yritetään kutsua.

24.5 Metodin parametrina olio

Olemme nähneet että metodien parametrina voi olla esim. int, double, String tai ArrayList. ArrayListit ja merkkijonot ovat olioita, joten kuten arvata saattaa, metodin parametriksi voi määritellä minkä tahansa tyyppisen olion. Demonstroidaan tätä esimerkillä.

Painonvartijoihin hyväksytään jäseniksi henkilöitä, joiden painoindeksi ylittää jonkun annetun rajan. Kaikissa painonvartijayhdistyksissä raja ei ole sama. Tehdään painonvartijayhdistystä vastaava luokka. Olioa luotaessa konstruktorille annetaan parametriksi pienin painoindeksi, jolla yhdistyksen jäseneksi pääsee.

public class PainonvartijaYhdistys {
    private double alinPainoindeksi;

    public PainonvartijaYhdistys(double indeksiRaja) {
        this.alinPainoindeksi = indeksiRaja;
    }
}

Tehdään sitten metodi, jonka avulla voidaan tarkastaa hyväksytäänkö tietty henkilö yhdistyksen jäseneksi, eli onko henkilön painoindeksi tarpeeksi suuri. Metodi palauttaa true jos parametrina annettu henkilö hyväksytään, false jos ei.

public class PainonvartijaYhdistys {
    private double alinPainoindeksi;

    public PainonvartijaYhdistys(double indeksiRaja) {
        this.alinPainoindeksi = indeksiRaja;
    }

    public boolean hyvaksytaanJaseneksi(Henkilo henkilo) {
        if (henkilo.painoIndeksi() < this.alinPainoindeksi) {
            return false;
        }

        return true;
    }
}

Painonvartijayhdistys-olion metodi hyvaksytaanJaseneksi saa siis parametriksi Henkilo-olion (tarkemmin sanottuna "langan" henkilöön) ja kutsuu parametrina saamansa henkilön metodia painoIndeksi.

Seuraavassa testipääohjelma jossa painonvartijayhdistyksen metodille annetaan ensin parametriksi henkilöolio matti ja sen jälkeen henkilöolio juhana:

public static void main(String[] args) {
    Henkilo matti = new Henkilo("Matti");
    matti.setPaino(86);
    matti.setPituus(180);

    Henkilo juhana = new Henkilo("Juhana");
    juhana.setPaino(64);
    juhana.setPituus(172);

    PainonvartijaYhdistys kumpulanPaino = new PainonvartijaYhdistys(25);

    if (kumpulanPaino.hyvaksytaanJaseneksi(matti)) {
        System.out.println(matti.getNimi() + " pääsee jäseneksi");
    } else {
        System.out.println(matti.getNimi() + " ei pääse jäseneksi");
    }

    if (kumpulanPaino.hyvaksytaanJaseneksi(juhana)) {
        System.out.println(juhana.getNimi() + " pääsee jäseneksi");
    } else {
        System.out.println(juhana.getNimi() + " ei pääse jäseneksi");
    }
}

Ohjelma tulostaa:

Matti pääsee jäseneksi
Juhana ei pääse jäseneksi

Muutama NetBeans-vihje

  • Kaikki NetBeans-vihjeet löytyvät MOOC.fi:n vinkeistä
  • Konstruktorien, getterien ja setterien automaattinen generointi

    Mene luokan koodilohkon sisäpuolelle mutta kaikkien metodien ulkopuolelle ja paina yhtä aikaa ctrl ja välilyönti. Jos luokallasi on esim. oliomuuttuja saldo, tarjoaa NetBeans mahdollisuuden generoida oliomuuttujalle getteri- ja setterimetodit sekä konstruktorin joka asettaa oliomuuttujalle alkuarvon. HUOM: laitoksen koneilla tämä saadaan aikaan painamalla yhtä aikaa ctrl, alt ja välilyönti.

Tehtävä 96: Kasvatuslaitos

Tehtäväpohjassasi on valmiina jo tutuksi tullut luokka Henkilo sekä runko luokalle Kasvatuslaitos. Kasvatuslaitosoliot käsittelevät ihmisiä eri tavalla, esim. punnitsevat ja syöttävät ihmisiä. Rakennamme tässä tehtävässä kasvatuslaitoksen. Luokan Henkilö koodiin ei tehtävässä ole tarkoitus koskea!

96.1 Henkilöiden punnitseminen

Kasvatuslaitoksen luokkarungossa on valmiina runko metodille punnitse:

public class Kasvatuslaitos {

    public int punnitse(Henkilo henkilo) {
        // palautetaan parametrina annetun henkilön paino
        return -1;
    }
}

Metodi saa parametrina henkilön ja metodin on tarkoitus palauttaa kutsujalleen parametrina olevan henkilön paino. Paino selviää kutsumalla parametrina olevan henkilön henkilo sopivaa metodia. Eli täydennä metodin koodi!

Seuraavassa on pääohjelma jossa kasvatuslaitos punnitsee kaksi henkilöä:

public static void main(String[] args) {
    // esimerkkipääohjelma tehtävän ensimmäiseen kohtaan

    Kasvatuslaitos haaganNeuvola = new Kasvatuslaitos();

    Henkilo eero = new Henkilo("Eero", 1, 110, 7);
    Henkilo pekka = new Henkilo("Pekka", 33, 176, 85);

    System.out.println(eero.getNimi() + " paino: " + haaganNeuvola.punnitse(eero) + " kiloa");
    System.out.println(pekka.getNimi() + " paino: " + haaganNeuvola.punnitse(pekka) + " kiloa");
}

Tulostuksen pitäisi olla seuraava:

Eero paino: 7 kiloa
Pekka paino: 85 kiloa

96.2 syötä

Parametrina olevan olion tilaa on mahdollista muuttaa. Tee kasvatuslaitokselle metodi public void syota(Henkilo henkilo) joka kasvattaa parametrina olevan henkilön painoa yhdellä.

Seuraavassa esimerkki, jossa henkilöt ensin punnitaan, ja tämän jälkeen neuvolassa syötetään eeroa kolme kertaa. Tämän jälkeen henkilöt taas punnitaan:

public static void main(String[] args) {
    Kasvatuslaitos haaganNeuvola = new Kasvatuslaitos();

    Henkilo eero = new Henkilo("Eero", 1, 110, 7);
    Henkilo pekka = new Henkilo("Pekka", 33, 176, 85);

    System.out.println(eero.getNimi() + " paino: " + haaganNeuvola.punnitse(eero) + " kiloa");
    System.out.println(pekka.getNimi() + " paino: " + haaganNeuvola.punnitse(pekka) + " kiloa");

    haaganNeuvola.syota(eero);
    haaganNeuvola.syota(eero);
    haaganNeuvola.syota(eero);

    System.out.println("");

    System.out.println(eero.getNimi() + " paino: " + haaganNeuvola.punnitse(eero) + " kiloa");
    System.out.println(pekka.getNimi() + " paino: " + haaganNeuvola.punnitse(pekka) + " kiloa");
}

Tulostuksen pitäisi paljastaa että Eeron paino on noussut kolmella:

Eero paino: 7 kiloa
Pekka paino: 85 kiloa

Eero paino: 10 kiloa
Pekka paino: 85 kiloa

96.3 laske punnitukset

Tee kasvatuslaitokselle metodi public int punnitukset() joka kertoo kuinka monta punnitusta kasvatuslaitos on ylipäätään tehnyt. Testipääohjelma:

public static void main(String[] args) {
    // esimerkkipääohjelma tehtävän ensimmäiseen kohtaan

    Kasvatuslaitos haaganNeuvola = new Kasvatuslaitos();

    Henkilo eero = new Henkilo("Eero", 1, 110, 7);
    Henkilo pekka = new Henkilo("Pekka", 33, 176, 85);

    System.out.println("punnituksia tehty " + haaganNeuvola.punnitukset());

    haaganNeuvola.punnitse(eero);
    haaganNeuvola.punnitse(pekka);

    System.out.println("punnituksia tehty " + haaganNeuvola.punnitukset());

    haaganNeuvola.punnitse(eero);
    haaganNeuvola.punnitse(eero);
    haaganNeuvola.punnitse(eero);
    haaganNeuvola.punnitse(eero);

    System.out.println("punnituksia tehty " + haaganNeuvola.punnitukset());
}

Tulostuu:

punnituksia tehty 0
punnituksia tehty 2
punnituksia tehty 6

Tehtävä 97: Maksukortti ja Kassapääte

97.1 "Tyhmä" Maksukortti

Teimme viime viikolla luokan Maksukortti. Kortilla oli metodit edullisesti ja maukkaasti syömistä sekä rahan lataamista varten.

Viime viikon tyylillä tehdyssä Maksukortti-luokassa oli kuitenkin ongelma. Kortti tiesi lounaiden hinnan ja osasi sen ansiosta vähentää saldoa oikean määrän. Entä kun hinnat nousevat? Tai jos myyntivalikoimaan tulee uusia tuotteita? Hintojen muuttaminen tarkoittaisi, että kaikki jo käytössä olevat kortit pitäisi korvata uusilla, uudet hinnat tuntevilla korteilla.

Parempi ratkaisu on tehdä kortit "tyhmiksi", hinnoista ja myytävistä tuotteista tietämättömiksi pelkän saldon säilyttäjiksi. Kaikki äly kannattaakin laittaa erillisiin olioihin, kassapäätteisiin.

Toteutetaan ensin Maksukortista "tyhmä" versio. Kortilla on ainoastaan metodit saldon kysymiseen, rahan lataamiseen ja rahan ottamiseen. Täydennä alla (ja tehtäväpohjassa) olevaan luokkaan metodin public boolean otaRahaa(double maara) ohjeen mukaan:

public class Maksukortti {
    private double saldo;

    public Maksukortti(double saldo) {
      this.saldo = saldo;
    }

    public double saldo() {
        return this.saldo;
    }

    public void lataaRahaa(double lisays) {
        this.saldo += lisays;
    }

    public boolean otaRahaa(double maara) {
        // toteuta metodi siten että se ottaa kortilta rahaa vain jos saldo on vähintään maara
        // onnistuessaan metodi palauttaa true ja muuten false
    }
}

Testipääohjelma:

public class Paaohjelma {
    public static void main(String[] args) {
        Maksukortti pekanKortti = new Maksukortti(10);

        System.out.println("rahaa " + pekanKortti.saldo());
        boolean onnistuiko = pekanKortti.otaRahaa(8);
        System.out.println("onnistuiko otto: " + onnistuiko);
        System.out.println("rahaa " + pekanKortti.saldo());

        onnistuiko = pekanKortti.otaRahaa(4);
        System.out.println("onnistuiko otto: " + onnistuiko);
        System.out.println("rahaa " + pekanKortti.saldo());
      }
}

Tulostuksen kuuluisi olla seuraavanlainen

rahaa 10.0
onnistuiko otto: true
rahaa 2.0
onnistuiko otto: false
rahaa 2.0

97.2 Kassapääte ja käteiskauppa

Unicafessa asioidessa asiakas maksaa joko käteisellä tai maksukortilla. Myyjä käyttää kassapäätettä kortin velottamiseen ja käteismaksujen hoitamiseen. Tehdään ensin kassapäätteestä käteismaksuihin sopiva versio.

Kassapäätteen runko. Metodien kommentit kertovat halutun toiminnallisuuden:

public class Kassapaate {
    private double rahaa;  // kassassa olevan käteisen määrä
    private int edulliset; // myytyjen edullisten lounaiden määrä
    private int maukkaat;  // myytyjen maukkaiden lounaiden määrä

    public Kassapaate() {
        // kassassa on aluksi 1000 euroa rahaa
    }

    public double syoEdullisesti(double maksu) {
        // edullinen lounas maksaa 2.50 euroa.
        // kasvatetaan kassan rahamäärää edullisen lounaan hinnalla ja palautetaan vaihtorahat
        // jos parametrina annettu maksu ei ole riittävän suuri, ei lounasta myydä ja metodi palauttaa koko summan
    }

    public double syoMaukkaasti(double maksu) {
        // maukas lounas maksaa 4.30 euroa.
        // kasvatetaan kassan rahamäärää maukkaan lounaan hinnalla ja palautetaan vaihtorahat
        // jos parametrina annettu maksu ei ole riittävän suuri, ei lounasta myydä ja metodi palauttaa koko summan
    }

    public String toString() {
        return "kassassa rahaa " + rahaa + " edullisia lounaita myyty " + edulliset + " maukkaita lounaita myyty " + maukkaat;
    }
}

Kassapäätteessä on aluksi rahaa 1000 euroa. Toteuta ylläolevan rungon metodit ohjeen ja alla olevan pääohjelman esimerkkitulosteen mukaan toimiviksi.

public class Paaohjelma {
    public static void main(String[] args) {
        Kassapaate unicafeExactum = new Kassapaate();

        double vaihtorahaa = unicafeExactum.syoEdullisesti(10);
        System.out.println("vaihtorahaa jäi " + vaihtorahaa);

        vaihtorahaa = unicafeExactum.syoEdullisesti(5);
        System.out.println("vaihtorahaa jäi " + vaihtorahaa);

        vaihtorahaa = unicafeExactum.syoMaukkaasti(4.3);
        System.out.println("vaihtorahaa jäi " + vaihtorahaa);

        System.out.println(unicafeExactum);
    }
}
vaihtorahaa jäi 7.5
vaihtorahaa jäi 2.5
vaihtorahaa jäi 0.0
kassassa rahaa 1009.3 edullisia lounaita myyty 2 maukkaita lounaita myyty 1

97.3 Kortilla maksaminen

Laajennetaan kassapäätettä siten että myös kortilla voi maksaa. Teemme kassapäätteelle siis metodit joiden parametrina kassapääte saa maksukortin jolta se vähentää valitun lounaan hinnan. Seuraavassa uusien metodien rungot ja ohje niiden toteuttamiseksi:

public class Kassapaate {
    // ...

    public boolean syoEdullisesti(Maksukortti kortti) {
        // edullinen lounas maksaa 2.50 euroa.
        // jos kortilla on tarpeeksi rahaa, vähennetään hinta kortilta ja palautetaan true
        // muuten palautetaan false
    }

    public boolean syoMaukkaasti(Maksukortti kortti) {
        // maukas lounas maksaa 4.30 euroa.
        // jos kortilla on tarpeeksi rahaa, vähennetään hinta kortilta ja palautetaan true
        // muuten palautetaan false
    }

    // ...
}

Huom: kortilla maksaminen ei lisää kassapäätteessä olevan käteisen määrää.

Seuraavassa testipääohjelma ja haluttu tulostus:

public class Paaohjelma {
    public static void main(String[] args) {
        Kassapaate unicafeExactum = new Kassapaate();

        double vaihtorahaa = unicafeExactum.syoEdullisesti(10);
        System.out.println("vaihtorahaa jäi " + vaihtorahaa);

        Maksukortti antinKortti = new Maksukortti(7);

        boolean onnistuiko = unicafeExactum.syoMaukkaasti(antinKortti);
        System.out.println("riittikö raha: " + onnistuiko);
        onnistuiko = unicafeExactum.syoMaukkaasti(antinKortti);
        System.out.println("riittikö raha: " + onnistuiko);
        onnistuiko = unicafeExactum.syoEdullisesti(antinKortti);
        System.out.println("riittikö raha: " + onnistuiko);

        System.out.println(unicafeExactum);
    }
}
vaihtorahaa jäi 7.5
riittikö raha: true
riittikö raha: false
riittikö raha: true
kassassa rahaa 1002.5 edullisia lounaita myyty 2 maukkaita lounaita myyty 1

97.4 Rahan lataaminen

Lisätään vielä kassapäätteelle metodi jonka avulla kortille voidaan ladata lisää rahaa. Muista, että rahan lataamisen yhteydessä ladattava summa viedään kassapäätteeseen. Metodin runko:

public void lataaRahaaKortille(Maksukortti kortti, double summa) {
    // ...
}

Testipääohjelma ja esimerkkisyöte:

public class Paaohjelma {
    public static void main(String[] args) {
        Kassapaate unicafeExactum = new Kassapaate();
        System.out.println(unicafeExactum);

        Maksukortti antinKortti = new Maksukortti(2);

        System.out.println("kortilla rahaa " + antinKortti.saldo() + " euroa");

        boolean onnistuiko = unicafeExactum.syoMaukkaasti(antinKortti);
        System.out.println("riittikö raha: " + onnistuiko);

        unicafeExactum.lataaRahaaKortille(antinKortti, 100);

        onnistuiko = unicafeExactum.syoMaukkaasti(antinKortti);
        System.out.println("riittikö raha: " + onnistuiko);

        System.out.println("kortilla rahaa " + antinKortti.saldo() + " euroa");

        System.out.println(unicafeExactum);
    }
}
kassassa rahaa 1000.0 edullisia lounaita myyty 0 maukkaita lounaita myyty 0
kortilla rahaa 2.0 euroa
riittikö raha: false
riittikö raha: true
kortilla rahaa 97.7 euroa
kassassa rahaa 1100.0 edullisia lounaita myyty 0 maukkaita lounaita myyty 1

24.6 Metodin parametrina toinen samantyyppinen olio

Jatkamme edelleen luokan Henkilo parissa. Kuten muistamme, henkilöt tietävät ikänsä:

public class Henkilo {

    private String nimi;
    private int ika;
    private int pituus;
    private int paino;

    // ...
}

Haluamme vertailla kahden henkilön ikää. Vertailu voidaan hoitaa usealla tavalla. Henkilölle voitaisiin määritellä getterimetodi getIka ja kahden henkilön iän vertailu tapauhtuisi tällöin seuraavasti:

Henkilo pekka = new Henkilo("Pekka");
Henkilo juhana = new Henkilo("Juhana")

if (pekka.getIka() > juhana.getIka()) {
    System.out.println(pekka.getNimi() + " on vanhempi kuin " + juhana.getNimi());
}

Opettelemme kuitenkin nyt hieman "oliohenkisemmän" tavan kahden henkilön ikävertailun tekemiseen.

Teemme Henkilö-luokalle metodin boolean vanhempiKuin(Henkilo verrattava) jonka avulla tiettyä henkilö-olioa voi verrata parametrina annettuun henkilöön iän perusteella.

Metodia on tarkoitus käyttää seuraavaan tyyliin:

public static void main(String[] args) {
    Henkilo pekka = new Henkilo("Pekka", 24);
    Henkilo antti = new Henkilo("Antti", 22);

    if (pekka.vanhempiKuin(antti)) {  //  sama kun pekka.vanhempiKuin(antti)==true
        System.out.println(pekka.getNimi() + " on vanhempi kuin " + antti.getNimi());
    } else {
        System.out.println(pekka.getNimi() + " ei ole vanhempi kuin " + antti.getNimi());
    }
}

Tässä siis kysytään onko pekka anttia vanhempi "jos pekka on vanhempi kuin antti". Metodi vanhempiKuin palauttaa arvon true jos olio jonka kohdalla metodia kutsutaan (olio.vanhempiKuin(parametrinaAnnettavaOlio)) on vanhempi kuin parametrina annettava olio, ja false muuten. Käytännössä yllä kutsutaan "Pekkaa" vastaavan olion, johon muuttuja pekka viittaa, metodia vanhempiKuin, jolle annetaan parametriksi "Anttia" vastaavan olion viite antti.

Ohjelman tulostaa:

Pekka on vanhempi kuin Antti

Metodi saa parametrikseen henkilöolion (tarkemmin sanottuna viitteen henkilöolioon, eli "langan päässä" olevan henkilön) ja vertaa omaa ikäänsä this.ika verrattavaksi annetun henkilön ikään verrattava.ika. Toteutus näyttää seuraavalta:

public class Henkilo {
    // ...

    public boolean vanhempiKuin(Henkilo verrattava) {
        if (this.ika > verrattava.ika) {
            return true;
        }

        return false;
    }
}

Vaikka ika onkin olion yksityinen (private) oliomuuttuja, pystymme lukemaan muuttujan arvon kirjoittamalla verrattava.ika. Tämä johtuu siitä, että private-muuttujat ovat luettavissa kaikissa metodeissa, jotka kyseinen luokka sisältää. Huomaa, että syntaksi (kirjoitusasu) vastaa tässä jonkin olion metodin kutsumista. Toisin kuin metodia kutsuttaessa, viittaamme olion kenttään, jolloin metodikutsun osoittavia sulkeita ei kirjoiteta.

24.7 Päiväys oliona

Toinen esimerkki samasta teemasta. Tehdään luokka, jonka avulla voidaan esittää päiväyksiä.

Olion sisällä päiväys esitetään kolmella oliomuuttujalla. Tehdään myös metodi jolla voi vertailla onko päivämäärä aiemmin kuin parametrina annettu päivämäärä:

public class Paivays {
    private int paiva;
    private int kuukausi;
    private int vuosi;

    public Paivays(int paiva, int kuukausi, int vuosi) {
        this.paiva = paiva;
        this.kuukausi = kuukausi;
        this.vuosi = vuosi;
    }

    public String toString() {
        return this.paiva + "." + this.kuukausi + "." + this.vuosi;
    }

    // metodilla tarkistetaan onko tämä päiväysolio (this) ennen 
    // parametrina annettavaa päiväysoliota (verrattava)
    public boolean aiemmin(Paivays verrattava) {
        // ensin verrataan vuosia
        if (this.vuosi < verrattava.vuosi) {
            return true;
        }

        // jos vuodet ovat samat, verrataan kuukausia
        if (this.vuosi == verrattava.vuosi && this.kuukausi < verrattava.kuukausi) {
            return true;
        }

        // vuodet ja kuukaudet samoja, verrataan päivää
        if (this.vuosi == verrattava.vuosi && this.kuukausi == verrattava.kuukausi &&
            this.paiva < verrattava.paiva) {
            return true;
        }

        return false;
    }
}

Käyttöesimerkki:

public static void main(String[] args) {
    Paivays p1 = new Paivays(14, 2, 2011);
    Paivays p2 = new Paivays(21, 2, 2011);
    Paivays p3 = new Paivays(1, 3, 2011);
    Paivays p4 = new Paivays(31, 12, 2010);

    System.out.println(p1 + " aiemmin kuin " + p2 + ": " + p1.aiemmin(p2));
    System.out.println(p2 + " aiemmin kuin " + p1 + ": " + p2.aiemmin(p1));

    System.out.println(p2 + " aiemmin kuin " + p3 + ": " + p2.aiemmin(p3));
    System.out.println(p3 + " aiemmin kuin " + p2 + ": " + p3.aiemmin(p2));

    System.out.println(p4 + " aiemmin kuin " + p1 + ": " + p4.aiemmin(p1));
    System.out.println(p1 + " aiemmin kuin " + p4 + ": " + p1.aiemmin(p4));
}
14.2.2011 aiemmin kuin 21.2.2011: true
21.2.2011 aiemmin kuin 14.2.2011: false
21.2.2011 aiemmin kuin 1.3.2011: true
1.3.2011 aiemmin kuin 21.2.2011: false
31.12.2010 aiemmin kuin 14.2.2011: true
14.2.2011 aiemmin kuin 31.12.2010: false

Tehtävä 98: Asuntovertailu

Asuntovälitystoimiston tietojärjestelmässä myynnissä olevaa asuntoa kuvataan seuraavan luokan olioilla:

public class Asunto {
    private int huoneita;
    private int nelioita;
    private int neliohinta;

    public Asunto(int huoneita, int nelioita, int neliohinta) {
        this.huoneita = huoneita;
        this.nelioita = nelioita;
        this.neliohinta = neliohinta;
    }
}

Tehtävänä on toteuttaa muutama metodi, joiden avulla myynnissä olevia asuntoja voidaan vertailla.

98.1 Suurempi

Tee metodi public boolean suurempi(Asunto verrattava) joka palauttaa true jos asunto-olio, jolle metodia kutsutaan on suurempi kuin verrattavana oleva asunto-olio.

Esimerkki metodin toiminnasta:

Asunto eiraYksio = new Asunto(1, 16, 5500);
Asunto kallioKaksio = new Asunto(2, 38, 4200);
Asunto jakomakiKolmio = new Asunto(3, 78, 2500);

System.out.println(eiraYksio.suurempi(kallioKaksio));       // false
System.out.println(jakomakiKolmio.suurempi(kallioKaksio));  // true

98.2 Hintaero

Tee metodi public int hintaero(Asunto verrattava) joka palauttaa asunto-olion jolle metodia kutsuttiin ja parametrina olevan asunto-olion hintaeron. Hintaero on asuntojen hintojen (=neliöhinta*neliöt) itseisarvo.

Esimerkki metodin toiminnasta:

Asunto eiraYksio = new Asunto(1, 16, 5500);
Asunto kallioKaksio = new Asunto(2, 38, 4200);
Asunto jakomakiKolmio = new Asunto(3, 78, 2500);

System.out.println(eiraYksio.hintaero(kallioKaksio));        // 71600
System.out.println(jakomakiKolmio.hintaero(kallioKaksio));   // 35400

98.3 Kalliimpi

Tee metodi public boolean kalliimpi(Asunto verrattava) joka palauttaa true jos asunto-olio, jolle metodia kutsutaan on kalliimpi kuin verrattavana oleva asunto-olio.

Esimerkki metodin toiminnasta:

Asunto eiraYksio = new Asunto(1, 16, 5500);
Asunto kallioKaksio = new Asunto(2, 38, 4200);
Asunto jakomakiKolmio = new Asunto(3, 78, 2500);

System.out.println(eiraYksio.kalliimpi(kallioKaksio));       // false
System.out.println(jakomakiKolmio.kalliimpi(kallioKaksio));   // true

24.8 Lisää olioita listalla

ArrayList:eihin voidaan laittaa minkä tahansa tyyppisiä oliota. Luodaan seuraavassa asuntolista, eli tyyppiä ArrayList<Asunto> oleva ArrayList, laitetaan sinne muutama aiemmin näkemämme asunto, ja etsitään asunnoista suurin -- hyödynnetään tässä edellisessä tehtävässä tehtyä metodia -- oletetaan lisäksi, että Asunto-luokalla on metodi getNeliot(), joka palauttaa asunnon neliöiden määrän:

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

    asunnot.add(new Asunto(1, 16, 5500));
    asunnot.add(new Asunto(2, 38, 4200));
    asunnot.add(new Asunto(3, 78, 2500));

    Asunto suurin = null;

    for(Asunto asunto: asunnot) {
        if (suurin == null) {
            suurin = asunto;
            continue; // jatketaan seuraavan asunnon tarkastelulla
        }

        // jos tarkasteltava asunto on suurempi kuin suurin, 
        // asetetaan tarkasteltava suurimmaksi
        if (asunto.suurempi(suurin)) {
            suurin = asunto;
        }
    }

    if (suurin == null) {
        System.out.println("Ei löytynyt suurinta asuntoa :(");
    } else {
        System.out.println("Suurin asunto löytyi!");
        System.out.println("Asunnossa on " + suurin.getNeliot() + " neliötä.");
    }
}

Ohjelman tulostus:

Suurin asunto löytyi!
Asunnossa on 78 neliötä.

Tehtävä 99: Opiskelija-luokka

99.1 Opiskelija-luokka

Tee luokka Opiskelija, johon tallennetaan seuraavat tiedot opiskelijasta:

  • nimi (String)
  • opiskelijanumero (String)

Tee luokkaan seuraavat metodit:

  • konstruktori, jolle annetaan opiskelijan nimi ja opiskelijanumero
  • haeNimi, joka palauttaa opiskelijan nimen, esim. Pekka Mikkola
  • haeOpiskelijanumero, joka palauttaa opiskelijan opiskelijanumeron, esim. 013141590
  • toString, joka palauttaa merkkijonoesityksen opiskelijasta muodossa: Pekka Mikkola (013141590)

Voit testata luokan toimintaa seuraavalla koodilla:

public class Main {
    public static void main(String[] args) {
        Opiskelija pekka = new Opiskelija("Pekka Mikkola", "013141590");
        System.out.println("Nimi: " + pekka.haeNimi());
        System.out.println("Opiskelijanumero: " + pekka.haeOpiskelijanumero());
        System.out.println(pekka);
    }
}

Ohjelman tulostuksen tulisi olla seuraava:

Nimi: Pekka Mikkola
Opiskelijanumero: 013141590
Pekka Mikkola (013141590)

99.2 Opiskelijalista

Tee edellisen tilalle uusi pääohjelma, joka kysyy alla olevan esimerkkitulostuksen tyyliin opiskelijoiden tietoja (ensin kysytään nimi ja sen jälkeen opiskelijanumero). Ohjelma luo jokaisesta opiskelijasta uuden olion ja tallentaa sen listaan. Kun käyttäjä antaa nimeksi tyhjän merkkijonon, ohjelma tulostaa listalla olevat opiskelijat.

Listan määrittelyn tulisi olla seuraava:

  ArrayList<Opiskelija> lista = new ArrayList<>();

Seuraavassa on esimerkki ohjelman suorituksesta:

Nimi: Alfred Apina
Opiskelijanumero: 017635727
Nimi: Bruno Banaani
Opiskelijanumero: 011288989
Nimi: Cecilia Cembalo
Opiskelijanumero: 013672548
Nimi:

Alfred Apina (017635727)
Bruno Banaani (011288989)
Cecilia Cembalo (013672548)

99.3 Opiskelijahaku

Laajenna edellisen tehtävän opiskelijalistaa siten, että listan syöttämisen jälkeen pääohjelma kysyy hakusanan, jonka avulla voi hakea opiskelijat, joiden nimessä on annettu hakusana. Tehtävänäsi on siis toteuttaa hakutoiminto.

Vihje: Käy opiskelijat läpi silmukassa ja tarkista String-luokan contains-metodilla, onko hakusana opiskelijan nimessä.

Seuraavassa on esimerkki ohjelman suorituksesta:

Nimi: Saku Silmukka
Opiskelijanumero: 015696234
Nimi: Cecilia Cembalo
Opiskelijanumero: 013672548
Nimi: Taina Taulukko
Opiskelijanumero: 014662803
Nimi:

Saku Silmukka (015696234)
Cecilia Cembalo (013672548)
Taina Taulukko (014662803)

Anna hakusana: ukk
Tulokset:
Saku Silmukka (015696234)
Taina Taulukko (014662803)

24.9 Olion sisällä olio

Olioiden sisällä voi olla olioita, ei pelkästään merkkijonoja vaan myös itse määriteltyjä oliota. Jatketaan taas Henkilo-luokan parissa ja lisätään henkilölle syntymäpäivä. Syntymäpäivä on luonnollista esittää aiemmin tehdyn Paivays-olion avulla:

public class Henkilo {
    private String nimi;
    private int ika;
    private int paino;
    private int pituus;
    private Paivays syntymaPaiva;

    // ...
  

Tehdään henkilölle uusi konstruktori, joka mahdollistaa syntymäpäivän asettamisen:

public Henkilo(String nimi, int paiva, int kuukausi, int vuosi) {
    this.nimi = nimi;
    this.paino = 0;
    this.pituus = 0;
    this.syntymaPaiva = new Paivays(paiva, kuukausi, vuosi);
}

Eli konstruktorin parametrina annetaan erikseen päiväyksen osat (päivä, kuukausi, vuosi), niistä luodaan päiväysolio joka sijoitetaan oliomuuttujaan syntymaPaiva.

Muokataan toString siten, että iän sijaan se näyttää syntymäpäivän:

public String toString() {
    return this.nimi + ", syntynyt " + this.syntymaPaiva;
}

Ja kokeillaan miten uusittu Henkilö-luokka toimii:

public static void main(String[] args) {
    Henkilo martin = new Henkilo("Martin", 24, 4, 1983);

    Henkilo juhana = new Henkilo("Juhana", 17, 9, 1985);

    System.out.println(martin);
    System.out.println(juhana);
  }

Tulostuu:

Martin, syntynyt 24.4.1983
Juhana, syntynyt 17.9.1985

Luvussa 22.4 todettiin, että oliot ovat "langan päässä". Kertaa nyt luku 22.4.

Henkilö-oliolla on oliomuuttujat nimi joka on merkkijono-olio ja syntymaPaiva joka on Päiväys-olio. Henkilön oliomuuttujat siis ovat molemmat olioita, eli teknisesti ottaen ne eivät sijaitse henkilö-olion sisällä vaan ovat "langan päässä", ts. henkilöllä on oliomuuttujissa tallessa viite niihin. Kuvana:

Pääohjelmalla on nyt siis langan päässä kaksi Henkilö-olioa. Henkilöllä on nimi ja syntymäpäivä. Koska molemmat ovat olioita, ovat ne henkilöllä langan päässä.

Syntymäpäivä vaikuttaa hyvältä laajennukselta Henkilö-luokkaan. Huomaamme kuitenkin, että oliomuuttuja ikä uhkaa jäädä turhaksi ja lienee syytä poistaa se. Iän pystyy nimittäin tarvittaessa selvittämään helposti nykyisen päivämäärän ja syntymäpäivän perusteella. Javassa nykyinen päivä selviää esim. seuraavasti:

int paiva = Calendar.getInstance().get(Calendar.DATE);
int kuukausi = Calendar.getInstance().get(Calendar.MONTH) + 1; // tammikuun numero 0 joten  lisätään 1
int vuosi = Calendar.getInstance().get(Calendar.YEAR);
System.out.println("tänään on " + paiva + "." + kuukausi + "." + vuosi);

Kun ikä poistetaan, täytyy vanhempiKuin-metodi muuttaa toimimaan syntymäpäiviä vertaamalla. Jätämme vertailun toteutuksen omatoimiseksi harjoitustehtäväksi.

Tehtävä 100: Kellosta olio

Edellisellä viikolla tehtiin luokka YlhaaltaRajoitettuLaskuri ja rakennettiin laskurien avulla pääohjelmaan kello. Tehdään nyt myös itse kellosta olio. Luokan kello runko näyttää seuraavalta:


public class Kello {
    private YlhaaltaRajoitettuLaskuri tunnit;
    private YlhaaltaRajoitettuLaskuri minuutit;
    private YlhaaltaRajoitettuLaskuri sekunnit;

    public Kello(int tunnitAlussa, int minuutitAlussa, int sekunnitAlussa) {
      // laskurit tunneille, minuuteille ja sekunneille; 
      // laskurien arvot tulee asettaa parametreina saatuun aikaan
    }

    public void etene() {
      // kello etenee sekunnilla
    }

    public String toString() {
        // palauttaa kellon merkkijonoesityksen
      }
}

Kopioi uuteen projektiin viime viikon tehtävässä YlhaaltaRajoitettuLaskuri-toteutettu YlhaaltaRajoitettuLaskuri-luokka.

Toteuta luokan Kello konstruktori ja puuttuvat metodit. Voit testata kelloasi seuraavalla pääohjelmalla:

public class Main {
    public static void main(String[] args) {
        Kello kello = new Kello(23, 59, 50);

        int i = 0;
        while (i < 20) {
            System.out.println(kello);
            kello.etene();
            i++;
        }
    }
}

Tulostuksen tulisi edetä seuraavasti:

23:59:50
23:59:51
23:59:52
23:59:53
23:59:54
23:59:55
23:59:56
23:59:57
23:59:58
23:59:59
00:00:00
00:00:01
...

24.10 Lisää omien metodien kutsumisesta

Olemme oppineet aiemmin, että metodeista voi kutsua muita metodeja. Esimerkiksi, seuraavassa luokassa Esine kutsutaan metodia getNimi metodista toString.

public class Esine {
    private String nimi;
    private int paino;

    public Esine(String nimi, int paino) {
        this.nimi = nimi;
        this.paino = paino;
    }

    public String getNimi() {
        return this.nimi;
    }

    public int getPaino() {
        return this.paino;
    }

    public String toString() {
        return this.getNimi() + " " + this.getPaino() + "kg.";
    }
}
Esine tuoli = new Esine("Tuoli", 1);
Esine tiskikone = new Esine("Tiskikone", 22);


System.out.println(tuoli);
System.out.println(tiskikone);
Tuoli 1kg.
Tiskikone 22kg.

Metodista voi kutsua myös samaa metodia.

Pohditaan hyvin sokkelomaista huoneistokompleksia. Jokaisessa huoneistokompleksin huoneessa on kolme ovea, joista pääsee muihin huoneisiin. Ovien lisäksi jokaisella huoneella on nimi.

Voimme mallintaa tätä sokkelomaista huoneistokompleksia luokan Huone avulla. Huoneeseen liittyy nimi, sekä kaksi muuta huonetta, joihin pääsee ovien kautta. Luokan Huone oliomuuttujat ovat seuraavanlaiset:

public class Huone {
    private String nimi;

    private Huone vasen;
    private Huone oikea;
}

Lisätään huoneelle konstruktori ja setterit. Setterien avulla voimme asettaa huoneeseen liittyvät huoneet vasta niiden luomisen jälkeen.

public class Huone {
    private String nimi;

    private Huone vasen;
    private Huone oikea;

    public Huone(String nimi) {
        this.nimi = nimi;
    }

    public String getNimi() {
        return this.nimi;
    }

    public void setVasen(Huone huone) {
        this.vasen = huone;
    }

    public void setOikea(Huone huone) {
        this.oikea = huone;
    }
}

Lisätään huoneeseen vielä metodit valitseVasenOvi ja valitseOikeaOvi, jotka johtavat valittaviin huoneisiin.

public class Huone {
    private String nimi;

    private Huone vasen;
    private Huone oikea;

    public Huone(String nimi) {
        this.nimi = nimi;
    }

    public String getNimi() {
        return this.nimi;
    }

    public Huone valitseVasenOvi() {
        return this.vasen;
    }

    public void setVasen(Huone huone) {
        this.vasen = huone;
    }

    public Huone valitseOikeaOvi() {
        return this.oikea;
    }

    public void setOikea(Huone huone) {
        this.oikea = huone;
    }
}

Luodaan huoneistosokkelo, mistä eteisestä pääsee olohuoneeseen ja kylpyhuoneeseen. Olohuoneesta pääsee kylpyhuoneeseen ja makuuhuoneeseen. Kylpyhuoneesta pääsee makuuhuoneeseen.

Huone eteinen = new Huone("Eteinen");
Huone kylpyhuone = new Huone("Kylpyhuone");
Huone olohuone = new Huone("Olohuone");
Huone makuuhuone = new Huone("Makuuhuone");

eteinen.setVasen(olohuone);
eteinen.setOikea(kylpyhuone);

olohuone.setOikea(kylpyhuone);
olohuone.setVasen(makuuhuone);

kylpyhuone.setVasen(makuuhuone);

Huoneistosokkelomme on siitä mielenkiintoinen, että siellä ei ole ovia takaisinpäin. Tämä on tarkoituksellista tässä esimerkissä -- näemme kohta miksi :). Pohditaan seuraavaksi huoneiden läpikäyntiä -- tehdään luokka Huoneistokauppias, joka käy huoneiston jokaisen huoneen läpi annetusta huoneesta lähtien, ja kertoo huoneen nimen.

public class Huoneistokauppias {
 
    public Huoneistokauppias() {
    }

    public void vieraile(Huone huone) {
        System.out.println("Olen huoneessa " + huone.getNimi());
    }
}

Yllä oleva metodimme ei kuitenkaan kerro muusta kuin nykyisestä huoneesta, ja haluaisimme että huoneistokauppias vierailee jokaisessa huoneessa. Voimme pyytää huoneistokauppiasta vierailemaan myös huoneen viereisessä huoneessa.

    public void vieraile(Huone huone) {
        System.out.println("Olen huoneessa " + huone.getNimi());
        
        this.vieraile(huone.valitseVasenOvi());
    }

Nyt huoneistokauppias vierailee jokaisen huoneen vasemmalla olevassa huoneessa. Mutta! Kun suoritamme seuraavan koodin, näemme virheen.

Huone eteinen = new Huone("Eteinen");
Huone kylpyhuone = new Huone("Kylpyhuone");
Huone olohuone = new Huone("Olohuone");
Huone makuuhuone = new Huone("Makuuhuone");

eteinen.setVasen(olohuone);
eteinen.setOikea(kylpyhuone);

olohuone.setOikea(kylpyhuone);
olohuone.setVasen(makuuhuone);

kylpyhuone.setVasen(makuuhuone);

Huoneistokauppias jethro = new Huoneistokauppias();
jethro.vieraile(eteinen);
Olen huoneessa Eteinen
Olen huoneessa Olohuone
Olen huoneessa Makuuhuone
Exception in thread "main" java.lang.NullPointerException
at Main.main(Main.java:rivi)
Java Result: 1

Virhe johtuu siitä, että huoneistokauppias on erittäin innostunut menemään myös huoneisiin, joita ei ole -- metodi getVasenOvi() palauttaa null-viitteen jos huoneen vasemmaksi huoneeksi ei ole asetettu mitään.

Voimme korjata tilanteen lisäämällä vierailumetodin alkuun tarkistuksen. Jos huone jossa vieraillaan on null, palataan metodista heti.

    public void vieraile(Huone huone) {
        if (huone == null) {
            return;
        }

        System.out.println("Olen huoneessa " + huone.getNimi());
        
        this.vieraile(huone.valitseVasenOvi());
    }

Nyt huoneistokauppias pystyy vierailemaan jokaisen huoneen vasemmalla olevan oven takana. Selvennetään huoneistokauppiaan kulkua vielä hieman lisäämällä tarkennus hänen kulkemiseen.

    public void vieraile(Huone huone) {
        if (huone == null) {
            return;
        }

        System.out.println("Olen huoneessa " + huone.getNimi());
        
        this.vieraile(huone.valitseVasenOvi());
        
        System.out.println("Poistun huoneesta " + huone.getNimi());
    }

Nyt ohjelmakoodi toimii varsin hienosti.

Huone eteinen = new Huone("Eteinen");
Huone kylpyhuone = new Huone("Kylpyhuone");
Huone olohuone = new Huone("Olohuone");
Huone makuuhuone = new Huone("Makuuhuone");

eteinen.setVasen(olohuone);
eteinen.setOikea(kylpyhuone);

olohuone.setOikea(kylpyhuone);
olohuone.setVasen(makuuhuone);

kylpyhuone.setVasen(makuuhuone);

Huoneistokauppias jethro = new Huoneistokauppias();
jethro.vieraile(eteinen);
Olen huoneessa Eteinen
Olen huoneessa Olohuone
Olen huoneessa Makuuhuone
Poistun huoneesta Makuuhuone
Poistun huoneesta Olohuone
Poistun huoneesta Eteinen

Huoneistokauppiaamme ei vieläkään käy kaikissa huoneissa, mikä oli alkuperäinen tavoite. Kokeile esimerkkiä itse, ja muokkaa koodia siten, että kaikissa huoneissa käydään!

Tehtävä 101: Sukututkija

Tehtäväpohjassa tulee mukana luokka Henkilo, jolla on oliomuuttujat nimi sekä isä ja äiti. Koodissa on lisäksi myös konstruktori, jolla asetetaan luotavalle henkilölle nimi. Konstruktorin lisäksi luokassa on gettereitä ja settereitä, joilla pääsee käsiksi oliomuuttujiin.

public class Henkilo {

    private String nimi;
    private Henkilo isa;
    private Henkilo aiti;

    public Henkilo(String nimi) {
        this.nimi = nimi;
    }

    public String getNimi() {
        return nimi;
    }

    public void setNimi(String nimi) {
        this.nimi = nimi;
    }

    public Henkilo getIsa() {
        return isa;
    }

    public void setIsa(Henkilo isa) {
        this.isa = isa;
    }

    public Henkilo getAiti() {
        return aiti;
    }

    public void setAiti(Henkilo aiti) {
        this.aiti = aiti;
    }
}

Luokan Henkilo avulla voi mallintaa sukupuuta. Alla olevassa koodissa hahmotellaan Charles II of Spainin esi-isiä.

Henkilo charles = new Henkilo("Charles II of Spain");
Henkilo mariana = new Henkilo("Mariana of Austria");
Henkilo philipIV = new Henkilo("Philip IV of Spain");

charles.setIsa(philipIV);
charles.setAiti(mariana);

Henkilo mariaSpain = new Henkilo("Maria Anna of Spain");
Henkilo ferdinandIII = new Henkilo("Ferdinand III, Holy Roman Emperor");

mariana.setIsa(ferdinandIII);
mariana.setAiti(mariaSpain);

Koodi luo käytännössä seuraavanlaisen sukupuun.

Tässä tehtävässä harjoitellaan olioviitteiden käsittelyä ja tehdään Sukututkija henkilöiden suhteiden tulostamiseen.

101.1 Sukututkija

Luo luokka Sukututkija. Luokalla ei ole yhtään oliomuuttujaa ja sillä tulee olla konstruktori, jolla ei ole yhtäkään parametria.

Lisää tämän jälkeen sukututkijalle metodi public void kerroHenkilosta(Henkilo henkilo). Metodin ei tarvitse tehdä vielä tässä vaiheessa mitään.

Voit nyt luoda uuden sukututkijan, ja kutsua sille metodia kerroHenkilosta seuraavasti. Alla käytössä myös tehtäväpohjassa valmiina tuleva luokka Henkilo.

Henkilo tupu = new Henkilo("Tupu Ankka");

Sukututkija peloton = new Sukututkija();
peloton.kerroHenkilosta(tupu);

Metodin kerroHenkilosta ei tarvitse vielä tehdä mitään.

101.2 Henkilöstä kertominen

Muokkaa metodia kerroHenkilosta siten, että se tulostaa parametrina annetun henkilön nimen sekä henkilön vanhempien nimet.

Jos henkilön vanhempia ei tunneta (ne ovat null), sanotaan että kyseinen vanhempi on tuntematon.

Henkilo hannu = new Henkilo("Hannu Hanhi");
Henkilo hjalmar = new Henkilo("Hjalmar Hanhi");
Henkilo dora = new Henkilo("Dora Ankka");

hannu.setIsa(hjalmar);
hannu.setAiti(dora);

Sukututkija peloton = new Sukututkija();
peloton.kerroHenkilosta(hannu);

Ylläolevan koodin tulee tulostaa seuraavanlainen lause:

Hannu Hanhi, äiti Dora Ankka ja isä Hjalmar Hanhi.

Jos vanhempaa ei ole asetettu, tulostetaan kyseisen vanhemman kohdalla tuntematon.

Henkilo tupu = new Henkilo("Tupu Ankka");
Henkilo della = new Henkilo("Della Ankka");
tupu.setAiti(della);

Sukututkija peloton = new Sukututkija();
peloton.kerroHenkilosta(tupu);

Ylläoleva koodi tulostaa.

Tupu Ankka, äiti Della Ankka ja isä tuntematon.
Henkilo kornelius = new Henkilo("Kornelius Ankanpää");
Henkilo julle = new Henkilo("Julle Ankanpää");
kornelius.setIsa(julle);

Sukututkija peloton = new Sukututkija();
peloton.kerroHenkilosta(kornelius);

Ylläoleva koodi tulostaa.

Kornelius Ankanpää, äiti tuntematon ja isä Julle Ankanpää.

Vastaavasti jos molemmat vanhemmat ovat tuntemattomia, tulostus on esimerkiksi.

Kornelius Ankanpää, äiti tuntematon ja isä tuntematon.

Olettaen että Kornelius Ankanpää on tulostettavan henkilön nimi.

101.3 Henkilön suvusta kertominen

Huom! Tämä tehtävä on kahden tehtäväpisteen arvoinen.

Lisää sukututkijalle metodi public void kerroHenkilonSuvusta(Henkilo henkilo), joka tulostaa henkilön sekä henkilön jokaisen sukulaisen tiedot edellisen tehtävän muodossa. Huom! Kutsun tulee myös tulostaa jokainen tutkittava esi-isä -- ei esimerkiksi vain kahta sukupolvea.

Alla muutamia esimerkkejä metodin toiminnasta.

Henkilo tupu = new Henkilo("Tupu Ankka");

Sukututkija peloton = new Sukututkija();
peloton.kerroHenkilonSuvusta(tupu);
Tupu Ankka, äiti tuntematon ja isä tuntematon.
Henkilo tupu = new Henkilo("Tupu Ankka");
Henkilo della = new Henkilo("Della Ankka");
tupu.setAiti(della);

Sukututkija peloton = new Sukututkija();
peloton.kerroHenkilonSuvusta(tupu);
Tupu Ankka, äiti Della Ankka ja isä tuntematon.
Della Ankka, äiti tuntematon ja isä tuntematon.

Jos osa henkilön esi-isistä ovat sukua toisilleen, ei haittaa vaikka osa henkilöistä tulostetaan useamman kerran. Alla esimerkkinä muutama Charles II of Spainin esi-isistä.

Henkilo charles = new Henkilo("Charles II of Spain");
Henkilo mariana = new Henkilo("Mariana of Austria");
Henkilo philipIV = new Henkilo("Philip IV of Spain");

charles.setIsa(philipIV);
charles.setAiti(mariana);

Henkilo mariaSpain = new Henkilo("Maria Anna of Spain");
Henkilo ferdinandIII = new Henkilo("Ferdinand III, Holy Roman Emperor");

mariana.setIsa(ferdinandIII);
mariana.setAiti(mariaSpain);

Henkilo philipIII = new Henkilo("Philip III of Spain");
Henkilo margaret = new Henkilo("Margaret of Austria");

philipIV.setIsa(philipIII);
philipIV.setAiti(margaret);

mariaSpain.setIsa(philipIII);
mariaSpain.setAiti(margaret);

Henkilo ferdinandII = new Henkilo("Ferdinand II, Holy Roman Emperor");
Henkilo mariaBavaria = new Henkilo("Maria Anna of Bavaria");

ferdinandIII.setIsa(ferdinandII);
ferdinandIII.setAiti(mariaBavaria);


Sukututkija darwin = new Sukututkija();
darwin.kerroHenkilonSuvusta(charles);
Charles II of Spain, äiti Mariana of Austria ja isä Philip IV of Spain.
Mariana of Austria, äiti Maria Anna of Spain ja isä Ferdinand III, Holy Roman Emperor.
Maria Anna of Spain, äiti Margaret of Austria ja isä Philip III of Spain.
Margaret of Austria, äiti tuntematon ja isä tuntematon.
Philip III of Spain, äiti tuntematon ja isä tuntematon.
Ferdinand III, Holy Roman Emperor, äiti Maria Anna of Bavaria ja isä Ferdinand II, Holy Roman Emperor.
Maria Anna of Bavaria, äiti tuntematon ja isä tuntematon.
Ferdinand II, Holy Roman Emperor, äiti tuntematon ja isä tuntematon.
Philip IV of Spain, äiti Margaret of Austria ja isä Philip III of Spain.
Margaret of Austria, äiti tuntematon ja isä tuntematon.
Philip III of Spain, äiti tuntematon ja isä tuntematon.

Ei siis haittaa jos osa henkilöistä tulostuu useamman kerran.

Huom! henkilöiden tulostusjärjestyksellä ei myöskään ole tässä väliä. Voit siis hyvin kertoa ensin esimerkiksi henkilöstä Philip III of Spain ennen Mariana of Austriasta kertomista.

Vinkki! Kannattanee hyödyntää edellisessä tehtävässä toteutettua metodia kerroHenkilosta. Kun olet toteuttanut metodin, joka kertoo nykyisestä henkilöstä, katso mitä käy jos annat metodille kerroHenkilonSuvusta tällä hetkellä tarkasteltavan henkilön vanhemman (jos sellainen on olemassa).

24.11 Olion sisällä listallinen olioita

Laajennetaan PainonvartijaYhdistys-oliota siten, että yhdistys tallettaa kaikki jäsenensä ArrayList-olioon. Listalle tulee siis Henkilo-olioita. Yhdistykselle annetaan laajennetussa versiossa konstruktorin parametrina nimi:

public class PainonvartijaYhdistys {
    private double alinPainoindeksi;
    private String nimi;
    private ArrayList<Henkilo> jasenet;

    public PainonvartijaYhdistys(String nimi, double alinPainoindeksi) {
        this.alinPainoindeksi = alinPainoindeksi;
        this.nimi = nimi;
        this.jasenet = new ArrayList<>();
    }

//..
}

Tehdään metodi jolla henkilö liitetään yhdistykseen. Metodi ei liitä yhdistykseen kuin tarpeeksi suuren painoindeksin omaavat henkilöt. Tehdään myös toString jossa tulostetaan jäsenten nimet:

public class PainonvartijaYhdistys {
  // ...

    public boolean hyvaksytaanJaseneksi(Henkilo henkilo) {
        if (henkilo.painoIndeksi() < this.alinPainoindeksi) {
            return false;
        }

        return true;
    }

    public void lisaaJaseneksi(Henkilo henkilo) {
        if (!hyvaksytaanJaseneksi(henkilo)) { // sama kuin hyvaksytaanJaseneksi(henkilo) == false
            return;
        }

        this.jasenet.add(henkilo);
    }

    public String toString() {
        String jasenetMerkkijonona = "";

        for (Henkilo jasen : this.jasenet) {
            jasenetMerkkijonona += "  " + jasen.getNimi() + "\n";
        }

        return "Painonvartijayhdistys " + this.nimi + " jäsenet: \n" + jasenetMerkkijonona;
    }
}

Metodi lisaaJaseneksi käyttää aiemmin tehtyä metodia hyvaksytaanJaseneksi.

Kokeillaan laajentunutta painonvartijayhdistystä:

public static void main(String[] args) {
    PainonvartijaYhdistys painonVartija = new PainonvartijaYhdistys("Kumpulan paino", 25);

    Henkilo matti = new Henkilo("Matti");
    matti.setPaino(86);
    matti.setPituus(180);
    painonVartija.lisaaJaseneksi(matti);

    Henkilo juhana = new Henkilo("Juhana");
    juhana.setPaino(64);
    juhana.setPituus(172);
    painonVartija.lisaaJaseneksi(juhana);

    Henkilo harri = new Henkilo("Harri");
    harri.setPaino(104);
    harri.setPituus(182);
    painonVartija.lisaaJaseneksi(harri);

    Henkilo petri = new Henkilo("Petri");
    petri.setPaino(112);
    petri.setPituus(173);
    painonVartija.lisaaJaseneksi(petri);

    System.out.println(painonVartija);
}

Tulostuksesta huomaamme, että Juhanaa ei kelpuutettu jäseneksi:

Painonvartijayhdistys Kumpulan paino jäsenet:
Matti
Harri
Petri

Tehtävä 102: Joukkueet ja pelaajat

102.1 Joukkue-luokka

Tee luokka Joukkue, johon tallennetaan joukkueen nimi (String). Tee luokkaan seuraavat metodit:

  • konstruktori, jolle annetaan joukkueen nimi
  • haeNimi, joka palauttaa joukkueen nimen

Seuraava pääohjelma testaa luokan toimintaa:

public class Main {
    public static void main(String[] args) {
        Joukkue tapiiri = new Joukkue("FC Tapiiri");
        System.out.println("Joukkue: " + tapiiri.haeNimi());
    }
}

Ohjelman tulostus on seuraava:

Joukkue: FC Tapiiri

102.2 Pelaaja

Luo luokka Pelaaja, johon tallennetaan pelaajan nimi ja tehtyjen maalien määrä. Tee luokkaan kaksi konstruktoria: yksi jolle annetaan vain pelaajan nimi, toinen jolle annetaan sekä pelaajan nimi että pelaajan tekemien maalien määrä. Lisää pelaajalle myös metodit:

  • haeNimi, joka palauttaa pelaajan nimen
  • maalit, joka palauttaa tehtyjen maalien määrän
  • toString, joka palauttaa pelaajan merkkijonoesityksen
public class Main {
    public static void main(String[] args) {
        Joukkue tapiiri = new Joukkue("FC Tapiiri");
        System.out.println("Joukkue: " + tapiiri.haeNimi());

        Pelaaja matti = new Pelaaja("Matti");
        System.out.println("Pelaaja: " + matti);

        Pelaaja pekka = new Pelaaja("Pekka", 39);
        System.out.println("Pelaaja: " + pekka);
    }
}
Joukkue: FC Tapiiri
Pelaaja: Matti, maaleja 0
Pelaaja: Pekka, maaleja 39

102.3 Pelaajat joukkueisiin

Lisää luokkaan Joukkue seuraavat metodit:

  • lisaaPelaaja, joka lisää pelaajan joukkueeseen
  • tulostaPelaajat, joka tulostaa joukkueessa olevat pelaajat

Tallenna joukkueessa olevat pelaajat Joukkue-luokan sisäiseen ArrayList-listaan.

Seuraava pääohjelma testaa luokan toimintaa:

public class Main {
    public static void main(String[] args) {
        Joukkue tapiiri = new Joukkue("FC Tapiiri");

        Pelaaja matti = new Pelaaja("Matti");
        Pelaaja pekka = new Pelaaja("Pekka", 39);

        tapiiri.lisaaPelaaja(matti);
        tapiiri.lisaaPelaaja(pekka);
        tapiiri.lisaaPelaaja(new Pelaaja("Mikael", 1)); //vaikutus on sama kuin edellisillä

        tapiiri.tulostaPelaajat();
    }
}

Ohjelman tulostuksen tulisi olla seuraava:

Matti, maaleja 0
Pekka, maaleja 39
Mikael, maaleja 1

102.4 Joukkueen maksimikoko ja nykyinen koko

Lisää luokkaan Joukkue seuraavat metodit:

  • asetaMaksimikoko(int maksimikoko), joka asettaa joukkueen maksimikoon (eli maksimimäärän pelaajia)
  • koko, joka palauttaa pelaajien määrän (int)

Joukkueen suurin sallittu pelaajamäärä on oletusarvoisesti 16. Metodin asetaMaksimikoko avulla tätä rajaa voi muuttaa. Muuta metodia lisaaPelaaja niin, että se ei lisää pelaajaa joukkueeseen, jos sallittu pelaajamäärä ylittyisi.

HUOM: muista lisätä oletusarvoinen maksimikoko koodiisi sillä muuten arvoksi tulee 0. Tämä aiheuttaa edellisen kohdan testien hajoamisen, sillä testit luovat oletusmaksimikokoisia joukkueita ja jos joukkueen maksimikoko on 0, ei joukkueeseen voi lisätä yhtään pelaajaa.

Seuraava pääohjelma testaa luokan toimintaa:

public class Main {
    public static void main(String[] args) {
        Joukkue tapiiri = new Joukkue("FC Tapiiri");
        tapiiri.asetaMaksimikoko(1);

        Pelaaja matti = new Pelaaja("Matti");
        Pelaaja pekka = new Pelaaja("Pekka", 39);
        tapiiri.lisaaPelaaja(matti);
        tapiiri.lisaaPelaaja(pekka);
        tapiiri.lisaaPelaaja(new Pelaaja("Mikael", 1)); //vaikutus on sama kuin edellisillä

        System.out.println("Pelaajia yhteensä: " + tapiiri.koko());
    }
}
Pelaajia yhteensä: 1

102.5 Joukkueen maalit

Lisää luokkaan Joukkue metodi:

  • maalit, joka palauttaa joukkueen pelaajien tekemien maalien yhteismäärän.

Seuraava pääohjelma testaa luokan toimintaa:

public class Main {
    public static void main(String[] args) {
        Joukkue tapiiri = new Joukkue("FC Tapiiri");

        Pelaaja matti = new Pelaaja("Matti");
        Pelaaja pekka = new Pelaaja("Pekka", 39);
        tapiiri.lisaaPelaaja(matti);
        tapiiri.lisaaPelaaja(pekka);
        tapiiri.lisaaPelaaja(new Pelaaja("Mikael", 1)); //vaikutus on sama kuin edellisillä

        System.out.println("Maaleja yhteensä: " + tapiiri.maalit());
    }
}
Maaleja yhteensä: 40

Tehtävä 103: Matkavalokuvat

Matkailijat tykkäävät ottaa valokuvia erilaisista kohteista. Usein kuvissa sattuu kuitenkin olemaan turisti kohteen edessä, mikä harmittaa. Tämä harmitus kasvaa erityisesti, jos samainen turisti esiintyy jokaisessa kuvassa.

Alla on kaksi kuvaa eräästä reissusta.

Tehdään ohjelma, minkä tavoitteena on poistaa turisti kuvasta. Apunamme meillä on kolmannella viikolla olleessa tehtävässä Fotari nähdyn apukirjaston toinen versio, joka tarjoaa apuvälineet kuvien lukemiseen ja näyttämiseen.

Fotarin tarjoama toiminnallisuus on seuraavanlainen:

  • Fotari.lataa(String kuva); lataa parametrina annettavan merkkijonon osoittamassa sijainnissa olevan kuvan, ja palauttaa Kuva-tyyppisen olion. Käyttöesimerkki: Kuva kuva = Fotari.lataa("G0010099.png");.
  • Fotari.nayta(Kuva kuva); avaa parametrina annettavan Kuva-tyyppisen kuvan erilliseen ikkunaan, mistä sitä voi tarkastella. Käyttöesimerkki:
    Kuva kuva = Fotari.lataa("G0010099.png");
    Fotari.nayta(kuva);
    

Tämän lisäksi, ladatuilla kuvaolioilla on käytössä seuraavat metodit:

  • getLeveys(); palauttaa kuvan leveyden. Esimerkki: int leveys = kuva.getLeveys();
  • getKorkeus(); palauttaa kuvan korkeuden. Esimerkki: int korkeus = kuva.getKorkeus();
  • punainen(int x, int y); palauttaa punaisen värin määrän annetussa koordinaatissa. Värin määrä on aina luku väliltä 0-255. Esimerkki: int punainen = kuva.punainen(3, 2);
  • vihrea(int x, int y); palauttaa vihreän värin määrän annetussa koordinaatissa. Värin määrä on aina luku väliltä 0-255. Esimerkki: int vihrea = kuva.vihrea(7, 4);
  • sininen(int x, int y); palauttaa sinisen värin määrän annetussa koordinaatissa. Värin määrä on aina luku väliltä 0-255. Esimerkki: int sininen = kuva.sininen(1, 1);
  • asetaVari(int x, int y, int punainen, int vihrea, int sininen); asettaa annettuun koordinaattiin annetut värimäärät. Esimerkki: kuva.asetaVari(0, 0, 255, 255, 255); -- lisää valkoisen pisteen kuvan kohtaan (0, 0).

Kuva-luokassa on olemassa myös konstruktori, minkä avulla voi luoda uusia Kuva-olioita. Konstruktori saa parametrikseen luotavan kuvan leveyden ja korkeuden. Esimerkiksi seuraava koodi luo uuden kuvan, jonka leveys on 200 ja korkeus on 100, sekä näyttää sen Fotarin avulla. Konstruktori asettaa oletuksena jokaiseen kuvan pikseliin mustan värin, missä punaisen, vihreän ja sinisen määrä on 0.

Kuva kuva = new Kuva(200, 100);
Fotari.nayta(kuva);

103.1 Yhdistin ja kuvien lataaminen

Toteutetaan kuvien yhdistämistä varten erillinen luokka Yhdistin. Luo luokka Yhdistin, jonka konstruktori saa parametrinaan merkkijonon, millä kuvataan yhdistämistapaa. Tarvitset myös oliomuuttujan yhdistämistavan tallentamiseen, sillä sitä hyödynnetään tehtävän myöhemmissä osissa.

Luo yhdistimelle lisäksi apumetodi lataaKuvat, joka saa parametrinaan ArrayList<String>-tyyppisen listan merkkijonoja, jotka sisältävät kuvien sijainteja. Metodin lataaKuvat tulee palauttaa taulukko ArrayList<Kuva>, mihin kuvat on ladattu.

Hyödynnä tässä sopivasti Fotarin tarjoamaa lataa-metodia.

Voit testata kuvien lataamisen toimintaa esimerkiksi seuraavalla koodilla:

ArrayList<String> tiedostot = new ArrayList<>();
tiedostot.add("G0010111.png");
tiedostot.add("G0010161.png");
tiedostot.add("G0010163.png");

Yhdistin yhdistin = new Yhdistin("vaalein");
ArrayList<Kuva> kuvat = yhdistin.lataaKuvat(tiedostot);

Fotari.nayta(kuvat.get(0));

Huom! Jos Fotari tai Kuva on alleviivattu, eikä ohjelma löydä niitä, lisää käyttämiesi luokkien alkuun seuraavat rivit:

import kuva.Fotari;
import kuva.Kuva;

103.2 Ladattujen kuvien yhdistäminen ja vaaleimman värin valinta

Nyt kun saat ladattua kuvia ja näytettyä niitä, tavoitteena on seuraavaksi yhdistää kuvia.

Toteuta yhdistimelle metodi yhdistaKuvat, jolle annetaan parametrina ArrayList<Kuva>-tyyppinen lista kuvia. Jos käyttäjä antaa yhdistimelle konstruktorin parametrina arvon "vaalein", tulee metodin toimia seuraavasti:

  1. Katsotaan ensimmäisen listalla olevan kuvan koko, ja luodaan uusi kuva sen perusteella.
  2. Käydään uuden kuvan jokainen pikseli läpi, ja kopioidaan pikseliin vanhojen kuvien saman pikselin suurin punainen, suurin vihrea, ja suurin sininen.

Suunnittele ylläoleva toteutus ennen kuin lähdet tekemään sitä. Kolmannella viikolla olleen Fotari-tehtävän muistelusta ja tarkastelusta lienee myös hyötyä.

Kun metodi on toteutettu, ohjelmaa voi testata seuraavasti:

ArrayList<String> tiedostot = new ArrayList<>();
// tiedostojen lisääminen

Yhdistin yhdistin = new Yhdistin("vaalein");
ArrayList<Kuva> kuvat = yhdistin.lataaKuvat(tiedostot);
Kuva kuva = yhdistin.yhdistaKuvat(kuvat);
        
Fotari.nayta(kuva);

Lopputulos voi näyttää esimerkiksi seuraavanlaiselta:

103.3 Tummimman värin valinta

Muokkaa toteutustasi siten, että yhdistimelle voi antaa konstruktorin parametrina myös arvon "tummin". Tällöin kuvien yhdistämisen tulee luoda uusi kuva, johon laitetaan yhdistettävien kuvien tummimmat pikselit.

Kun metodi on toteutettu, ohjelmaa voi testata seuraavasti:

ArrayList<String> tiedostot = new ArrayList<>();
// tiedostojen lisääminen

Yhdistin yhdistin = new Yhdistin("tummin");
ArrayList<Kuva> kuvat = yhdistin.lataaKuvat(tiedostot);
Kuva kuva = yhdistin.yhdistaKuvat(kuvat);
        
Fotari.nayta(kuva);

103.4 Värien mediaani

Noniin, hankkiudutaan turistista eroon.

Muokkaa toteutustasi siten, että yhdistimelle voi antaa konstruktorin parametrina arvon "mediaani". Tällöin kuvien yhdistämisen tulee luoda uusi kuva, johon laitetaan yhdistettävien pikseleiden keskimmäinen arvo, eli mediaani. Esimerkiksi, jos viiden kuvan sinisten värien arvot ovat 211, 123, 17, 155, 8, on niiden mediaani 123. Saat mediaanin selville järjestämällä arvot, ja valitsemalla listan keskimmäisen arvon.

Kun metodi on toteutettu, ohjelmaa voi testata seuraavasti:

ArrayList<String> tiedostot = new ArrayList<>();
// tiedostojen lisääminen

Yhdistin yhdistin = new Yhdistin("mediaani");
ArrayList<Kuva> kuvat = yhdistin.lataaKuvat(tiedostot);
Kuva kuva = yhdistin.yhdistaKuvat(kuvat);
        
Fotari.nayta(kuva);

Toteutuksen pitäisi poistaa ärsyttävä turisti:

Huippua!