Sisällysluettelo

Tehtävät

Kysely: Ohjelmointirutiini ja muuttujien ymmärrys, osa 3

Tutkimus: Ohjelmointirutiini ja muuttujien ymmärrys

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

Tässä kyselyssä kartoitetaan tähän mennessä kertynyttä ohjelmointirutiiniasi sekä muuttujien ymmärrystä. Vastaathan kyselyyn nykyisen osaamisesi perusteella, älä siis esimerkiksi kokeile koodinpätkiä tietokoneella ennen vastauksen antamista.

 

Tutki seuraavaa koodia.

if (a > b) {
    if (b > c) {
        System.out.println(c);
    } else {
        System.out.println(b);
    }
} else {
    if (a > c) {
        System.out.println(c);
    } else {
        System.out.println(a);
    }
}

Kun pohdit ylläolevaa koodia, mikä seuraavista muuttujien arvoista johtaa muuttujan b arvon tulostamiseen?

a=1; b=2; c=3;
a=1; b=3; c=2;
a=2; b=1; c=3;
a=3; b=2; c=1;

 

Kuvaa yhdellä lauseella ylläolevan koodin toimintaa ja tarkoitusta. Huom! Älä kerro koodin toimintaa rivi riviltä, vaan kerro koodin tarkoitus.

Kysely: Opiskelutyyli

Tutkimus: Opiskelutyyli

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

Tässä kyselyssä kartoitetaan suosittuja oppimistyylejä. Aseta jokaisessa kohdassa numero 3 sille vaihtoehdolle, joka luonnehtii Sinua parhaiten, ja numero 1 sille vaihtoehdolle, joka luonnehtii Sinua vähiten. Aseta numero 2 väliin jääville.

 

  1. Opin helpoiten
    • näkemällä, katsomalla
    • kuulemalla, keskustelemalla
    • motorisesti tehden, kosketellen
  2. Muistan parhaiten asiat jos
    • olen nähnyt ne
    • muistettavaan on liittynyt ääniä, puhetta
    • niihin on liittynyt motorista tekemistä, koskettamista
  3. Toisista ihmisistä muistan helpoiten
    • ulkomuodon, kasvot, vaatteet
    • hänen äänensä, puheensa
    • hänen kanssaan tehdyt, koetut asiat
  4. Puherytmini on
    • nopeaa
    • keskinopeaa
    • hidasta
  5. Sanavalinnoissani sanon tyypillisesti
    • miltä näyttää?
    • miltä kuulostaa?
    • miltä tuntuu?
  6. Suunnitellessani suosin
    • visuaalista materiaalia, kuvia, tekstiä
    • juttelen jonkun kanssa tai sisäistä itsepuhelua
    • teen konkreettisia kokeiluja ja tunnustelen, miltä eri vaihtoehdot tuntuvat
  7. Vieraalla paikkakunnalla löydän
    • karttaa/visuaalisia opasteita tutkimalla
    • kysymällä joltakulta
    • lähden etsimään kävellen/kulkuvälineellä ja samalla painan paikkoja mieleen
  8. Merkittävimmät elämykset saan
    • kauniista näkymistä
    • Hyvästä musiikista tai muista äänistä
    • kosketus- ja/tai liikunnallisista kokemuksista
  9. Vaateostoksia tehdessäni pohdin voimakkaimmin
    • miltä ne näyttävät
    • millaisia puheita niistä käydään
    • miltä ne tuntuvat ylläni
  10. Jos on täysi vapaus valita, suunnittelen olohuoneeni niin, että
    • se miellyttää minun silmääni/visuaalista kauneustajuani
    • sen ääniympäristö on miellyttävä (äänet omasta huoneesta, naapurista, ulkoa)
    • oleminen, tekeminen, liikkuminen on helppoa ja mukavaa (materiaalit, sijainnit)

25 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 muun muassa ehto- ja toistolauseiden alueen rajaamisessa. Tärkeä lohkon ominaisuus on se, että lohkossa määritellyt muuttujat ovat voimassa vain lohkon sisällä.

Seuraavassa esimerkissä määritellään ehtolausekkeeseen liittyvän lohkon sisällä tekstimuuttuja 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!";
}

System.out.println(teksti); // ei toimi!

Lohkossa voidaan käyttää ja muuttaa sen ulkopuolella ennen lohkoa määriteltyjä muuttujia.

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

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

Harjoitellaan taas toistolausekkeiden, muuttujien ja indeksien käyttöä.

Tehtävä 104: Lukutulostin

Toteuta luokka Lukutulostin ja tee sille seuraavat toiminnallisuudet. Huom! Tässä tehtävässä ei ole etenemistä ohjaavia automaattisia testejä, jotta voit harjoitella ohjelmointia ilman "apupyöriä". Palauta tehtävä kun tiedät itse, että se toimii halutusti. Pajassa saat tukea myös tehtäviin, joissa apupyöriä ei ole!

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

104.2 Rajattu lukuporras

Toteuta luokalle Lukutulostin metodi public void rajattuLukuporras(int alku, int loppu). Metodin tulee toimia seuraavasti.

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

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

1
1 2

104.3 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

104.4 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ä 105: Sanatulostin

Toteuta luokka Sanatulostin, jossa on seuraavat toiminnallisuudet. Kuten edellisessä tehtävässä, myös tässäkään tehtävässä ei ole apupyöriä.

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

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

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

26 Olioharjoittelua

Kertaa edellisen viikon materiaali ennen jatkamista!

Tehtävä 106: Ostoslista

Jatkamme vielä tehtävien tekemistä siten, että niissä ei ole apupyöriä. Tehdään tässä luokka ostoslistan ylläpitoon.

106.1 Lisääminen ja tarkistus

Toteuta luokka Ostoslista, jolla on parametriton konstruktori, ja joka sisältää listan String-olioita. Toteuta luokalle metodit public void lisaa(String tuote) ja public boolean onkoListalla(String tuote).

Luokkaa Ostoslista ja luotavia metodeja tulee voida käyttää seuraavasti:

Ostoslista lista = new Ostoslista();
lista.lisaa("porkkana");
lista.lisaa("nauris");

if (!lista.onkoListalla("kauris")) {
    System.out.println("kauris ei ole listalla");
}

if (lista.onkoListalla("nauris")) {
    System.out.println("nauris on listalla");
}
kauris ei ole listalla
nauris on listalla

106.2 Kappalemäärä

Lisää luokalle Ostoslista metodi public int kappalemaara(String tuote), joka palauttaa listalla olevien tuotteiden lukumäärän haetulle tuotteelle. Metodin tulee toimia seuraavasti.

Ostoslista lista = new Ostoslista();
lista.lisaa("porkkana");
lista.lisaa("porkkana");
lista.lisaa("nauris");
lista.lisaa("porkkana");

System.out.println("Porkkanaa: " + lista.kappalemaara("porkkana") + " kpl");
System.out.println("Tomaatteja: " + lista.kappalemaara("tomaatti") + " kpl");
Porkkanaa: 3 kpl
Tomaatteja: 0 kpl

106.3 Listalta poistaminen

Lisää luokalle Ostoslista metodi public void poista(String tuote), joka poistaa yhden kappaleen annettua tuotetta. Metodin tulee toimia seuraavasti.

Ostoslista lista = new Ostoslista();
lista.lisaa("porkkana");
lista.lisaa("porkkana");
lista.lisaa("nauris");
lista.lisaa("porkkana");

System.out.println("Porkkanaa: " + lista.kappalemaara("porkkana") + " kpl");
System.out.println("Tomaatteja: " + lista.kappalemaara("tomaatti") + " kpl");

lista.poista("porkkana");

System.out.println();

System.out.println("Porkkanaa: " + lista.kappalemaara("porkkana") + " kpl");
System.out.println("Tomaatteja: " + lista.kappalemaara("tomaatti") + " kpl");
Porkkanaa: 3 kpl
Tomaatteja: 0 kpl

Porkkanaa: 2 kpl
Tomaatteja: 0 kpl

106.4 Siisti tulostus

Lisää luokalle Ostoslista metodi, jonka avulla siitä tehtävä olio voidaan tulostaa System.out.println()-komennon avulla. Tulostuksen tulee toimia seuraavasti (huomaathan että tuotteet tulostetaan aakkosjärjestyksessä):

Ostoslista lista = new Ostoslista();
lista.lisaa("porkkana");
lista.lisaa("porkkana");
lista.lisaa("nauris");
lista.lisaa("porkkana");
lista.lisaa("satsuma");

System.out.println(lista);

lista.poista("porkkana");

System.out.println(lista);
Ostetaan seuraavat tuotteet
nauris 1 kpl.
porkkana 3 kpl.
satsuma 1 kpl.

Ostetaan seuraavat tuotteet
nauris 1 kpl.
porkkana 2 kpl.
satsuma 1 kpl.

26.1 Metodi palauttaa olion

Olemme nähneet metodeja jotka palauttavat totuusarvoja, lukuja, listoja ja merkkijonoja. On helppoa arvata, että metodi voi palauttaa minkä tahansa tyyppisen olion. Tehdään painovartijayhdistykselle metodi, jolla saadaan tietoon yhdistyksen suurimman painoindeksin omaava henkilö.

public class PainonvartijaYhdistys {
    // ...

    public Henkilo suurinPainoindeksinen() {
        // jos jasenlista on tyhjä, palautetaan null-viite
        if (this.jasenet.isEmpty()) {
            return null;
        }

        Henkilo painavinTahanAsti = this.jasenet.get(0);

        for (Henkilo henkilo : this.jasenet) {
            if (henkilo.painoIndeksi() > painavinTahanAsti.painoIndeksi()) {
                painavinTahanAsti = henkilo;
            }
        }

        return painavinTahanAsti;
    }
}

Logiikaltaan edeltävä metodi toimii samaan tapaan kuin suurimman luvun etsiminen taulukosta. Käytössä on apumuuttuja painavinTahanAsti joka laitetaan aluksi viittaamaan listan ensimmäiseen henkilöön. Sen jälkeen käydään lista läpi ja katsotaan tuleeko vastaan suuremman painoindeksin omaavia henkilöitä, jos tulee, niin otetaan viite talteen muuttujaan painavinTahanAsti. Lopuksi palautetaan muuttujan arvo eli viite henkilöolioon.

Tehdään lisäys edelliseen pääohjelmaan. Pääohjelma ottaa vastaan metodin palauttaman viitteen muuttujaan painavin.

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

    // ..

    Henkilo painavin = painonVartija.suurinPainoindeksinen();
    System.out.print("suurin painoindeksinen jäsen: " + painavin.getNimi());
    System.out.println(" painoindeksi " + String.format("%.2f", painavin.painoIndeksi()));
}

Tulostuu:

suurin painoindeksinen jäsen: Petri
painoindeksi 37,42

26.2 Metodi palauttaa luomansa olion

Edellisessä esimerkissä metodi palautti yhden painonVartija-olion sisältämistä Henkilo-olioista. On myös mahdollista, että metodi palauttaa kokonaan uuden olion. Seuraavassa yksinkertainen laskuri, jolla on metodi kloonaa, jonka avulla laskurista voidaan tehdä klooni, eli uusi laskurio-olio, jolla on luomishetkellä sama arvo kuin kloonattavalla laskurilla:

public Laskuri {
    private int arvo;

    public Laskuri() {
        this(0);
    }

    public Laskuri(int alkuarvo) {
        this.arvo = alkuarvo;
    }

    public void kasvata() {
        this.arvo++;
    }

    public String toString() {
        return "arvo: " + arvo;
    }

    public Laskuri kloonaa() {
        // luodaan uusi laskuriolio, joka saa alkuarvokseen kloonattavan laskurin arvon
        Laskuri klooni = new Laskuri(this.arvo);

        // palautetaan klooni kutsujalle
        return klooni;
    }
}

Seuraavassa käyttöesimerkki:

Laskuri laskuri = new Laskuri();
laskuri.kasvata();
laskuri.kasvata();

System.out.println(laskuri);         // tulostuu 2

Laskuri klooni = laskuri.kloonaa();

System.out.println(laskuri);         // tulostuu 2
System.out.println(klooni);          // tulostuu 2

laskuri.kasvata();
laskuri.kasvata();
laskuri.kasvata();
laskuri.kasvata();

System.out.println(laskuri);         // tulostuu 6
System.out.println(klooni);          // tulostuu 2

klooni.kasvata();

System.out.println(laskuri);         // tulostuu 6
System.out.println(klooni);          // tulostuu 3

Kloonattavan ja kloonin arvo on siis kloonauksen tapahduttua sama. Kyseessä on kuitenkin kaksi erillistä olioa, eli jatkossa kun toista laskureista kasvatetaan, ei kasvatus vaikuta toisen arvoon millään tavalla.

Vastaavasti myös Tehdas-olio voisi luoda ja palauttaa uusia Auto-olioita. Alla on hahmoteltu tehtaan runkoa -- tehdas tietää myös luotavien autojen merkin.

public class Tehdas {
    private String merkki;

    public Tehdas(String merkki) {
        this.merkki = merkki;
    }

    public Auto tuotaAuto() {
        return new Auto(this.merkki);
    }
}

Tehtävä 107: Päiväys

Tehtäväpohjan mukana tulee aiemmin esitelty luokka Paivays, jossa päivämäärä talletetaan oliomuuttujien vuosi, kuukausi, ja paiva avulla:

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

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

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

    public boolean aiemmin(Paivays verrattava) {
        // ensin verrataan vuosia
        if (this.vuosi < verrattava.vuosi) {
            return true;
        }

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

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

        return false;
    }
}

Tässä tehtäväsarjassa laajennetaan luokkaa.

107.1 Seuraava päivä

Toteuta metodi public void etene(), joka siirtää päiväystä yhdellä päivällä. Tässä tehtävässä oletetaan, että jokaisessa kuukaudessa on 30 päivää. Huom! Sinun tulee tietyissä tilanteissa muuttaa kuukauden ja vuoden arvoa.

107.2 Tietty määrä päiviä eteenpäin

Toteuta metodi public void etene(int montakoPaivaa), joka siirtää päiväystä annetun päivien määrän verran. Käytä apuna edellisessä tehtävässä toteutettua metodia etene().

107.3 Ajan kuluminen

Lisätään Paivays-olioon mahdollisuus edistää aikaa. Tee oliolle metodi Paivays paivienPaasta(int paivia), joka luo uuden Paivays-olion, jonka päiväys on annetun päivien lukumäärän verran suurempi kuin oliolla, jolle sitä kutsuttiin. Voit edelleen olettaa, että jokaisessa kuukaudessa on 30 päivää. Huomaa, että vanhan päiväysolion on pysyttävä muuttumattomana!

Koska metodissa on luotava uusi olio, tulee rungon olla suunnilleen seuraavanlainen:

public Paivays paivienPaasta(int paivia) {
    Paivays uusiPaivays = new Paivays( ... );

    // tehdään jotain...

    return uusiPaivays;
}

Ohessa on esimerkki metodin toiminnasta.

public static void main(String[] args) {
    Paivays pvm = new Paivays(13, 2, 2015);
    System.out.println("Tarkistellun viikon perjantai on " + pvm);

    Paivays uusiPvm = pvm.paivienPaasta(7);
    for (int i = 1; i <= 7; ++i) {
        System.out.println("Perjantai " + i + " viikon kuluttua on " + uusiPvm);
        uusiPvm = uusiPvm.paivienPaasta(7);
    }


    System.out.println("Päivämäärä 790:n päivän päästä tarkistellusta perjantaista on ... kokeile itse!"); 
//    System.out.println("Kokeile " + pvm.paivienPaasta(790));
}

Ohjelma tulostaa:

Tarkistellun viikon perjantai on 13.2.2015
Perjantai 1 viikon kuluttua on 20.2.2015
Perjantai 2 viikon kuluttua on 27.2.2015
Perjantai 3 viikon kuluttua on 4.3.2015
Perjantai 4 viikon kuluttua on 11.3.2015
Perjantai 5 viikon kuluttua on 18.3.2015
Perjantai 6 viikon kuluttua on 25.3.2015
Perjantai 7 viikon kuluttua on 2.4.2015
Päivämäärä 790:n päivän päästä tarkistellusta perjantaista on ... kokeile itse!

Huom! Sen sijaan, että muuttaisimme vanhan olion tilaa palautamme uuden olion. Kuvitellaan, että Paivays-luokalle on olemassa metodi edista, joka toimii vastaavasti kuin ohjelmoimamme metodi, mutta se muuttaa vanhan olion tilaa. Tällöin seuraava koodin pätkä tuottaisi ongelmia.

Paivays nyt = new Paivays(13, 2, 2015);
Paivays viikonPaasta = nyt;
viikonPaasta.edista(7);

System.out.println("Nyt: " + nyt);
System.out.println("Viikon päästä: " + viikonPaasta);

Ohjelman tulostus olisi seuraavanlainen:

Nyt 20.2.2015
Viikon päästä 20.2.2015

Tämä johtuu siitä, että tavallinen sijoitus kopioi ainoastaan viitteen olioon. Siis itse asiassa ohjelman oliot nyt ja viikonPaasta viittavaat yhteen ja samaan Paivays-olioon.

Tehtävä 108: Päivämäärien erotus

Jatketaan luokan Päiväys laajentamista. Tämä tehtävä ei riipu edellisestä tehtävästä, saat tehtäväpohjan mukana Paivays-luokan jossa ei ole edellisen tehtävän lisäyksiä.

108.1 Kahden päiväyksen erotus vuosissa

Lisää päiväykselle metodi public int erotusVuosissa(Paivays verrattava), jonka avulla saadaan selville päiväyksen ja verrattavan päiväyksen ero vuosissa. Huomioi seuraavat:

  • Ensimmäisessä versiossa metodi toimii vasta aika karkealla tasolla, se ainoastaan laskee verrattavien päiväysten vuosilukujen erotuksen.
  • Metodin tarvitsee toimia ainoastaan siten, että parametriksi annettava päivämäärä on aiempi kuin se päivämäärä jolle metodia kutsutaan.

Seuraava pääohjelma demonstroi metodin käyttöä:

public class Paaohjelma {
    public static void main(String[] args) {
        Paivays eka = new Paivays(24, 12, 2009);
        Paivays toka = new Paivays(1, 1, 2011);
        Paivays kolmas = new Paivays(25, 12, 2010);

        System.out.println(toka + " ja " + eka + " ero vuosissa: " + toka.erotusVuosissa(eka));

        System.out.println(kolmas + " ja " + eka + " ero vuosissa: " + kolmas.erotusVuosissa(eka));

        System.out.println(toka + " ja " + kolmas + " ero vuosissa: " + toka.erotusVuosissa(kolmas));
    }
}

Tulos näyttää seuraavalta:

1.1.2011 ja 24.12.2009 ero vuosissa: 2
25.12.2010 ja 24.12.2009 ero vuosissa: 1
1.1.2011 ja 25.12.2010 ero vuosissa: 1

108.2 Tarkennettu versio

Vuosien laskenta ei edellisessä versiossa ollut vielä kovin tarkkaa. Esim. 1.1.2011 ja 25.12.2010 välillä ilmoitettiin olevan vuoden ero. Tarkennetaan metodin toiminta sellaiseksi, että se osaa laskea vuodet kunnolla. Laske erotukseen mukaan vain täydet vuodet. Eli vaikka päiväysten ero olisi 1 vuosi ja 364 päivää, ilmoittaa metodi eroksi vuoden.

Metodin tämänkin version tarvitsee toimia ainoastaan siten, että parametriksi annettava päivämäärä on aiempi kuin se päivämäärä jolle metodia kutsutaan.

Edellisen esimerkin tulos on nyt:

1.1.2011 ja 24.12.2009 ero vuosissa: 1
25.12.2010 ja 24.12.2009 ero vuosissa: 1
1.1.2011 ja 25.12.2010 ero vuosissa: 0

108.3 Ja lopullinen versio

Laitetaan metodi toimimaan samoin riippumatta onko parametrina annettava päiväys myöhempi vai aiempi kuin päiväys mille metodia kutsutaan. Esimerkkipääohjelma:

public class Paaohjelma {
    public static void main(String[] args) {
        Paivays eka = new Paivays(24, 12, 2009);
        Paivays toka = new Paivays(1, 1, 2011);
        Paivays kolmas = new Paivays(25, 12, 2010);

        System.out.println(eka + " ja " + toka + " ero vuosissa: " + toka.erotusVuosissa(eka));
        System.out.println(toka + " ja " + eka + " ero vuosissa: " + eka.erotusVuosissa(toka));
        System.out.println(eka + " ja " + kolmas + " ero vuosissa: " + kolmas.erotusVuosissa(eka));
        System.out.println(kolmas + " ja " + eka + " ero vuosissa: " + eka.erotusVuosissa(kolmas));
        System.out.println(kolmas + " ja " + toka + " ero vuosissa: " + toka.erotusVuosissa(kolmas));
        System.out.println(toka + " ja " + kolmas + " ero vuosissa: " + kolmas.erotusVuosissa(toka));
    }
}
24.12.2009 ja 1.1.2011 ero vuosissa: 1
1.1.2011 ja 24.12.2009 ero vuosissa: 1
24.12.2009 ja 25.12.2010 ero vuosissa: 1
25.12.2010 ja 24.12.2009 ero vuosissa: 1
1.1.2011 ja 25.12.2010 ero vuosissa: 0
25.12.2010 ja 1.1.2011 ero vuosissa: 0

Tehtävä 109: Luokan Henkilö muokkaaminen

109.1 Iän laskeminen syntymäpäivän perusteella

Lisäsimme aiemmin henkilölle oliomuuttujaksi syntymäpäivän kertova Paivays-olio. Samalla huomattiin, että oliomuuttuja ika kannattaa poistaa sillä iän pystyy laskemaan päiväyksen ja syntymäpäivän avulla.

Toteuta metodi ika joka palauttaa henkilön iän.

Huom: edellisessä tehtävässä lisättiin luokalle Paivays metodi public int erotusVuosissa(Paivays verrattava). Kannattaa kopioida metodi tässä tehtävässä olevaan luokkaan, se helpottaa tehtävän tekemistä oleellisesti!

import java.util.Calendar;

public class Henkilo {
    private String nimi;
    private Paivays syntymaPaiva;

    public Henkilo(String nimi, int pp, int kk, int vv) {
        this.nimi = nimi;
        this.syntymaPaiva = new Paivays(pp, kk, vv);
    }

    public int ika() {
        // laske henkilön ikä syntymäpäivän ja tämän päivän perusteella
        // tämä päivä saadaan selville seuraavasti
        // Calendar.getInstance().get(Calendar.DATE);
        // Calendar.getInstance().get(Calendar.MONTH) + 1; // tammikuun numero on 0 joten lisätään 1
        // Calendar.getInstance().get(Calendar.YEAR);
        return -1;
    }

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

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

Voit testata Henkilöä seuraavalla pääohjelmalla. Lisää myös itsesi ohjelmaan ja varmista että ikäsi tulostuu oikein.

public class Main {
    public static void main(String[] args) {
        Henkilo joni = new Henkilo("Joni", 15, 1, 1993);
        Henkilo leo = new Henkilo("Leo", 1, 1, 1990);

        System.out.println(joni.getNimi() + " ikä " + joni.ika() + " vuotta");
        System.out.println(leo.getNimi() + " ikä " + leo.ika() + " vuotta");
    }
}

Tulostus:

Joni ikä 22 vuotta
Leo ikä 25 vuotta

109.2 Iän vertailu syntymäpäivien perusteella

Tee henkilölle metodi jonka avulla se vertaa ikäänsä parametrina annettuun henkilöön. Jos henkilö on vanhempi eli syntynyt aiemmin, palauttaa metodi true ja muuten false.

public class Henkilo {
    // ...

    public boolean vanhempiKuin(Henkilo verrattava) {
        // vertaa henklöiden ikiä käyttäen henkilöiden syntymäpäivää
    }
}

Ja testaa laajennettua Henkilö-luokkaa esim. seuraavasti:

public class Main {
    public static void main(String[] args) {
        Henkilo pekka = new Henkilo("Pekka", 15, 2, 1983);
        Henkilo martin = new Henkilo("Martin", 1, 3, 1983);

        System.out.println(martin.getNimi() + " vanhempi kuin " + pekka.getNimi() + ": " + martin.vanhempiKuin(pekka));
        System.out.println(pekka.getNimi() + " vanhempi kuin " + martin.getNimi() + ": " + pekka.vanhempiKuin(martin));
    }
}

Tulostus:

Martin vanhempi kuin Pekka: false
Pekka vanhempi kuin Martin: true

109.3 Uusia konstruktoreja

Tee Henkilo-luokalle kaksi uutta konstruktoria:

  • public Henkilo(String nimi, Paivays syntymapaiva) - jossa konstruktori käyttää annettua Paivays-oliota syntymäpäivänä
  • public Henkilo(String nimi) - jossa konstruktori määrittää syntymäpäiväksi tämänhetkisen päivän

Testaa uusia konstruktoreja esim. seuraavasti:

public class Main {
      public static void main(String[] args) {
      Henkilo pekka = new Henkilo("Pekka", new Paivays(15, 2, 1983));
      Henkilo sepe = new Henkilo("Sepe");

      System.out.println(pekka);
      System.out.println(sepe);
    }
}

Esimerkkitulostus:

Pekka, syntynyt 15.2.1983
Sepe, syntynyt 13.2.2015

Huom: jälkimmäinen rivi riippuu päivämäärästä, jolloin koodi ajetaan!

MUISTUTUS kun lisäät ohjelmaasi ArrayList:in, Scanner:in tai Random:in ei Java tunnista luokkaa ellet "importoi" sitä lisäämällä ohjelmatiedoston alkuun:

import java.util.ArrayList;    // importoi ArrayListin
import java.util.*;            // importoi kaikki java.util:sissa olevat työkalut, mm. ArrayListin, Scannerin ja Randomin

Tehtävä 110: Puhelinmuistio

Tehtävässä tehdään puhelinmuistio.

110.1 Henkilö

Tee ensin luokka Henkilo Luokan pitää toimia seuraavan esimerkin osoittamalla tavalla:

public static void main(String[] args) {
    Henkilo pekka = new Henkilo("Pekka Mikkola", "040-123123");

    System.out.println(pekka.haeNimi());
    System.out.println(pekka.haeNumero());

    System.out.println(pekka);

    pekka.vaihdaNumeroa("050-333444");
    System.out.println(pekka);
}
  

Tulostuu:

Pekka Mikkola
040-123123
Pekka Mikkola  puh: 040-123123
Pekka Mikkola  puh: 050-333444

Tee siis luokalle

  • metodi public String toString(), joka palauttaa henkilön merkkijonoesityksen (yo. esimerkin tapaan muotoiltuna)
  • konstruktori, jolla asetetaan henkilölle nimi ja puhelinnumero
  • public String haeNimi(), joka palauttaa nimen
  • public String haeNumero(), joka palauttaa puhelinnumeron
  • metodi public void vaihdaNumeroa(String uusiNumero), joka muuttaa henkilön puhelinnumeroa

110.2 Henkilöiden lisäys puhelinmuistioon

Tee luokka Puhelinmuistio joka tallettaa sisällään olevaan ArrayListiin Henkilo-olioita. Tässä vaiheessa luokalle tehdään seuraavat metodit:

  • public void lisaa(String nimi, String numero) luo Henkilo-olion ja lisää sen puhelinmuistion ArrayListiin.
  • public void tulostaKaikki(), tulostaa puhelinmuistion sisällön

Esimerkki muistion toiminnasta:

public static void main(String[] args) {
    Puhelinmuistio muistio = new Puhelinmuistio();

    muistio.lisaa("Pekka Mikkola", "040-123123");
    muistio.lisaa("Antti Laaksonen", "045-456123");
    muistio.lisaa("Juhana Laurinharju", "050-222333");

    muistio.tulostaKaikki();
}
  

Ohjelman tulostus oikein toteutetuilla luokilla on:

Pekka Mikkola  puh: 040-123123
Antti Laaksonen  puh: 045-456123
Juhana Laurinharju  puh: 050-222333

110.3 Numerojen haku muistiosta

Tehdään puhelinmuistiolle metodi public String haeNumero(String nimi), joka palauttaa parametrina annetun henkilön numeron. Jos henkilö ei ole muistiossa, palautetaan merkkijono "numero ei tiedossa". Esimerkki metodin toiminnasta:

public static void main(String[] args) {
    Puhelinmuistio muistio = new Puhelinmuistio();
    muistio.lisaa("Pekka Mikkola", "040-123123");
    muistio.lisaa("Antti Laaksonen", "045-456123");
    muistio.lisaa("Juhana Laurinharju", "050-222333");

    String numero = muistio.haeNumero("Pekka Mikkola");
    System.out.println(numero);

    numero = muistio.haeNumero("Martti Tienari");
    System.out.println(numero);
}

Tulostuu:

040-123123
numero ei tiedossa

Tehtävä 111: Raha

Maksukortti-tehtävässä käytimme rahamäärän tallettamiseen double-tyyppistä oliomuuttujaa. Todellisissa sovelluksissa näin ei kannata tehdä, sillä kuten jo olemme nähneet, doubleilla laskenta ei ole tarkkaa. Onkin järkevämpää toteuttaa rahamäärän käsittely oman luokkansa avulla. Seuraavassa on luokan runko:

public class Raha {

    private final int euroa;
    private final int senttia;

    public Raha(int euroa, int senttia) {
        this.euroa = euroa;
        this.senttia = senttia;
    }

    public int eurot() {
      return euroa;
    }

    public int sentit() {
        return senttia;
    }

    public String toString() {
        String nolla = "";
        if (senttia <= 10) {
            nolla = "0";
        }

        return euroa + "." + nolla + senttia + "e";
    }
}

Määrittelyssä pistää silmään oliomuuttujien määrittelyn yhteydessä käytetty sana final, tällä saadaan aikaan se, että oliomuuttujien arvoa ei pystytä muuttamaan sen jälkeen kun ne on konstruktorissa asetettu. Raha-luokan oliot ovatkin muuttumattomia eli immutaabeleita, eli jos halutaan esim. kasvattaa rahamäärää, on luotava uusi olio, joka kuvaa kasvatettua rahasummaa.

Luomme seuraavassa muutaman operaation rahojen käsittelyyn.

111.1 Plus

Tee ensin metodi public Raha plus(Raha lisattava), joka palauttaa uuden raha-olion, joka on arvoltaan yhtä suuri kuin se olio jolle metodia kutsuttiin ja parametrina oleva olio yhteensä.

Metodin runko on seuraavanlainen:

public Raha plus(Raha lisattava) {
    Raha uusi = new Raha(...); // luodaan uusi Raha-olio jolla on oikea arvo

    // palautetaan uusi Raha-olio
    return uusi;
}

Seuraavassa esimerkkejä metodin toiminnasta

Raha a = new Raha(10,0);
Raha b = new Raha(5,0);

Raha c = a.plus(b);

System.out.println(a);  // 10.00e
System.out.println(b);  // 5.00e
System.out.println(c);  // 15.00e

a = a.plus(c);          // HUOM: tässä syntyy uusi Raha-olio, joka laitataan "a:n langan päähän"
//       vanha a:n langan päässä ollut 10 euroa häviää ja Javan roskien kerääjä korjaa sen pois

System.out.println(a);  // 25.00e
System.out.println(b);  // 5.00e
System.out.println(c);  // 15.00e

111.2 Vähemmän

Tee metodi public boolean vahemman(Raha verrattava), joka palauttaa true jos raha-olio jolle metodia kutsutaan on arvoltaan pienempi kuin raha-olio, joka on metodin parametrina.

Raha a = new Raha(10, 0);
Raha b = new Raha(3, 0);
Raha c = new Raha(5, 0);

System.out.println(a.vahemman(b));  // false
System.out.println(b.vahemman(c));  // true

111.3 Miinus

Tee metodi public Raha miinus(Raha vahentaja), joka palauttaa uuden raha-olion, jonka arvoksi tulee sen olion jolle metodia kutsuttiin ja parametrina olevan olion arvojen erotus. Jos erotus olisi negatiivinen, tulee luotavan raha-olion arvoksi 0.

Seuraavassa esimerkkejä metodin toiminnasta

Raha a = new Raha(10, 0);
Raha b = new Raha(3, 50);

Raha c = a.miinus(b);

System.out.println(a);  // 10.00e
System.out.println(b);  // 3.50e
System.out.println(c);  // 6.50e

c = c.miinus(a);        // HUOM: tässä syntyy uusi Raha-olio, joka laitataan "c:n langan päähän"
//       vanha c:n langan päässä ollut 6.5 euroa häviää ja Javan roskien kerääjä korjaa sen pois

System.out.println(a);  // 10.00e
System.out.println(b);  // 3.50e
System.out.println(c);  // 0.00e

26.3 Merkkijonot ovat muuttumattomia

Javan String-oliot ovat Raha-luokan olioiden tyyliin muuttumattomia, eli immutaabelena. Jos esim. merkkijonon perään katenoidaan eli liitetään +-operaatiolla uusi merkkijono, ei alkuperäistä merkkijonoa pidennetä vaan syntyy uusi merkkijono-olio:

String jono = "koe";
jono + "häntä";

System.out.println(jono);  // koe

Merkkijonoa ei siis voi muuttaa, mutta voimme ottaa katenoimalla syntyvän uuden merkkijonon talteen vanhaan muuttujaan:

String jono = "koe";
jono = jono + "häntä";   // tai jono += "häntä";

System.out.println(jono);  // koehäntä

Nyt siis muuttuja jono viittaa uuteen merkkijono-olioon, joka luotiin yhdistämällä muuttujan aiemmin viittaama merkkijono "koe" ja merkkijono "häntä". Merkkijono-olioon "koe" ei enää viitata.

27 Taulukko

Olemme käyttäneet kurssin aikana lukemattomia kertoja ArrayList:ejä erilaisten olioiden säilömiseen. ArrayList on helppokäyttöinen sillä se tarjoaa paljon valmiita työvälineitä jotka helpottavat ohjelmoijan elämää: muun muassa automaattisen listan kasvatuksen, jonka ansioista listalta ei lopu tila kesken (ellei lista kasva niin suureksi että se käyttää loppuun ohjelmalle varatun muistimäärän).

ArrayListin helppokäyttöisyydesta huolimatta ohjelmissa on joskus tarvetta ArrayListin esi-isälle eli taulukolle.

Taulukko on olio, joka voidaan käsittää eräänlaisena järjestettynä lokerikkona arvoille. Taulukon pituus tai koko on lokerikon paikkojen lukumäärä, eli kuinka monta arvoa taulukkoon voi laittaa. Taulukon arvoja kutsutaan taulukon alkioiksi. ArrayLististä poiketen taulukon kokoa (eli sen alkioiden määrää) ei voi muuttaa, taulukon kasvattaminen vaatii siis aina uuden taulukon luomista ja vanhassa olevien alkioiden kopiointia uuteen.

Taulukon voi luoda kahdella eri tavalla. Tutustutaan ensin tapaan jossa taulukolle annetaan sisältö luomisen yhteydessä. Kolmen alkion kokonaislukutyyppinen taulukko määritellään seuraavasti:

int[] luvut = {100, 1, 42};

Taulukko-olion tyyppi merkitään int[], joka tarkoittaa taulukkoa, jonka alkiot ovat tyyppiä int. Taulukko-olion nimi on esimerkissä luvut ja se sisältää kolme lukua {100, 1, 42}. Taulukko alustetaan lohkolla, jossa taulukkoon asetettavat arvot on esitelty pilkulla eriteltyinä.

Taulukon arvot voivat olla mitä tahansa aikaisemmin nähtyjä muuttujatyyppejä. Alla on esitelty ensin merkkijonoja sisältävä taulukko, jonka jälkeen esitellään liukulukuja sisältävä taulukko.

String[] merkkijonotaulukko = {"Matti P.", "Matti V."};
double[] liukulukutaulukko = {1.20, 3.14, 100.0, 0.6666666667};

Taulukon alkioihin viitataan indeksillä, joka on kokonaisluku. Indeksi kertoo alkion paikan taulukossa. Taulukon ensimmäinen alkio on paikassa nolla, seuraava kohdassa yksi ja niin edelleen. Taulukon tietyssä indeksissä olevaa arvoa tutkittaessa indeksi annetaan taulukko-olion nimen perään hakasulkeiden sisällä.

// indeksi       0   1    2    3   4   5     6     7
int[] luvut = {100,  1,  42,  23,  1,  1, 3200, 3201};

System.out.println(luvut[0]);  // tulostaa luvun taulukon indeksistä 0, eli luvun 100
System.out.println(luvut[2]);  // tulostaa luvun taulukon indeksistä 2, eli luvun 42

Yllä olevan taulukon koko (eli pituus) on 8.

Huomaat todennäköisesti että ArrayListin metodi get käyttäytyy hyvin samalla tavalla kuin taulukon tietystä indeksistä haku. Taulukon kohdalla vain syntaksi, eli merkintätapa, on erilainen.

Yksittäisen arvon asettaminen taulukon tiettyyn paikkaan tapahtuu kuten arvon asetus tavalliseen muuttujaan, mutta taulukkoon asetettaessa paikka eli indeksi tulee kertoa. Indeksi kerrotaan hakasulkeiden sisällä.

int[] luvut = {100,1,42};

luvut[0] = 1;    // asetetaan luku 1 indeksiin 0
luvut[1] = 101;  // asetetaan luku 101 indeksiin 1

// luvut-taulukko on nyt {1,101,42}

Jos indeksillä osoitetaan taulukon ohi, eli alkioon jota ei ole olemassa, niin saadaan virheilmoitus ArrayIndexOutOfBoundsException, joka kertoo että indeksiä johon osoitimme ei ole olemassa. Taulukon ohi, eli indeksiin joka on pienempi kuin 0 tai suurempi tai yhtäsuuri kuin taulukon koko ei siis saa viitata.

Huomaamme, että taulukko on selvästi sukua ArrayList:ille. Aivan kuten listoilla, myös taulukossa alkiot ovat tietyssä paikassa!

27.1 Taulukon läpikäynti

Taulukko-olion koon saa selville kirjoittamalla koodiin taulukko.length, huomaa että ilmauksessa ei tule käyttää sulkuja eli taulukko.length() ei toimi!

Taulukon alkioiden läpikäynti on helppo toteuttaa while-komennon avulla:

int[] luvut = {1, 8, 10, 3, 5};

int i = 0;
while (i < luvut.length) {
    System.out.println(luvut[i]);
    i++;
}

Esimerkissä käydään muuttujan i avulla läpi indeksit 0, 1, 2, 3, ja 4, ja tulostetaan taulukon kussakin indeksissä olevan muuttujan arvo. Ensin siis tulostuu luvut[0], sitten luvut[1] jne. Muuttujan i kasvatus loppuu kun koko taulukko on käyty läpi, eli kun sen arvo on yhtäsuuri kuin taulukon pituus.

Taulukon läpikäynnissä ei ole aina todellista tarvetta taulukon indeksien luetteluun, vaan ainoa kiinnostava asia ovat taulukon arvot. Tällöin voidaan käyttää aiemmin tutuksi tullutta for-each-rakennetta arvojen läpikäyntiin. Nyt toistolauseen rungossa annetaan vain muuttujan nimi, johon kukin taulukon arvo asetetaan vuorollaan, ja taulukon nimi kaksoispisteellä erotettuna.

int[] luvut = {1,8,10,3,5};

for (int luku : luvut) {
    System.out.println(luku);
}
String[] nimet = {"Juhana L.", "Matti P.", "Matti L.", "Pekka M."};

    for (String nimi : nimet) {
        System.out.println(nimi);
    }
}

Huom: for-each-tyylisellä läpikäynnillä taulukon alkioihin ei voi asettaa arvoja! Seuraavaksi nähtävällä for-lauseen toisella muodolla sekin onnistuu.

27.2 for-komennon toinen muoto

Olemme toistaiseksi käyttäneet toistolauseissa whileä tai for-lauseen ns. "for-each"-muotoa. Toistolauseesta for on olemassa myös toinen muoto, joka on kätevä erityisesti taulukoiden käsittelyn yhteydessä. Seuraavassa tulostetaan for-toistolauseen avulla luvut 0, 1 ja 2:

for (int i = 0; i < 3; i++) {
    System.out.println(i);
}

Esimerkin for toimii täsmälleen samalla tavalla kuin alla oleva while:

int i = 0;  // toistossa käytettävän muuttujan alustus
while (i < 3) {  // toistoehto
    System.out.println(i);
    i++;   // toistossa käytettävän muuttujan päivitys
}

for-komento, kuten yllä esitelty for (int i = 0; i < 3; i++) sisältää kolme osaa: looppimuuttujien alustus; toistoehto; looppimuuttujien päivitys:

  • Ensimmäisessä osassa alustetaan toistolauseessa käytettävät muuttujat. Yllä olevassa esimerkissä alustimme muuttujan i lauseella int i=0. Ensimmäinen osa suoritetaan vain kerran, juuri for:in suorituksen alussa.
  • Toisessa osassa määritellään toistoehto, joka määrittelee miten kauan for:in yhteydessä olevassa koodilohkossa olevaa koodia suoritetaan. Esimerkissämme toistoehto oli i < 3. Toistoehdon voimassaolo tarkastetaan ennen jokaista for:in toistokertaa. Toistoehto toimii täsmälleen samoin kuin while:n toistoehto.
  • Kolmas osa, joka esimerkissämme on i++ suoritetaan aina kertaalleen forin koodilohkon suorituksen jälkeen.

for on hieman while:ä selkeämpi tapa toteuttaa toistoja joissa toistojen määrä perustuu esim. laskurin kasvatukseen. Taulukkojen läpikäynnissä tilanne on yleensä juuri tämä. Seuraavassa tulostetaan taulukon luvut sisältö for:illa

int[] luvut = {1, 3, 5, 9, 17, 31, 57, 105};

for(int i = 3; i < 7; i++) {
    System.out.println(luvut[i]);
}

Forilla voidaan aloittaa läpikäynti luonnollisesti muualtakin kuin nollasta ja läpikäynti voi edetä "ylhäältä alas". Esimerkiksi taulukon paikoissa 6, 5, 4, ja 3 olevat alkiot voidaan tulostaa seuraavasti:

int[] luvut = {1, 3, 5, 9, 17, 31, 57, 105};

for(int i = 6; i>2 ; i--) {
    System.out.println(luvut[i]);
}

27.3 for ja taulukon pituus

Kaikkien taulukon alkioiden läpikäynti for:in avulla onnistuu seuraavasti:

int[] luvut = {1, 8, 10, 3, 5};

for (int i = 0; i < luvut.length; i++) {
    System.out.println(luvut[i]);
}

Huomaa, että toistoehdossa i < luvut.length verrataan looppimuuttujan arvoa taulukolta kysyttyyn pituuteen. Ehtoa ei kannata missään tapauksessa "kovakoodata" tyyliin i < 5 sillä yleensä taulukon pituudesta ei ole etukäteen varmuutta.

27.4 Taulukko parametrina

Taulukkoja voidaan käyttää metodin parametrina aivan kuten muitakin olioita. Huomaa, että kuten kaikkien olioiden tapauksessa, metodi saa parametrina viitteen taulukkoon, eli kaikki metodissa tapahtuvat taulukon sisältöön vaikuttavat muutokset näkyvät myös pääohjelmassa.

public static void listaaAlkiot(int[] kokonaislukuTaulukko) {

    System.out.println("taulukon alkiot ovat: ");
    for(int luku : kokonaislukuTaulukko) {
        System.out.print(luku + " ");
    }

    System.out.println("");
}

public static void  main(String[] args) {
    int[] luvut = {1, 2, 3, 4, 5};
    listaaAlkiot(luvut);
}

Kuten jo tiedämme, parametrin nimi metodin sisällä voi olla aivan vapaasti valittu, nimen ei tarvitse missään tapauksessa olla sama kuin kutsuvassa. Edellä taulukkoa kutsutaan metodin sisällä nimellä kokonaislukuTaulukko, metodin kutsuja taas näkee saman taulukon luvut-nimisenä.

Tehtävä 112: Taulukon lukujen summa

Huom: tämän tehtävän metodi samoin kuin muutamien seuraavien tehtävien taulukkoa käsittelevät metodit ovat viikkojen 2 ja 3 tapaan static eli staattisia metodeja. Karkeasti ottaen tämä johtuu siitä, että metodi ei liity mihinkään olioon, vaan saa kaiken datan jota se käyttää eli tässä tapauksessa taulukon, parametrina. Palaamme seuraavalla viikolla tarkemmin aiheeseen staattiset vs. olioihin liittyvät metodit.

Tee metodi public static int laskeTaulukonLukujenSumma(int[] taulukko), joka palauttaa taulukossa olevien lukujen summan.

Ohjelman runko on seuraava:

public class Main {
    public static void main(String[] args) {
        // Tässä voit testata metodia
        int[] taulukko = {5, 1, 3, 4, 2};
        System.out.println(laskeTaulukonLukujenSumma(taulukko));
    }

    public static int laskeTaulukonLukujenSumma(int[] taulukko) {
        // Kirjoita koodia tänne
        return 0;
    }
}

Ohjelman tulostus on seuraava:

15

Tehtävä 113: Tyylikäs tulostus

Tee metodi public static void tulostaTyylikkaasti(int[] taulukko), joka tulostaa taulukossa olevat luvut tyylikkäästi. Lukujen väliin tulee pilkku ja välilyönti. Viimeisen luvun jälkeen ei pilkkua tule.

Ohjelman runko on seuraava:

public class Main {
    public static void main(String[] args) {
        // Tässä voit testata metodia
        int[] taulukko = {5, 1, 3, 4, 2};
        tulostaTyylikkaasti(taulukko);
    }

    public static void tulostaTyylikkaasti(int[] taulukko) {
        // Kirjoita koodia tänne
    }
}

Ohjelman tulostus on seuraava:

  5, 1, 3, 4, 2

27.5 Uuden taulukon luonti

Jos taulukon koko ei ohjelmassa ole aina sama, eli se riippuu esim. käyttäjän syötteestä, ei äsken esitelty taulukon luontitapa kelpaa. Taulukko on mahdollista luoda myös siten, että sen koko määritellään muuttujan avulla:

int alkioita = 99;
int[] taulukko = new int[alkioita];

Yllä luodaan int-tyyppinen taulukko, jossa on 99 paikkaa. Tällä vaihtoehtoisella tavalla taulukon luominen tapahtuu siis kuten muidenkin olioiden luominen, eli new-komennolla. Komentoa new seuraa taulukon sisältämien muuttujien tyyppi, ja hakasuluissa taulukon koko.

int alkioita = 99;
int[] taulukko = new int[alkioita]; //luodaan muuttujan alkioita sisältämän arvon kokoinen taulukko

if (taulukko.length == alkioita) {
    System.out.println("Taulukon pituus on " + alkioita);
} else {
    System.out.println("Jotain epätodellista tapahtui. Taulukon pituus on eri kuin " + alkioita);
}

Seuraavassa esimerkissä on ohjelma, joka kysyy käyttäjältä lukujen määrän ja joukon lukuja. Tämän jälkeen ohjelma tulostaa luvut uudestaan samassa järjestyksessä. Käyttäjän antamat luvut tallennetaan taulukkoon.

System.out.print("Kuinka monta lukua? ");
int lukuja = Integer.parseInt(lukija.nextLine());

int[] luvut = new int[lukuja];

System.out.println("Anna luvut:");
for(int i = 0; i < lukuja; i++) {
    luvut[i] = Integer.parseInt(lukija.nextLine());
}

System.out.println("Luvut uudestaan:");
for(int i = 0; i < lukuja; i++) {
    System.out.println(luvut[i]);
}

Eräs ohjelman suorituskerta voisi olla seuraavanlainen:

Kuinka monta lukua? 4
Anna luvut:
4
8
2
1
Luvut uudestaan:
4
8
2
1

27.6 Taulukko paluuarvona

Koska metodit voivat palauttaa olioita, voivat ne palauttaa myös taulukkoja. Eräs merkkijonotaulukon palauttava metodi on seuraavannäköinen -- huomaa että taulukkoihin voi aivan hyvin siis laittaa myös olioita.

public static String[] annaMerkkijonoTaulukko() {
    String[] opet = new String[3];

    opet[0] = "Bonus";
    opet[1] = "Ihq";
    opet[2] = "Lennon";

    return opet;
}

public static void main(String[] args) {
    String[] opettajat = annaMerkkijonoTaulukko();

    for (String opettaja : opettajat) {
        System.out.println(opettaja);
    }
}

Tehtävä 114: Taulukon kopiointi ja kääntaminen

114.1 Kopiointi

Tee metodi public static int[] kopioi(int[] taulukko) joka luo kopion parametrina saadusta taulukosta. Vihje: koska metodin on luotava taulukosta kopio, tulee metodin sisällä luoda uusi taulukko ja kopioida vanhan taulukon sisältö uudelle taulukolle alkio alkiolta.

Seuraavassa esimerkki metodin käytöstä (koodissa myös Arrays-luokan tarjoama kätevä apuväline taulukon sisällön tulostamiseen):

public static void main(String[] args) {
    int[] alkuperainen = {1, 2, 3, 4};
    int[] kopio = kopioi(alkuperainen);

    // muutetaan kopioa
    kopio[0] = 99;

    // tulostetaan molemmat
    System.out.println("alkup: " + Arrays.toString(alkuperainen));
    System.out.println("kopio: " + Arrays.toString(kopio));
  }

Kuten tulostuksesta huomaa, ei kopioon tehty muutos vaikuta alkuperäiseen:

alkup: [1, 2, 3, 4]
kopio: [99, 2, 3, 4]

114.2 Kääntäminen

Tee metodi public static int[] kaanna(int[] taulukko) joka luo käänteisessä järjestyksessä olevan kopion parametrinaan saamastaan taulukosta.

Eli jos parametrina on taulukko jossa esim. luvut 5, 6, 7 palauttaa metodi uuden taulukon jonka sisältönä luvut 7, 6, 5. Parametrina oleva taulukko ei saa muuttua.

Seuraavassa esimerkki metodin käytöstä:

public static void main(String[] args) {
    int[] alkuperainen = {1, 2, 3, 4};
    int[] kaannetty = kaanna(alkuperainen);

    // tulostetaan molemmat
    System.out.println("alkup: " +Arrays.toString(alkuperainen));
    System.out.println("käännetty: " +Arrays.toString(kaannetty));
}

Tulostuksesta pitäisi selvitä, että alkuperäinen taulukko on muuttumaton:

alkup: [1, 2, 3, 4]
käännetty: [4, 3, 2, 1]

Tehtävä 115: Taulukko tähtinä

Kirjoita metodi public static void tulostaTaulukkoTahtina(int[] taulukko), joka tulostaa jokaista taulukossa olevaa lukua vastaavan pituisen rivin tähtiä.

Ohjelman runko on seuraava:

public class Main {
    public static void main(String[] args) {
        // Tässä voit testata metodia
        int[] taulukko = {5, 1, 3, 4, 2};
        tulostaTaulukkoTahtina(taulukko);
    }

    public static void tulostaTaulukkoTahtina(int[] taulukko) {
        // Kirjoita tulostuskoodi tänne
    }
}

Edellisen esimerkin mukaisella syötteellä ohjelman tulostus on seuraava:

*****
*
***
****
**

Eli koska taulukon nollannessa paikassa on luku 5, tulee ensimmäiselle riville 5 tähteä. Seuraavalla 1 tähti jne.

Tehtävä 116: Tähtitaivas

Luodaan ohjelma tähtitaivaan tulostamiseen. Tähtitaivaan tähtien määrä kerrotaan tiheyden avulla. Esimerkiksi jos tähtitaivaan tiheys on 0.2, on noin 20% tähtitaivaasta peitettynä tähdillä. Pääset harjoittelemaan siis myös satunnaislukujen käyttöä.

Käytä tähtien tulostamiseen *-merkkiä. Alla on esimerkki lopullisen Tahtitaivas-luokan käytöstä ja käyttöä vastaavasti tulostuksesta.

Tahtitaivas tahtitaivas = new Tahtitaivas(0.1, 39, 10);
tahtitaivas.tulosta();
System.out.println("Tähtiä: " + tahtitaivas.tahtiaViimeTulostuksessa());
System.out.println("");

tahtitaivas = new Tahtitaivas(0.2, 15, 6);
tahtitaivas.tulosta();
System.out.println("Tähtiä: " + tahtitaivas.tahtiaViimeTulostuksessa());
  
*     *                  *
*             * *         *      **
*
*       *      *         *  *
*     *                     *
*            * *                   *
*  * *           *          * *  **
*  *
*               *
*                             *
Tähtiä: 36

* * *     *
* *   *
*     *
*  *       *
*       *   * *
* ** **     *
Tähtiä: 22
  

Huom! tehtävissä kannattaa käyttää for-lauseketta. Vaikka edellinen luku puhuukin sisäkkäisistä toistolauseista, tässä tehtävässä "sisempi" toisto piilotetaan metodin sisälle.

116.1 Tahtitaivas-luokka ja yhden rivin tulostaminen

Luo luokka Tahtitaivas, jolla on kolme oliomuuttujaa: tiheys (double), leveys (int), ja korkeus (int). Luo luokalle myös kolme konstruktoria:

  • public Tahtitaivas(double tiheys) Luo tähtitaivas-olion, jolla on parametrina annettu tiheys, leveys saa arvon 20, korkeus saa arvon 10.
  • public Tahtitaivas(int leveys, int korkeus) Luo tähtitaivas-olion, jolla on parametrina annetut leveys ja korkeus, tiheys saa arvon 0.1.
  • public Tahtitaivas(double tiheys, int leveys, int korkeus) Luo tähtitaivas-olion, jolla on parametrina annetut tiheys, leveys ja korkeus.

Lisää luokalle Tahtitaivas metodi tulostaRivi, joka tulostaa yhden rivin. Rivin leveyden määrää oliomuuttuja leveys. Oliomuuttuja tiheys kertoo todennäköisyyden tähdelle. Arvo jokaisen merkin kohdalla tulostetaanko tähti vai ei Random-luokan nextDouble-metodin avulla.

Testaa ohjelmaasi, esimerkkinä seuraava kutsu ja esimerkkitulostus.

Tahtitaivas tahtitaivas = new Tahtitaivas(0.1);
tahtitaivas.tulostaRivi();
  
*  *                  *
  

116.2 Tähtitaivaan tulostus

Luo Tahtitaivas-luokalle metodi tulosta, joka tulostaa koko tähtitaivaan. Käytä tässä hyödyksesi aiempaa tulostaRivi-metodia.

Tahtitaivas tahtitaivas = new Tahtitaivas(8, 4);
tahtitaivas.tulosta();
  
*

*
*

116.3 Tähtien laskeminen

Lisää Tahtitaivas-luokalle oliomuuttuja tahtiaViimeTulostuksessa (int) ja metodi tahtiaViimeTulostuksessa(), joka palauttaa viime tulostuksessa tulostuneiden tähtien lukumäärän. Toteuta ohjelmaasi tähtien laskeminen.

Tahtitaivas tahtitaivas = new Tahtitaivas(8, 4);
tahtitaivas.tulosta();
System.out.println("Tähtiä: " + tahtitaivas.tahtiaViimeTulostuksessa());
System.out.println("");

tahtitaivas.tulosta();
System.out.println("Tähtiä: " + tahtitaivas.tahtiaViimeTulostuksessa());


*

Tähtiä: 1

*
*
*

Tähtiä: 3