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

24 Lohkoista ja sisäkkäisistä toistolauseista

Aaltosululla { alkavaa ja aaltosululla } loppuvaa koodia sisältävää aluetta kutsutaan lohkoksi. Kuten on jo nähty, lohkoja käytetään luokkaan kuuluvan alueen rajaamisessa, metodiin kuuluvan alueen määrittelyn rajaamisessa sekä ehto- ja toistolauseiden alueen rajaamisessa. Avautuvalle aaltosululle tulee aina löytyä vastaava sulkeva pari.

Eräs tärkeä, liian pieneen rooliin jäänyt aaltosulkuihin ja lohkoon liittyvä tieto on se, että lohkon sisällä määritellyt muuttujat ovat olemassa vain kyseisen lohkon sisällä.

Alla määritellään ehtolauseeseen liittyvän lohkon sisällä tekstimuuttuja teksti, joka on olemassa vain lohkon sisällä. Lohkossa esitellyn muuttujan tulostus lohkon ulkopuolella ei toimi!

int luku = 5;

if (luku == 5) {
    String teksti = "Oho!";
}

// muuttujaa teksti ei ole olemassa täällä: 
// se määriteltiin edelläolevan lohkon sisällä
System.out.println(teksti);

Lohkossa voidaan käyttää ja muuttaa sen ulkopuolella määriteltyjä muuttujia, kunhan ne on määritelty ennen lohkoa.

int luku = 5;

if (luku == 5) {
    luku = 6;
}

System.out.println(luku); // tulostaa luvun 6
String teksti = "Jee!";
int luku = 5;

if (luku == 5) {
    teksti = "Oho!";
}

System.out.println(teksti); // tulostaa "Oho!"

Lohkon sisällä voi olla lähes mitä tahansa koodia. Esimerkiksi while-toistolauseen määrittelemän lohkon sisällä voi olla toinen while-toistolauseke. Tarkastellaan seuraavaa ohjelmaa:

int rivinumero = 0;

while (rivinumero < 3) {
    System.out.print(rivinumero + ": ");

    int luku = 0;
    while (luku < 3) {
        System.out.print(luku + " ");
        luku++;
    }
    
    System.out.println();
    rivinumero++;
}

Ohjelman tulostus on seuraava:

0: 0 1 2
1: 0 1 2
2: 0 1 2

Eli mitä ohjelmassa tapahtuukaan? Jos ajatellaan pelkkää ulommaista while-lohkoa, on toiminnallisuus helppo ymmärtää:

int rivinumero = 0;

while (rivinumero < 3) {
    System.out.print(rivinumero + ": ");

    // sisempi while

    System.out.println();
    rivinumero++;
}

Eli ensin rivinumero=0 ja tulostuu 0: ja rivinvaihto. Tämän jälkeen rivinumero kasvaa ja tulostuu ykkönen, jne., eli ulompi for saa aikaan seuraavan:

0:
1:
2:

Myös sisempi while on helppo ymmärtää erillään. Se saa aina aikaan tulosteen 0 1 2. Kun yhdistämme nämä kaksi, huomaamme, että sisempi while-toistolause suorittaa tulosteensa aina juuri ennen ulomman while-toistolauseen tulostamaa rivinvaihtoa.

int luku = 0;
while (luku < 3) {
    System.out.print(luku + " ");
    luku++;
}
0 1 2

24.1 Toisen muuttujan käyttö toiston ehtona

Tutkitaan seuraavaa muunnosta edelliseen esimerkkiin:

int rivinumero = 0;

while (rivinumero < 3) {
    System.out.print(rivinumero + ": ");

    int luku = 0;
    while (luku <= rivinumero) {
        System.out.print(luku + " ");
        luku++;
    }
    
    System.out.println();
    rivinumero++;
}

Sisemmän toistolausekkeen toistojen määrä riippuukin nyt ulommassa toistolausekkeessa käytettävän muuttujan rivinumero arvosta. Eli kun rivinumero=0, tulostaa sisempi toistolause luvun 0, kun rivinumero=1, tulostaa sisempi toistolauseke "0 1 ", jne. Koko ohjelman tulostus on seuraava:

0: 0
1: 0 1
2: 0 1 2

Seuraava ohjelma tulostaa lukujen 1..10 kertotaulun.

int rivinumero = 1;

while (rivinumero <= 10) {
    int sarake = 1;

    while (sarake <= 10) {
        System.out.print(rivinumero * sarake + " ");
        sarake++;
    }

    System.out.println();
    rivinumero++;
}

Tulostus näyttää seuraavalta:

1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27 30
4 8 12 16 20 24 28 32 36 40
5 10 15 20 25 30 35 40 45 50
6 12 18 24 30 36 42 48 54 60
7 14 21 28 35 42 49 56 63 70
8 16 24 32 40 48 56 64 72 80
9 18 27 36 45 54 63 72 81 90
10 20 30 40 50 60 70 80 90 100

Ylimmällä rivillä on luvun 1 kertotaulu. Alussa rivinumero=1 ja sisemmän toistolauseen muuttuja sarake saa arvot 1...10. Jokaisella rivinumero, sarake -arvoparilla tulostetaan niiden tulo. Eli alussa rivinumero=1, sarake=1, sitten rivinumero=1, sarake=2, ..., rivinumero=1, sarake=10 seuraavaksi rivinumero=2, sarake=1, jne.

Kertotaulu-ohjelman voi pilkkoa pienempiin osiin. Voimme määritellä metodit public void tulostaKertotaulunRivi(int kerroin, int montakokertaa) ja public void tulostaKertotaulu(int mihinAsti).

public class Kertotaulu {

    public void tulosta(int mihinAsti) {
        int rivinumero = 1;

        while (rivinumero <= mihinAsti) {
            tulostaKertotaulunRivi(rivinumero, mihinAsti);

            System.out.println();
            rivinumero++;
        }
    }


    public void tulostaKertotaulunRivi(int rivi, int lukuja) {
        int sarake = 1;

        while (sarake <= lukuja) {
            System.out.print(rivi * sarake + " ");
            sarake++;
        }
    }
}

Nyt kutsu new Kertotaulu().tulosta(5); tulostaa allaolevan kertotaulun.

1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
4 8 12 16 20
5 10 15 20 25

Kerrataan ja syvennytään seuraavaksi aiemmillakin viikoilla harjoiteltuja toistolausekkeita, muuttujia sekä indeksejä. Seuraavassa kahdessa tehtävässä aiemmin tehdyistä tehtävistä 44, 45, 58 sekä aiemmin tehdyistä tehtävistä on hyötyä.

Tehtävä 94: Lukutulostin

Toteuta luokka Lukutulostin ja tee sille seuraavat toiminnallisuudet.

94.1 Lukuporras

Toteuta luokalle Lukutulostin metodi public void lukuporras(int luku). Metodin tulee toimia seuraavasti.

Lukutulostin tulostin = new Lukutulostin();
tulostin.lukuporras(2);
System.out.println();
tulostin.lukuporras(3);
1
1 2

1
1 2
1 2 3
Lukutulostin tulostin = new Lukutulostin();
tulostin.lukuporras(5);
System.out.println();
tulostin.lukuporras(2);
1
1 2 
1 2 3
1 2 3 4
1 2 3 4 5

1
1 2

94.2 Jatkuva lukuporras

Toteuta luokalle Lukutulostin metodi public void jatkuvaLukuporras(int luku). Metodin tulee toimia seuraavasti.

Lukutulostin tulostin = new Lukutulostin();
tulostin.jatkuvaLukuporras(2);
System.out.println();
tulostin.jatkuvaLukuporras(3);
1
2 3

1
2 3
4 5 6
Lukutulostin tulostin = new Lukutulostin();
tulostin.jatkuvaLukuporras(5);
System.out.println();
tulostin.jatkuvaLukuporras(2);
1
2 3 
4 5 6
7 8 9 10
11 12 13 14 15

1
2 3

94.3 Kertokolmio

Toteuta luokalle Lukutulostin metodi public void kertokolmio(int luku). Kertokolmio-metodin tulee toimia seuraavien esimerkkien mukaisesti.

Lukutulostin tulostin = new Lukutulostin();
tulostin.kertokolmio(2);
1
2 4
Lukutulostin tulostin = new Lukutulostin();
tulostin.kertokolmio(5);
System.out.println();
tulostin.kertokolmio(3);
1
2 4 
3 6 9
4 8 12 16
5 10 15 20 25

1
2 4
3 6 9

Tehtävä 95: Sanatulostin

Toteuta luokka Sanatulostin, jossa on seuraavat toiminnallisuudet. Vinkki: merkkijonon metodilla charAt saa merkin annetusta indeksistä; tässä tehtävässä myös jakojäännöksestä saattaa olla hyötyä.

95.1 Sanaporras

Toteuta luokalle Sanatulostin merkkijonon vastaanottava konstruktori sekä metodi public void sanaporras(int luku), joka toimii seuraavasti:

Sanatulostin tulostin = new Sanatulostin("Ananas");
tulostin.sanaporras(2);
System.out.println();
tulostin.sanaporras(4);
A
na

A
na
nas
Anan
Sanatulostin tulostin = new Sanatulostin("Sauna");
tulostin.sanaporras(3);
S
au
naS

95.2 Laskeva sanaporras

Toteuta luokalle Sanatulostin metodi public void laskevaSanaporras(int luku) joka toimii seuraavasti:

Sanatulostin tulostin = new Sanatulostin("Ananas");
tulostin.laskevaSanaporras(2);
System.out.println();
tulostin.laskevaSanaporras(3);
An
a

Ana
na
s
Sanatulostin tulostin = new Sanatulostin("Sauna");
tulostin.laskevaSanaporras(4);
Saun
aSa
un
a

95.3 Sanapyramidi

Toteuta luokalle Sanatulostin metodi public void sanapyramidi(int luku), joka toimii seuraavasti:

Sanatulostin tulostin = new Sanatulostin("Nauris");
tulostin.sanapyramidi(3);
System.out.println();
tulostin.sanapyramidi(1);
N
au
ris
Na
u

N
Sanatulostin tulostin = new Sanatulostin("Saippuakauppias");
tulostin.sanapyramidi(4);
System.out.println();
tulostin.sanapyramidi(2);
S
ai
ppu
akau
ppi
as
S

S
ai
p

24.2 Lohkot ja luokat

Luokan määrittely alkaa luokan nimellä, jota seuraa lohko.

// luokan Esimerkki määrittely
public class Esimerkki {

}

Tämän lohkon sisälle tulee oliomuuttujat, eli ne muuttujat, joita luokasta tehtävät oliot saavat omakseen.

// luokan Esimerkki määrittely
public class Esimerkki {
  // oliomuuttujat
}

Oliomuuttujien lisäksi luokan määrittelyyn käytettävä lohko sisältää konstruktoreja ja metodeja.

// luokan Esimerkki määrittely
public class Esimerkki {
  // oliomuuttujat

  // konstruktorit

  // metodit
}

Jokainen konstruktori ja metodi myös määrittelee oman lohkonsa.

// luokan Esimerkki määrittely
public class Esimerkki {
  // oliomuuttujat

  // konstruktorit
  public Esimerkki() {
    // konstruktorin oma lohko
  }

  // metodit
  public void tulostaLuku() {
    // metodin tulostaLuku oma lohko
  }
}

Konstruktoreissa ja metodeissa voi olla myös sisäisiä lohkoja.

// luokan Esimerkki määrittely
public class Esimerkki {
  // oliomuuttujat

  // konstruktorit
  public Esimerkki() {
    // konstruktorin oma lohko
  }

  // metodit
  public void tulostaLuku() {
    // metodin tulostaLuku oma lohko
    int luku = 5;
    if (luku > 4) {
      // lisää lohkoja!
    }
  }
}

Lohkoja kaikkialla!

25 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ä 96: 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ä (vinkki: plus!).

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();

        // tallennetaan päivät listalle
        ArrayList<String> paivat = new ArrayList<>();
        paivat.add("Ma");
        paivat.add("Ti");
        paivat.add("Ke");
        paivat.add("To");
        paivat.add("Pe");
        paivat.add("La");
        paivat.add("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ä 97: Salasanan arpoja

Vihje: näin muutat kokonaisluvun luku kirjaimeksi:

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

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

Tehtävä 98: 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.

26 Lisää luokista ja olioista

Merkittävä osa tämän viikon tehtävistä liittyy olioiden sekä niiden välisten viittausten rakentamiseen ja ymmärtämiseen. Aloitamme yksittäisten olioiden rakentamisella, jonka jälkeen pohdimme viittaustyyppisen muuttujan toiminnallisuutta "olio on langan päässä"-lauseen kautta. Käsittelemme tämän jälkeen olioita, jotka käsittelevät olioita, jonka jälkeen siirrymme olioihin, jotka voivat sisältää oliota sekä listallisia muita olioita.

Tällä viikolla -- kuten muutenkin -- tekee hyvää ottaa välillä askel taaemmaksi TMC:stä ja piirtää paperille käytettyjen olioiden välisiä viitteitä. Kaikki oma harjoittelu esimerkiksi hiekkalaatikossa on myös erittäin hyödyllistä.

26.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 ada = new Henkilo("Ada");

    System.out.println(pekka);
    System.out.println(ada);
}
Pekka, ikä 24 vuotta
Ada, 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ää.

26.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, eli nimen parametrinaan saava konstruktori, on oikeastaan alemman, eli nimen ja iän parametrinaan saavan konstruktorin, 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

26.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ä 99: Kuormitettu laskuri

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

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

26.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 varaamaan 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.

26.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ä 100: 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!

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

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

100.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ä 101: Maksukortti ja Kassapääte

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

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

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

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

26.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 ada = new Henkilo("Ada")

if (pekka.getIka() > ada.getIka()) {
    System.out.println(pekka.getNimi() + " on vanhempi kuin " + ada.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 ada = new Henkilo("Ada", 22);

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

Tässä siis kysytään onko pekka anttia vanhempi "jos pekka on vanhempi kuin ada". 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 "Adaa" vastaavan olion viite antti.

Ohjelman tulostaa:

Pekka on vanhempi kuin Ada

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.

26.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ä 102: 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.

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

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

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

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

Vaihtoehtoinen tapa edellisen ohjelman toteutuksella on seuraava. Jos listalla ei ole yhtäkään asuntoa, emme jatka metodin suoritusta eteenpäin (komento return). Muuten otamme suurimmaksi asunnoksi listan ensimmäisen alkion, ja vertailemme asunnot kuten edellä. Tällä tavalla vältämme toistolauseen sisällä olevan nul-vertailun.

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

    if(asunnot.isEmpty()) {
        System.out.println("Ei löytynyt suurinta asuntoa :(");
        return; // lopetetaan void-tyyppisen metodin suoritus
    }

    Asunto suurin = asunnot.get(0);

    for(Asunto asunto: asunnot) {
        if (asunto.suurempi(suurin)) {
            suurin = asunto;
        }
    }

    System.out.println("Suurin asunto löytyi!");
}

Ohjelman tulostus:

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

Tehtävä 103: Opiskelija-luokka

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

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

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

26.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 26.4 todettiin, että oliot ovat "langan päässä". Kertaa nyt luku 26.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ä 104: 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
      }
}

Luokkaan YlhaaltaRajoitettuLaskuri on kopioitu eräs ratkaisu viime viikon tehtävään. Toteuta luokan Kello konstruktori ja puuttuvat metodit kolmea ylhäältä rajoitettua laskuria hyödyntäen.

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

26.10 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ä 105: Joukkueet ja pelaajat

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

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

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

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

105.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ä 106: 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);

106.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;

106.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:

106.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);

106.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!

Viikon 5 Loppukysely

Viikon 5 Loppukysely

 

Alla on esitetty viikkoon liittyviä väittämiä. Jos olet täysin eri mieltä väittämän kanssa, valitse 1. Jos olet täysin samaa mieltä väittämän kanssa, valitse 7. Muuten, valitse luku väliltä.

Täysin eri mieltä.
1
2
3
4
5
6
7
Täysin samaa mieltä.

 

1. Viikko oli työläs.
2. Viikko oli opettavainen.
3. Viikko oli turhauttava.
4. Viikko oli mukava.
5. Viikko oli vaikea.