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)

27 Näppäimistön ja muunlaisten syötteiden lukeminen

Olemme kurssin ensimmäiseltä viikolta lähtien käyttäneet Scanner-luokasta tehtyä oliota käyttäjän kirjoittaman syötteen lukemiseen.

// luodaan Scanner-olio, joka lukee näppäimistösyötettä
Scanner lukija = new Scanner(System.in);

// jatketaan syötteen lukemista kunnes käyttäjä syöttää
// rivin "loppu"
while (true) {
    String rivi = lukija.nextLine();

    if (rivi.equals("loppu")) {
        break;
    }
}

Yllä Scanner-luokan konstruktorille annetaan parametrina järjestelmän syötevirta, johon näppäinpanallukset ohjataan ohjelman tekstikäyttöliittymästä. Scanner voi saada konstruktorin parametrina myös muita olioita.

27.1 Lukeminen tiedostosta

Tiedostot ovat tietokoneella sijaitsevia tietokokoelmia, jotka voivat sisältää vaikkapa kuvia tai musiikkia. Tutustutaan tässä tekstitiedostojen lukemiseen.

Myös tiedoston voi lukea Scanner-oliolla. Tällöin Scanner-oliolle annetaan konstruktorin parametrina File-olio, joka kertoo tiedoston sijainnin tiedostojärjestelmässä. Tiedostoja lukiessa voidaan kohdata virhetilanne, joten tiedoston lukeminen vaatii erillisen "yrittämisen" (try) sekä mahdollisen virheen kiinnioton (catch). Palaamme mahdollisten virheiden käsittelyyn tarkemmin myöhemmin tällä kurssilla.

ArrayList<String> rivit = new ArrayList<>();

// luodaan lukija tiedoston lukemista varten
try (Scanner lukija = new Scanner(new File("tiedosto.txt"))) {

  // luetaan kaikki tiedoston rivit
  while (lukija.hasNextLine()) {
    rivit.add(lukija.nextLine());
  }
} catch (Exception e) {
  System.out.println("Virhe: " + e.getMessage());
}

// tee jotain luetuilla riveillä

Jos tiedosto löytyy ja sen lukeminen onnistuu, tulee ohjelman suorituksen lopussa tiedoston "tiedosto.txt" rivit olemaan listamuuttujassa rivit. Jos taas tiedostoa ei löydy, tai sen lukeminen epäonnistuu, ilmoittaaa Java tästä virheviestillä. Alla eräs mahdollisuus:

Virhe: tiedosto.txt (No such file or directory)

Tehtävä 107: Vieraslista tiedostosta

Toteutimme neljännellä viikolla vieraslistaohjelman, missä käyttäjää pyydettiin syöttämään nimiä vieraslistalle. Kun nimet oli syötetty, käyttäjä syötti yksittäisiä nimiä joiden olemassaolo tarkitettiin vieraslistalta. Ohjelman toiminta oli seuraavanlainen:

Syötä nimiä vieraslistalle, tyhjä rivi lopettaa.
Jack Bauer
Jack Bower
Jack Baur
Jack Bowr

Syötä nimiä, tyhjä rivi lopettaa.
Chuck Norris
Nimi ei ole listalla.
Jack Baluer
Nimi ei ole listalla.
Jack Bauer
Nimi on listalla.
Jack Bower
Nimi on listalla.

Kiitos!

Muokataan tässä ohjelmaa siten, että toisin kuin aiemmin, nimet luetaan tiedostosta. Pääohjelmametodi tulee kokonaisuudessaan olemaan seuraavanlainen:

Scanner lukija = new Scanner(System.in);

System.out.println("Minkä niminen tiedosto luetaan? ");
String tiedosto = lukija.nextLine();

// kun toteutat metodit, voit testata toimintaa täällä
ArrayList<String> lista = lueNimet(tiedosto);

System.out.println("");
tarkistaNimet(lukija, lista);
System.out.println("Kiitos!");

Toteuteta metodi lueNimet, joka lukee nimet parametrina annetusta tiedostosta. Kun metodi on valmis, ohjelma toimii kuten aiempi vieraslistasovellus, mutta nimet luetaan tiedostosta -- nimiä ei siis tarvitse syöttää jokaisen käynnistyksen yhteydessä.

Kun olet valmis, kokeile vielä että ohjelma toimii halutusti tehtävänannon alussa olevalla koodilla.

Huom! Tehtäväpohjassa on mukana kaksi tiedostoa, nimet.txt ja toiset-nimet.txt, joiden sisällöt ovat seuravat. Älä muuta näiden tiedostojen sisältöä!

nimet.txt:

ada
arto
leena
testi

toiset-nimet.txt:

leo
jarmo
alicia

Tehtävä 108: Desibelimittaukset tiedostosta

Olemme toteuttaneet aiemmin kurssilla muutamaan otteeseen ohjelman, missä käyttäjä syöttää desibelimittauksia. Kun desibelimittaukset on syötetty, poistetaan luetuista luvuista virheelliset ja/tai turhat luvut. Lopulta kerrotaan lukujen keskiarvo, tai jos lukuja ei ole lainkaan, kerrotaan ettei mittaustuloksia ole.

Tässä tehtävässä toteutat metodin public static ArrayList<Integer> lueLuvut(String tiedosto), joka lukee luvut parametrina annetusta tiedostosta, ja palauttaa ne listalla. Kun metodi on toteutettu, voit kokeilla ohjelman toimintaa seuraavalla ohjelmakoodilla.

Scanner lukija = new Scanner(System.in);

System.out.println("Minkä niminen tiedosto luetaan? ");
String tiedosto = lukija.nextLine();

ArrayList<Integer> lista = lueLuvut(tiedosto);
lista = valitseLuvutValilta(lista, 0, Integer.MAX_VALUE);

if (lista.isEmpty()) {
    System.out.println("Ei lukuja.");
} else {
    System.out.println("Lukujen keskiarvo: " + keskiarvo(lista));
}

Huom! Tehtäväpohjassa on mukana kaksi tiedostoa, mittaukset-1.txt ja mittaukset-2.txt, joiden sisällöt ovat seuravat. Älä muuta näiden tiedostojen sisältöä!

mittaukset-1.txt:

300
9
20
15

mittaukset-2.txt:

123
-5
12
67
-300
1902

27.2 Syötteen lukeminen verkkoyhteyden yli

Lähes kaikki verkkosivut, kuten tämäkin oppimateriaali, voidaan lukea tekstimuodossa ohjelmallista käsittelyä varten. Scanner-oliolle voi antaa konstruktorin parametrina oikeastaan lähes minkälaisen syötevirran tahansa. Alla olevassa esimerkissä luodaan URL-olio annetusta web-osoitteesta, pyydetään siihen liittyvää tietovirtaa, ja annetaan se juuri luotavalle Scanner-oliolle luettavaksi.

ArrayList<String> rivit = new ArrayList<>();

// luodaan lukija web-osoitteen lukemista varten
try (Scanner lukija = new Scanner(new URL("http://www.cs.helsinki.fi/home/").openStream())) {

  // luetaan osoitteesta http://www.cs.helsinki.fi/home/
  // saatava vastaus 
  while (lukija.hasNextLine()) {
    rivit.add(lukija.nextLine());
  }
} catch (Exception e) {
  System.out.println("Virhe: " + e.getMessage());
}

// tehdään jotain vastauksella

Web-selain on oikeastaan ohjelma siinä missä muutkin ohjelmat. Toisin kuin yllä toteutettu yksinkertainen web-sivun lataaja, web-selain osaa myös tulkita vastauksena tulevan HTML-muotoisen lähdekoodin.

28 Olioharjoittelua

Kerrataan hieman aiemmin oppimaamme, ja tutustutaan vielä muutamiin olioiden ominaisuuksiin.

Tehtävä 109: Ostoslista

Toteutetaan sovellus ostoslistan hallintaan. Ostoslistalle voi lisätä ostettavia asioita, tarkistaa kunkin ostettavan asian lukumäärän, sekä poistaa ostettavia asioita. Voit toteuttaa Ostoslista-olion sisäisen toiminnallisuuden esimerkiksi HashMap- tai ArrayList-tietorakenteen avulla.

109.1 Lisääminen ja kappalemäärä

Toteuta luokka Ostoslista, jolla on parametriton konstruktori, ja joka sisältää listan String-olioita. Toteuta luokalle metodit public void lisaa(String tuote), joka lisää yhden kappaleen tuotetta ostoslistalle, ja public int kappalemaara(String tuote), joka palauttaa tuotteen kappalemäärän listalta. Jos tuotetta ei ole ostoslistalla, tulee metodin kappalemaara palauttaa arvo 0.

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

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

System.out.println("Kauriita: " + lista.kappalemaara("kauris"));
System.out.println("Nauriita: " + lista.kappalemaara("nauris"));
Kauriita: 0
Nauriita: 1

Toinen esimerkki

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

109.2 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

28.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("Kumpulan 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

28.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ä 110: 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.

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

110.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().

110.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ä 111: 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ä.

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

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

111.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ä 112: Luokan Henkilö muokkaaminen

112.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 verna = new Henkilo("Verna", 15, 1, 1993);
        Henkilo leo = new Henkilo("Leo", 1, 1, 1990);

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

Tulostus:

Verna ikä 22 vuotta
Leo ikä 25 vuotta

112.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 venla = new Henkilo("Venla", 22, 7, 2015);

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

Tulostus:

Venla vanhempi kuin Pekka: false
Pekka vanhempi kuin Venla: true

112.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 iina = new Henkilo("Iina");

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

Esimerkkitulostus:

Pekka, syntynyt 15.2.1983
Iina, syntynyt 6.10.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ä 113: Puhelinmuistio

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

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

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

113.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ä 114: 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.

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

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

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

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

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

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

29.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]);
}

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

29.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ä 115: 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ä 116: 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

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

29.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ä 117: Taulukon kopiointi ja kääntaminen

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

117.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ä 118: 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ä 119: 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.

119.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();
  
*  *                  *
  

119.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();
  
*

*
*

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

29.7 Kaksiulotteinen taulukko

Taulukon voi luoda myös kaksiulotteisena. Tämä on kätevää esimerkiksi silloin, jos haluamme ylläpitää sijainteja koordinaatistossa. Kaksiulotteisen taulukon, jossa on kaksi riviä ja kolme saraketta, tapahtuu seuraavasti:

int rivit = 2;
int sarakkeet = 3;
int[][] kaksiulotteinenTaulukko = new int[rivit][sarakkeet];

Yllä luomme taulukon, jonka jokainen rivi sisältää uuden taulukon, jossa on tietty määrä sarakkeita. Kaksiulotteisen taulukon läpikäynti onnistuu esimerkiksi kahden sisäkkäisen for-toistolauseen avulla seuraavasti:

int rivit = 2;
int sarakkeet = 3;
int[][] kaksiulotteinenTaulukko = new int[rivit][sarakkeet];

for (int y = 0; y < kaksiulotteinenTaulukko.length; y++) {
    for (int x = 0; x < kaksiulotteinenTaulukko[y].length; x++) {
        System.out.println("arvo kohdassa (" + x + ", " + y + "): " + kaksiulotteinenTaulukko[y][x]);

    }
}

Ylläolevan ohjelman tulostus on seuraavan, sillä int-tyyppisiä muuttujia sisältävä taulukko alustaa solujen arvon oletuksena nollaksi.

arvo kohdassa (0, 0): 0
arvo kohdassa (1, 0): 0
arvo kohdassa (2, 0): 0
arvo kohdassa (0, 1): 0
arvo kohdassa (1, 1): 0
arvo kohdassa (2, 1): 0

Voimme muuttaa taulukon arvoja kuten ennenkin. Alla asetamme kahteen kohtaan uudet arvot.

int rivit = 2;
int sarakkeet = 3;
int[][] kaksiulotteinenTaulukko = new int[rivit][sarakkeet];

kaksiulotteinenTaulukko[0][1] = 4;
kaksiulotteinenTaulukko[1][1] = 1;
kaksiulotteinenTaulukko[1][0] = 8;

for (int y = 0; y < kaksiulotteinenTaulukko.length; y++) {
    for (int x = 0; x < kaksiulotteinenTaulukko[y].length; x++) {
        System.out.println("arvo kohdassa (" + x + ", " + y + "): " + kaksiulotteinenTaulukko[y][x]);

    }
}

Nyt tulostus näyttää seuraavalta:

arvo kohdassa (0, 0): 0
arvo kohdassa (1, 0): 4
arvo kohdassa (2, 0): 0
arvo kohdassa (0, 1): 8
arvo kohdassa (1, 1): 1
arvo kohdassa (2, 1): 0

Kaksiulotteinen taulukko on oikeastaan myös matriisi. Matriiseja käytetään muunmuassa tietokonegrafiikassa. Esimerkiksi kuviin yksittäiset pikselit (esimerkiksi kuvan punainen, sininen ja vihreä väri kohdassa (x, y)) esitetään usein matriisin avulla.

Tehtävä 120: Taikaneliö

Taikaneliöt ovat kokonaisluvuista järjestettyjä neliöitä, joiden jokaisen rivin, sarakkeen ja lävistäjän summa on sama. Harjoitellaan taulukoiden käyttöä taikaneliöiden yhteydessä.

Ohjelmassa on annettu osittain toteutettu luokka Taikanelio, jota voidaan käyttää lähtökohtana. Tehtävänäsi on ensin lisätä luokkaan toiminnallisuutta, jolla tarkistetaan onko neliö taikaneliö. Tämän jälkeen toteutat algoritmin taikaneliön luomiseen.

120.1 Rivien summat

Luokassa Taikanelio on valmiina metodi public ArrayList<Integer> rivienSummat(), joka palauttaa tyhjän ArrayList-olion. Muuta metodin toiminnallisuutta siten, että se palauttaa listan, jossa on jokaisen taikaneliön rivin summa.

Esimerkiksi seuraavanlaisella taikaneliöllä rivienSummat-metodin pitäisi palauttaa lista, jossa on luvut 15, 15, 15.

         8 1 6 
         3 5 7
         4 9 2

Vaikka taikaneliö ei olisi "oikea" taikaneliö, tulee rivien summat silti palauttaa. Allaolevalla esimerkillä rivienSummat-metodin pitäisi palauttaa lista, jossa on luvut 6, 15, 24.

         1 2 3
         4 5 6
         7 8 9

120.2 Sarakkeiden summat

Luokassa Taikanelio on valmiina metodi public ArrayList<Integer> sarakkeidenSummat(), joka palauttaa tyhjän ArrayList-olion. Muuta metodin toiminnallisuutta siten, että se palauttaa listan, jossa on jokaisen taikaneliön sarakkeen summa.

Esimerkiksi seuraavanlaisella taikaneliöllä sarakkeidenSummat-metodin pitäisi palauttaa lista, jossa on luvut 15, 15, 15.

         8 1 6 
         3 5 7
         4 9 2

Vaikka taikaneliö ei olisi "oikea" taikaneliö, tulee sarakkeiden summat silti palauttaa. Allaolevalla esimerkillä sarakkeidenSummat-metodin pitäisi palauttaa lista, jossa on luvut 12, 15, 18.

         1 2 3
         4 5 6
         7 8 9

120.3 Lävistäjien summat

Toteuta seuraavaksi metodi public ArrayList<Integer> lavistajienSummat(), joka palauttaa listan, jossa on taikaneliön lävistäjien summat.

Esimerkiksi seuraavanlaisella taikaneliöllä lavistajienSummat-metodin pitäisi palauttaa lista, jossa on luvut 15, 15 (8 + 5 + 2) ja (4 + 5 + 6).

         8 1 6 
         3 5 7
         4 9 2

Vaikka taikaneliö ei olisi "oikea" taikaneliö, tulee lävistäjien summat silti palauttaa. Allaolevalla esimerkillä lavistajienSummat-metodin pitäisi palauttaa lista, jossa on luvut 15, 15 (1 + 5 + 9) ja (7 + 5 + 3).

         1 2 3
         4 5 6
         7 8 9

120.4 Taikaneliön luominen

Huom! Tämä tehtävä on melko visainen, kannattanee palauttaa edelliset osat ennen tämän aloitusta.

Taikaneliön pystyy myös luomaan. Tutustutaan Siamese method-menetelmään, jonka avulla voidaan luoda parittomien lukujen kokoisia taikaneliöitä.

Siamese method -algoritmi toimii siten, että numero yksi asetetaan ylimmän rivin keskimmäiseen sarakkeeseen. Tämän jälkeen siirrytään yksi ylös ja yksi oikealle ja asetetaan luku kaksi. Tämän jälkeen taas siirrytään yksi ylös ja yksi oikealle, ja asetetaan luku kolme jne.

Lukujen lisäämiseen liittyy kaksi sääntöä:

  1. Jos siirtymä tapahtuu siten, että mennään taikaneliön alueen ulkopuolelle, hypätään toiselle laidalle. Jos siis mennään "oikealta yli" mennään vasempaan laitaan ja jos mennään "ylhäältä yli" mennään alalaitaan.
  2. Jos kohdassa on jo luku, ei mennäkään ylös ja oikealle, vaan astutaan yksi askel alaspäin.

Siamese method -algoritmin suoritus. Kuva Wikipediasta (Siamese method).

Toteuta parittomien taikalukujen luominen luokan Taikaneliotehdas metodiin luoTaikanelio. Metodin tarvitsee toimia vain tilanteissa, missä neliön leveys on pariton luku.

Viikon 6 Loppukysely

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