Viikko 5

22 Lisää luokista ja olioista

22.1 Useita konstruktoreja

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

public class Henkilo {

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

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

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

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

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

        return true;
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

Nyt olioiden luonti onnistuu kahdella vaihtoehtoisella tavalla:

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

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

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

22.2 Oman konstruktorin kutsuminen

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

Konstruktoreista ylempi on oikeastaan alemman erikoistapaus. Entä jos ylempi konstruktori voisi "kutsua" alempaa konstruktoria? Tämä onnistuu, sillä konstruktorin sisältä voi kutsua toista konstruktoria juuri tähän olioon liittyvän this-ilmauksen avulla!

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

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

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

Oman konstruktorin kutsu this(nimi, 0); saattaa vaikuttaa erikoiselta. Asiaa voi vaikka ajatella siten, että kutsun kohdalle tulee "copy-pastena" automaattisesti alemman konstruktorin koodi, siten että ika parametrin arvoksi tulee 0.

22.3 Metodin kuormittaminen

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

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

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

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

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

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

Tulostuu:

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

Henkilöllä on siis vanhene-nimisiä metodeja 2 kappaletta. Se kumpi metodeista valitaan suoritettavaksi, riippuu metodikutsussa käytettyjen parametrien määrästä. Metodin vanhene voi myös toteuttaa metodin vanhene(int vuodet) avulla:

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

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

84.1 Monta konstruktoria

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

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

ja seuraavat metodit:

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

84.2 Vaihtoehtoiset metodit

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

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

22.4 Olio on langan päässä

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

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

    System.out.println( pekka );
}

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

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

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

    System.out.println( pekka );

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

    System.out.println( pekka );
}

Tulostuu:

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

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

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

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

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

    System.out.println( pekka );

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

    System.out.println( pekka );

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

Tulostuu:

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

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

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

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

    System.out.println( pekka );

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

    System.out.println( pekka );

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

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

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

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

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

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

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

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

    System.out.println( pekka );

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

Tulos:

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

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

22.5 Metodin parametrina olio

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

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

public class PainonvartijaYhdistys {
    private double alinPainoindeksi;

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

}

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

public class PainonvartijaYhdistys {
    // ...

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

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

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

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

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

    PainonvartijaYhdistys kumpulanPaino = new PainonvartijaYhdistys(25);

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

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

Ohjelma tulostaa:

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

Muutama NetBeans-vihje

  • Kaikki NetBeans-vihjeet löytyvät täältä
  • Konstruktorien, getterien ja setterien automaattinen generointi

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

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

85.1 Henkilöiden punnitseminen

Kasvatuslaitoksen luokkarungossa on valmiina runko metodille punnitse:

public class Kasvatuslaitos {

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

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

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

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

    Kasvatuslaitos haaganNeuvola = new Kasvatuslaitos();

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

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

Tulostuksen pitäisi olla seuraava:

Eero paino: 7 kiloa
Pekka paino: 85 kiloa

85.2 syötä

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

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

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

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

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

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

    System.out.println("");

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

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

Eero paino: 7 kiloa
Pekka paino: 85 kiloa

Eero paino: 10 kiloa
Pekka paino: 85 kiloa

85.3 laske punnitukset

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

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

    Kasvatuslaitos haaganNeuvola = new Kasvatuslaitos();

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

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

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

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

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

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

Tulostuu:

punnituksia tehty 0
punnituksia tehty 2
punnituksia tehty 6

86.1 "Tyhmä" Lyyra-kortti

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

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

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

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

public class LyyraKortti {
    private double saldo;

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

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

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

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

Testipääohjelma:

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

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

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

Tulostuksen kuuluisi olla seuraavanlainen

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

86.2 Kassapääte ja käteiskauppa

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

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

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

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

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

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

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

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

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

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

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

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

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

86.3 Kortilla maksaminen

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

public class Kassapaate {    
    // ...

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

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

    // ...
}

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

Seuraavassa testipääohjelma ja haluttu tulostus:

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

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

        LyyraKortti antinKortti = new LyyraKortti(7);      

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

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

86.4 Rahan lataaminen

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

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

Testipääohjelma ja esimerkkisyöte:

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

        LyyraKortti antinKortti = new LyyraKortti(2);

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

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

        unicafeExactum.lataaRahaaKortille(antinKortti, 100);

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

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

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

22.6 Metodin parametrina toinen samantyyppinen olio

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

public class Henkilo {

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

    // ...
}

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

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

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

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

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

Metodia on tarkoitus käyttää seuraavaan tyyliin:

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

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

Tässä siis kysytään Pekalta onko hän Anttia vanhempi, Pekka vastaa true jos on ja false muuten. Käytännössä kutsutaan "Pekkaa" vastaavan olion johon pekka viittaa metodia vanhempiKuin, jolle annetaan parametriksi "Anttia" vastaavan olion viite antti.

Ohjelman tulostaa:

Pekka on vanhempi kuin Antti

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

public class Henkilo {
    // ... 

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

        return false;
      }
}

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

22.7 Päiväys oliona

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

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

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

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

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

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

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

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

        return false;
    }
}

Käyttöesimerkki:

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

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

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

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

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

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

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

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

87.1 Suurempi

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

Esimerkki metodin toiminnasta:

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

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

87.2 Hintaero

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

Esimerkki metodin toiminnasta:

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

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

87.3 Kalliimpi

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

Esimerkki metodin toiminnasta:

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

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

22.8 Olioita listalla

Olemme käyttäneet ArrayList:ejä jo monessa esimerkissä ja tehtävässä. ArrayList-olioon pystyy lisäämään esimerkiksi merkkijonoja ja listalla olevien merkkijonojen läpikäynti, etsiminen, poistaminen, järjestäminen ym. ovat vaivattomia toimenpiteitä.

ArrayList:eihin voidaan laittaa minkä tahansa tyyppisiä oliota. Luodaan seuraavassa henkilölista, eli tyyppiä ArrayList<Henkilo> oleva ArrayList ja laitetaan sinne muutama henkilöolio:

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

    // voidaan ensin ottaa henkilö muuttujaan
    Henkilo opettaja = new Henkilo("Juhana");
    // ja lisätä se sitten listalle
    opettajat.add(opettaja);

    // tai voidaan myös luoda olio lisättäessä:
    opettajat.add( new Henkilo("Matti") );
    opettajat.add( new Henkilo("Martin") );

    System.out.println("opettajat vastasyntyneenä: ");
    for ( Henkilo hlo : opettajat ) {
        System.out.println( hlo );
    }

    for ( Henkilo hlo : opettajat ) {
        hlo.vanhene( 30 );
    }

    System.out.println("30 vuoden kuluttua: ");
    for ( Henkilo hlo : opettajat ) {
        System.out.println( hlo );
    }
}

Ohjelman tulostus:

opettajat vastasyntyneenä: 
Juhana, ikä 0 vuotta
Matti, ikä 0 vuotta
Martin, ikä 0 vuotta
30 vuoden kuluttua: 
Juhana, ikä 30 vuotta
Matti, ikä 30 vuotta
Martin, ikä 30 vuotta

88.1 Opiskelija-luokka

Tee luokka Opiskelija, johon tallennetaan seuraavat tiedot opiskelijasta:

  • nimi (String)
  • opiskelijanumero (String)

Tee luokkaan seuraavat metodit:

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

Voit testata luokan toimintaa seuraavalla koodilla:

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

Ohjelman tulostuksen tulisi olla seuraava:

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

88.2 Opiskelijalista

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

Listan määrittelyn tulisi olla seuraava:

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

Seuraavassa on esimerkki ohjelman suorituksesta:

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

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

88.3 Opiskelijahaku

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

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

Seuraavassa on esimerkki ohjelman suorituksesta:

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

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

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

22.9 Olion sisällä olio

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

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

    // ...
  

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

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

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

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

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

Ja kokeillaan miten uusittu Henkilö-luokka toimii:

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

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

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

Tulostuu:

Martin, syntynyt 24.4.1983
Juhana, syntynyt 17.9.1985

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

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

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

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

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

Kun ikä poistetaan, täytyy vanhempiKuin-metodi muuttaa toimimaan syntymäpäiviä vertaamalla. Teemme muutoksen harjoitustehtävänä.

Edellisen viikon tehtävässä 78 tehtiin ensin luokka YlhaaltaRajoitettuLaskuri ja rakennettiin laskurien avulla pääohjelmaan kello. Tehdään nyt myös itse kellosta olio. Luokan kello runko näyttää seuraavalta:


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

    public Kello(int tunnitAlussa, int minuutitAlussa, int sekunnitAlussa) {
      // luodaan kello joka asetetaan parametrina annettuun aikaan
    }

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

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

Kopioi uuteen projektiin viime viikon tehtävän 78 YlhaaltaRajoitettuLaskuri-luokka.

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

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

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

Tulostuksen tulisi edetä seuraavasti:

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

22.10 Olion sisällä listallinen olioita

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

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

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

//..
} 

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

public class PainonvartijaYhdistys {
  // ...

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

        return true;
    }

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

        this.jasenet.add(henkilo);
    }

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

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

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

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

Kokeillaan laajentunutta painonvartijayhdistystä:

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

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

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

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

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

    System.out.println( painonVartija );
}

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

Painonvartijayhdistys Kumpulan paino jäsenet: 
Matti
Harri
Petri

90.1 Joukkue-luokka

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

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

Seuraava pääohjelma testaa luokan toimintaa:

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

Ohjelman tulostus on seuraava:

Joukkue: FC Tapiiri

90.2 Pelaaja

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

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

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

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

90.3 Pelaajat joukkueisiin

Lisää luokkaan Joukkue seuraavat metodit:

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

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

Seuraava pääohjelma testaa luokan toimintaa:

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

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

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

        tapiiri.tulostaPelaajat();
    }
}

Ohjelman tulostuksen tulisi olla seuraava:

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

90.4 Joukkueen maksimikoko ja nykyinen koko

Lisää luokkaan Joukkue seuraavat metodit:

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

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

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

Seuraava pääohjelma testaa luokan toimintaa:

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

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

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

90.5 Joukkueen maalit

Lisää luokkaan Joukkue metodi:

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

Seuraava pääohjelma testaa luokan toimintaa:

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

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

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

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

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

Tehtäväpohjan mukana tulee luvussa 24.7 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.

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

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

91.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(25, 2, 2011);
    Paivays uusi_pvm = pvm.paivienPaasta(7);
    for (int i = 1; i <= 7; ++i) {
        System.out.println("Perjantai " + i + " viikon kuluttua on " + uusi_pvm);
        uusi_pvm = uusi_pvm.paivienPaasta(7);
    }
    System.out.println("Tämän viikon perjantai on " + pvm);
    System.out.println("Päivämäärä 790:n päivän päästä tämän viikon perjantaista on  " + pvm.paivienPaasta(790));
}

Ohjelma tulostaa:

Perjantai 1 viikon kuluttua on 2.3.2011
Perjantai 2 viikon kuluttua on 9.3.2011
Perjantai 3 viikon kuluttua on 16.3.2011
Perjantai 4 viikon kuluttua on 23.3.2011
Perjantai 5 viikon kuluttua on 30.3.2011
Perjantai 6 viikon kuluttua on 7.4.2011
Perjantai 7 viikon kuluttua on 14.4.2011
Tämän viikon perjantai on 25.2.2011
Päivämäärä 790:n päivän päästä tämän viikon perjantaista on  5.5.2013

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(20,2,2011);
Paivays viikonPaasta = nyt;
viikonPaasta.edista(7);
System.out.println("Nyt: " + nyt);
System.out.println("Viikon päästä: " + viikonPaasta);

Ohjelman tulostus olisi seuraavanlainen:

Nyt 27.2.2011
Viikon päästä 27.2.2011

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.

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

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

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

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

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

Luvussa 24.9. lisättiin henkilölle oliomuuttujaksi syntymäpäivän kertova Paivays-olio. Samalla todettiin, 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);
    }

    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 pekka = new Henkilo("Pekka", 15, 2, 1993);
        Henkilo antti = new Henkilo("Antti", 1, 3, 1955);

        System.out.println( antti.getNimi() + " ikä " + antti.ika() + " vuotta");
        System.out.println( pekka.getNimi() + " ikä " + pekka.ika() + " vuotta");
    }
}

Tulostus:

Antti ikä 57 vuotta
Pekka ikä 20 vuotta

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

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

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