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
Tässä osallistut taas kokeellisessa psykologiassa käytettyyn tehtävänvaihtotestiin, missä tutkitaan aivojen mukautumista kahden tai useamman samanaikaisen tehtävän suorittamiseen. Tavoitteenamme on selvittää vaikuttaako ohjelmointi aivojen kykyyn hoitaa useampia samanaikaisia tehtäviä. Tämä testi tehdään useamman kerran kurssin aikana.
Käytössä olevassa testiohjelmassa on neljä osaa. Ensimmäisessä osassa mitataan reaktioaikaa, toisessa osassa parittoman (odd) ja parillisen (even) luvun tunnistamista, kolmannessa osassa konsonantin (consonant) ja vokaalin (vowel) tunnistamista, ja viimeisessä osassa yhdistetään osat kaksi ja kolme. Näet jokaisen osion jälkeen edellisen osion tulokset.
Testin tulokset tulevat vain tutkimuskäyttöön, eikä mahdollisesti raportoitavista tuloksista voida yksilöidä yksittäisiä vastaajia. Tehtävän suorittamiseen menee noin 10-15 minuuttia.
Kun olet vastannut allaolevaan lomakkeeseen, testi aukeaa uudessa ikkunassa. Varmistathan, että selaimesi sallii uusien ikkunoiden avaamisen tästä ohjelmointimateriaalista!
Aaltosululla {
alkavaa ja aaltosululla }
loppuvaa koodia sisältävää aluetta kutsutaan lohkoksi. Kuten on jo nähty, lohkoja käytetään luokkaan kuuluvan alueen rajaamisessa, metodiin kuuluvan alueen määrittelyn rajaamisessa sekä ehto- ja toistolauseiden alueen rajaamisessa. Avautuvalle aaltosululle tulee aina löytyä vastaava sulkeva pari.
Eräs tärkeä, liian pieneen rooliin jäänyt aaltosulkuihin ja lohkoon liittyvä tieto on se, että lohkon sisällä määritellyt muuttujat ovat olemassa vain kyseisen lohkon sisällä.
Alla määritellään ehtolauseeseen liittyvän lohkon sisällä tekstimuuttuja teksti
, joka on olemassa vain lohkon sisällä. Lohkossa esitellyn muuttujan tulostus lohkon ulkopuolella ei toimi!
int luku = 5; if (luku == 5) { String teksti = "Oho!"; } // muuttujaa teksti ei ole olemassa täällä: // se määriteltiin edelläolevan lohkon sisällä System.out.println(teksti);
Lohkossa voidaan käyttää ja muuttaa sen ulkopuolella määriteltyjä muuttujia, kunhan ne on määritelty ennen lohkoa.
int luku = 5; if (luku == 5) { luku = 6; } System.out.println(luku); // tulostaa luvun 6
String teksti = "Jee!"; int luku = 5; if (luku == 5) { teksti = "Oho!"; } System.out.println(teksti); // tulostaa "Oho!"
Lohkon sisällä voi olla lähes mitä tahansa koodia. Esimerkiksi while-toistolauseen määrittelemän lohkon sisällä voi olla toinen while-toistolauseke. Tarkastellaan seuraavaa ohjelmaa:
int rivinumero = 0; while (rivinumero < 3) { System.out.print(rivinumero + ": "); int luku = 0; while (luku < 3) { System.out.print(luku + " "); luku++; } System.out.println(); rivinumero++; }
Ohjelman tulostus on seuraava:
0: 0 1 2 1: 0 1 2 2: 0 1 2
Eli mitä ohjelmassa tapahtuukaan? Jos ajatellaan pelkkää ulommaista while-lohkoa, on toiminnallisuus helppo ymmärtää:
int rivinumero = 0; while (rivinumero < 3) { System.out.print(rivinumero + ": "); // sisempi while System.out.println(); rivinumero++; }
Eli ensin rivinumero=0
ja tulostuu 0:
ja rivinvaihto. Tämän jälkeen rivinumero kasvaa ja tulostuu ykkönen, jne., eli ulompi for saa aikaan seuraavan:
0: 1: 2:
Myös sisempi while on helppo ymmärtää erillään. Se saa aina aikaan tulosteen 0 1 2
. Kun yhdistämme nämä kaksi, huomaamme, että sisempi while-toistolause suorittaa tulosteensa aina juuri ennen ulomman while-toistolauseen tulostamaa rivinvaihtoa.
int luku = 0; while (luku < 3) { System.out.print(luku + " "); luku++; }
0 1 2
Tutkitaan seuraavaa muunnosta edelliseen esimerkkiin:
int rivinumero = 0; while (rivinumero < 3) { System.out.print(rivinumero + ": "); int luku = 0; while (luku <= rivinumero) { System.out.print(luku + " "); luku++; } System.out.println(); rivinumero++; }
Sisemmän toistolausekkeen toistojen määrä riippuukin nyt ulommassa toistolausekkeessa käytettävän muuttujan rivinumero
arvosta. Eli kun rivinumero=0
, tulostaa sisempi toistolause luvun 0, kun rivinumero=1
, tulostaa sisempi toistolauseke "0 1 ", jne. Koko ohjelman tulostus on seuraava:
0: 0 1: 0 1 2: 0 1 2
Seuraava ohjelma tulostaa lukujen 1..10 kertotaulun.
int rivinumero = 1; while (rivinumero <= 10) { int sarake = 1; while (sarake <= 10) { System.out.print(rivinumero * sarake + " "); sarake++; } System.out.println(); rivinumero++; }
Tulostus näyttää seuraavalta:
1 2 3 4 5 6 7 8 9 10 2 4 6 8 10 12 14 16 18 20 3 6 9 12 15 18 21 24 27 30 4 8 12 16 20 24 28 32 36 40 5 10 15 20 25 30 35 40 45 50 6 12 18 24 30 36 42 48 54 60 7 14 21 28 35 42 49 56 63 70 8 16 24 32 40 48 56 64 72 80 9 18 27 36 45 54 63 72 81 90 10 20 30 40 50 60 70 80 90 100
Ylimmällä rivillä on luvun 1 kertotaulu. Alussa rivinumero=1
ja sisemmän toistolauseen muuttuja sarake
saa arvot 1...10. Jokaisella rivinumero, sarake
-arvoparilla tulostetaan niiden tulo. Eli alussa rivinumero=1, sarake=1
, sitten rivinumero=1, sarake=2
, ..., rivinumero=1, sarake=10
seuraavaksi rivinumero=2, sarake=1
, jne.
Kertotaulu-ohjelman voi pilkkoa pienempiin osiin. Voimme määritellä metodit public void tulostaKertotaulunRivi(int kerroin, int montakokertaa)
ja public void tulostaKertotaulu(int mihinAsti)
.
public class Kertotaulu { public void tulosta(int mihinAsti) { int rivinumero = 1; while (rivinumero <= mihinAsti) { tulostaKertotaulunRivi(rivinumero, mihinAsti); System.out.println(); rivinumero++; } } public void tulostaKertotaulunRivi(int rivi, int lukuja) { int sarake = 1; while (sarake <= lukuja) { System.out.print(rivi * sarake + " "); sarake++; } } }
Nyt kutsu new Kertotaulu().tulosta(5);
tulostaa allaolevan kertotaulun.
1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 5 10 15 20 25
Kerrataan ja syvennytään seuraavaksi aiemmillakin viikoilla harjoiteltuja toistolausekkeita, muuttujia sekä indeksejä. Seuraavassa kahdessa tehtävässä aiemmin tehdyistä tehtävistä 44, 45, 58 sekä aiemmin tehdyistä tehtävistä on hyötyä.
Toteuta luokka Lukutulostin
ja tee sille seuraavat toiminnallisuudet.
Toteuta luokalle Lukutulostin
metodi public void lukuporras(int luku)
. Metodin tulee toimia seuraavasti.
Lukutulostin tulostin = new Lukutulostin(); tulostin.lukuporras(2); System.out.println(); tulostin.lukuporras(3);
1 1 2 1 1 2 1 2 3
Lukutulostin tulostin = new Lukutulostin(); tulostin.lukuporras(5); System.out.println(); tulostin.lukuporras(2);
1 1 2 1 2 3 1 2 3 4 1 2 3 4 5 1 1 2
Toteuta luokalle Lukutulostin
metodi public void jatkuvaLukuporras(int luku)
. Metodin tulee toimia seuraavasti.
Lukutulostin tulostin = new Lukutulostin(); tulostin.jatkuvaLukuporras(2); System.out.println(); tulostin.jatkuvaLukuporras(3);
1 2 3 1 2 3 4 5 6
Lukutulostin tulostin = new Lukutulostin(); tulostin.jatkuvaLukuporras(5); System.out.println(); tulostin.jatkuvaLukuporras(2);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1 2 3
Toteuta luokalle Lukutulostin
metodi public void kertokolmio(int luku)
. Kertokolmio-metodin tulee toimia seuraavien esimerkkien mukaisesti.
Lukutulostin tulostin = new Lukutulostin(); tulostin.kertokolmio(2);
1 2 4
Lukutulostin tulostin = new Lukutulostin(); tulostin.kertokolmio(5); System.out.println(); tulostin.kertokolmio(3);
1 2 4 3 6 9 4 8 12 16 5 10 15 20 25 1 2 4 3 6 9
Toteuta luokka Sanatulostin
, jossa on seuraavat toiminnallisuudet. Vinkki: merkkijonon metodilla charAt saa merkin annetusta indeksistä; tässä tehtävässä myös jakojäännöksestä saattaa olla hyötyä.
Toteuta luokalle Sanatulostin
merkkijonon vastaanottava konstruktori sekä metodi public void sanaporras(int luku)
, joka toimii seuraavasti:
Sanatulostin tulostin = new Sanatulostin("Ananas"); tulostin.sanaporras(2); System.out.println(); tulostin.sanaporras(4);
A na A na nas Anan
Sanatulostin tulostin = new Sanatulostin("Sauna"); tulostin.sanaporras(3);
S au naS
Toteuta luokalle Sanatulostin
metodi public void laskevaSanaporras(int luku)
joka toimii seuraavasti:
Sanatulostin tulostin = new Sanatulostin("Ananas"); tulostin.laskevaSanaporras(2); System.out.println(); tulostin.laskevaSanaporras(3);
An a Ana na s
Sanatulostin tulostin = new Sanatulostin("Sauna"); tulostin.laskevaSanaporras(4);
Saun aSa un a
Toteuta luokalle Sanatulostin
metodi public void sanapyramidi(int luku)
, joka toimii seuraavasti:
Sanatulostin tulostin = new Sanatulostin("Nauris"); tulostin.sanapyramidi(3); System.out.println(); tulostin.sanapyramidi(1);
N au ris Na u N
Sanatulostin tulostin = new Sanatulostin("Saippuakauppias"); tulostin.sanapyramidi(4); System.out.println(); tulostin.sanapyramidi(2);
S ai ppu akau ppi as S S ai p
Luokan määrittely alkaa luokan nimellä, jota seuraa lohko.
// luokan Esimerkki määrittely public class Esimerkki { }
Tämän lohkon sisälle tulee oliomuuttujat, eli ne muuttujat, joita luokasta tehtävät oliot saavat omakseen.
// luokan Esimerkki määrittely public class Esimerkki { // oliomuuttujat }
Oliomuuttujien lisäksi luokan määrittelyyn käytettävä lohko sisältää konstruktoreja ja metodeja.
// luokan Esimerkki määrittely public class Esimerkki { // oliomuuttujat // konstruktorit // metodit }
Jokainen konstruktori ja metodi myös määrittelee oman lohkonsa.
// luokan Esimerkki määrittely public class Esimerkki { // oliomuuttujat // konstruktorit public Esimerkki() { // konstruktorin oma lohko } // metodit public void tulostaLuku() { // metodin tulostaLuku oma lohko } }
Konstruktoreissa ja metodeissa voi olla myös sisäisiä lohkoja.
// luokan Esimerkki määrittely public class Esimerkki { // oliomuuttujat // konstruktorit public Esimerkki() { // konstruktorin oma lohko } // metodit public void tulostaLuku() { // metodin tulostaLuku oma lohko int luku = 5; if (luku > 4) { // lisää lohkoja! } } }
Lohkoja kaikkialla!
Tietokoneohjelmissa satunnaisuutta käytetään esimerkiksi salausalgoritmeissa, koneoppimisessa sekä tietokonepelien ennustettavuuden vähentämisessä. Satunnaisuutta mallinnetaan käytännössä satunnaislukujen avulla, joiden luomiseen Java valmiin Random
-luokan. Random-luokasta voi tehdä olion jota voi käyttää seuraavalla tavalla.
import java.util.Random; public class Arvontaa { public static void main(String[] args) { Random arpoja = new Random(); // luodaan arpoja apuväline int i = 0; while (i < 10) { // Arvotaan ja tulostetaan jokaisella kierroksella satunnainen luku System.out.println(arpoja.nextInt(10)); i++; } } }
Yllä olevassa koodissa luodaan ensin Random
-luokasta olio käyttäen avainsanaa new
-- samoin kuin muitakin olioita luodessa. Random-olio tarjoaa metodin nextInt
, jolle annetaan parametrina kokonaisluku. Metodi palauttaa satunnaisen kokonaisluvun väliltä 0..(annettu kokonaisluku - 1).
Ohjelman tulostus voisi olla vaikka seuraavanlainen:
2 2 4 3 4 5 6 0 7 8
Satunnaisia kokonaislukuja voidaan käyttää esimerkiksi nopanheittojen mallintamiseen.
Tehtäväpohjassa on luokka Noppa
, jonka runko on seuraava:
import java.util.Random; public class Noppa { private Random random; private int tahkojenMaara; public Noppa(int tahkojenMaara) { this.random = new Random(); // Alusta oliomuuttuja tahkojenMaara tässä } public int heita() { // arvo täällä luku jonka tulee olla yhdestä tahkojen määrään // ([1-tahkojenMaara]) ja palauta se } }
Muokkaa luokkaa siten, että sen konstruktoriNoppa(int tahkojenMaara)
luo uuden noppa-olion annetulla nopan tahkojen (eri oman numeronsa sisältämien "puolien") määrällä. Muokkaa myös metodia heita
siten, että se antaa satunnaisen nopanheiton tuloksen, jonka arvon tulee olla väliltä 1...tahkojen määrä
(vinkki: plus!).
Seuraavassa noppaa testaava pääohjelma:
public class Ohjelma { public static void main(String[] args) { Noppa noppa = new Noppa(6); int i = 0; while (i < 10) { System.out.println(noppa.heita()); i++; } } }
Tulostus voisi olla esimerkiksi seuraava:
1 6 3 5 3 3 2 2 6 1
Random-luokasta tehdyn olion kautta päästään käsiksi myös satunnaisiin liukulukuihin, joita käytetään muunmuassa todennäköisyyslaskennan yhteydessä; tietokoneilla todennäköisyyksiä simuloidaan yleensä väliltä [0..1] olevilla luvuilla.
Random-oliolta satunnaisia liukulukuja saa metodilla nextDouble
. Tarkastellaan seuraavia säämahdollisuuksia:
Luodaan edellä olevista arvioista sääennustaja.
import java.util.ArrayList; import java.util.Random; public class SaaEnnustaja { private Random random; public SaaEnnustaja() { this.random = new Random(); } public String ennustaSaa() { double todennakoisyys = this.random.nextDouble(); if (todennakoisyys <= 0.1) { return "Sataa räntää"; } else if (todennakoisyys <= 0.4) { // 0.1 + 0.3 return "Sataa lunta"; } else { // loput, 1.0 - 0.4 = 0.6 return "Aurinko paistaa"; } } public int ennustaLampotila() { return (int) (4 * this.random.nextGaussian() - 3); } }
Metodi ennustaLampotila
on monella tapaa mielenkiintoinen. Metodin sisällä tehtävä kutsu this.random.nextGaussian()
on tavallinen metodikutsu, jonka kaltaisia olemme nähneet aikaisemminkin. Kiinnostavaa tässä Random
-luokan ilmentymän tarjoamassa metodissa on se, että metodin palauttama luku on normaalijakautunut (jos et koe mielenkiintoa satunnaisuuden eri lajeihin se ei haittaa!).
public int ennustaLampotila() { return (int) (4 * this.random.nextGaussian() - 3); }
Edellisessä lausekkeessa kiinnostava on myös osa (int)
. Tämä kohta lausekkeessa muuttaa suluissa olevan liukuluvun kokonaisluvuksi. Vastaavalla menetelmällä voidaan muuttaa myös kokonaislukuja liukuluvuiksi kirjoittamalla (double) kokonaisluku
. Tätä kutsutaan eksplisiittiseksi tyyppimuunnokseksi.
Luodaan vielä pääohjelma josta luokkaa SaaEnnustaja
käytetään.
public class Ohjelma { public static void main(String[] args) { SaaEnnustaja ennustaja = new SaaEnnustaja(); // tallennetaan päivät listalle ArrayList<String> paivat = new ArrayList<>(); paivat.add("Ma"); paivat.add("Ti"); paivat.add("Ke"); paivat.add("To"); paivat.add("Pe"); paivat.add("La"); paivat.add("Su"); System.out.println("Seuraavan viikon sääennuste:"); for(String paiva : paivat) { String saaEnnuste = ennustaja.ennustaSaa(); int lampotilaEnnuste = ennustaja.ennustaLampotila(); System.out.println(paiva + ": " + saaEnnuste + " " + lampotilaEnnuste + " astetta."); } } }
Ohjelman tulostus voisi olla esimerkiksi seuraavanlainen:
Seuraavan viikon sääennuste: Ma: Sataa lunta 1 astetta. Ti: Sataa lunta 1 astetta. Ke: Aurinko paistaa -2 astetta. To: Aurinko paistaa 0 astetta. Pe: Sataa lunta -3 astetta. La: Sataa lunta -3 astetta. Su: Aurinko paistaa -5 astetta
Vihje: näin muutat kokonaisluvun luku kirjaimeksi:
int luku = 17; char merkki = "abcdefghijklmnopqrstuvwxyz".charAt(luku);
Tehtävänäsi on täydentää luokkaa SalasananArpoja
, jossa on seuraavat toiminnot:
SalasananArpoja
luo uuden olion, joka käyttää annettua salasanan pituuttaluoSalasana
palauttaa uuden, merkeistä a-z muodostetun konstruktorin parametrin määräämän pituisen salasanan Luokan runko on seuraava:
import java.util.Random; public class SalasananArpoja { // Määrittele muuttuja tässä public SalasananArpoja(int salasananPituus) { // Alusta muuttuja tässä } public String luoSalasana() { // Kirjoita tähän koodi, joka palauttaa uuden salasanan } }
Seuraavassa ohjelma joka käyttää SalasananArpoja-olioa:
public class Ohjelma { public static void main(String[] args) { SalasananArpoja arpoja = new SalasananArpoja(13); System.out.println("Salasana: " + arpoja.luoSalasana()); System.out.println("Salasana: " + arpoja.luoSalasana()); System.out.println("Salasana: " + arpoja.luoSalasana()); System.out.println("Salasana: " + arpoja.luoSalasana()); } }
Ohjelman tulostus voisi näyttää seuraavalta:
Salasana: mcllsoompezvs Salasana: urcxboisknkme Salasana: dzaccatonjcqu Salasana: bpqmedlbqaopq
Tehtävänäsi on täydentää luokkaa LottoRivi
, joka arpoo viikon lottonumerot. Lottonumerot ovat väliltä 1–39 ja niitä arvotaan 7. Lottorivi koostuu siis 7:stä eri numerosta väliltä 1–39. Luokassa on seuraavat toiminnot:
LottoRivi
luo uuden LottoRivi-olion joka sisältää uudet, arvotut numerotnumerot
palauttaa tämän lottorivin lottonumerotsisaltaaNumeron
kertoo onko arvotuissa numeroissa annettu numeroarvoNumerot
arpoo riville uudet numerotLuokan runko on seuraava:
import java.util.ArrayList; import java.util.Random; public class LottoRivi { private ArrayList<Integer> numerot; public LottoRivi() { // Arvo numerot heti LottoRivin luomisen yhteydessä this.arvoNumerot(); } public ArrayList<Integer> numerot() { return this.numerot; } public boolean sisaltaaNumeron(int numero) { // Testaa tässä onko numero jo arvottujen numeroiden joukossa } public void arvoNumerot() { // Alustetaan lista numeroille this.numerot = new ArrayList<>(); // Kirjoita numeroiden arvonta tänne käyttämällä metodia sisaltaaNumeron() } }
Tehtäväpohjan mukana tulee seuraava pääohjelma:
import java.util.ArrayList; public class Ohjelma { public static void main(String[] args) { LottoRivi lottoRivi = new LottoRivi(); ArrayList<Integer> lottonumerot = lottoRivi.numerot(); System.out.println("Lottonumerot:"); for (int numero : lottonumerot) { System.out.print(numero + " "); } System.out.println(""); } }
Ohjelman mahdollisia tulostuksia ovat seuraavat:
Lottonumerot: 3 5 10 14 15 27 37
Lottonumerot: 2 9 11 18 23 32 34
Huom! Sama numero saa esiintyä lottorivissä vain kerran.
Merkittävä osa tämän viikon tehtävistä liittyy olioiden sekä niiden välisten viittausten rakentamiseen ja ymmärtämiseen. Aloitamme yksittäisten olioiden rakentamisella, jonka jälkeen pohdimme viittaustyyppisen muuttujan toiminnallisuutta "olio on langan päässä"-lauseen kautta. Käsittelemme tämän jälkeen olioita, jotka käsittelevät olioita, jonka jälkeen siirrymme olioihin, jotka voivat sisältää oliota sekä listallisia muita olioita.
Tällä viikolla -- kuten muutenkin -- tekee hyvää ottaa välillä askel taaemmaksi TMC:stä ja piirtää paperille käytettyjen olioiden välisiä viitteitä. Kaikki oma harjoittelu esimerkiksi hiekkalaatikossa on myös erittäin hyödyllistä.
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 luoda henkilöitä myös siten, että konstruktorin parametrina annettaisiin ikä nimen lisäksi. Tämä onnistuu, 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 ada = new Henkilo("Ada"); System.out.println(pekka); System.out.println(ada); }
Pekka, ikä 24 vuotta Ada, 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, aiemmin 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, eli nimen parametrinaan saava konstruktori, on oikeastaan alemman, eli nimen ja iän parametrinaan saavan konstruktorin, 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. Huom! Jos konstruktorista kutsutaan toista konstruktoria, tulee konstruktorin kutsun tulee olla ensimmäinen toiminto konstruktorin sisällä.
Olioiden luonti onnistuu aivan kuten edellisessä esimerkissä:
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
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 nyt siis käytännössä kaksi kappaletta vanhene
-nimisiä metodeja. Se kumpi metodeista valitaan suoritettavaksi, riippuu metodikutsussa käytettyjen parametrien määrästä.
Ohjelmaa voi muokata myös niin, että parametriton metodi vanhene
toteutetaan 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.Aiemmin 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 varaamaan 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, metodin parametriksi voi määritellä 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 { 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 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 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
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
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
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
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 ada = new Henkilo("Ada") if (pekka.getIka() > ada.getIka()) { System.out.println(pekka.getNimi() + " on vanhempi kuin " + ada.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 ada = new Henkilo("Ada", 22); if (pekka.vanhempiKuin(ada)) { // sama kun pekka.vanhempiKuin(ada)==true System.out.println(pekka.getNimi() + " on vanhempi kuin " + ada.getNimi()); } else { System.out.println(pekka.getNimi() + " ei ole vanhempi kuin " + ada.getNimi()); } }
Tässä siis kysytään onko pekka anttia vanhempi "jos pekka on vanhempi kuin ada". 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 "Pekkaa" vastaavan olion, johon muuttuja pekka
viittaa, metodia vanhempiKuin
, jolle annetaan parametriksi "Adaa" vastaavan olion viite antti
.
Ohjelman tulostaa:
Pekka on vanhempi kuin Ada
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; } // 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; } }
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
ArrayList:eihin voidaan laittaa minkä tahansa tyyppisiä oliota. Luodaan seuraavassa asuntolista, eli tyyppiä ArrayList<Asunto>
oleva ArrayList, laitetaan sinne muutama aiemmin näkemämme asunto, ja etsitään asunnoista suurin -- hyödynnetään tässä edellisessä tehtävässä tehtyä metodia -- oletetaan lisäksi, että Asunto-luokalla on metodi getNeliot()
, joka palauttaa asunnon neliöiden määrän:
public static void main(String[] args) { ArrayList<Asunto> asunnot = new ArrayList<>(); asunnot.add(new Asunto(1, 16, 5500)); asunnot.add(new Asunto(2, 38, 4200)); asunnot.add(new Asunto(3, 78, 2500)); Asunto suurin = null; for(Asunto asunto: asunnot) { if (suurin == null) { suurin = asunto; continue; // jatketaan seuraavan asunnon tarkastelulla } // jos tarkasteltava asunto on suurempi kuin suurin, // asetetaan tarkasteltava suurimmaksi if (asunto.suurempi(suurin)) { suurin = asunto; } } if (suurin == null) { System.out.println("Ei löytynyt suurinta asuntoa :("); } else { System.out.println("Suurin asunto löytyi!"); System.out.println("Asunnossa on " + suurin.getNeliot() + " neliötä."); } }
Ohjelman tulostus:
Suurin asunto löytyi! Asunnossa on 78 neliötä.
Vaihtoehtoinen tapa edellisen ohjelman toteutuksella on seuraava. Jos listalla ei ole yhtäkään asuntoa, emme jatka metodin suoritusta eteenpäin (komento return). Muuten otamme suurimmaksi asunnoksi listan ensimmäisen alkion, ja vertailemme asunnot kuten edellä. Tällä tavalla vältämme toistolauseen sisällä olevan nul-vertailun.
public static void main(String[] args) { ArrayList<Asunto> asunnot = new ArrayList<>(); asunnot.add(new Asunto(1, 16, 5500)); asunnot.add(new Asunto(2, 38, 4200)); asunnot.add(new Asunto(3, 78, 2500)); if(asunnot.isEmpty()) { System.out.println("Ei löytynyt suurinta asuntoa :("); return; // lopetetaan void-tyyppisen metodin suoritus } Asunto suurin = asunnot.get(0); for(Asunto asunto: asunnot) { if (asunto.suurempi(suurin)) { suurin = asunto; } } System.out.println("Suurin asunto löytyi!"); }
Ohjelman tulostus:
Suurin asunto löytyi! Asunnossa on 78 neliötä.
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<>();
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 26.4 todettiin, että oliot ovat "langan päässä". Kertaa nyt luku 26.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. Jätämme vertailun toteutuksen omatoimiseksi harjoitustehtäväksi.
Edellisellä viikolla tehtiin 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) { // 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 viikon 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 ...
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<>(); } //.. }
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
Matkailijat tykkäävät ottaa valokuvia erilaisista kohteista. Usein kuvissa sattuu kuitenkin olemaan turisti kohteen edessä, mikä harmittaa. Tämä harmitus kasvaa erityisesti, jos samainen turisti esiintyy jokaisessa kuvassa.
Alla on kaksi kuvaa eräästä reissusta.
Tehdään ohjelma, minkä tavoitteena on poistaa turisti kuvasta. Apunamme meillä on kolmannella viikolla olleessa tehtävässä Fotari
nähdyn apukirjaston toinen versio, joka tarjoaa apuvälineet kuvien lukemiseen ja näyttämiseen.
Fotarin tarjoama toiminnallisuus on seuraavanlainen:
Fotari.lataa(String kuva);
lataa parametrina annettavan merkkijonon osoittamassa sijainnissa olevan kuvan, ja palauttaa Kuva
-tyyppisen olion. Käyttöesimerkki: Kuva kuva = Fotari.lataa("G0010099.png");
.Fotari.nayta(Kuva kuva);
avaa parametrina annettavan Kuva
-tyyppisen kuvan erilliseen ikkunaan, mistä sitä voi tarkastella. Käyttöesimerkki:
Kuva kuva = Fotari.lataa("G0010099.png"); Fotari.nayta(kuva);
Tämän lisäksi, ladatuilla kuvaolioilla on käytössä seuraavat metodit:
getLeveys();
palauttaa kuvan leveyden. Esimerkki: int leveys = kuva.getLeveys();
getKorkeus();
palauttaa kuvan korkeuden. Esimerkki: int korkeus = kuva.getKorkeus();
punainen(int x, int y);
palauttaa punaisen värin määrän annetussa koordinaatissa. Värin määrä on aina luku väliltä 0-255. Esimerkki: int punainen = kuva.punainen(3, 2);
vihrea(int x, int y);
palauttaa vihreän värin määrän annetussa koordinaatissa. Värin määrä on aina luku väliltä 0-255. Esimerkki: int vihrea = kuva.vihrea(7, 4);
sininen(int x, int y);
palauttaa sinisen värin määrän annetussa koordinaatissa. Värin määrä on aina luku väliltä 0-255. Esimerkki: int sininen = kuva.sininen(1, 1);
asetaVari(int x, int y, int punainen, int vihrea, int sininen);
asettaa annettuun koordinaattiin annetut värimäärät. Esimerkki: kuva.asetaVari(0, 0, 255, 255, 255);
-- lisää valkoisen pisteen kuvan kohtaan (0, 0). Kuva-luokassa on olemassa myös konstruktori, minkä avulla voi luoda uusia Kuva-olioita. Konstruktori saa parametrikseen luotavan kuvan leveyden ja korkeuden. Esimerkiksi seuraava koodi luo uuden kuvan, jonka leveys on 200 ja korkeus on 100, sekä näyttää sen Fotarin avulla. Konstruktori asettaa oletuksena jokaiseen kuvan pikseliin mustan värin, missä punaisen, vihreän ja sinisen määrä on 0.
Kuva kuva = new Kuva(200, 100); Fotari.nayta(kuva);
Toteutetaan kuvien yhdistämistä varten erillinen luokka Yhdistin
. Luo luokka Yhdistin
, jonka konstruktori saa parametrinaan merkkijonon, millä kuvataan yhdistämistapaa. Tarvitset myös oliomuuttujan yhdistämistavan tallentamiseen, sillä sitä hyödynnetään tehtävän myöhemmissä osissa.
Luo yhdistimelle lisäksi apumetodi lataaKuvat
, joka saa parametrinaan ArrayList<String>
-tyyppisen listan merkkijonoja, jotka sisältävät kuvien sijainteja. Metodin lataaKuvat
tulee palauttaa taulukko ArrayList<Kuva>
, mihin kuvat on ladattu.
Hyödynnä tässä sopivasti Fotarin tarjoamaa lataa
-metodia.
Voit testata kuvien lataamisen toimintaa esimerkiksi seuraavalla koodilla:
ArrayList<String> tiedostot = new ArrayList<>(); tiedostot.add("G0010111.png"); tiedostot.add("G0010161.png"); tiedostot.add("G0010163.png"); Yhdistin yhdistin = new Yhdistin("vaalein"); ArrayList<Kuva> kuvat = yhdistin.lataaKuvat(tiedostot); Fotari.nayta(kuvat.get(0));
Huom! Jos Fotari tai Kuva on alleviivattu, eikä ohjelma löydä niitä, lisää käyttämiesi luokkien alkuun seuraavat rivit:
import kuva.Fotari; import kuva.Kuva;
Nyt kun saat ladattua kuvia ja näytettyä niitä, tavoitteena on seuraavaksi yhdistää kuvia.
Toteuta yhdistimelle metodi yhdistaKuvat
, jolle annetaan parametrina ArrayList<Kuva>
-tyyppinen lista kuvia. Jos käyttäjä antaa yhdistimelle konstruktorin parametrina arvon "vaalein"
, tulee metodin toimia seuraavasti:
Suunnittele ylläoleva toteutus ennen kuin lähdet tekemään sitä. Kolmannella viikolla olleen Fotari-tehtävän muistelusta ja tarkastelusta lienee myös hyötyä.
Kun metodi on toteutettu, ohjelmaa voi testata seuraavasti:
ArrayList<String> tiedostot = new ArrayList<>(); // tiedostojen lisääminen Yhdistin yhdistin = new Yhdistin("vaalein"); ArrayList<Kuva> kuvat = yhdistin.lataaKuvat(tiedostot); Kuva kuva = yhdistin.yhdistaKuvat(kuvat); Fotari.nayta(kuva);
Lopputulos voi näyttää esimerkiksi seuraavanlaiselta:
Muokkaa toteutustasi siten, että yhdistimelle voi antaa konstruktorin parametrina myös arvon "tummin". Tällöin kuvien yhdistämisen tulee luoda uusi kuva, johon laitetaan yhdistettävien kuvien tummimmat pikselit.
Kun metodi on toteutettu, ohjelmaa voi testata seuraavasti:
ArrayList<String> tiedostot = new ArrayList<>(); // tiedostojen lisääminen Yhdistin yhdistin = new Yhdistin("tummin"); ArrayList<Kuva> kuvat = yhdistin.lataaKuvat(tiedostot); Kuva kuva = yhdistin.yhdistaKuvat(kuvat); Fotari.nayta(kuva);
Noniin, hankkiudutaan turistista eroon.
Muokkaa toteutustasi siten, että yhdistimelle voi antaa konstruktorin parametrina arvon "mediaani". Tällöin kuvien yhdistämisen tulee luoda uusi kuva, johon laitetaan yhdistettävien pikseleiden keskimmäinen arvo, eli mediaani. Esimerkiksi, jos viiden kuvan sinisten värien arvot ovat 211, 123, 17, 155, 8
, on niiden mediaani 123
. Saat mediaanin selville järjestämällä arvot, ja valitsemalla listan keskimmäisen arvon.
Kun metodi on toteutettu, ohjelmaa voi testata seuraavasti:
ArrayList<String> tiedostot = new ArrayList<>(); // tiedostojen lisääminen Yhdistin yhdistin = new Yhdistin("mediaani"); ArrayList<Kuva> kuvat = yhdistin.lataaKuvat(tiedostot); Kuva kuva = yhdistin.yhdistaKuvat(kuvat); Fotari.nayta(kuva);
Toteutuksen pitäisi poistaa ärsyttävä turisti:
Huippua!