Tämä materiaali on lisensoitu Creative Commons BY-NC-SA-lisenssillä, joten voit käyttää ja levittää sitä vapaasti, kunhan alkuperäisten tekijöiden nimiä ei poisteta. Jos teet muutoksia materiaaliin ja haluat levittää muunneltua versiota, se täytyy lisensoida samanlaisella vapaalla lisenssillä. Materiaalien käyttö kaupalliseen tarkoitukseen on ilman erillistä lupaa kielletty.
Tekijät: Arto Vihavainen ja Matti Luukkainen
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ää.
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.
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; }
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 arvonpublic void lisaa()
lisää laskurin arvoa yhdelläpublic void vahenna()
vähentää laskurin arvoa yhdellä,
mutta ei alle nollan jos tarkistus on päällä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.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.
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
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!
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
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
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
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
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
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
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
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.
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.
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
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
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
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
Tee luokka Opiskelija
,
johon tallennetaan seuraavat tiedot opiskelijasta:
String
)String
)Tee luokkaan seuraavat metodit:
haeNimi
, joka palauttaa opiskelijan nimen, esim. Pekka MikkolahaeOpiskelijanumero
, joka palauttaa opiskelijan opiskelijanumeron, esim. 013141590toString
, 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)
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)
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)
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 ...
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
Tee luokka Joukkue
,
johon tallennetaan joukkueen nimi (String
).
Tee luokkaan seuraavat metodit:
haeNimi
, joka palauttaa joukkueen nimenSeuraava 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
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 nimenmaalit
, joka palauttaa tehtyjen maalien määräntoString
, joka palauttaa pelaajan merkkijonoesityksenpublic 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
Lisää luokkaan Joukkue
seuraavat metodit:
lisaaPelaaja
, joka lisää pelaajan joukkueeseentulostaPelaajat
, joka tulostaa joukkueessa olevat pelaajatTallenna 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
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
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
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
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.
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.
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()
.
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ä.
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:
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
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
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
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
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
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änTestaa 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!