Tehtävät
Neljännen osion tavoitteet

Kurssin neljännessä osassa kerrataan aiempaa ja tuodaan taas käyttöön uusia välineitä. Keskitymme luokkien ja olioiden käyttöön ja luomme olioille uutta toimintaa. Tutustumme sekä olioihin listalla että listoihin oliomuuttujana. Olion elämä tulee myös syvällisemmin tutuksi.

Tavoitteena on, että tämän osion jälkeen osaat tunnistaa annetusta ongelmasta käsitteitä ja muodostaa niiden perusteella luokkia. Osaat tehdä näistä luokista olioita. Osaat luoda ohjelman, joka ratkaisee ongelman olioita hyödyntämällä. Osaat myös luoda ja käsitellä olioita, jotka sisältävät muita olioita.

Huom! Jos teet Helsingin yliopiston Ohjelmoinnin perusteet -kurssia tai aikataulutettua MOOCia, tee konekoe ennen tämän osion aloittamista.

Luokkia ja olioita

Kerrataan lyhyesti edellistä osiota. Alla on luokka. Luokka määrittelee minkälaisia olioita siitä voidaan luoda. Se sisältää oliomuuttujat, olion luomiseen käytettävän konstruktorin, sekä metodeja.

// luokka
public class Suorakulmio {

    // oliomuuttujat
    private int leveys;
    private int korkeus;

    // konstruktori
    public Suorakulmio(int leveys, int korkeus) {
        this.leveys = leveys;
        this.korkeus = korkeus;
    }

    // metodit
    public void levenna() {
        this.leveys++;
    }

    public void kavenna() {
        if (this.leveys > 0) {
            this.leveys--;
        }
    }

    public int pintaAla() {
        return this.leveys * this.korkeus;
    }

    public String toString() {
        return "(" + this.leveys + ", " + this.korkeus + ")";
    }
}

Osa metodeista eivät palauta arvoa (metodit, joiden määrittelyssä käytetään avainsanaa void), ja osa metodeista palauttaa arvon (metodit, joiden määrittelyssä kerrotaan palautettavan muuttujan tyyppi). Yllä olevassa luokassa on määriteltynä myös metodi toString, jota käytetään olion tulostamiseen.

Luokasta luodaan olioita konstruktorin avulla new-komennolla. Alla luodaan kaksi suorakulmiota ja tulostaan niihin liittyvää tietoa.

Suorakulmio eka = new Suorakulmio(40, 80);
Suorakulmio nelio = new Suorakulmio(10, 10);
System.out.println(eka);
System.out.println(nelio);

eka.kavenna();
System.out.println(eka);
System.out.println(eka.pintaAla());
(40, 80)
(10, 10)
(39, 80)
3920

Luo luokka Kirja, joka esittää fyysistä kirjaa. Jokaisella kirjalla on kirjailija, nimi ja sivujen lukumäärä.

Luokalla tulee olla:

  • Konstruktori public Kirja(String kirjailija, String nimi, int sivuja)
  • Metodi public String getKirjailija() joka palauttaa kirjan kirjailijan nimen.
  • Metodi public String getNimi() joka palauttaa kirjan nimen.
  • Metodi public int getSivuja() joka palauttaa kirjan sivujen lukumäärän.
  • Tee kirjalle lisäksi public String toString()-metodi, jota käytetään kirja-olion tulostamiseen. Metodin kutsun tulee tuottaa esimerkiksi seuraavanlainen tulostus:
    J. K. Rowling, Harry Potter ja viisasten kivi, 223 sivua
    

Edellisessä tehtävässä tehty luokka on todennäköisesti luokkakaaviona seuraavanlainen (oliomuuttujien nimet saattavat olla erilaiset).

[Kirja|-kirjailija:String;-nimi:String;-sivuja:int|+Kirja(kirjailija:String¸nimi:String¸sivuja: int);+getKirjailija():String;+getNimi():String;+getSivuja():int;+toString():String]

 

Tarkastele seuraavaa luokkakaaviota, ja luo sen perusteella luokka.

[Paita|-vari:String;-koko:int;-materiaali:String|+Paita(vari:String¸koko:int¸materiaali:String);+getKoko():int;+toString():String]
Luokkakaavioksi ja takaisin

Luokkakaavio ei ota kantaa luokassa määriteltyjen konstruktorien ja metodien sisäiseen toteutukseen. Se vain määrää niiden olemassaolon. Pelkän luokkakaavion pohjalta ei siis voi olettaa miten ohjelman metodit toimivat.

Tarkastellaan seuraavaksi listojen käyttöä.

Lista on olio, johon pystyy lisäämään arvoja. Listalle lisättyjä arvoja voidaan tarkastella indeksin perusteella, ja listalla olevia arvoja voidaan etsiä ja poistaa. Kaikkia listan tarjoamia toimintoja käytetään sen metodien kautta.

Listalle lisättävien muuttujien tyyppi määrätään listan luomisen yhteydessä annettavan tyyppiparametrin avulla. Esimerkiksi ArrayList<String> sisältää merkkijonoja, ArrayList<Integer> sisältää kokonaislukuja, ja ArrayList<Double> sisältää liukulukuja.

Alla olevassa esimerkissä lisätään ensin merkkijonoja listalle, jonka jälkeen listalla olevat merkkijonot tulostetaan yksitellen.

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

// merkkijono voidaan ensin muuttujaan
String nimi = "Betty Jennings";
// ja sitten lisätä se listalle
nimet.add(nimi);

// merkkijono voidaan myös lisätä suoraan listalle:
nimet.add("Betty Snyder");
nimet.add("Frances Spence");
nimet.add("Kay McNulty");
nimet.add("Marlyn Wescoff");
nimet.add("Ruth Lichterman");

// listan alkioiden läpikäynti onnistuu toistolauseen avulla
int indeksi = 0;
while (indeksi < nimet.size()) {
    System.out.println(nimet.get(indeksi));
    indeksi++;
}  
Betty Jennings
Betty Snyder
Frances Spence
Kay McNulty
Marlyn Wescoff
Ruth Lichterman

Olioita listalla

Merkkijonokin on olio, joten olemme käsitelleet olioita listalla jo useampaan otteeseen. Tarkastellaan seuraavaksi olioiden lisäämistä listalle tarkemmin.

Oletetaan, että käytössämme on alla oleva luokka.

public class Henkilo {

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

    // muita konstruktoreja ja metodeja
  
    public String getNimi() {
        return this.nimi;
    }

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

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

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

    public double painoIndeksi() {
        double pituusPerSata = this.pituus / 100.0;
        return this.paino / (pituusPerSata * pituusPerSata);
    }

    @Override
    public String toString() {
        return this.nimi + ", ikä " + this.ika + " vuotta";
    }
}

Olioiden käsittely listalla ei oikeastaan poikkea aiemmin näkemästämme listan käytöstä millään tavalla. Oleellista on vain listalle lisättävien olioiden tyypin määrittely listan luomisen yhteydessä.

Alla olevassa esimerkissä luodaan ensin Henkilo-tyyppisille olioille tarkoitettu lista, jonka jälkeen listalle lisätään henkilöolioita. Lopulta henkilöoliot tulostetaan yksitellen.

ArrayList<Henkilo> henkilot = new ArrayList<>();

// henkilöolio voidaan ensin luoda
Henkilo juhana = new Henkilo("Juhana");
// ja sitten lisätä se listalle
henkilot.add(juhana);

// henkilöolio voidaan myös lisätä listalle "samassa lauseessa"
henkilot.add(new Henkilo("Matti"));
henkilot.add(new Henkilo("Martin"));

int indeksi = 0;
while (indeksi < henkilot.size()) {
    System.out.println(henkilot.get(indeksi));
    indeksi++;
}
Juhana, ikä 0 vuotta
Matti, ikä 0 vuotta
Martin, ikä 0 vuotta

Aiemmin käyttämämme rakenne syötteiden lukemiseen on yhä varsin käytännöllinen.

Scanner lukija = new Scanner(System.in);
ArrayList<Henkilo> henkilot = new ArrayList<>();

while (true) {
    System.out.print("Kirjoita nimi, tyhjä lopettaa: ");
    String nimi = lukija.nextLine();
    if (nimi.isEmpty()) {
        break;
    }

    henkilot.add(new Henkilo(nimi));
}

System.out.println();
System.out.println("Henkilöitä yhteensä: " + henkilot.size());
System.out.println("Henkilöt: ");  

int indeksi = 0;
while (indeksi < henkilot.size()) {
    Henkilo henkilo = henkilot.get(indeksi);
    System.out.println(henkilo);
    // tai: System.out.println(henkilot.get(indeksi));

    indeksi++;
}

Läpikäytäviä olioita voidaan myös tarkastella. Alla olevassa esimerkissä tulostetaan vain täysi-ikäiset henkilöt.

// ..
int indeksi = 0;
while (indeksi < henkilot.size()) {
    Henkilo henkilo = henkilot.get(indeksi);

    if (henkilo.getIka() >= 18) {
        System.out.println(henkilo);
    }
  
    indeksi++;
}

Ikärajan voi kysyä myös käyttäjältä.

// ..
System.out.print("Mikä ikäraja? ");
int ikaraja = Integer.parseInt(lukija.nextLine());
  
int indeksi = 0;
while (indeksi < henkilot.size()) {
    Henkilo henkilo = henkilot.get(indeksi);

    if (henkilo.getIka() >= ikaraja) {
        System.out.println(henkilo);
    }
  
    indeksi++;
}

Tehtäväpohjassa on valmiina televisio-ohjelmaa kuvaava luokka Ohjelma. Luokalla Ohjelma on oliomuuttujat nimi ja pituus, konstruktori, ja muutamia metodeja.

Toteuta ohjelma, joka ensin lukee käyttäjältä televisio-ohjelmia. Kun käyttäjä syöttää tyhjän ohjelman nimen, televisio-ohjelmien lukeminen lopetetaan.

Tämän jälkeen käyttäjältä kysytään ohjelman maksimipituutta. Kun käyttäjä on syöttänyt ohjelman maksimipituuden, tulostetaan kaikki ne ohjelmat, joiden pituus on pienempi tai yhtäsuuri kuin haluttu maksimipituus.

Nimi: Salatut elämät
Pituus: 30
Nimi: Miehen puolikkaat
Pituus: 30
Nimi: Remppa vai muutto
Pituus: 60
Nimi: House
Pituus: 60

Ohjelman maksimipituus? 30
Salatut elämät, 30 minuuttia
Miehen puolikkaat, 30 minuuttia

Toteuta ohjelma, joka ensin lukee kirjojen tietoja käyttäjältä. Jokaisesta kirjasta tulee lukea kirjan nimi, sivujen lukumäärä sekä kirjoitusvuosi. Kirjojen lukeminen lopetetaan kun käyttäjä syöttää tyhjän kirjan nimen.

Tämän jälkeen käyttäjältä kysytään mitä tulostetaan. Jos käyttäjä syöttää merkkijonon "kaikki", tulostetaan kirjojen nimet, sivujen lukumäärät sekä kirjoitusvuodet. Jos taas käyttäjä syöttää merkkijonon "nimi", tulostetaan vain kirjojen nimet.

Ohjelmaa varten kannattanee toteuttaa Kirjaa kuvaava luokka. Tehtävä on kahden tehtäväpisteen arvoinen.

Nimi: Minä en sitten muutu
Sivuja: 201
Kirjoitusvuosi: 2010
Nimi: Nalle Puh ja elämisen taito
Sivuja: 100
Kirjoitusvuosi: 2005
Nimi: Beautiful Code
Sivuja: 593
Kirjoitusvuosi: 2007
Nimi: KonMari
Sivuja: 222
Kirjoitusvuosi: 2011

Mitä tulostetaan? kaikki
Minä en sitten muutu, 201 sivua, 2010
Nalle Puh ja elämisen taito, 100 sivua, 2005
Beautiful Code, 593 sivua, 2007
KonMari, 222 sivua, 2011
Nimi: Minä en sitten muutu
Sivuja: 201
Kirjoitusvuosi: 2010
Nimi: Nalle Puh ja elämisen taito
Sivuja: 100
Kirjoitusvuosi: 2005
Nimi: Beautiful Code
Sivuja: 593
Kirjoitusvuosi: 2007
Nimi: KonMari
Sivuja: 222
Kirjoitusvuosi: 2011

Mitä tulostetaan? nimi
Minä en sitten muutu
Nalle Puh ja elämisen taito
Beautiful Code
KonMari

Oliot ja viitteet

Mitä oikein tapahtuu kun olio luodaan?

Henkilo joan = new Henkilo("Joan Ball");

Konstruktorikutsun new yhteydessä tapahtuu monta asiaa. Ensin tietokoneen muistista varataan tila oliomuuttujille. Tämän jälkeen oliomuuttujiin asetetaan oletus- tai alkuarvot (esimerkiksi int-tyyppisten muuttujien arvoksi tulee 0). Lopulta suoritetaan konstruktorissa oleva lähdekoodi.

Konstruktorikutsu palauttaa viitteen olioon. Viite asetetaan muuttujan arvoksi.

Muuttujan arvo on siis olioon liittyvien tietojen paikka eli viite. Yllä oleva kuva paljastaa myös sen, että nimi -- tai tarkemmin merkkijonot -- ovat myös olioita.

Muuttujan arvon asettaminen kopioi viitteen

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

Henkilo joan = new Henkilo("Joan Ball");
System.out.println(joan);

Henkilo ball = joan;

Lause Henkilo ball = joan; luo uuden henkilömuuttujan, jonka arvoksi kopioidaan muuttujan joan arvo. Tämä saa aikaan sen, että ball viittaa samaan olioon kuin joan.

Tarkastellan samaa esimerkkiä hieman pidemmälle.

Henkilo joan = new Henkilo("Joan Ball");
System.out.println(joan);
  
Henkilo ball = joan;
ball.vanhene();  
ball.vanhene();  
  
System.out.println(joan);
Joan Ball, ikä 0 vuotta
Joan Ball, ikä 2 vuotta

Joan Ball -- viite muuttujassa joan on alussa 0-vuotias. Tämän jälkeen muuttujaan ball asetetaan muuttujan joan arvo. Henkilöoliota ball vanhennetaan kaksi vuotta ja sen seurauksena Joan Ball vanhenee!

Olion sisäinen tila ei kopioidu muuttujan arvoa asetettaessa. Lauseessa Henkilo ball = joan; ei siis luoda henkilöä -- muuttujan ball arvoksi asetetaan kopio muuttujan joan arvosta, eli viite olioon.

Seuraavassa esimerkkiä on jatkettu siten, että joan-muuttujaa varten luodaan uusi olio, jonka viite asetetaan muuttujan arvoksi. Muuttuja ball viittaa yhä aiemmin luotuun olioon.

Henkilo joan = new Henkilo("Joan Ball");
System.out.println(joan);

Henkilo ball = joan;
ball.vanhene();  
ball.vanhene();  

System.out.println(joan);

joan = new Henkilo("Joan B.");
System.out.println(joan);

Tulostuu:

Joan Ball, ikä 0 vuotta
Joan Ball, ikä 2 vuotta
Joan B., ikä 0 vuotta

Muuttujassa joan on siis alussa viite yhteen olioon, mutta lopussa sen arvoksi on kopioitu toisen muuttujan viite. Seuraavassa kuva tilanteesta viimeisen koodirivin jälkeen.

Muuttujan arvo null

Jatketaan vielä esimerkkiä asettamalla muuttujan ball arvoksi null, eli viite "ei mihinkään".

Henkilo joan = new Henkilo("Joan Ball");
System.out.println(joan);

Henkilo ball = joan;
ball.vanhene();  
ball.vanhene();  

System.out.println(joan);

joan = new Henkilo("Joan B.");
System.out.println(joan);

ball = null;

Viimeisen rivin jälkeen ohjelman tila on seuraavanlainen.

Olioon, jonka nimi on Joan Ball, ei enää viittaa kukaan. Oliosta on siis tullut "roska". Java-ohjelmointikielessä ohjelmoijan ei tarvitse huolehtia ohjelman käyttämästä muistista. Javan automaattinen roskienkerääjä käy siivoamassa roskaksi joutuneet oliot aika ajoin. Jos automaattista roskien keruuta ei tapahtuisi, jäisivät roskaksi joutuneet oliot varaamaan muistia ohjelman suorituksen loppuun asti.

Kokeillaan vielä mitä käy kun yritämme tulostaa muuttujaa, jonka arvona on viite "ei mihinkään" eli null.

Henkilo joan = new Henkilo("Joan Ball");
System.out.println(joan);

Henkilo ball = joan;
ball.vanhene();  
ball.vanhene();  

System.out.println(joan);

joan = new Henkilo("Joan B.");
System.out.println(joan);

ball = null;
System.out.println(ball);
Joan Ball, ikä 0 vuotta
Joan Ball, ikä 2 vuotta
Joan B., ikä 0 vuotta
null

Viitteen null tulostus tulostaa "null". Entäpä jos yritämme kutsua ei mihinkään viittaavan olion metodia, esimerkiksi metodia vanhene:

Henkilo joan = new Henkilo("Joan Ball");
System.out.println(joan);

joan = null;
joan.vanhene();

Tulos:

Joan Ball, ikä 0 vuotta
  Exception in thread "main" java.lang.NullPointerException
    at Main.main(Main.java:(rivi))
    Java Result: 1

Käy huonosti. Tämä on ehkä ensimmäinen kerta kun näet tekstin NullPointerException. Ohjelmassa tapahtuu virhe, joka liittyy siihen, että olemme kutsuneet ei mihinkään viittaavan muuttujan metodia.

Voimme luvata, että tulet näkemään edellisen virheen vielä uudelleen. Tällöin ensimmäinen askel on etsiä muuttujia, joiden arvona saattaisi olla null. Virheilmoitus on onneksi myös hyödyllinen: se kertoo millä rivillä virhe tapahtuu. Kokeile vaikka itse!

Toteuta ohjelma, jonka suorittaminen aiheuttaa virheen NullPointerException. Virheen tulee tapahtua heti kun ohjelma suoritetaan -- älä siis esimerkiksi lue käyttäjältä syötettä.

 

Listakin sisältää viitteitä

Kun olio lisätään listalle, listalle kopioidaan viite. Kuten aiemmin, olion sisäisestä tilasta ei siis luoda kopiota, vaan listalle lisätään viite olemassa olevaan olioon.

Alla olevassa esimerkissä luodaan ensin olio juhana, joka lisätään listalle. Tämän jälkeen listalle lisätään kaksi muuta oliota. Seuraavaksi juhana-olion metodia vanhene kutsutaan. Lopulta jokaista listalla olevaa oliota vanhennetaan.

ArrayList<Henkilo> henkilot = new ArrayList<>();

Henkilo juhana = new Henkilo("Juhana");
henkilot.add(juhana);

henkilot.add(new Henkilo("Matti"));
henkilot.add(new Henkilo("Martin"));

// juhana vanhenee 2 vuotta
juhana.vanhene();
juhana.vanhene();

// jokainen listalla oleva henkilöolio vanhenee vuoden
int indeksi = 0;
while (indeksi < henkilot.size()) {
    Henkilo henkilo = henkilot.get(indeksi);
    henkilo.vanhene();
    indeksi++;
}

// tulostetaan henkilöt
indeksi = 0;
while (indeksi < henkilot.size()) {
    Henkilo henkilo = henkilot.get(indeksi);
    System.out.println(henkilo);

    // tai: System.out.println(henkilot.get(indeksi));
  
    indeksi++;
}  
Juhana, ikä 3 vuotta
Matti, ikä 1 vuotta
Martin, ikä 1 vuotta

Listalle on kopioituna viitteet olioihin. Yllä olevassa esimerkissä muuttujan juhana arvona on sama viite kuin listalla, joten "Juhanan" ikä muuttuu myös jos hän vanhenee listan ulkopuolella.

 

Olio oliomuuttujana

Oliot voivat sisältää viitteitä olioihin.

Jatketaan Henkilo-luokan parissa ja lisätään henkilölle syntymäpäivä. Syntymäpäivä on luonnollista esittää Paivays-olion 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 int getPaiva() {
        return this.paiva;
    }

    public int getKuukausi() {
        return this.kuukausi;
    }

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

Koska tiedämme syntymäpäivän, henkilön ikää ei enää tarvitse säilöä. Se on pääteltävissä syntymäpäivästä.

public class Henkilo {
    private String nimi;
    private Paivays syntymaPaiva;
    private int paino = 0;
    private int pituus = 0;
  
    // ...

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.syntymaPaiva = new Paivays(paiva, kuukausi, vuosi);
    this.paino = 0;
    this.pituus = 0;
}

Konstruktorin parametrina annetaan erikseen päiväyksen osat (päivä, kuukausi, vuosi), niistä luodaan päiväysolio, ja lopulta päiväysolion viite kopioidaan oliomuuttujan syntymaPaiva arvoksi.

Luokkakaavio: luokka tietää toisesta luokasta

Luokkakaaviossa luokkaan Henkilo ei merkitä syntymäpäivää, vaan luokasta piirretään nuoli luokkaan Paivays. Nuolen yläpuolelle voidaan kirjoittaa yhteyteen liittyvä tieto -- tässä "syntymapaiva".

Kaavio luetaan esimerkiksi "Henkilöllä on syntymäpäivänä päiväys".

[Paivays|-paiva:int;-kuukausi:int;-vuosi:int|+Paivays(paiva:int¸kuukausi:int¸vuosi:int);+getPaiva():int;+getKuukausi():int;+getVuosi():int;+toString():String]
		     [Henkilo|-nimi:String;-paino:int;-pituus:int|+Henkilo(nimi:String¸paiva:int¸kuukausi:int¸vuosi:int);+...;+toString():String]
		     [Henkilo]-syntymapaiva>[Paivays]

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

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

Kokeillaan miten uusittu Henkilöluokka toimii.

Henkilo muhammad = new Henkilo("Muhammad ibn Musa al-Khwarizmi", 1, 1, 780);
Henkilo pascal = new Henkilo("Blaise Pascal", 19, 6, 1623);

System.out.println(muhammad);
System.out.println(pascal);
Muhammad ibn Musa al-Khwarizmi, syntynyt 1.1.870
Blaise Pascal, syntynyt 19.6.1623
Algoritmi -- al-Khwarizmi

Tietojenkäsittelytieteen ytimessä ovat algoritmit eli yksityiskohtaiset kuvaukset ongelman ratkaisuun tarvittavista askelista.

Termi algoritmi juontaa juurensa keski-aasialaisen tieteilijän Muhammad ibn Musa al-Khwarizmin nimeen. Hän kehitti muun muassa menetelmiä ensimmäisen ja toisen asteen matemaattisten yhtälöiden ratkaisemiseksi.

Yllä olevassa esimerkissä esiintynyt Blaise Pascal oli myös merkittävä tietojenkäsittelytieteen alaa edistänyt henkilö. Vaikka hänet tunnetaan yleensä matematiikasta (mm. Binomikertoimen arvot helposti kertova Pascalin kolmio), hän kehitti myös mekaanisen laskimen.

Henkilöoliolla on nyt oliomuuttujat nimi ja syntymaPaiva. Muuttuja nimi on merkkijono, joka sekin on siis olio, ja muuttuja syntymaPaiva on Päiväysolio.

Molemmat muuttujat sisältävät arvon olioon. Henkilöolio sisältää siis kaksi viitettä.

 

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. Totesimme aiemmin, että oliomuuttuja ika voidaan laskea syntymäpäivästä, joten siitä hankkiuduttiin eroon.

Javassa nykyinen päivä selviää seuraavasti:

LocalDate nyt = LocalDate.now();
int vuosi = nyt.getYear();
int kuukausi = nyt.getMonthValue();
int paiva = nyt.getDayOfMonth();

System.out.println("tänään on " + paiva + "." + kuukausi + "." + vuosi);

Tehtäväpohjassa tulee mukana edellä nähdyt luokat Henkilo ja Paivays. Täydennä luokan Henkilo metodia public int ikaVuosina() siten, että se laskee ja palauttaa henkilön tämän hetkisen iän vuosina.

Voit olettaa, että jokaisessa vuodessa on tasan 360 päivää.

Vinkki! Näppärä lähestymistapa on laskea päivien summa vuosien, kuukausien ja päivien perusteella. Erottamalla "nykypäivää" vastaavan päivien summan syntymäpäivän päivien summasta saat elettyjen päivien määrän. Eletyt päivät saa muunnettua takaisin vuosiksi jakolaskulla.

Edellisessä osassa tehtiin luokka YlhaaltaRajoitettuLaskuri ja rakennettiin laskurien avulla pääohjelmaan kello. Tehdään nyt kellosta olio. Luokan kello runko näyttää seuraavalta:

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

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

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

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

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

Voit testata kelloasi seuraavalla pääohjelmalla:

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

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

Tulostuksen tulisi edetä seuraavasti:

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

Olio metodin parametrina

Olemme nähneet että metodien parametrina voi olla esimerkiksi int, String tai ArrayList. Kuten arvata saattaa, metodin parametriksi voi määritellä minkä tahansa tyyppisen olion. Demonstroidaan tätä esimerkillä.

Painonvartijoihin hyväksytään jäseniksi henkilöitä, joiden painoindeksi ylittää 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 seuraavaksi metodi, jonka avulla voidaan tarkastaa hyväksytäänkö tietty henkilö yhdistyksen jäseneksi, eli onko henkilön painoindeksi tarpeeksi suuri. Metodi palauttaa true jos parametrina annettu henkilö hyväksytään, false jos ei.

public class PainonvartijaYhdistys {
    private double alinPainoindeksi;

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

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

        return true;
    }
}

Painonvartijayhdistys-olion metodille hyvaksytaanJaseneksi annetaan siis parametrina Henkilo-olio. Kuten aiemmin, muuttujan arvo -- eli tässä viite -- kopioituu metodin käyttöön. Metodissa käsitellään kopioitua viitettä ja kutsutaan parametrina saadun henkilön metodia painoIndeksi.

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

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
Konstruktorien, getterien ja setterien automaattinen generointi

Ohjelmointiympäristöt osaavat auttaa ohjelmoijaa. Jos luokalle on määriteltynä oliomuuttujat, onnistuu konstruktorien, getterien ja setterien generointi automaattisesti.

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.

Joillain Linux-koneilla, kuten Kumpulassa olevilla 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!

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

Syöttäminen

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

Punnitusten laskeminen

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

"Tyhmä" Maksukortti

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

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

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

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

public class Maksukortti {
    private double saldo;

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

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

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

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

Testipääohjelma:

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

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

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

Tulostuksen kuuluisi olla seuraavanlainen

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

Kassapääte ja käteiskauppa

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

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

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

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

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

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

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

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

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

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

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

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

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

Kortilla maksaminen

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

public class Kassapaate {
    // ...

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

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

    // ...
}

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

Seuraavassa testipääohjelma ja haluttu tulostus:

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

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

        Maksukortti antinKortti = new Maksukortti(7);

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

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

Rahan lataaminen

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

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

Testipääohjelma ja esimerkkisyöte:

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

        Maksukortti antinKortti = new Maksukortti(2);

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

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

        unicafeExactum.lataaRahaaKortille(antinKortti, 100);

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

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

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

Samantyyppinen olio metodin parametrina

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

public class Henkilo {

    private String nimi;
    private Paivays syntymaPaiva;
    private int pituus;
    private int paino;

    // ...
}

Haluamme vertailla kahden henkilön ikää. Vertailu voidaan hoitaa usealla tavalla. Voisimme käyttää esimerkiksi aiemmassa tehtävässä toteutettua metodia ikaVuosina(), jolloin kahden henkilön iän vertailu tapauhtuisi tällöin seuraavasti:

Henkilo muhammad = new Henkilo("Muhammad ibn Musa al-Khwarizmi", 1, 1, 780);
Henkilo pascal = new Henkilo("Blaise Pascal", 19, 6, 1623);

if (muhammad.ikaVuosina() > pascal.ikaVuosina()) {
    System.out.println(muhammad.getNimi() + " on vanhempi kuin " + pascal.getNimi());
}

Harjoittelemme 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:

Henkilo muhammad = new Henkilo("Muhammad ibn Musa al-Khwarizmi", 1, 1, 780);
Henkilo pascal = new Henkilo("Blaise Pascal", 19, 6, 1623);

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

Tässä siis kysytään onko al-Khwarizmi Pascalia vanhempi "jos A on vanhempi kuin B". Metodi vanhempiKuin palauttaa arvon true jos olio jonka kohdalla metodia kutsutaan (olio.vanhempiKuin(parametrinaAnnettavaOlio)) on vanhempi kuin parametrina annettava olio, ja false muuten.

Käytännössä yllä kutsutaan "Muhammad ibn Musa al-Khwarizmia" vastaavan olion, johon muuttuja muhammad viittaa, metodia vanhempiKuin, jolle annetaan parametriksi "Blaise Pascal" vastaavan olion viite pascal.

Ohjelman tulostaa:

Muhammad ibn Musa al-Khwarizmi on vanhempi kuin Blaise Pascal

Metodille vanhempiKuin annetaan parametrina henkilöolio. Tarkemmin sanottuna metodin parametriksi määriteltyyn muuttujaan kopioituu parametrina annettavan muuttujan sisältämä arvo, eli viite olioon.

Metodin toteutus näyttää seuraavalta. Huomaa, että metodi voi palauttaa arvon useammasta kohtaa -- alla vertailu on pilkottu useampaan osaan:

public class Henkilo {
    // ...

    public boolean vanhempiKuin(Henkilo verrattava) {
        // toteutus
        if (this.getSyntymaPaiva().getVuosi() < verrattava.getSyntymaPaiva().getVuosi()) {
            return true;
        }

        if (this.getSyntymaPaiva().getVuosi() == verrattava.getSyntymaPaiva().getVuosi()
            && this.getSyntymaPaiva().getKuukausi() < verrattava.getSyntymaPaiva().getKuukausi()) {
            return true;
        }

        if (this.getSyntymaPaiva().getVuosi() == verrattava.getSyntymaPaiva().getVuosi()
            && this.getSyntymaPaiva().getKuukausi() == verrattava.getSyntymaPaiva().getKuukausi()
            && this.getSyntymaPaiva().getPaiva() < verrattava.getSyntymaPaiva().getPaiva()) {
            return true;
        }

        return false;
    }
}

Tässä vaiheessa pitäisi tuntua hieman epämukavalta.

Jos luokka Henkilo kuvaa henkilöä ja luokka Paivays päiväystä, kummalle näistä pitäisi kuulua vastuu päivämäärien vertailusta? Vastaus on: ei luokalle henkilö.

Luodaan luokalle Paivays metodi public boolean aiemmin(Paivays verrattava). Metodi palauttaa arvon true, jos metodille parametrina annettu päiväys on kyseisen olion päiväyksen jälkeen.

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

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

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

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

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

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

        return false;
    }
}

Vaikka oliomuuttujat vuosi, kuukausi ja paiva ovat olion yksityisiä (private) oliomuuttujia, pystymme lukemaan niiden arvon kirjoittamalla verrattava.muuttujanNimi. 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.

Metodin 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

Muunnetaan vielä henkilöä järkevämmäksi.

public class Henkilo {
    // ...

    public boolean vanhempiKuin(Henkilo verrattava) {
        if (this.syntymaPaiva.aiemmin(verrattava.getSyntymaPaiva())) {
            return true;
        }

        return false;
    }
}

Nyt päivämäärän konkreettinen vertailu on toteutettu luokassa, johon se loogisesti (luokkien nimien perusteella) kuuluukin.

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.

Onko asunto 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

Asuntojen 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

Onko asunto 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

Olio metodin paluuarvona

Olemme nähneet metodeja jotka palauttavat totuusarvoja, lukuja, listoja ja merkkijonoja. On helppoa arvata, että metodi voi palauttaa minkä tahansa tyyppisen olion.

Seuraavassa esimerkissä on yksinkertainen laskuri, jolla on metodi kloonaa. Metodin 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 sisältämä arvo on kloonauksen tapahduttua sama. Kyseessä on kuitenkin kaksi erillistä olioa, eli 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ä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.

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.

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

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);
    int vk = 1;
    while (vk <= 7) {
        System.out.println("Perjantai " + vk + " viikon kuluttua on " + uusiPvm);
        uusiPvm = uusiPvm.paivienPaasta(7);

        vk++;
    }


    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.

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.

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

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

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

Lista oliomuuttujana

Listat ovat olioita, joten oliomuuttujaksi voi asettaa listan. Tarkastellaan tätä seuraavaksi.

Olemme aiemmin huomanneet, että listat ovat esimerkiksi näppäriä silloin, silloin kun haluamme pitää kirjaa useammasta erillisestä asiasta. Alla olevassa esimerkissä käsitteelle soittolista on luotu luokka. Soittolista sisältää kappaleita.

// importit

public class Soittolista {
    private ArrayList<String> kappaleet;

    public Soittolista() {
        this.kappaleet = new ArrayList<>();
    }

    public void lisaaKappale(String kappale) {
        this.kappaleet.add(kappale);
    }

    public void poistaKappale(String kappale) {
        this.kappaleet.remove(kappale);
    }

    public void tulostaKappaleet() {
        int indeksi = 0;
        while (indeksi < this.kappaleet.size()) {
            String kappale = this.kappaleet.get(indeksi);
            System.out.println(kappale);

            indeksi++;
        } 
    }
}

Kokeile yllä olevan soittolistan käyttöä hiekkalaatikossa!

Kumpulan kampuksella Helsingissä toimivaan Unicafe-nimiseen gourmet-ravintolaan tarvitaan uusi ruokalista. Keittiömestari tietää ohjelmoinnista, ja haluaa listan hallinnointiin tietokonejärjestelmän. Toteutetaan tässä tehtävässä järjestelmän sydän, luokka Ruokalista.

Tehtäväpohjan mukana tulee Main-luokka, jossa voit testata ruokalistan toimintaa. Ruokalistan toteuttamista varten saat seuraavanlaisen tehtäväpohjan:

import java.util.ArrayList;

public class Ruokalista {

    private ArrayList<String> ateriat;

    public Ruokalista() {
        this.ateriat = new ArrayList<>();
    }

    // toteuta tänne tarvittavat metodit
}

Ruokalistaoliolla on oliomuuttujana ArrayList, jonka on tarkoitus tallentaa ruokalistalla olevien ruokalajien nimet.

Ruokalistan tulee tarjota metodit public void lisaaAteria(String ateria), public void tulostaAteriat(), ja public void tyhjennaRuokalista() -- luokkakaaviona lopullinen toteutus näyttää seuraavalta.

Aterian lisääminen

Toteuta metodi public void lisaaAteria(String ateria), joka lisää uuden aterian listalle ateriat. Jos lisättävä ateria on jo listalla, sitä ei lisätä uudelleen.

Aterioiden tulostaminen

Toteuta metodi public void tulostaAteriat(), joka tulostaa ateriat. Kolmen aterian lisäyksen jälkeen tulostuksen tulee olla seuraavanlainen.

ensimmäisenä lisätty ateria
toisena lisätty ateria
kolmantena lisätty ateria

Ruokalistan tyhjentäminen

Toteuta metodi public void tyhjennaRuokalista() joka tyhjentää ruokalistan. ArrayList-luokalla on metodi josta on tässä hyötyä. NetBeans osaa vihjata käytettävissä olevista metodeista kun kirjoitat olion nimen ja pisteen. Yritä kirjoittaa ateriat. metodirungon sisällä ja katso mitä käy.

Oliomuuttujana oleva lista voi sisältää myös muunlaisia olioita. Laajennetaan luokkaa PainonvartijaYhdistys siten, että yhdistys lisää kaikki jäsenensä listalle. Laajennetussa versiossa konstruktorille annetaan alimman painoindeksin lisäksi myös nimi:

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

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

    //..
}

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

public class PainonvartijaYhdistys {
// ...

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

        return true;
    }

    public void lisaaJaseneksi(Henkilo henkilo) {
        // sama kuin hyvaksytaanJaseneksi(henkilo) == false
        if (!hyvaksytaanJaseneksi(henkilo)) {
            // void-tyyppisistä metodeista voi palata
            // return-kutsulla
            return;
        }

        this.jasenet.add(henkilo);
    }

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

        int indeksi = 0;
        while (indeksi < this.jasenet.size()) {
            Henkilo jasen = this.jasenet.get(indeksi);
            jasenetMerkkijonona += "  " + jasen.getNimi() + "\n";
            indeksi++;
        }

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

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

Kokeillaan laajentunutta painonvartijayhdistystä:

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

Tehdään vielä lopuksi 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);

        int indeksi = 0;
        while (indeksi < this.jasenet.size()) {
            Henkilo henkilo = this.jasenet.get(indeksi);
            if (henkilo.painoIndeksi() > painavinTahanAsti.painoIndeksi()) {
                painavinTahanAsti = henkilo;
            }

            indeksi++;
        }

        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.

PainonvartijaYhdistys painonVartija = new PainonvartijaYhdistys("Kumpulan paino", 25);

// .. lisätään listalle ..

Henkilo painavin = painonVartija.suurinPainoindeksinen();
System.out.print("suurin painoindeksi on jäsenellä " + painavin.getNimi());
suurin painoindeksi on jäsenellä Petri

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

Henkilö

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

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

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:

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

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:

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

Joukkue

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

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

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

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

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

Breakout on Atarin vuonna 1976 julkaisema videopeli. Pelin ideana käyttää pelin alalaidassa olevaa mailaa pelissä liikkuvan pallon lyömiseen siten, että pallolla saadaan rikottua ylälaidassa olevia palasia.

Termi "Breakout" tulee tilanteesta, missä pelaaja saa pallon ylälaidassa olevien palasten yläpuolelle, missä pallo tekee tuhoa useammalle palalle samaan aikaan.

Tässä tehtävässä fiilistellään Breakout-pelin tekemistä.

Tehtäväpohjaan on toteutettuna ensimmäisiä palasia Breakout peliin. Tehtävänäsi on täydentää pelin toimintaa. Alla lista täydennysehdotuksista:

  1. Tällä hetkellä pelin ylälaidassa on vain muutama vaivainen palikka. Muokkaa peliä siten, että ylälaidassa on useita erivärisiä palikoita. Hae inspiraatiota Googlen kuvahausta avainsanalla "Breakout".
  2. Pelin pelattavuus on tällä hetkellä melko heikko. Mailalla ei osuta palloon, vaikka kuinka yritettäisiin. Lisää peliin mahdollisuus osua mailalla palloon -- palloon osumisen pitäisi muuttaa pallon suuntaa. Osoitteesta http://www.edu4java.com/en/game/game6.html olevasta oppaasta saattaa olla hyötyä. Hifistelyä kaipaavat voivat lähteä liikenteeseen kysymyksestä A ball hits the corner, where will it deflect?
  3. Kun pallon osuminen mailaan on hanskassa, lisää samanlainen osumistoiminnallisuus kaikkiin paloihin. Tässä kohtaa paloja ei vielä tarvitse poistaa.
  4. Kun pallo osuu palaan, pala pitäisi poistaa. Sehän on melkein jo peli!
  5. Mieti minkälaisia luokkia pelissä kannattaisi olla. Nyt piirtämiseen käytetty paikka sisältää varmaankin jo hyvin paljon koodia.. Siistimisen paikka!

Tehtävässä ei ole automaattisia testejä ja se on yhden pisteen arvoinen. Voit palauttaa tehtävän jo kun saat ensimmäisen parannusehdotuksen tehtyä, mutta peliä saa toki viilata ikuisuuksiin asti.

Sisällysluettelo