Muistathan aloittaa kurssin ensimmäisen konekokeen viimeistään 27.9. Ohjeet edellisen osan alussa.
Tietää olio-ohjelmoinnin perusperiaatteet ja luo luokkia, jotka kuvaavat annettua ongelma-aluetta. Tunnistaa käsitteet alkeis- ja viittaustyyppinen muuttuja. Osaa määritellä olioita, jotka sisältävät olioita. Osaa käyttää olioita metodin parametrina sekä luoda metodeja, jotka palauttavat olioita. Tuntee listarakenteen ja osaa lisätä ja poistaa listalla olevia alkioita. Tuntee käsitteen indeksi ja osaa käydä listan läpi while-toistolauseen avulla.
Olio-ohjelmointi jatkuu
Jatketaan edellisen osan olio-ohjelmointiteemalla ja kerrataan aluksi olio-ohjelmoinnin oleellisia käsitteitä. Yleisesti ottaen teemana on siis olio-ohjelmointi, missä on kyse käsiteltävän ongelma-alueen eristämistä omiksi erillisiksi kokonaisuuksikseen, joita käytetään yhteistoiminnassa ongelma-alueen ratkaisemiseksi.
Olio
Olio on itsenäinen kokonaisuus, johon liittyy tietoa (oliomuuttujat) sekä käyttäytymistä (metodit). Oliot voivat olla hyvin erilaisia rakenteeltaan ja toiminnaltaan: jotkut voivat kuvata ongelma-alueen käsitteitä, ja jotkut voivat koordinoida olioiden välistä toimintaa. Olioiden kommunikointi tapahtuu metodikutsujen avulla -- metodikutsuilla sekä kysytään tietoa olioita että annetaan olioille käskyjä.
Yleisesti ottaen jokaisella oliolla on selkeästi määritellyt rajat ja toiminnallisuudet, jonka lisäksi jokainen olio tietää vain niistä muista olioista, joita se tarvitsee tehtävänsä tekemiseen. Toisin sanoen, olio piilottaa oman sisäisen toimintansa ja tarjoaa pääsyn toiminnallisuuksiin selkeästi määriteltyjen metodien kautta. Tämän lisäksi olio on riippumaton niistä olioista, joita se ei tehtäväänsä tarvitse.
Edellisessä osassa käsiteltiin Henkilö-oliota, jota varten luotiin Henkilö-luokka. Kertauksen vuoksi on hyvä muistella luokan tehtävää: luokka sisältää olioiden tekemiseen tarvittavat rakennuspiirrustukset sekä määrittelee olioiden muuttujat ja metodit. Olio luodaan luokassa olevan konstruktorin perusteella.
Henkilö-olioomme liittyi nimi, ikä, paino ja pituus sekä muutamia metodeja. Jos mietimme henkilö-oliomme rakennetta tarkemmin, keksisimme varmaankin lisää henkilöihin liittyviä muuttujia kuten henkilöturvatunnus, puhelinnumero, osoite ja silmien väri. Pitäydytään toistaiseksi kuitenkin edellä mainituissa muuttujissa.
Olion käyttäytyminen määräytyy metodien avulla. Todellisuudessa henkilöt voivat tehdä hyvin monia erilaisia asioita, mutta henkilöitä käsittelevää sovellusta rakennettaessa henkilöön liittyvät toiminnallisuudet rakennetaan ongelma-alueen perusteella. Esimerkiksi elämänhallintaan tarkoitettu sovellus voisi pitää kirjaa edellä mainituista iästä, painosta ja pituudesta, sekä tarjota mahdollisuuden painoindeksin ja maksimisykkeen laskemiseen.
Oliot tarjoavat tyypillisesti pääsyn myös niiden tilaan. Olion tila on sen oliomuuttujien arvo kullakin ajanhetkellä.
Java-ohjelmointikielellä Henkilö-olion, joka pitää kirjaa nimestä, iästä, painosta ja pituudesta, sekä tarjoaa mahdollisuuden painoindeksi ja maksimisykkeen laskemiseen näyttäisi esimerkiksi seuraavalta. Huomaa, että alla oleva esimerkki poikkeaa hieman edellisessä osassa käytetystä esimerkistä. Alla pituus ja paino ilmaistaan doubleina -- pituuden yksikkö on metri.
public class Henkilo {
private String nimi;
private int ika;
private double paino;
private double pituus;
public Henkilo(String nimi, int ika, double paino, double pituus) {
this.nimi = nimi;
this.ika = ika;
this.paino = paino;
this.pituus = pituus;
}
public double painoindeksi() {
return this.paino / (this.pituus * this.pituus);
}
public double maksimisyke() {
return 206.3 - (0.711 * this.ika);
}
public String toString() {
return this.nimi + ", BMI: " + this.painoindeksi()
+ ", maksimisyke: " + this.maksimisyke();
}
}
Annetun henkilön maksimisykkeen ja painoindeksin selvittäminen on suoraviivaista edellä kuvatun Henkilo-luokan avulla.
Scanner lukija = new Scanner(System.in);
System.out.println("Mikä on nimesi?");
String nimi = lukija.nextLine();
System.out.println("Mikä on ikäsi?");
int ika = Integer.parseInt(lukija.nextLine());
System.out.println("Mikä on painosi?");
double paino = Double.parseDouble(lukija.nextLine());
System.out.println("Mikä on pituutesi?");
double pituus = Double.parseDouble(lukija.nextLine());
Henkilo henkilo = new Henkilo(nimi, ika, paino, pituus);
System.out.println(henkilo);
Mikä on nimesi? Napoleone Buonaparte Mikä on ikäsi? 51 Mikä on painosi? 80 Mikä on pituutesi? 1.70 Napoleone Buonaparte, BMI: 27.68166089965398, maksimisyke: 170.03900000000002
Luokka
Luokka määrittelee minkälaisia olioita siitä voidaan luoda. Se sisältää olion tietoa kuvaavat oliomuuttujat, olion luomiseen käytettävän konstruktorin tai konstruktorit, sekä olion käyttäytymisen määrittelevät metodit. Alla on kuvattuna luokka Suorakulmio, joka määrittelee eräänlaisen suorakulmion toiminnallisuuden-
// luokka
public class Suorakulmio {
// oliomuuttujat
private int leveys;
private int korkeus;
// konstruktori
public Suorakulmio(int leveys, int korkeus) {
this.leveys = leveys;
this.korkeus = korkeus;
}
// metodit
public void levenna() {
this.leveys++;
}
public void kavenna() {
if (this.leveys > 0) {
this.leveys--;
}
}
public int pintaAla() {
return this.leveys * this.korkeus;
}
public String toString() {
return "(" + this.leveys + ", " + this.korkeus + ")";
}
}
Osa edellä määritellyistä metodeista ei palauta arvoa (metodit, joiden määrittelyssä käytetään avainsanaa void), ja osa metodeista palauttaa arvon (metodit, joiden määrittelyssä kerrotaan palautettavan muuttujan tyyppi). Yllä olevassa luokassa on määriteltynä myös metodi toString, jota käytetään olion sisäisen tilan tulostamiseen.
Luokasta luodaan olioita konstruktorin avulla new-komennolla. Alla luodaan kaksi suorakulmiota ja tulostaan niihin liittyvää tietoa.
Suorakulmio eka = new Suorakulmio(40, 80);
Suorakulmio nelio = new Suorakulmio(10, 10);
System.out.println(eka);
System.out.println(nelio);
eka.kavenna();
System.out.println(eka);
System.out.println(eka.pintaAla());
(40, 80) (10, 10) (39, 80) 3920
Luo luokka Kirja
, joka esittää fyysistä kirjaa. Jokaisella kirjalla on kirjailija, nimi ja sivujen lukumäärä.
Luokalla tulee olla:
- Konstruktori
public Kirja(String kirjailija, String nimi, int sivuja)
- Metodi
public String getKirjailija()
joka palauttaa kirjan kirjailijan nimen. - Metodi
public String getNimi()
joka palauttaa kirjan nimen. - Metodi
public int getSivuja()
joka palauttaa kirjan sivujen lukumäärän. - Tee kirjalle lisäksi
public String toString()
-metodi, jota käytetään kirja-olion tulostamiseen. Metodin kutsun tulee tuottaa esimerkiksi seuraavanlainen tulostus:J. K. Rowling, Harry Potter ja viisasten kivi, 223 sivua
Karvosen kaavan avulla voidaan laskea tavoitesyke fyysistä harjoittelua varten. Tavoitesykkeen laskeminen perustuu kaavaan (maksimisyke - leposyke) * (tavoitesyke) + leposyke
, missä tavoitesyke annetaan prosenttina maksimisykkeestä.
Esimerkiksi, jos henkilön maksimisyke on 200, leposyke 50, ja tavoitesyke 75% maksimisykkeestä, on tavoiteltava sydämen syke noin 162.5 lyöntiä minuutissa.
Luo luokka Harjoitusapuri
, jolle annetaan konstruktorin parametrina ikä ja leposyke. Harjoitusapurin tulee tarjota metodi tavoitesyke, jolle annetaan parametrina prosentuaalista maksimisykkeen osuutta kuvaava double-tyyppinen luku. Osuus annetaan lukuna nollan ja yhden välillä. Luokalla tulee olla:
- Konstruktori
public Harjoitusapuri(int ika, int leposyke)
- Metodi
public double tavoitesyke(double prosenttiaMaksimista)
, joka laskee ja palauttaa tavoiteltavan sykkeen.
Käytä maksimisykkeen laskemiseen kaavaa 206.3 - (0.711 * ikä)
.
Käyttöesimerkki:
Harjoitusapuri apuri = new Harjoitusapuri(30, 60);
double prosenttiosuus = 0.5;
while (prosenttiosuus < 1.0) {
double tavoite = apuri.tavoitesyke(prosenttiosuus);
System.out.println("Tavoite " + (prosenttiosuus * 100) + "% maksimista: " + tavoite);
prosenttiosuus += 0.1;
}
Tavoite 50.0% maksimista: 122.48500000000001 Tavoite 60.0% maksimista: 134.98200000000003 Tavoite 70.0% maksimista: 147.479 Tavoite 80.0% maksimista: 159.976 Tavoite 89.99999999999999% maksimista: 172.473 Tavoite 99.99999999999999% maksimista: 184.97000000000003
Huom! Tehtäväpohjassa ei ole testejä. Testaa sovellusta käsin ja palauta sovellus kun se toimii halutusti.
Olio-ohjelmoinnin periaatteet
Olio-ohjelmointiin kuuluu oleellisesti kolme periaatetta: abstrahointi, kapselointi ja perintä. Käsittelemme periaatteet tässä lyhyesti; perintään tutustutaan tarkemmin ohjelmoinnin jatkokurssille.
Abstrahointi
Abstrahoinnin tavoitteena on ongelma-alueen käsitteellistäminen. Ohjelmoija pyrkii nimeämään ongelma-alueen käsitteitä kuvaavat luokat, oliot, metodit ja muuttujat mahdollisimman hyvin, jolloin muut näitä käyttävät ohjelmoijat ymmärtävät mistä kussakin on kyse.
Käytännössä kyse on siis prosessista, missä ongelma-alueesta tunnistetaan ja eristetään oleellisimmat piirteet, joiden perusteella niistä luodaan ohjelmaan toiminnallisuutta. Samalla pyritään tilanteeseen, missä ongelma-alueesta on poimittu vain ne käsitteet, jotka ovat oleellisia käsiteltävän ongelman ratkaisun kannalta.
Otetaan analogia tosielämästä ja puhutaan käsitteestä auto. Jokainen tietää mistä autossa on kyse ja mihin sitä käytetään. Moni pystyisi myös piirtämään auton pyydettäessä. Käsite auto kuitenkin piilottaa paljon pienempiä osia: autossa on esimerkiksi renkaat, runko, moottori, istuimia, ... Kukin näistä käsitteistä on myös oma abstraktionsa. Esimerkiksi rengas piilottaa myös pienempiä osia -- renkaalla on vanne, ulkokumi, sisäkumi, jnejne.
Abstrahoinnista on ohjelmoijalle useita etuja. Se helpottaa asioista puhumista ja sitä kautta niiden käsittelyä. Se helpottaa ohjelmointia -- esimerkiksi käyttämämme apukirjastot kuten Scanner ovat toisten tekemiä valmiita abstraktioita ohjelmointiin liittyvistä tyypillisistä ongelmista. Se myös helpottaa omaa ohjelmointiamme: aivan kuten auto koostuu useammasta pienemmästä osasta, voimme koostaa ohjelmamme useammasta abstraktiosta ja luoda tätä kautta uusia abstraktioita.
Kapselointi
Kapseloinissa on kyse toteutuksen piilottamisesta julkisen rajapinnan taakse. Käytännössä olio-ohjelmoinissa kyse on muuttujien ja metodien konkreettisen toiminnallisuuden piilottamisesta olion "sisään". Olion käyttöön tarvittavat metodit (ml. konstruktorit) -- eli rajapinta -- ovat käyttäjän nähtävissä, mutta käyttäjä ei pääse käsiksi olion sisäiseen toteutukseen.
Tällöin toiset oliot voivat kutsua olion metodeja ilman, että niiden tarvitsee tietää kutsuttavan olion sisäisestä tilasta tai metodien konkreettisesta toteutuksesta. Tällöin kukin olio on myös itsenäinen, eikä niiden sisäinen tila ole riippuvainen toisten olioiden sisäisestä tilasta.
Perintä
Olio-ohjelmoinnissa on mahdollista luoda luokkia, jotka perivät toisen luokan ominaisuuksia (eli oliomuuttujat ja metodit). Tällöin luokasta tehdyt oliot ovat samalla myös perityn luokan ilmentymiä, jolloin oliot voivat esiintyä useampina erilaisina olioina käyttötarpeesta riippuen.
Palaamme perintään ohjelmoinnin jatkokurssilla...
Tässä tehtävässä tehdään luokka YlhaaltaRajoitettuLaskuri
ja sovelletaan sitä kellon tekemiseen.
Rajoitettu laskuri
Tehdään luokka YlhaaltaRajoitettuLaskuri
. Luokan olioilla on seuraava toiminnallisuus:
- Laskurilla on oliomuuttuja, joka muistaa laskurin arvon. Laskurin arvo on luku väliltä 0...yläraja.
- Aluksi laskurin arvo on 0.
- Olion konstruktori määrittää laskurin ylärajan.
- Metodi
seuraava
kasvattaa laskurin arvoa. Mutta jos laskurin arvo ylittää ylärajan, sen arvoksi tulee 0. - Metodi
toString
palauttaa laskurin arvon merkkijonona.
Tehtäväpohjassa on valmiina pääohjelmaa varten tiedosto Paaohjelma
. Aloita tekemällä luokka YlhaaltaRajoitettuLaskuri
vastaavasti kuin Maksukortti-tehtävässä. Näin tehdään myös tulevissa tehtäväsarjoissa.
Luokan rungoksi tulee seuraava:
public class YlhaaltaRajoitettuLaskuri {
private int arvo;
private int ylaraja;
public YlhaaltaRajoitettuLaskuri(int ylarajanAlkuarvo) {
// kirjoita koodia tähän
}
public void seuraava() {
// kirjoita koodia tähän
}
public String toString() {
// kirjoita koodia tähän
}
}
Vihje: et voi palauttaa toStringissä suoraan kokonaislukutyyppisen oliomuuttujan laskuri
arvoa. Kokonaislukumuuttujasta arvo
saa merkkijonomuodon esim. lisäämällä sen eteen tyhjän merkkijonon eli kirjoittamalla "" + arvo
.
Seuraavassa on pääohjelma, joka käyttää laskuria:
public class Paaohjelma {
public static void main(String[] args) {
YlhaaltaRajoitettuLaskuri laskuri = new YlhaaltaRajoitettuLaskuri(4);
System.out.println("arvo alussa: " + laskuri);
int i = 0;
while (i < 10) {
laskuri.seuraava();
System.out.println("arvo: " + laskuri);
i++;
}
}
}
Laskurille asetetaan konstruktorissa ylärajaksi 4, joten laskurin arvo on luku 0:n ja 4:n väliltä. Huomaa, miten metodi seuraava
vie laskurin arvoa eteenpäin, kunnes se pyörähtää 4:n jälkeen 0:aan:
Ohjelman tulostuksen tulisi olla seuraava:
arvo alussa: 0 arvo: 1 arvo: 2 arvo: 3 arvo: 4 arvo: 0 arvo: 1 arvo: 2 arvo: 3 arvo: 4 arvo: 0
Etunolla tulostukseen
Tee toString
-metodista sellainen, että se lisää arvon merkkijonoesitykseen etunollan, jos laskurin arvo on vähemmän kuin 10. Eli jos laskurin arvo on esim. 3, palautetaan merkkijono "03", jos arvo taas on esim. 12, palautetaan normaaliin tapaan merkkijono "12".
Muuta pääohjelma seuraavaan muotoon ja varmista, että tulos on haluttu.
public class Paaohjelma {
public static void main(String[] args) {
YlhaaltaRajoitettuLaskuri laskuri = new YlhaaltaRajoitettuLaskuri(14);
System.out.println("arvo alussa: " + laskuri);
int i = 0;
while (i < 16) {
laskuri.seuraava();
System.out.println("arvo: " + laskuri);
i++;
}
}
}
arvo alussa: 00 arvo: 01 arvo: 02 arvo: 03 arvo: 04 arvo: 05 arvo: 06 arvo: 07 arvo: 08 arvo: 09 arvo: 10 arvo: 11 arvo: 12 arvo: 13 arvo: 14 arvo: 00 arvo: 01
Kello, ensimmäinen versio
Käyttämällä kahta laskuria voimme muodostaa kellon. Tuntimäärä on laskuri, jonka yläraja on 23, ja minuuttimäärä on laskuri jonka yläraja on 59. Kuten kaikki tietävät, kello toimii siten, että aina kun minuuttimäärä pyörähtää nollaan, tuntimäärä kasvaa yhdellä.
Tee ensin laskurille metodi arvo
, joka palauttaa laskurin arvon:
public int arvo() {
// kirjoita koodia tähän
}
Tee sitten kello täydentämällä seuraava pääohjelmarunko (kopioi tämä pääohjelmaksesi sekä täydennä tarvittavilta osin kommenttien ohjaamalla tavalla):
public class Paaohjelma {
public static void main(String[] args) {
YlhaaltaRajoitettuLaskuri minuutit = new YlhaaltaRajoitettuLaskuri(59);
YlhaaltaRajoitettuLaskuri tunnit = new YlhaaltaRajoitettuLaskuri(23);
int i = 0;
while (i < 121) {
System.out.println(tunnit + ":" + minuutit); // tulostetaan nykyinen aika
// minuuttimäärä kasvaa
// jos minuuttimäärä menee nollaan, tuntimäärä kasvaa
i++;
}
}
}
Jos kellosi toimii oikein, sen tulostus näyttää suunnilleen seuraavalta:
00:00 00:01 ... 00:59 01:00 01:01 01:02 ... 01:59 02:00
Kello, toinen versio
Laajenna kelloasi myös sekuntiviisarilla. Tee lisäksi luokalle YlhaaltaRajoitettuLaskuri
metodi asetaArvo
, jolla laskurille pystyy asettamaan halutun arvon -- jos et ole ihan varma mitä tässä pitäisi tehdä, kertaa materiaalista kohta missä puhutaan "settereistä".
Jos laskurille yritetään asettaa kelvoton arvo eli negatiivinen luku tai ylärajaa suurempi luku, ei laskurin arvo muutu.
Tämän metodin avulla voit muuttaa kellon ajan heti ohjelman alussa haluamaksesi.
Voit testata kellon toimintaa seuraavalla ohjelmalla
import java.util.Scanner;
public class Paaohjelma {
public static void main(String[] args) {
Scanner lukija = new Scanner(System.in);
YlhaaltaRajoitettuLaskuri sekunnit = new YlhaaltaRajoitettuLaskuri(59);
YlhaaltaRajoitettuLaskuri minuutit = new YlhaaltaRajoitettuLaskuri(59);
YlhaaltaRajoitettuLaskuri tunnit = new YlhaaltaRajoitettuLaskuri(23);
System.out.print("sekunnit: ");
int sek = // kysy sekuntien alkuarvo käyttäjältä
System.out.print("minuutit: ");
int min = // kysy minuuttien alkuarvo käyttäjältä
System.out.print("tunnit: ");
int tun = // kysy tuntien alkuarvo käyttäjältä
sekunnit.asetaArvo(sek);
minuutit.asetaArvo(min);
tunnit.asetaArvo(tun);
int i = 0;
while (i < 121) {
// lisää edelliseen myös sekuntiviisari
i++;
}
}
}
Kokeile laittaa kellosi alkamaan ajasta 23:59:50 ja varmista, että vuorokauden vaihteessa kello toimii odotetusti!
Bonus-tehtävä: ikuisesti käyvä kello (tehtävää ei palauteta!)
Ennen kuin alat tekemään tätä tehtävää, palauta jo tekemäsi kello!
Muuta pääohjelmasi seuraavaan muotoon:
public class Paaohjelma {
public static void main(String[] args) throws Exception {
YlhaaltaRajoitettuLaskuri sekunnit = new YlhaaltaRajoitettuLaskuri(59);
YlhaaltaRajoitettuLaskuri minuutit = new YlhaaltaRajoitettuLaskuri(59);
YlhaaltaRajoitettuLaskuri tunnit = new YlhaaltaRajoitettuLaskuri(23);
sekunnit.asetaArvo(50);
minuutit.asetaArvo(59);
tunnit.asetaArvo(23);
while (true) {
System.out.println(tunnit + ":" + minuutit + ":" + sekunnit);
Thread.sleep(1000);
// lisää kellon aikaa sekunnilla eteenpäin
}
}
}
Nyt kello käy ikuisesti ja kasvattaa arvoaan sekunnin välein. Sekunnin odotus tapahtuu komennolla Thread.sleep(1000);
, komennon parametri kertoo nukuttavan ajan millisekunteina. Jotta komento toimisi, pitää main:in esittelyriville tehdä pieni lisäys: public static void main(String[] args) throws Exception {
, eli tummennettuna oleva throws Exception
.
Saat ohjelman lopetettua painamalla NetBeans-konsolin (eli sen osan johon kello tulostaa arvonsa) vasemmalla laidalla olevasta punaisesta laatikosta.
Oliot ja viitteet
Oletetaan, että käytössämme on alla oleva luokka.
public class Henkilo {
private String nimi;
private int ika;
private int paino;
private int pituus;
public Henkilo(String nimi) {
this.nimi = nimi;
this.ika = 0;
this.paino = 0;
this.pituus = 0;
}
// muita konstruktoreja ja metodeja
public String getNimi() {
return this.nimi;
}
public int getIka() {
return this.ika;
}
public void vanhene() {
this.ika++;
}
public void setPituus(int uusiPituus) {
this.pituus = uusiPituus;
}
public void setPaino(int uusiPaino) {
this.paino = uusiPaino;
}
public double painoIndeksi() {
double pituusPerSata = this.pituus / 100.0;
return this.paino / (pituusPerSata * pituusPerSata);
}
@Override
public String toString() {
return this.nimi + ", ikä " + this.ika + " vuotta";
}
}
Mitä oikein tapahtuu kun olio luodaan?
Henkilo joan = new Henkilo("Joan Ball");
Konstruktorikutsun new
yhteydessä tapahtuu monta asiaa. Ensin tietokoneen muistista varataan tila oliomuuttujille. Tämän jälkeen oliomuuttujiin asetetaan oletus- tai alkuarvot (esimerkiksi int-tyyppisten muuttujien arvoksi tulee 0). Lopulta suoritetaan konstruktorissa oleva lähdekoodi.
Konstruktorikutsu palauttaa viitteen olioon. Viite on tieto olioon liittyvien tietojen paikasta.
Muuttujan arvoksi asetetaan siis viite, eli tieto olioon liittyvien tietojen paikasta. Yllä oleva kuva paljastaa myös sen, että nimi -- tai tarkemmin merkkijonot -- ovat myös olioita.
Muuttujan arvon asettaminen kopioi viitteen
Lisätään ohjelmaan Henkilo
-tyyppinen muuttuja ball
ja annetaan sille alkuarvoksi joan
. Mitä nyt tapahtuu?
Henkilo joan = new Henkilo("Joan Ball");
System.out.println(joan);
Henkilo ball = joan;
Lause Henkilo ball = joan;
luo uuden henkilömuuttujan, jonka arvoksi kopioidaan muuttujan joan
arvo. Tämä saa aikaan sen, että ball
viittaa samaan olioon kuin joan
.
Tarkastellan samaa esimerkkiä hieman pidemmälle.
Henkilo joan = new Henkilo("Joan Ball");
System.out.println(joan);
Henkilo ball = joan;
ball.vanhene();
ball.vanhene();
System.out.println(joan);
Joan Ball, ikä 0 vuotta Joan Ball, ikä 2 vuotta
Joan Ball -- eli henkilöolio, johon viite muuttujassa joan
osoittaa -- on alussa 0-vuotias. Tämän jälkeen muuttujaan ball
asetetaan (eli kopioidaan) muuttujan joan
arvo. Henkilöoliota ball
vanhennetaan kaksi vuotta ja sen seurauksena Joan Ball vanhenee!
Olion sisäinen tila ei kopioidu muuttujan arvoa asetettaessa. Lauseessa Henkilo ball = joan;
ei siis luoda henkilöä -- muuttujan ball arvoksi asetetaan kopio muuttujan joan arvosta, eli viite olioon.
Seuraavassa esimerkkiä on jatkettu siten, että joan
-muuttujaa varten luodaan uusi olio, jonka viite asetetaan muuttujan arvoksi. Muuttuja ball
viittaa yhä aiemmin luotuun olioon.
Henkilo joan = new Henkilo("Joan Ball");
System.out.println(joan);
Henkilo ball = joan;
ball.vanhene();
ball.vanhene();
System.out.println(joan);
joan = new Henkilo("Joan B.");
System.out.println(joan);
Tulostuu:
Joan Ball, ikä 0 vuotta Joan Ball, ikä 2 vuotta Joan B., ikä 0 vuotta
Muuttujassa joan
on siis alussa viite yhteen olioon, mutta lopussa sen arvoksi on kopioitu toisen muuttujan viite. Seuraavassa kuva tilanteesta viimeisen koodirivin jälkeen.
Muuttujan arvo null
Jatketaan vielä esimerkkiä asettamalla muuttujan ball
arvoksi null
, eli viite "ei mihinkään".
Henkilo joan = new Henkilo("Joan Ball");
System.out.println(joan);
Henkilo ball = joan;
ball.vanhene();
ball.vanhene();
System.out.println(joan);
joan = new Henkilo("Joan B.");
System.out.println(joan);
ball = null;
Viimeisen rivin jälkeen ohjelman tila on seuraavanlainen.
Olioon, jonka nimi on Joan Ball, ei enää viittaa kukaan. Oliosta on siis tullut "roska". Java-ohjelmointikielessä ohjelmoijan ei tarvitse huolehtia ohjelman käyttämästä muistista. Javan automaattinen roskienkerääjä käy siivoamassa roskaksi joutuneet oliot aika ajoin. Jos automaattista roskien keruuta ei tapahtuisi, jäisivät roskaksi joutuneet oliot varaamaan muistia ohjelman suorituksen loppuun asti.
Kokeillaan vielä mitä käy kun yritämme tulostaa muuttujaa, jonka arvona on viite "ei mihinkään" eli null
.
Henkilo joan = new Henkilo("Joan Ball");
System.out.println(joan);
Henkilo ball = joan;
ball.vanhene();
ball.vanhene();
System.out.println(joan);
joan = new Henkilo("Joan B.");
System.out.println(joan);
ball = null;
System.out.println(ball);
Joan Ball, ikä 0 vuotta Joan Ball, ikä 2 vuotta Joan B., ikä 0 vuotta null
Viitteen null
tulostus tulostaa "null". Entäpä jos yritämme kutsua ei mihinkään viittaavan olion metodia, esimerkiksi metodia vanhene
:
Henkilo joan = new Henkilo("Joan Ball");
System.out.println(joan);
joan = null;
joan.vanhene();
Tulos:
Joan Ball, ikä 0 vuotta Exception in thread "main" java.lang.NullPointerException at Main.main(Main.java:(rivi)) Java Result: 1
Käy huonosti. Tämä on ehkä ensimmäinen kerta kun näet tekstin NullPointerException. Ohjelmassa tapahtuu virhe, joka liittyy siihen, että olemme kutsuneet ei mihinkään viittaavan muuttujan metodia.
Voimme luvata, että tulet näkemään edellisen virheen vielä uudelleen. Tällöin ensimmäinen askel on etsiä muuttujia, joiden arvona saattaisi olla null
. Virheilmoitus on onneksi myös hyödyllinen: se kertoo millä rivillä virhe tapahtuu. Kokeile vaikka itse!
Toteuta ohjelma, jonka suorittaminen aiheuttaa virheen NullPointerException. Virheen tulee tapahtua heti kun ohjelma suoritetaan -- älä siis esimerkiksi lue käyttäjältä syötettä.
Olio oliomuuttujana
Oliot voivat sisältää viitteitä olioihin.
Jatketaan Henkilo
-luokan parissa ja lisätään henkilölle syntymäpäivä. Syntymäpäivä on luonnollista esittää Paivays
-olion avulla:
public class Paivays {
private int paiva;
private int kuukausi;
private int vuosi;
public Paivays(int paiva, int kuukausi, int vuosi) {
this.paiva = paiva;
this.kuukausi = kuukausi;
this.vuosi = vuosi;
}
public int getPaiva() {
return this.paiva;
}
public int getKuukausi() {
return this.kuukausi;
}
public int getVuosi() {
return this.vuosi;
}
@Override
public String toString() {
return this.paiva + "." + this.kuukausi + "." + this.vuosi;
}
}
Koska tiedämme syntymäpäivän, henkilön ikää ei enää tarvitse säilöä. Se on pääteltävissä syntymäpäivästä.
public class Henkilo {
private String nimi;
private Paivays syntymaPaiva;
private int paino = 0;
private int pituus = 0;
// ...
Tehdään henkilölle uusi konstruktori, joka mahdollistaa syntymäpäivän asettamisen:
public Henkilo(String nimi, int paiva, int kuukausi, int vuosi) {
this.nimi = nimi;
this.syntymaPaiva = new Paivays(paiva, kuukausi, vuosi);
this.paino = 0;
this.pituus = 0;
}
Konstruktorin parametrina annetaan erikseen päiväyksen osat (päivä, kuukausi, vuosi), niistä luodaan päiväysolio, ja lopulta päiväysolion viite kopioidaan oliomuuttujan syntymaPaiva
arvoksi.
Muokataan Henkilo-luokassa olevaa toString
-metodia siten, että metodi palauttaa iän sijaan syntymäpäivän:
public String toString() {
return this.nimi + ", syntynyt " + this.syntymaPaiva;
}
Kokeillaan miten uusittu Henkilöluokka toimii.
Henkilo muhammad = new Henkilo("Muhammad ibn Musa al-Khwarizmi", 1, 1, 780);
Henkilo pascal = new Henkilo("Blaise Pascal", 19, 6, 1623);
System.out.println(muhammad);
System.out.println(pascal);
Muhammad ibn Musa al-Khwarizmi, syntynyt 1.1.870 Blaise Pascal, syntynyt 19.6.1623
Henkilöoliolla on nyt oliomuuttujat nimi
ja syntymaPaiva
. Muuttuja nimi
on merkkijono, joka sekin on siis olio, ja muuttuja syntymaPaiva
on Päiväysolio.
Molemmat muuttujat sisältävät arvon olioon. Henkilöolio sisältää siis kaksi viitettä.
Pääohjelmalla on nyt siis langan päässä kaksi Henkilö-olioa. Henkilöllä on nimi ja syntymäpäivä. Koska molemmat ovat olioita, ovat ne henkilöllä langan päässä.
Syntymäpäivä vaikuttaa hyvältä laajennukselta Henkilö-luokkaan. Totesimme aiemmin, että oliomuuttuja ika
voidaan laskea syntymäpäivästä, joten siitä hankkiuduttiin eroon.
Javassa nykyinen päivä selviää seuraavasti:
import java.time.LocalDate;
// ...
LocalDate nyt = LocalDate.now();
int vuosi = nyt.getYear();
int kuukausi = nyt.getMonthValue();
int paiva = nyt.getDayOfMonth();
System.out.println("tänään on " + paiva + "." + kuukausi + "." + vuosi);
// ...
Tehtäväpohjassa tulee mukana edellä nähdyt luokat Henkilo ja Paivays. Täydennä luokan Henkilo metodia public int ikaVuosina()
siten, että se laskee ja palauttaa henkilön tämän hetkisen iän vuosina.
Voit olettaa, että jokaisessa vuodessa on tasan 360 päivää.
Vinkki! Näppärä lähestymistapa on laskea päivien summa vuosien, kuukausien ja päivien perusteella. Erottamalla "nykypäivää" vastaavan päivien summan syntymäpäivän päivien summasta saat elettyjen päivien määrän. Eletyt päivät saa muunnettua takaisin vuosiksi jakolaskulla.
Teimme aiemmin luokan YlhaaltaRajoitettuLaskuri
ja rakennettiin laskurien avulla pääohjelmaan kello. Tehdään tässä tehtävässä kellostakin oma olio -- kello sisältää kolme viisaria, jotka jokainen esitetään ylhäältä rajoitetun laskurin avulla. Luokan kello runko näyttää seuraavalta:
public class Kello {
private YlhaaltaRajoitettuLaskuri tunnit;
private YlhaaltaRajoitettuLaskuri minuutit;
private YlhaaltaRajoitettuLaskuri sekunnit;
public Kello(int tunnitAlussa, int minuutitAlussa, int sekunnitAlussa) {
// laskurit tunneille, minuuteille ja sekunneille;
// laskurien arvot tulee asettaa parametreina saatuun aikaan
}
public void etene() {
// kello etenee sekunnilla
}
public String toString() {
// palauttaa kellon merkkijonoesityksen
}
}
Luokkaan YlhaaltaRajoitettuLaskuri on kopioitu eräs ratkaisu viime osan tehtävään. Toteuta luokan Kello
konstruktori ja puuttuvat metodit kolmea ylhäältä rajoitettua laskuria hyödyntäen.
Voit testata kelloasi seuraavalla pääohjelmalla:
public class Main {
public static void main(String[] args) {
Kello kello = new Kello(23, 59, 50);
int i = 0;
while (i < 20) {
System.out.println(kello);
kello.etene();
i++;
}
}
}
Tulostuksen tulisi edetä seuraavasti:
23:59:50 23:59:51 23:59:52 23:59:53 23:59:54 23:59:55 23:59:56 23:59:57 23:59:58 23:59:59 00:00:00 00:00:01 ...
Ensimmäiset korkeakulttuurit syntyivät (laajemman) lähi-idän alueelle, mikä nopeutti siellä myös henkistä kasvua. Lähi-idässä oltiin merkittävästi muuta maailmaa edellä muunmuassa matematiikassa ja tähtitieteessä -- esimerkiksi Euroopassa 1500-luvulla tapahtunut murros tähtitieteessä (maa kiertääkin aurinkoa eikä toisin päin), tapahtui laajemman lähi-idän vaikutuspiirissä olleessa kreikassa jo noin 300 vuotta ennen ajanlaskumme alkua.
Nimi al-Khwarizmi viittaa oikeastaan alueeseen, tai hieman laajemmin, etuosa al- viittaa usein henkilön synty- tai kotipaikkaan. Muhammad ibn Musa al-Khwarizmi -- tai hänen isänsä tai esi-isänsä -- tulivat keskiaasiasta alueelta, joka tunnetaan nykyään suomen kielessä nimellä Harezm. Nykyään käytetty termi algoritmi on siis sekä hatunnosto Muhammad ibn Musa al-Khwarizmille, että laajemmin hänen syntyperälleen.
Merkittävä osa al-Khwarizmin työstä tapahtui Baghdadissa sijaitsevassa Viisauden talossa, joka paikallisen hallinnon tukemana keräsi tiedemiehiä eri puolilta maailmaa yhteen. Tavoitteena oli "pienimuotoisesti" kerätä kaikki maailman tieto yhteen paikkaan ja kääntää se arabian kielelle, jota sitten jaettiin eteenpäin. Tätä kautta tietoa valui myös eurooppaan: esimerkiksi al-Khwarizmin kirja intialaisilla numeroilla laskemisesta (latinaksi "Algoritmi de numero Indorum") toi arabialaisten numeroiden käytön eurooppaan.
Tämä terminologia näkyy yhä esimerkikiksi espanjan kielessä. Espanjankielinen sana guarismo -- eli suomeksi luku -- tulee ilmeisesti juurikin al-Khwarizmin nimestä.
Vaikka Muhammad ibn Musa al-Khwarizmi kytketään nykyään -- ainakin tietojenkäsittelytieteilijöiden parissa -- ensisijaisesti algoritmeihin, on hän ennen kaikkea vaikuttanut merkittävästi algebran kehitykseen. Hänen työnsä tuolla alueella kontribuoi mm. ensimmäisen ja toisen asteen yhtälöiden ratkaisemiseen. Työn keskiössä olivat konkreettiset esimerkit sekä selkokieliset askeleittaiset ratkaisut -- numeroita tuossa työssä ei esiintynyt.
Olio metodin parametrina
Olemme nähneet että metodien parametrina voi olla esimerkiksi int
tai String
tyyppisiä muuttujia. Kuten arvata saattaa, metodin parametriksi voi määritellä minkä tahansa tyyppisen olion. Demonstroidaan tätä esimerkillä.
Painonvartijoihin hyväksytään jäseniksi henkilöitä, joiden painoindeksi ylittää annetun rajan. Kaikissa painonvartijayhdistyksissä raja ei ole sama. Tehdään painonvartijayhdistystä vastaava luokka. Olioa luotaessa konstruktorille annetaan parametriksi pienin painoindeksi, jolla yhdistyksen jäseneksi pääsee.
public class PainonvartijaYhdistys {
private double alinPainoindeksi;
public PainonvartijaYhdistys(double indeksiRaja) {
this.alinPainoindeksi = indeksiRaja;
}
}
Tehdään seuraavaksi metodi, jonka avulla voidaan tarkastaa hyväksytäänkö tietty henkilö yhdistyksen jäseneksi, eli onko henkilön painoindeksi tarpeeksi suuri. Metodi palauttaa true
jos parametrina annettu henkilö hyväksytään, false
jos ei.
public class PainonvartijaYhdistys {
private double alinPainoindeksi;
public PainonvartijaYhdistys(double indeksiRaja) {
this.alinPainoindeksi = indeksiRaja;
}
public boolean hyvaksytaanJaseneksi(Henkilo henkilo) {
if (henkilo.painoIndeksi() < this.alinPainoindeksi) {
return false;
}
return true;
}
}
Painonvartijayhdistys-olion metodille hyvaksytaanJaseneksi
annetaan siis parametrina Henkilo
-olio. Kuten aiemmin, muuttujan arvo -- eli tässä viite -- kopioituu metodin käyttöön. Metodissa käsitellään kopioitua viitettä ja kutsutaan parametrina saadun henkilön metodia painoIndeksi
.
Seuraavassa testipääohjelma jossa painonvartijayhdistyksen metodille annetaan ensin parametriksi henkilöolio matti
ja sen jälkeen henkilöolio juhana
:
Henkilo matti = new Henkilo("Matti");
matti.setPaino(86);
matti.setPituus(180);
Henkilo juhana = new Henkilo("Juhana");
juhana.setPaino(64);
juhana.setPituus(172);
PainonvartijaYhdistys kumpulanPaino = new PainonvartijaYhdistys(25);
if (kumpulanPaino.hyvaksytaanJaseneksi(matti)) {
System.out.println(matti.getNimi() + " pääsee jäseneksi");
} else {
System.out.println(matti.getNimi() + " ei pääse jäseneksi");
}
if (kumpulanPaino.hyvaksytaanJaseneksi(juhana)) {
System.out.println(juhana.getNimi() + " pääsee jäseneksi");
} else {
System.out.println(juhana.getNimi() + " ei pääse jäseneksi");
}
Ohjelma tulostaa:
Matti pääsee jäseneksi Juhana ei pääse jäseneksi
Ohjelmointiympäristöt osaavat auttaa ohjelmoijaa. Jos luokalle on määriteltynä oliomuuttujat, onnistuu konstruktorien, getterien ja setterien generointi automaattisesti.
Mene luokan koodilohkon sisäpuolelle mutta kaikkien metodien ulkopuolelle ja paina yhtä aikaa ctrl ja välilyönti. Jos luokallasi on esim. oliomuuttuja saldo
, tarjoaa NetBeans mahdollisuuden generoida oliomuuttujalle getteri- ja setterimetodit sekä konstruktorin joka asettaa oliomuuttujalle alkuarvon.
Joillain Linux-koneilla, kuten Kumpulassa olevilla koneilla, tämä saadaan aikaan painamalla yhtä aikaa ctrl, alt ja välilyönti.
Tehtäväpohjassasi on valmiina jo tutuksi tullut luokka Henkilo
sekä runko luokalle Kasvatuslaitos
. Kasvatuslaitosoliot käsittelevät ihmisiä eri tavalla, esim. punnitsevat ja syöttävät ihmisiä. Rakennamme tässä tehtävässä kasvatuslaitoksen. Luokan Henkilö koodiin ei tehtävässä ole tarkoitus koskea!
Henkilöiden punnitseminen
Kasvatuslaitoksen luokkarungossa on valmiina runko metodille punnitse
:
public class Kasvatuslaitos {
public int punnitse(Henkilo henkilo) {
// palautetaan parametrina annetun henkilön paino
return -1;
}
}
Metodi saa parametrina henkilön ja metodin on tarkoitus palauttaa kutsujalleen parametrina olevan henkilön paino. Paino selviää kutsumalla parametrina olevan henkilön henkilo
sopivaa metodia. Eli täydennä metodin koodi!
Seuraavassa on pääohjelma jossa kasvatuslaitos punnitsee kaksi henkilöä:
public static void main(String[] args) {
// esimerkkipääohjelma tehtävän ensimmäiseen kohtaan
Kasvatuslaitos haaganNeuvola = new Kasvatuslaitos();
Henkilo eero = new Henkilo("Eero", 1, 110, 7);
Henkilo pekka = new Henkilo("Pekka", 33, 176, 85);
System.out.println(eero.getNimi() + " paino: " + haaganNeuvola.punnitse(eero) + " kiloa");
System.out.println(pekka.getNimi() + " paino: " + haaganNeuvola.punnitse(pekka) + " kiloa");
}
Tulostuksen pitäisi olla seuraava:
Eero paino: 7 kiloa Pekka paino: 85 kiloa
Syöttäminen
Parametrina olevan olion tilaa on mahdollista muuttaa. Tee kasvatuslaitokselle metodi public void syota(Henkilo henkilo)
joka kasvattaa parametrina olevan henkilön painoa yhdellä.
Seuraavassa esimerkki, jossa henkilöt ensin punnitaan, ja tämän jälkeen neuvolassa syötetään eeroa kolme kertaa. Tämän jälkeen henkilöt taas punnitaan:
public static void main(String[] args) {
Kasvatuslaitos haaganNeuvola = new Kasvatuslaitos();
Henkilo eero = new Henkilo("Eero", 1, 110, 7);
Henkilo pekka = new Henkilo("Pekka", 33, 176, 85);
System.out.println(eero.getNimi() + " paino: " + haaganNeuvola.punnitse(eero) + " kiloa");
System.out.println(pekka.getNimi() + " paino: " + haaganNeuvola.punnitse(pekka) + " kiloa");
haaganNeuvola.syota(eero);
haaganNeuvola.syota(eero);
haaganNeuvola.syota(eero);
System.out.println("");
System.out.println(eero.getNimi() + " paino: " + haaganNeuvola.punnitse(eero) + " kiloa");
System.out.println(pekka.getNimi() + " paino: " + haaganNeuvola.punnitse(pekka) + " kiloa");
}
Tulostuksen pitäisi paljastaa että Eeron paino on noussut kolmella:
Eero paino: 7 kiloa Pekka paino: 85 kiloa Eero paino: 10 kiloa Pekka paino: 85 kiloa
Punnitusten laskeminen
Tee kasvatuslaitokselle metodi public int punnitukset()
joka kertoo kuinka monta punnitusta kasvatuslaitos on ylipäätään tehnyt. Testipääohjelma:
public static void main(String[] args) {
// esimerkkipääohjelma tehtävän ensimmäiseen kohtaan
Kasvatuslaitos haaganNeuvola = new Kasvatuslaitos();
Henkilo eero = new Henkilo("Eero", 1, 110, 7);
Henkilo pekka = new Henkilo("Pekka", 33, 176, 85);
System.out.println("punnituksia tehty " + haaganNeuvola.punnitukset());
haaganNeuvola.punnitse(eero);
haaganNeuvola.punnitse(pekka);
System.out.println("punnituksia tehty " + haaganNeuvola.punnitukset());
haaganNeuvola.punnitse(eero);
haaganNeuvola.punnitse(eero);
haaganNeuvola.punnitse(eero);
haaganNeuvola.punnitse(eero);
System.out.println("punnituksia tehty " + haaganNeuvola.punnitukset());
}
Tulostuu:
punnituksia tehty 0 punnituksia tehty 2 punnituksia tehty 6
"Tyhmä" Maksukortti
Teimme viime viikolla luokan Maksukortti. Kortilla oli metodit edullisesti ja maukkaasti syömistä sekä rahan lataamista varten.
Viime viikon tyylillä tehdyssä Maksukortti-luokassa oli kuitenkin ongelma. Kortti tiesi lounaiden hinnan ja osasi sen ansiosta vähentää saldoa oikean määrän. Entä kun hinnat nousevat? Tai jos myyntivalikoimaan tulee uusia tuotteita? Hintojen muuttaminen tarkoittaisi, että kaikki jo käytössä olevat kortit pitäisi korvata uusilla, uudet hinnat tuntevilla korteilla.
Parempi ratkaisu on tehdä kortit "tyhmiksi", hinnoista ja myytävistä tuotteista tietämättömiksi pelkän saldon säilyttäjiksi. Kaikki äly kannattaakin laittaa erillisiin olioihin, kassapäätteisiin.
Toteutetaan ensin Maksukortista "tyhmä" versio. Kortilla on ainoastaan metodit saldon kysymiseen, rahan lataamiseen ja rahan ottamiseen. Täydennä alla (ja tehtäväpohjassa) olevaan luokkaan metodin public boolean otaRahaa(double maara)
ohjeen mukaan:
public class Maksukortti {
private double saldo;
public Maksukortti(double saldo) {
this.saldo = saldo;
}
public double saldo() {
return this.saldo;
}
public void lataaRahaa(double lisays) {
this.saldo += lisays;
}
public boolean otaRahaa(double maara) {
// toteuta metodi siten että se ottaa kortilta rahaa vain jos saldo on vähintään maara
// onnistuessaan metodi palauttaa true ja muuten false
}
}
Testipääohjelma:
public class Paaohjelma {
public static void main(String[] args) {
Maksukortti pekanKortti = new Maksukortti(10);
System.out.println("rahaa " + pekanKortti.saldo());
boolean onnistuiko = pekanKortti.otaRahaa(8);
System.out.println("onnistuiko otto: " + onnistuiko);
System.out.println("rahaa " + pekanKortti.saldo());
onnistuiko = pekanKortti.otaRahaa(4);
System.out.println("onnistuiko otto: " + onnistuiko);
System.out.println("rahaa " + pekanKortti.saldo());
}
}
Tulostuksen kuuluisi olla seuraavanlainen
rahaa 10.0 onnistuiko otto: true rahaa 2.0 onnistuiko otto: false rahaa 2.0
Kassapääte ja käteiskauppa
Unicafessa asioidessa asiakas maksaa joko käteisellä tai maksukortilla. Myyjä käyttää kassapäätettä kortin velottamiseen ja käteismaksujen hoitamiseen. Tehdään ensin kassapäätteestä käteismaksuihin sopiva versio.
Kassapäätteen runko. Metodien kommentit kertovat halutun toiminnallisuuden:
public class Kassapaate {
private double rahaa; // kassassa olevan käteisen määrä
private int edulliset; // myytyjen edullisten lounaiden määrä
private int maukkaat; // myytyjen maukkaiden lounaiden määrä
public Kassapaate() {
// kassassa on aluksi 1000 euroa rahaa
}
public double syoEdullisesti(double maksu) {
// edullinen lounas maksaa 2.50 euroa.
// kasvatetaan kassan rahamäärää edullisen lounaan hinnalla ja palautetaan vaihtorahat
// jos parametrina annettu maksu ei ole riittävän suuri, ei lounasta myydä ja metodi palauttaa koko summan
}
public double syoMaukkaasti(double maksu) {
// maukas lounas maksaa 4.30 euroa.
// kasvatetaan kassan rahamäärää maukkaan lounaan hinnalla ja palautetaan vaihtorahat
// jos parametrina annettu maksu ei ole riittävän suuri, ei lounasta myydä ja metodi palauttaa koko summan
}
public String toString() {
return "kassassa rahaa " + rahaa + " edullisia lounaita myyty " + edulliset + " maukkaita lounaita myyty " + maukkaat;
}
}
Kassapäätteessä on aluksi rahaa 1000 euroa. Toteuta yllä olevan rungon metodit ohjeen ja alla olevan pääohjelman esimerkkitulosteen mukaan toimiviksi.
public class Paaohjelma {
public static void main(String[] args) {
Kassapaate unicafeExactum = new Kassapaate();
double vaihtorahaa = unicafeExactum.syoEdullisesti(10);
System.out.println("vaihtorahaa jäi " + vaihtorahaa);
vaihtorahaa = unicafeExactum.syoEdullisesti(5);
System.out.println("vaihtorahaa jäi " + vaihtorahaa);
vaihtorahaa = unicafeExactum.syoMaukkaasti(4.3);
System.out.println("vaihtorahaa jäi " + vaihtorahaa);
System.out.println(unicafeExactum);
}
}
vaihtorahaa jäi 7.5 vaihtorahaa jäi 2.5 vaihtorahaa jäi 0.0 kassassa rahaa 1009.3 edullisia lounaita myyty 2 maukkaita lounaita myyty 1
Kortilla maksaminen
Laajennetaan kassapäätettä siten että myös kortilla voi maksaa. Teemme kassapäätteelle siis metodit joiden parametrina kassapääte saa maksukortin jolta se vähentää valitun lounaan hinnan. Seuraavassa uusien metodien rungot ja ohje niiden toteuttamiseksi:
public class Kassapaate {
// ...
public boolean syoEdullisesti(Maksukortti kortti) {
// edullinen lounas maksaa 2.50 euroa.
// jos kortilla on tarpeeksi rahaa, vähennetään hinta kortilta ja palautetaan true
// muuten palautetaan false
}
public boolean syoMaukkaasti(Maksukortti kortti) {
// maukas lounas maksaa 4.30 euroa.
// jos kortilla on tarpeeksi rahaa, vähennetään hinta kortilta ja palautetaan true
// muuten palautetaan false
}
// ...
}
Huom: kortilla maksaminen ei lisää kassapäätteessä olevan käteisen määrää.
Seuraavassa testipääohjelma ja haluttu tulostus:
public class Paaohjelma {
public static void main(String[] args) {
Kassapaate unicafeExactum = new Kassapaate();
double vaihtorahaa = unicafeExactum.syoEdullisesti(10);
System.out.println("vaihtorahaa jäi " + vaihtorahaa);
Maksukortti antinKortti = new Maksukortti(7);
boolean onnistuiko = unicafeExactum.syoMaukkaasti(antinKortti);
System.out.println("riittikö raha: " + onnistuiko);
onnistuiko = unicafeExactum.syoMaukkaasti(antinKortti);
System.out.println("riittikö raha: " + onnistuiko);
onnistuiko = unicafeExactum.syoEdullisesti(antinKortti);
System.out.println("riittikö raha: " + onnistuiko);
System.out.println(unicafeExactum);
}
}
vaihtorahaa jäi 7.5 riittikö raha: true riittikö raha: false riittikö raha: true kassassa rahaa 1002.5 edullisia lounaita myyty 2 maukkaita lounaita myyty 1
Rahan lataaminen
Lisätään vielä kassapäätteelle metodi jonka avulla kortille voidaan ladata lisää rahaa. Muista, että rahan lataamisen yhteydessä ladattava summa viedään kassapäätteeseen. Metodin runko:
public void lataaRahaaKortille(Maksukortti kortti, double summa) {
// ...
}
Testipääohjelma ja esimerkkisyöte:
public class Paaohjelma {
public static void main(String[] args) {
Kassapaate unicafeExactum = new Kassapaate();
System.out.println(unicafeExactum);
Maksukortti antinKortti = new Maksukortti(2);
System.out.println("kortilla rahaa " + antinKortti.saldo() + " euroa");
boolean onnistuiko = unicafeExactum.syoMaukkaasti(antinKortti);
System.out.println("riittikö raha: " + onnistuiko);
unicafeExactum.lataaRahaaKortille(antinKortti, 100);
onnistuiko = unicafeExactum.syoMaukkaasti(antinKortti);
System.out.println("riittikö raha: " + onnistuiko);
System.out.println("kortilla rahaa " + antinKortti.saldo() + " euroa");
System.out.println(unicafeExactum);
}
}
kassassa rahaa 1000.0 edullisia lounaita myyty 0 maukkaita lounaita myyty 0 kortilla rahaa 2.0 euroa riittikö raha: false riittikö raha: true kortilla rahaa 97.7 euroa kassassa rahaa 1100.0 edullisia lounaita myyty 0 maukkaita lounaita myyty 1
Samantyyppinen olio metodin parametrina
Jatkamme edelleen luokan Henkilo
parissa. Kuten muistamme, henkilöt tietävät syntymäpäivänsä:
public class Henkilo {
private String nimi;
private Paivays syntymaPaiva;
private int pituus;
private int paino;
// ...
}
Haluamme vertailla kahden henkilön ikää. Vertailu voidaan hoitaa usealla tavalla. Voisimme käyttää esimerkiksi aiemmassa tehtävässä toteutettua metodia ikaVuosina()
, jolloin kahden henkilön iän vertailu tapauhtuisi tällöin seuraavasti:
Henkilo muhammad = new Henkilo("Muhammad ibn Musa al-Khwarizmi", 1, 1, 780);
Henkilo pascal = new Henkilo("Blaise Pascal", 19, 6, 1623);
if (muhammad.ikaVuosina() > pascal.ikaVuosina()) {
System.out.println(muhammad.getNimi() + " on vanhempi kuin " + pascal.getNimi());
}
Harjoittelemme nyt hieman "oliohenkisemmän" tavan kahden henkilön ikävertailun tekemiseen.
Teemme Henkilöluokalle metodin boolean vanhempiKuin(Henkilo verrattava)
jonka avulla tiettyä henkilö-olioa voi verrata parametrina annettuun henkilöön iän perusteella.
Metodia on tarkoitus käyttää seuraavaan tyyliin:
Henkilo muhammad = new Henkilo("Muhammad ibn Musa al-Khwarizmi", 1, 1, 780);
Henkilo pascal = new Henkilo("Blaise Pascal", 19, 6, 1623);
if (muhammad.vanhempiKuin(pascal)) { // sama kun muhammad.vanhempiKuin(pascal)==true
System.out.println(muhammad.getNimi() + " on vanhempi kuin " + pascal.getNimi());
} else {
System.out.println(muhammad.getNimi() + " ei ole vanhempi kuin " + pascal.getNimi());
}
Tässä siis kysytään onko al-Khwarizmi Pascalia vanhempi "jos A on vanhempi kuin B". Metodi vanhempiKuin
palauttaa arvon true
jos olio jonka kohdalla metodia kutsutaan (olio.vanhempiKuin(parametrinaAnnettavaOlio)
) on vanhempi kuin parametrina annettava olio, ja false
muuten.
Käytännössä yllä kutsutaan "Muhammad ibn Musa al-Khwarizmia" vastaavan olion, johon muuttuja muhammad
viittaa, metodia vanhempiKuin
, jolle annetaan parametriksi "Blaise Pascal" vastaavan olion viite pascal
.
Ohjelma tulostaa:
Muhammad ibn Musa al-Khwarizmi on vanhempi kuin Blaise Pascal
Metodille vanhempiKuin
annetaan parametrina henkilöolio. Tarkemmin sanottuna metodin parametriksi määriteltyyn muuttujaan kopioituu parametrina annettavan muuttujan sisältämä arvo, eli viite olioon.
Metodin toteutus näyttää seuraavalta. Huomaa, että metodi voi palauttaa arvon useammasta kohtaa -- alla vertailu on pilkottu useampaan osaan:
public class Henkilo {
// ...
public boolean vanhempiKuin(Henkilo verrattava) {
// toteutus
if (this.getSyntymaPaiva().getVuosi() < verrattava.getSyntymaPaiva().getVuosi()) {
return true;
}
if (this.getSyntymaPaiva().getVuosi() == verrattava.getSyntymaPaiva().getVuosi()
&& this.getSyntymaPaiva().getKuukausi() < verrattava.getSyntymaPaiva().getKuukausi()) {
return true;
}
if (this.getSyntymaPaiva().getVuosi() == verrattava.getSyntymaPaiva().getVuosi()
&& this.getSyntymaPaiva().getKuukausi() == verrattava.getSyntymaPaiva().getKuukausi()
&& this.getSyntymaPaiva().getPaiva() < verrattava.getSyntymaPaiva().getPaiva()) {
return true;
}
return false;
}
}
Mietitään hieman olio-ohjelmoinnin periatteiden abstrahointia. Abstrahoinnin ajatuksena on käsitteellistää ohjelmakoodia siten, että kullakin käsitteellä on omat selkeät vastuunsa. Kun pohdimme yllä esitettyä ratkaisua, huomaamme, että päivämäärien vertailutoiminnallisuus kuuluisi mielummin luokkaan Paivays
luokan Henkilo
-sijaan.
Luodaan luokalle Paivays
metodi public boolean aiemmin(Paivays verrattava)
. Metodi palauttaa arvon true
, jos metodille parametrina annettu päiväys on kyseisen olion päiväyksen jälkeen.
public class Paivays {
private int paiva;
private int kuukausi;
private int vuosi;
public Paivays(int paiva, int kuukausi, int vuosi) {
this.paiva = paiva;
this.kuukausi = kuukausi;
this.vuosi = vuosi;
}
public String toString() {
return this.paiva + "." + this.kuukausi + "." + this.vuosi;
}
// metodilla tarkistetaan onko tämä päiväysolio (this
) ennen
// parametrina annettavaa päiväysoliota (verrattava
)
public boolean aiemmin(Paivays verrattava) {
// ensin verrataan vuosia
if (this.vuosi < verrattava.vuosi) {
return true;
}
// jos vuodet ovat samat, verrataan kuukausia
if (this.vuosi == verrattava.vuosi && this.kuukausi < verrattava.kuukausi) {
return true;
}
// vuodet ja kuukaudet samoja, verrataan päivää
if (this.vuosi == verrattava.vuosi && this.kuukausi == verrattava.kuukausi &&
this.paiva < verrattava.paiva) {
return true;
}
return false;
}
}
Vaikka oliomuuttujat vuosi
, kuukausi
ja paiva
ovat olion kapseloimia (private
) oliomuuttujia, pystymme lukemaan niiden arvon kirjoittamalla verrattava.muuttujanNimi
. Tämä johtuu siitä, että private
-muuttujat ovat luettavissa kaikissa metodeissa, jotka kyseinen luokka sisältää. Huomaa, että syntaksi (kirjoitusasu) vastaa tässä jonkin olion metodin kutsumista. Toisin kuin metodia kutsuttaessa, viittaamme olion kenttään, jolloin metodikutsun osoittavia sulkeita ei kirjoiteta.
Metodin käyttöesimerkki:
public static void main(String[] args) {
Paivays p1 = new Paivays(14, 2, 2011);
Paivays p2 = new Paivays(21, 2, 2011);
Paivays p3 = new Paivays(1, 3, 2011);
Paivays p4 = new Paivays(31, 12, 2010);
System.out.println(p1 + " aiemmin kuin " + p2 + ": " + p1.aiemmin(p2));
System.out.println(p2 + " aiemmin kuin " + p1 + ": " + p2.aiemmin(p1));
System.out.println(p2 + " aiemmin kuin " + p3 + ": " + p2.aiemmin(p3));
System.out.println(p3 + " aiemmin kuin " + p2 + ": " + p3.aiemmin(p2));
System.out.println(p4 + " aiemmin kuin " + p1 + ": " + p4.aiemmin(p1));
System.out.println(p1 + " aiemmin kuin " + p4 + ": " + p1.aiemmin(p4));
}
14.2.2011 aiemmin kuin 21.2.2011: true 21.2.2011 aiemmin kuin 14.2.2011: false 21.2.2011 aiemmin kuin 1.3.2011: true 1.3.2011 aiemmin kuin 21.2.2011: false 31.12.2010 aiemmin kuin 14.2.2011: true 14.2.2011 aiemmin kuin 31.12.2010: false
Muunnetaan vielä henkilön metodia vanhempiKuin siten, että hyödynnämme jatkossa päivämäärän tarjoamaa vertailutoiminnallisuutta.
public class Henkilo {
// ...
public boolean vanhempiKuin(Henkilo verrattava) {
if (this.syntymaPaiva.aiemmin(verrattava.getSyntymaPaiva())) {
return true;
}
return false;
}
}
Nyt päivämäärän konkreettinen vertailu on toteutettu luokassa, johon se loogisesti (luokkien nimien perusteella) kuuluukin.
Asuntovälitystoimiston tietojärjestelmässä myynnissä olevaa asuntoa kuvataan seuraavan luokan olioilla:
public class Asunto {
private int huoneita;
private int nelioita;
private int neliohinta;
public Asunto(int huoneita, int nelioita, int neliohinta) {
this.huoneita = huoneita;
this.nelioita = nelioita;
this.neliohinta = neliohinta;
}
}
Tehtävänä on toteuttaa muutama metodi, joiden avulla myynnissä olevia asuntoja voidaan vertailla.
Onko asunto suurempi
Tee metodi public boolean suurempi(Asunto verrattava)
joka palauttaa true jos asunto-olio, jolle metodia kutsutaan on suurempi kuin verrattavana oleva asunto-olio.
Esimerkki metodin toiminnasta:
Asunto eiraYksio = new Asunto(1, 16, 5500);
Asunto kallioKaksio = new Asunto(2, 38, 4200);
Asunto jakomakiKolmio = new Asunto(3, 78, 2500);
System.out.println(eiraYksio.suurempi(kallioKaksio)); // false
System.out.println(jakomakiKolmio.suurempi(kallioKaksio)); // true
Asuntojen hintaero
Tee metodi public int hintaero(Asunto verrattava)
joka palauttaa asunto-olion jolle metodia kutsuttiin ja parametrina olevan asunto-olion hintaeron. Hintaero on asuntojen hintojen (=neliöhinta*neliöt) itseisarvo.
Esimerkki metodin toiminnasta:
Asunto eiraYksio = new Asunto(1, 16, 5500);
Asunto kallioKaksio = new Asunto(2, 38, 4200);
Asunto jakomakiKolmio = new Asunto(3, 78, 2500);
System.out.println(eiraYksio.hintaero(kallioKaksio)); // 71600
System.out.println(jakomakiKolmio.hintaero(kallioKaksio)); // 35400
Onko asunto kalliimpi
Tee metodi public boolean kalliimpi(Asunto verrattava)
joka palauttaa true jos asunto-olio, jolle metodia kutsutaan on kalliimpi kuin verrattavana oleva asunto-olio.
Esimerkki metodin toiminnasta:
Asunto eiraYksio = new Asunto(1, 16, 5500);
Asunto kallioKaksio = new Asunto(2, 38, 4200);
Asunto jakomakiKolmio = new Asunto(3, 78, 2500);
System.out.println(eiraYksio.kalliimpi(kallioKaksio)); // false
System.out.println(jakomakiKolmio.kalliimpi(kallioKaksio)); // true
Olioiden yhtäsuuruuden vertailu
Opimme merkkijonojen käsittelyn yhteydessä, että merkkijonojen vertailu tulee toteuttaa equals
-metodin avullla. Tämä tapahtuu seuraavasti.
Scanner lukija = new Scanner(System.in);
System.out.println("Syötä kaksi sanaa, kumpikin omalle rivilleen.")
String eka = lukija.nextLine();
String toka = lukija.nextLine();
if (eka.equals(toka)) {
System.out.println("Sanat olivat samat.");
} else {
System.out.println("Sanat eivät olleet samat.");
}
Alkeistyyppisten muuttujien kuten int
kanssa muuttujien vertailu on mahdollista kahden yhtäsuuruusmerkin avulla. Tämä johtuu siitä, että alkeistyyppisten muuttujien arvo sijaitsee "muuttujan lokerossa". Viittaustyyppisten muuttujien arvo on taas osoite viitattavaan olioon, eli viittaustyyppisten muuttujien "lokerossa" on viite muistipaikkaan. Kahden yhtäsuuruusmerkin avulla verrataan "muuttujan lokeron" sisällön yhtäsuuruutta -- viittaustyyppisillä muuttujilla vertailu tarkastelisi siis muuttujien osoitteita.
Metodi equals
taas tarkastelee muuttujaan liittyvän olion sisältöä. Jos haluamme pystyä vertailemaan kahta itse toteuttamaamme oliota equals-metodilla, tulee metodi määritellä luokkaan. Metodi equals määritellään luokkaan boolean-tyyppisen arvon palauttavana metodina -- boolean-muuttujan arvo kertoo ovatko oliot samat.
Metodi equals
toteutetaan siten, että sen avulla voidaan vertailla nykyistä oliota mihin tahansa muuhun olioon. Metodi saa parametrinaan Object-tyyppisen olion -- kaikki oliot ovat oman tyyppinsä lisäksi Object-tyyppisiä. Metodissa ensin vertaillaan ovatko osoitteet samat: jos kyllä, oliot ovat samat. Tämän jälkeen tarkastellaan ovatko olion tyypit samat: jos ei, oliot eivät ole samat. Tämän jälkeen parametrina saatu Object-olio muunnetaan tyyppimuunnoksella tarkasteltavan olion muotoiseksi, ja oliomuuttujien arvoja vertaillaan. Alla vertailu on toteutettu Paivays-oliolle.
public class Paivays {
private int paiva;
private int kuukausi;
private int vuosi;
public Paivays(int paiva, int kuukausi, int vuosi) {
this.paiva = paiva;
this.kuukausi = kuukausi;
this.vuosi = vuosi;
}
public int getPaiva() {
return this.paiva;
}
public int getKuukausi() {
return this.kuukausi;
}
public int getVuosi() {
return this.vuosi;
}
public boolean equals(Object verrattava) {
// jos muuttujat sijaitsevat samassa paikassa, ovat ne samat
if (this == verrattava) {
return true;
}
// jos verrattava olio ei ole Paivays-tyyppinen, oliot eivät ole samat
if (!(verrattava instanceof Paivays)) {
return false;
}
// muunnetaan oli Paivays-olioksi
Paivays verrattavaPaivays = (Paivays) verrattava;
// jos olioiden oliomuuttujien arvot ovat samat, ovat oliot samat
if (this.paiva == verrattavaPaivays.paiva &&
this.kuukausi == verrattavaPaivays.kuukausi &&
this.vuosi == verrattavaPaivays.vuosi) {
return true;
}
// muulloin oliot eivät ole samat
return false;
}
@Override
public String toString() {
return this.paiva + "." + this.kuukausi + "." + this.vuosi;
}
}
Vastaavan vertailutoiminnallisuuden rakentaminen onnistuu myös Henkilö-olioille. Alla vertailu on toteutettu Henkilo-oliolle, jolla ei ole erillista Paivays-oliota. Huomaa, että henkilöiden nimet ovat merkijonoja (eli olioita), joten niiden vertailussa käytetään equals-metodia.
public class Henkilo {
private String nimi;
private int ika;
private int paino;
private int pituus;
// konstruktorit ja metodit
public boolean equals(Object verrattava) {
// jos muuttujat sijaitsevat samassa paikassa, ovat ne samat
if (this == verrattava) {
return true;
}
// jos verrattava olio ei ole Henkilo-tyyppinen, oliot eivät ole samat
if (!(verrattava instanceof Henkilo)) {
return false;
}
// muunnetaan olio Henkilo-olioksi
Henkilo verrattavaHenkilo = (Henkilo) verrattava;
// jos olioiden oliomuuttujien arvot ovat samat, ovat oliot samat
if (this.nimi.equals(verrattavaHenkilo.nimi) &&
this.ika == verrattavaHenkilo.ika &&
this.paino == verrattavaHenkilo.paino &&
this.pituus == verrattavaHenkilo.pituus) {
return true;
}
// muulloin oliot eivät ole samat
return false;
}
// .. metodeja
}
Tehtäväpohjassa on luokka Kappale
, jonka perusteella voidaan luoda musiikkikappaleita esittäviä olioita. Lisää luokkaan kappale metodi equals
, jonka avulla voidaan tarkastella musiikkikappaleiden samankaltaisuutta.
Kappale jackSparrow = new Kappale("The Lonely Island", "Jack Sparrow", 196);
Kappale toinenSparrow = new Kappale("The Lonely Island", "Jack Sparrow", 196);
if (jackSparrow.equals(toinenSparrow)) {
System.out.println("Kappaleet olivat samat.");
}
if (jackSparrow.equals("Toinen olio")) {
System.out.println("Nyt on jotain hassua.");
}
Kappaleet olivat samat.
Tehtäväpohjassa on luokka Henkilo
, johon liittyy Paivays
-olio. Lisää luokalle Henkilo metodi public boolean equals(Object verrattava)
, jonka avulla voidaan verrata henkilöiden samuutta. Vertailussa tulee verrata kaikkien henkilön muuttujien yhtäsuuruutta (ml. syntymäpäivä).
Tehtäväpohjassa ei ole testejä. Keksi erilaisia esimerkkikoodeja, joilla voit testata ohjelman toimintaa. Alla pari esimerkkiä.
Paivays pvm = new Paivays(24, 2, 2017);
Paivays pvm2 = new Paivays(23, 7, 2017);
Henkilo leevi = new Henkilo("Leevi", pvm, 62, 9);
Henkilo lilja = new Henkilo("Lilja", pvm2, 65, 8);
if (leevi.equals(lilja)) {
System.out.println("Meniköhän nyt ihan oikein?");
}
Henkilo leeviEriPainolla = new Henkilo("Leevi", pvm, 62, 10);
if (leevi.equals(leeviEriPainolla)) {
System.out.println("Meniköhän nyt ihan oikein?");
}
Olio-ohjelmoinnin periaatteissa todettiin seuraavaa: Olio-ohjelmoinnissa on mahdollista luoda luokkia, jotka perivät toisen luokan ominaisuuksia (eli oliomuuttujat ja metodit). Tällöin luokasta tehdyt oliot ovat samalla myös perityn luokan ilmentymiä, jolloin oliot voivat esiintyä useampina erilaisina olioina käyttötarpeesta riippuen.
Jokainen luomamme luokka (ja Javan valmis luokka) perii luokan Object, vaikkei sitä erikseen ohjelmakoodissa näy. Tämän takia mistä tahansa luokasta tehty ilmentymä voidaan asettaa parametriksi metodiin, joka saa parametrina Object-tyyppisen muuttujan. Object-luokan periminen näkyy myös muissa asioissa: esimerkiksi metodi toString
on olemassa vaikkei sitä erikseen toteuteta, aivan samalla tavalla kuin metodi equals
.
Esimerkiksi seuraava lähdekoodi siis "toimii" oikein, vaikkei toiminta ehkä olekaan haluttua.
public class Lintu {
private String nimi;
public Lintu(String nimi) {
this.nimi = nimi;
}
}
Lintu red = new Lintu("Red");
System.out.println(red);
Lintu chuck = new Lintu("Chuck");
System.out.println(chuck);
if (red.equals(chuck)) {
System.out.println(red + " on sama kuin " + chuck);
}
Vaikka ohjelma on syntaktisesti oikein, ei lopputulos liene kuitenkaan toivottu.
Olio metodin paluuarvona
Olemme nähneet metodeja jotka palauttavat totuusarvoja, lukuja ja merkkijonoja. On helppoa arvata, että metodi voi palauttaa minkä tahansa tyyppisen olion.
Seuraavassa esimerkissä on yksinkertainen laskuri, jolla on metodi kloonaa
. Metodin avulla laskurista voidaan tehdä klooni, eli uusi laskurio-olio, jolla on luomishetkellä sama arvo kuin kloonattavalla laskurilla:
public Laskuri {
private int arvo;
public Laskuri() {
this(0);
}
public Laskuri(int alkuarvo) {
this.arvo = alkuarvo;
}
public void kasvata() {
this.arvo++;
}
public String toString() {
return "arvo: " + arvo;
}
public Laskuri kloonaa() {
// luodaan uusi laskuriolio, joka saa alkuarvokseen kloonattavan laskurin arvon
Laskuri klooni = new Laskuri(this.arvo);
// palautetaan klooni kutsujalle
return klooni;
}
}
Seuraavassa käyttöesimerkki:
Laskuri laskuri = new Laskuri();
laskuri.kasvata();
laskuri.kasvata();
System.out.println(laskuri); // tulostuu 2
Laskuri klooni = laskuri.kloonaa();
System.out.println(laskuri); // tulostuu 2
System.out.println(klooni); // tulostuu 2
laskuri.kasvata();
laskuri.kasvata();
laskuri.kasvata();
laskuri.kasvata();
System.out.println(laskuri); // tulostuu 6
System.out.println(klooni); // tulostuu 2
klooni.kasvata();
System.out.println(laskuri); // tulostuu 6
System.out.println(klooni); // tulostuu 3
Kloonattavan ja kloonin sisältämä arvo on kloonauksen tapahduttua sama. Kyseessä on kuitenkin kaksi erillistä olioa, eli kun toista laskureista kasvatetaan, ei kasvatus vaikuta toisen arvoon millään tavalla.
Vastaavasti myös Tehdas
-olio voisi luoda ja palauttaa uusia Auto
-olioita. Alla on hahmoteltu tehtaan runkoa -- tehdas tietää myös luotavien autojen merkin.
public class Tehdas {
private String merkki;
public Tehdas(String merkki) {
this.merkki = merkki;
}
public Auto tuotaAuto() {
return new Auto(this.merkki);
}
}
Tehtäväpohjan mukana tulee aiemmin esitelty luokka Paivays
, jossa päivämäärä talletetaan oliomuuttujien vuosi
, kuukausi
, ja paiva
avulla:
public class Paivays {
private int paiva;
private int kuukausi;
private int vuosi;
public Paivays(int paiva, int kuukausi, int vuosi) {
this.paiva = paiva;
this.kuukausi = kuukausi;
this.vuosi = vuosi;
}
public String toString() {
return this.paiva + "." + this.kuukausi + "." + this.vuosi;
}
public boolean aiemmin(Paivays verrattava) {
// ensin verrataan vuosia
if (this.vuosi < verrattava.vuosi) {
return true;
}
// jos vuodet ovat samat, verrataan kuukausia
if (this.vuosi == verrattava.vuosi && this.kuukausi < verrattava.kuukausi) {
return true;
}
// vuodet ja kuukaudet samoja, verrataan päivää
if (this.vuosi == verrattava.vuosi && this.kuukausi == verrattava.kuukausi &&
this.paiva < verrattava.paiva) {
return true;
}
return false;
}
}
Tässä tehtäväsarjassa laajennetaan luokkaa.
Seuraava päivä
Toteuta metodi public void etene()
, joka siirtää päiväystä yhdellä päivällä. Tässä tehtävässä oletetaan, että jokaisessa kuukaudessa on 30 päivää. Huom! Sinun tulee tietyissä tilanteissa muuttaa kuukauden ja vuoden arvoa.
Tietty määrä päiviä eteenpäin
Toteuta metodi public void etene(int montakoPaivaa)
, joka siirtää päiväystä annetun päivien määrän verran. Käytä apuna edellisessä tehtävässä toteutettua metodia etene()
.
Ajan kuluminen
Lisätään Paivays
-olioon mahdollisuus edistää aikaa. Tee oliolle metodi Paivays paivienPaasta(int paivia)
, joka luo uuden Paivays
-olion, jonka päiväys on annetun päivien lukumäärän verran suurempi kuin oliolla, jolle sitä kutsuttiin. Voit edelleen olettaa, että jokaisessa kuukaudessa on 30 päivää. Huomaa, että vanhan päiväysolion on pysyttävä muuttumattomana!
Koska metodissa on luotava uusi olio, tulee rungon olla suunnilleen seuraavanlainen:
public Paivays paivienPaasta(int paivia) {
Paivays uusiPaivays = new Paivays( ... );
// tehdään jotain...
return uusiPaivays;
}
Ohessa on esimerkki metodin toiminnasta.
public static void main(String[] args) {
Paivays pvm = new Paivays(13, 2, 2015);
System.out.println("Tarkistellun viikon perjantai on " + pvm);
Paivays uusiPvm = pvm.paivienPaasta(7);
int vk = 1;
while (vk <= 7) {
System.out.println("Perjantai " + vk + " viikon kuluttua on " + uusiPvm);
uusiPvm = uusiPvm.paivienPaasta(7);
vk++;
}
System.out.println("Päivämäärä 790:n päivän päästä tarkistellusta perjantaista on ... kokeile itse!");
// System.out.println("Kokeile " + pvm.paivienPaasta(790));
}
Ohjelma tulostaa:
Tarkistellun viikon perjantai on 13.2.2015 Perjantai 1 viikon kuluttua on 20.2.2015 Perjantai 2 viikon kuluttua on 27.2.2015 Perjantai 3 viikon kuluttua on 4.3.2015 Perjantai 4 viikon kuluttua on 11.3.2015 Perjantai 5 viikon kuluttua on 18.3.2015 Perjantai 6 viikon kuluttua on 25.3.2015 Perjantai 7 viikon kuluttua on 2.4.2015 Päivämäärä 790:n päivän päästä tarkistellusta perjantaista on ... kokeile itse!
Huom! Sen sijaan, että muuttaisimme vanhan olion tilaa palautamme uuden olion. Kuvitellaan, että Paivays
-luokalle on olemassa metodi edista
, joka toimii vastaavasti kuin ohjelmoimamme metodi, mutta se muuttaa vanhan olion tilaa. Tällöin seuraava koodin pätkä tuottaisi ongelmia.
Paivays nyt = new Paivays(13, 2, 2015);
Paivays viikonPaasta = nyt;
viikonPaasta.edista(7);
System.out.println("Nyt: " + nyt);
System.out.println("Viikon päästä: " + viikonPaasta);
Ohjelman tulostus olisi seuraavanlainen:
Nyt 20.2.2015 Viikon päästä 20.2.2015
Tämä johtuu siitä, että tavallinen sijoitus kopioi ainoastaan viitteen olioon. Siis itse asiassa ohjelman oliot nyt
ja viikonPaasta
viittavaat yhteen ja samaan Paivays
-olioon.
Maksukortti-tehtävässä käytimme rahamäärän tallettamiseen double-tyyppistä oliomuuttujaa. Todellisissa sovelluksissa näin ei kannata tehdä, sillä kuten jo olemme nähneet, doubleilla laskenta ei ole tarkkaa. Onkin järkevämpää toteuttaa rahamäärän käsittely oman luokkansa avulla. Seuraavassa on luokan runko:
public class Raha {
private final int euroa;
private final int senttia;
public Raha(int euroa, int senttia) {
this.euroa = euroa;
this.senttia = senttia;
}
public int eurot() {
return euroa;
}
public int sentit() {
return senttia;
}
public String toString() {
String nolla = "";
if (senttia <= 10) {
nolla = "0";
}
return euroa + "." + nolla + senttia + "e";
}
}
Määrittelyssä pistää silmään oliomuuttujien määrittelyn yhteydessä käytetty sana final
, tällä saadaan aikaan se, että oliomuuttujien arvoa ei pystytä muuttamaan sen jälkeen kun ne on konstruktorissa asetettu. Raha-luokan oliot ovatkin muuttumattomia eli immutaabeleita, eli jos halutaan esim. kasvattaa rahamäärää, on luotava uusi olio, joka kuvaa kasvatettua rahasummaa.
Luomme seuraavassa muutaman operaation rahojen käsittelyyn.
Plus
Tee ensin metodi public Raha plus(Raha lisattava)
, joka palauttaa uuden raha-olion, joka on arvoltaan yhtä suuri kuin se olio jolle metodia kutsuttiin ja parametrina oleva olio yhteensä.
Metodin runko on seuraavanlainen:
public Raha plus(Raha lisattava) {
Raha uusi = new Raha(...); // luodaan uusi Raha-olio jolla on oikea arvo
// palautetaan uusi Raha-olio
return uusi;
}
Seuraavassa esimerkkejä metodin toiminnasta
Raha a = new Raha(10,0);
Raha b = new Raha(5,0);
Raha c = a.plus(b);
System.out.println(a); // 10.00e
System.out.println(b); // 5.00e
System.out.println(c); // 15.00e
a = a.plus(c); // HUOM: tässä syntyy uusi Raha-olio, joka laitataan "a:n langan päähän"
// vanha a:n langan päässä ollut 10 euroa häviää ja Javan roskien kerääjä korjaa sen pois
System.out.println(a); // 25.00e
System.out.println(b); // 5.00e
System.out.println(c); // 15.00e
Vähemmän
Tee metodi public boolean vahemman(Raha verrattava)
, joka palauttaa true jos raha-olio jolle metodia kutsutaan on arvoltaan pienempi kuin raha-olio, joka on metodin parametrina.
Raha a = new Raha(10, 0);
Raha b = new Raha(3, 0);
Raha c = new Raha(5, 0);
System.out.println(a.vahemman(b)); // false
System.out.println(b.vahemman(c)); // true
Miinus
Tee metodi public Raha miinus(Raha vahentaja)
, joka palauttaa uuden raha-olion, jonka arvoksi tulee sen olion jolle metodia kutsuttiin ja parametrina olevan olion arvojen erotus. Jos erotus olisi negatiivinen, tulee luotavan raha-olion arvoksi 0.
Seuraavassa esimerkkejä metodin toiminnasta
Raha a = new Raha(10, 0);
Raha b = new Raha(3, 50);
Raha c = a.miinus(b);
System.out.println(a); // 10.00e
System.out.println(b); // 3.50e
System.out.println(c); // 6.50e
c = c.miinus(a); // HUOM: tässä syntyy uusi Raha-olio, joka laitataan "c:n langan päähän"
// vanha c:n langan päässä ollut 6.5 euroa häviää ja Javan roskien kerääjä korjaa sen pois
System.out.println(a); // 10.00e
System.out.println(b); // 3.50e
System.out.println(c); // 0.00e
Listarakenne
Ohjelmoidessa tulee vastaan tilanteita, joissa haluamme käsitellä useita arvoja. Epäkäytännöllinen mutta tähän mennessä käytännössä ainoa tapa on ollut määritellä jokaiselle arvolle oma muuttuja.
String sana1;
String sana2;
String sana3;
// ...
String sana10;
Tämä ratkaisu on oikeastaan kelvoton -- ajattele ylläoleva esimerkki vaikkapa tuhannella sanalla.
Ohjelmointikielet tarjoavat apuvälineitä, joiden avulla on helppo säilyttää useita arvoja. Tutustumme nyt Java-ohjelmointikielen ehkäpä eniten käytettyyn apuvälineeseen ArrayListiin (linkki vie Javan omaan dokumentaatioon). ArrayList on käytännössä taulukko, jonka koko kasvaa sitä mukaa kun sinne lisätään tietoa.
ArrayList on Javan valmis luokka. Se kapseloi listan käsittelyyn tarvittavan konkreettisen ohjelmakoodin. ArrayList-muuttujan tyyppi on ArrayList
, jonka lisäksi sille määritellään listalle lisättävien arvojen tyyppi. Esimerkiksi merkkijonoja sisältävän ArrayListin tyyppi on ArrayList<String>
ja kokonaislukuja sisältävän ArrayListin tyyppi on ArrayList<Integer>
. Listan luominen tapahtuu komennolla new ArrayList<>();
.
Yhteen listaan lisätään aina samantyyppisiä arvoja.
Seuraavassa esimerkissä esitellään merkkijonoja tallentava ArrayList, johon lisätään muutama merkkijono. Tämän jälkeen tulostetaan listan nollannessa kohdassa oleva arvo eli ensimmäinen merkkijono (ohjelmoijat aloittavat laskemisen aina nollasta, lue tarkemmin täältä).
import java.util.ArrayList;
public class SanalistaEsimerkki {
public static void main(String[] args) {
ArrayList<String> sanalista = new ArrayList<>();
sanalista.add("Ensimmäinen");
sanalista.add("Toinen");
System.out.println(sanalista.get(0));
}
}
Ensimmäinen
Ohjelmaan on toteutettu valmiina pohja, joka lukee käyttäjältä syötteitä listalle. Syötteiden lukeminen päätetään kun käyttäjä syöttää tyhjän merkkijonon.
Ohjelma tulostaa tämän jälkeen listan ensimmäisen arvon. Muokkaa ohjelmaa siten, että ensimmäisen arvon sijaan tulostetaan kolmas arvo. Huomaa, että ohjelmoijat aloittavat laskemisen nollasta! Ohjelma saa rikkoutua rikkoutua jos listalla ei ole vähintään kolmea arvoa. Tarkastele minkälainen virheilmoitus ohjelmasta tällöin tulee.
Terho Elina Aleksi Mari Aleksi
Elina Aleksi Mari Mari
Jos ArrayListiltä haetaan arvoa olemattomasta sijainnista, ohjelma tulostaa viestin IndexOutOfBoundsException.
import java.util.ArrayList;
public class Esimerkki {
public static void main(String[] args) {
ArrayList<String> sanalista = new ArrayList<>();
sanalista.add("Ensimmäinen");
sanalista.add("Toinen");
System.out.println(sanalista.get(2));
}
}
Virheviesti antaa myös pientä vinkkiä ArrayList-olion kapseloimasta toteutuksesta. Se siis kertoo metodit, joita kutsuttaessa virhe tapahtui.
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 2, Size: 2 at java.util.ArrayList.rangeCheck(ArrayList.java:653) at java.util.ArrayList.get(ArrayList.java:429) at Esimerkki.main(Esimerkki.java:(rivi)) Java Result: 1
Lista on erittäin hyödyllinen kun halutaan tallentaa muuttujien arvoja myöhempää käsittelyä varten. Sillä on myös helpohko tehdä virheitä.
Tehtäväpohjassa on listaa käyttävä ohjelma. Muokkaa ohjelmaa siten, että sen suorittaminen tuottaa aina virheen IndexOutOfBoundsException
. Ohjelman tulee olla sellainen, että käyttäjän ei tarvitse antaa konelle syötettä (esim. näppäimistöltä).
Ohjelmassa näkyy myös toisenlainen tapa listan läpikäyntiin -- palaamme siihen myöhemmin kurssilla.
Jokaisella Javan valmiilla luokalla on nimi ja sijainti. Valmiin luokan tuominen ohjelman käyttöön tapahtuu import
-käskyllä. Käskylle kerrotaan luokan sijainti ja luokan nimi. Esimerkiksi ArrayListin käyttöönotto vaatii komennon import java.util.ArrayList;
ohjelman ylälaitaan.
import java.util.ArrayList;
public class ListaOhjelma {
public static void main(String[] args) {
ArrayList<String> sanalista = new ArrayList<>();
sanalista.add("Ensimmäinen");
sanalista.add("Toinen");
}
}
Sama pätee myös muillekin Javan luokille. Olemmekin jo aiemmin käyttäneet lukemiseen tarkoitettua Scanner-luokkaa, jonka on saanut käyttöön komennolla import java.util.Scanner;
Useamman apuvälineen käyttöönotto on helppoa. Käyttöön tuotavat apuvälineet listataan allekkain ohjelman ylälaitaan.
import java.util.ArrayList;
import java.util.Scanner;
public class ListaOhjelma {
public static void main(String[] args) {
Scanner lukija = new Scanner(System.in);
ArrayList<String> sanalista = new ArrayList<>();
sanalista.add("Ensimmäinen");
sanalista.add(lukija.nextLine());
}
}
Listaan liittyviä metodeja
ArrayList-luokalla on useita hyödyllisiä metodeja. Metodin toiminnallisuus suoritetaan aina sille listaoliolle, mihin liittyen metodia kutsutaan -- yhteys määritellään pisteellä. Alla olevassa esimerkissä käsitellään vain yhtä listaoliota, eli listaa opettajat.
ArrayList<String> opettajat = new ArrayList<>();
opettajat.add("Veera");
opettajat.add("Jyri");
opettajat.add("Verna");
opettajat.add("Mikko");
opettajat.add("Pihla");
opettajat.add("Sami");
System.out.println("opettajien lukumäärä " + opettajat.size());
System.out.println("listalla ensimmäisenä " + opettajat.get(0));
System.out.println("listalla kolmantena " + opettajat.get(2));
opettajat.remove("Juha");
if (opettajat.contains("Juha")) {
System.out.println("Juha on opettajien listalla");
} else {
System.out.println("Juha ei ole opettajien listalla");
}
opettajien lukumäärä 6 listalla ensimmäisena Veera listalla kolmantena Verna Juha ei ole opettajien listalla
Alla yhteenveto listan metodeista.
Listalle lisääminen: add
Listan metodi add lisää listalle metodille parametrina annetun arvon. Huomaa, että listalle lisättävän arvon tulee olla saman tyyppinen kuin listan määrittelyssä kuvattu tyyppi. Alla olevassa esimerkissä lista on määritelty sisältämään merkkijonoja, ja listalle lisätään merkkijono.
ArrayList<String> lista = new ArrayList<>();
// listalle lisääminen
lista.add("Arvo");
Listalla olevien arvojen lukumäärä: size
Listan metodi size palauttaa listalla olevien arvojen lukumäärän. Lukumäärä on kokonaisluku (int
) ja sitä voidaan käyttää osana lauseketta tai se voidaan asettaa kokonaislukumuuttujaan myöhempää käyttöä varten.
ArrayList<String> lista = new ArrayList<>();
System.out.println("Listalla arvoja: " + lista.size());
lista.add("Eka");
System.out.println("Listalla arvoja: " + lista.size());
int arvoja = lista.size();
lista.add("Toka");
System.out.println("Listalla arvoja: " + arvoja);
Listalla arvoja: 0 Listalla arvoja: 1 Listalla arvoja: 1
Tehtäväpohjassa on ohjelma, joka lukee käyttäjältä syötteitä. Muokkaa ohjelman toimintaa siten, että kun syötteiden lukeminen lopetetaan, ohjelma tulostaa listalla olevien arvojen lukumäärän.
Terho Elina Aleksi Mari Yhteensä: 4
Juno Elizabeth Mauri Irene Outi Lauri Iisa Risto Markus Ville Oskari Yhteensä: 11
Listan tietystä kohdasta hakeminen: get
Listan metodi get palauttaa listan parametrina annettuun paikkaan liittyvä sisältö. Parametri annetaan kokonaislukuna. Listan paikkojen numerointi alkaa nollasta, eli ensimmäisenä lisätty arvo on paikassa numero 0, toisena lisätty paikassa numero 1 jne.
ArrayList<String> lista = new ArrayList<>();
lista.add("Eka");
lista.add("Toka");
lista.add("Kolmas");
System.out.println("Paikka 1 eli toinen paikka: " + lista.get(1));
System.out.println("Paikka 0 eli ensimmäinen paikka: " + lista.get(0));
Toka Eka
Tehtäväpohjassa on ohjelma, joka lukee käyttäjältä syötteitä. Muokkaa ohjelman toimintaa siten, että kun syötteiden lukeminen lopetetaan, ohjelma tulostaa kolmanneksi viimeisenä luetun arvon. Voit olettaa, että listalle luetaan vähintään 3 arvoa.
Terho Elina Aleksi Mari Elina
Juno Elizabeth Mauri Irene Outi Lauri Iisa Risto Markus Ville Oskari Markus
Huom! Listan koon palauttava metodi on tässä hyödyllinen.
Listan tietystä kohdasta poistaminen: remove
Listan metodi remove poistaa listalta parametrina annettuun paikkaan liittyvän arvon. Parametri annetaan kokonaislukuna.
ArrayList<String> lista = new ArrayList<>();
lista.add("Eka");
lista.add("Toka");
lista.add("Kolmas");
lista.remove(1);
System.out.println("Paikka 0 eli ensimmäinen: " + lista.get(0));
System.out.println("Paikka 1 eli toinen: " + lista.get(1));
Eka Kolmas
Jos parametri on listan sisältämien arvojen tyyppinen, mutta ei kokonaisluku (kokonaislukua käytetään paikasta poistamiseen), voidaan sitä käyttää arvon poistamiseen listalta suoraan.
ArrayList<String> lista = new ArrayList<>();
lista.add("Eka");
lista.add("Toka");
lista.add("Kolmas");
lista.remove("Eka");
System.out.println("Paikka 0 eli ensimmäinen: " + lista.get(0));
System.out.println("Paikka 1 eli toinen: " + lista.get(1));
Toka Kolmas
Jos lista sisältää kokonaislukuja, ei listalta voi poistaa lukua antamalla remove-metodille luvun int-tyyppisenä parametrina. Tämä poistaisi luvun annetusta indeksistä. Kokonaislukutyyppisten arvojen poistaminen tapahtuu muuttamalla arvot Integer-tyyppisiksi, eli kapseloimalla ne Integer-olioiksi, Integer-luokan metodilla valueOf
.
ArrayList<String> lista = new ArrayList<>();
lista.add(15);
lista.add(18);
lista.add(21);
lista.add(24);
lista.remove(2);
lista.remove(Integer.valueOf(15));
System.out.println("Paikka 0 eli ensimmäinen: " + lista.get(0));
System.out.println("Paikka 1 eli toinen: " + lista.get(1));
18 24
Onko listalla: contains
Listan metodi contains kertoo löytyykö haettua arvoa listalta. Metodi saa haettavan arvon parametrina, ja se palauttaa totuustyyppisen arvon (boolean) true
tai false
.
ArrayList<String> lista = new ArrayList<>();
lista.add("Eka");
lista.add("Toka");
lista.add("Kolmas");
System.out.println("Löytyykö eka? " + lista.contains("Eka"));
if (lista.contains("Toka")) {
System.out.println("Toka löytyi");
}
Löytyykö eka? true Toka löytyi
Tehtäväpohjassa on ohjelma, joka lukee käyttäjältä syötteitä. Lisää ohjelmaan toiminnallisuus, missä syötteiden lukemisen jälkeen kysytään vielä yhtä merkkijonoa. Ohjelma kertoo tämän jälkeen löytyikö käyttäjän syöttämä merkkijono listalta vai ei.
Terho Elina Aleksi Mari Ketä etsitään? Mari Mari löytyi!
Terho Elina Aleksi Mari Ketä etsitään? Arto Arto ei löytynyt!
Listan paikkojen numerointi eli indeksointi alkaa aina nollasta. Listan ensimmäinen arvo on indeksissä 0, toinen arvo indeksissä 1, kolmas arvo indeksissä 2 ja niin edelleen.
Ylläolevassa listassa ensimmäisenä on arvo 6 ja toisena arvo 1. Jos ylläolevaan listaan lisättäisiin uusi arvo kutsumalla luvut
-listan metodia add
parametrilla 8, menisi luku 8 listan indeksiin 6 eli seitsemänneksi luvuksi.
Vastaavasti kutsumalla metodia get
parametrilla 4, listalta haettaisiin viidettä lukua.
Listan läpikäynti
Seuraavassa esimerkissä listalle lisätään neljä nimeä, jonka jälkeen listan sisältö tulostetaan.
ArrayList<String> opettajat = new ArrayList<>();
opettajat.add("Sami");
opettajat.add("Samu");
opettajat.add("Anne");
opettajat.add("Anna");
System.out.println(opettajat.get(0));
System.out.println(opettajat.get(1));
System.out.println(opettajat.get(2));
System.out.println(opettajat.get(3));
Sami Samu Anne Anna
Esimerkki on kömpelö. Entä jos listalla olisi enemmän arvoja? Tai vähemmän? Entäs jos emme tietäisi listalla olevien arvojen määrää?
Tehdään ensin välivaiheen versio jossa pidetään kirjaa tulostettavasta paikasta muuttujan paikka
avulla:
ArrayList<String> opettajat = new ArrayList<>();
opettajat.add("Sami");
opettajat.add("Samu");
opettajat.add("Anne");
opettajat.add("Anna");
int paikka = 0;
System.out.println(opettajat.get(paikka));
paikka++;
System.out.println(opettajat.get(paikka)); // paikka = 1
paikka++;
System.out.println(opettajat.get(paikka)); // paikka = 2
paikka++;
System.out.println(opettajat.get(paikka)); // paikka = 3
paikka++;
Huomaamme, että ylläolevassa ohjelmassa on toistoa. Voimme hyödyntää toistolausetta ja kasvattaa muuttujaa paikka
niin kauan kunnes se kasvaa liian suureksi:
ArrayList<String> opettajat = new ArrayList<>();
opettajat.add("Sami");
opettajat.add("Samu");
opettajat.add("Anne");
opettajat.add("Anna");
int paikka = 0;
while (paikka < opettajat.size()) {
System.out.println(opettajat.get(paikka));
paikka++;
}
// muistatko miksi paikka <= opettajat.size() ei toimi?
Nyt tulostus toimii riippumatta listalla olevien alkioiden määrästä.
Listalle lisättävien muuttujien tyyppi
Lista voi sisältää vain tietyntyyppisiä arvoja. Nämä arvot määritellään listaa luodessa, esimerkiksi ArrayList<String>
sisältää merkkijonotyyppisiä muuttujia.
Aiemmin käyttämämme muuttujatyypit int
ja double
sekä lähes kaikki muut pienellä alkukirjaimella kirjoitettavat muuttujat ovat alkeistyyppisiä ja ne eivät käy suoraan listan sisältämien alkioiden tyypiksi. Jos haluamme lisätä alkeistyyppisiä muuttujia listalle, tulee ne määritellä niiden viittaustyppisten muuttujatyyppien avulla. Esimerkiksi int
-tyyppisten muuttujien tyypiksi tulee asettaa Integer
. Vastaavasti double
-tyyppisten muuttujien tyypiksi tulee asettaa Double
.
ArrayList<String> nimet = new ArrayList<>();
nimet.add("Sami");
nimet.add("Samu");
nimet.add("Anne");
nimet.add("Anna");
ArrayList<Integer> iat = new ArrayList<>();
iat.add(6);
iat.add(11);
iat.add(38);
iat.add(14);
int paikka = 0;
while (paikka < 4) {
System.out.print("Nimi: " + nimet.get(paikka));
System.out.print("\t");
int ika = iat.get(paikka);
System.out.println("Ikä: " + ika);
// tai:
// System.out.print("Ikä: " + iat.get(paikka));
System.out.println();
paikka++;
}
Nimi: Sami Ikä 6 Nimi: Samu Ikä 11 Nimi: Anne Ikä 38 Nimi: Anna Ikä 14
Tämä rajoitus liittyy siihen, miten ArrayList on toteutettu. Javan muuttujat voidaan jakaa kahteen kategoriaan: alkeistyyppisiin muuttujiin ja viittaustyyppisiin muuttujiin. Alkeistyyppiset muuttujat kuten int
ja double
sisältävät niihin liittyvät arvot. Viittaustyyppiset muuttujat taas, kuten esimerkiksi ArrayList
sisältävät viitteen paikkaan, joka sisältää muuttujaan liittyvät arvot.
Hieman yksinkertaistaen: alkeistyyppiset muuttujat pystyvät sisältämään vain rajatun määrän tietoa, kun taas viitteen taakse tietoa voi säilöä lähes rajattomasti.
ArrayList olettaa, että sen sisältämät muuttujat ovat viittaustyyppisiä. Java muuntaa automaattisesti int
-tyyppisen muuttujan Integer
-tyyppiseksi kun se lisätään listalle, sama tapahtuu myös kun muuttuja haetaan listalta. Vastaava muunnos tapahtuu myös double
-tyyppiselle muuttujalle, josta tulee Double
-tyyppinen muuttuja.
Palaamme tähän jatkossakin, sillä muuttujat vaikuttavat oleellisesti ohjelmamme suoritukseen.
Ohjelmaan on toteutettu valmiina pohja, joka lukee käyttäjältä lukuja listalle. Syötteiden lukeminen päätetään kun käyttäjä syöttää luvun -1.
Lisää ohjelmaan toiminnallisuus, joka lukujen lukemisen jälkeen tulostaa listalla olevien lukujen summan.
72 2 8 11 -1 Summa: 93
Ohjelmaan on toteutettu valmiina pohja, joka lukee käyttäjältä lukuja listalle. Syötteiden lukeminen päätetään kun käyttäjä syöttää luvun -1.
Lisää ohjelmaan toiminnallisuus, joka etsii listalta listan suurimman luvun ja tulostaa sen arvon. Ohjelman pitäisi toimia seuraavasti.
72 2 8 93 11 -1 Listan suurin luku: 93
Ota mallia allaolevasta pienintä lukua etsivästä lähdekoodista.
// oletetaan, että käytössämme on lista, jossa on kokonaislukuja
int pienin = lista.get(0);
int indeksi = 0;
while (indeksi < lista.size()) {
int luku = lista.get(indeksi);
if (pienin > luku) {
pienin = luku;
}
indeksi++;
}
System.out.println("Listan pienin luku: " + pienin);
Toteuta ohjelma, joka lukee käyttäjältä lukuja listalle. Syötteiden lukeminen päätetään kun käyttäjä syöttää luvun -1.
Kun lukujen lukeminen lopetetaan, laske listalla olevien lukujen keskiarvo ja tulosta se.
72 2 8 11 -1 Keskiarvo: 23.25
Ohjelmaan on toteutettu valmiina pohja, joka lukee käyttäjältä lukuja listalle. Syötteiden lukeminen päätetään kun käyttäjä syöttää luvun -1.
Lisää ohjelmaan toiminnallisuus, joka kysyy käyttäjältä lukua ja kertoo luvun indeksin. Jos lukua ei löydy, tulee siitä ilmoittaa erikseen (vihje: contains!).
72 2 8 8 11 -1 Mitä etsitään? 2 Luku 2 on indeksissä 1
72 2 8 8 11 -1 Mitä etsitään? 7 Lukua 7 ei löydy.
72 2 8 8 11 -1 Mitä etsitään? 8 Luku 8 on indeksissä 2 Luku 8 on indeksissä 3
Toteuta ohjelma, joka lukee käyttäjältä lukuja. Kun käyttäjä syöttää luvun 9999, lukujen lukeminen lopetetaan. Ohjelma tulostaa tämän jälkeen pienimmän listalla olevan luvun sekä indeksit, joista pienin luku löytyy. Pienin luku voi siis esiintyä useamman kerran.
72 2 8 8 11 9999 Pienin luku on 2 Pienin luku löytyy indeksistä 1
72 44 8 8 11 9999 Pienin luku on 8 Pienin luku löytyy indeksistä 2 Pienin luku löytyy indeksistä 3
Fibonaccin lukujonon ajatuksena on laskea yhteen kaksi edellistä lukua, ja näin saada seuraavan luvun arvo. Lukujonon ensimmäiset kaksi lukua ovat 0 ja 1. Seuraavan luvun saa laskettua aina kahden edellisen luvun summana.
Toteuta ohjelma, joka ensin laskee Fibonaccin lukujonon ensimmäiset 40 lukua listalle. Ohjelma kysyy tämän jälkeen käyttäjältä halutun Fibonaccin luvun kohtaa. Kun käyttäjä syöttää luvun -- oleta, että luku on indeksi listalla -- ohjelma tulostaa halutun luvun.
Ohjelman suoritus päättyy kun käyttäjä syöttää luvun -1.
Monesko luku? 0 0 Monesko luku? 1 1 Monesko luku? 7 13 Monesko luku? 22 17711 Monesko luku? -1 Kiitos!
Tässä tehtävässä täydennetään erästä tietokonepelien klassikkoa, Pongia. Tavoitteena pelissä on saada pallo lyötyä vastustajan mailasta ohi. Pisteen saa aina kun pallo osuu seinään. Tehtäväpohjaan on hahmoteltu Pong-peliä, joka näyttää seuraavalta:
Pelin saa käynnistettyä valitsemalla luokan PongApplication ja suorittamalla sen. Vasenta mailaa voi ohjata napeilla w ja s, oikeaa mailaa nuolinäppäimillä.
Pohjasta puuttuu kuitenkin merkittävä osa pelin toiminnallisuudesta. Tässä tehtävässä tavoitteenasi on tutustua olemassaolevaan ohjelmaan ja täydentää peliä sopivasti. Tässä muutamia täydennysehdotuksia:
- Pisteiden tulee muuttua kun pallo osuu seinään.
- Jos pallo osuu mailaan sen tulee kimmota mailasta.
- Mailan nopeuden pitäisi olla suurempi.
- Mailan ei pitäisi poistua alueelta.
- pallon nopeuden tulee kasvaa pelin edetessä.
- ...
Tehtävässä ei ole testejä ja sen tekemisestä saa yhden pisteen. Saat itse käytännössä määritellä mitä tehtävän tekeminen tarkoittaa, eli voit vapaasti tehdä pelistä hyvinkin monimuotoisen, jota ehkäpä päädyt jopa demoamaan jollekin.
Crowdsorcerer: Arvioi tehtäviä
Ohjelmointikurssin toisessa osassa loimme ensimmäisiä omia tehtäviä Crowdsorcererin avulla. Nyt on erinomainen hetki vertaisarviointiin -- on aika arvioida Crowdsorcereriin lähetettyjä tehtäviä! Anna vertaispalautetta kahdesta jonkun toisen kurssilaisen lähettämästä tehtävästä ja arvioi lopuksi itse tekemääsi tehtävää. Itse tekemäsi tehtävä näkyy vain jos olet tehnyt sen -- jos et tehnyt tehtävää, pääset arvioimaan yhden ylimääräisen tehtävän.
Alla on kolme Crowdsorcereriin tehtyä tehtävää: kaksi jonkun kurssitoverisi lähettämää ja yksi itsearviointia varten. Niiden yhteydessä on muistin virkistykseksi ohjeistus, jonka pohjalta kyseiset tehtävänannot on tehty.
Tarkastele jokaisen tehtävän eri osia: tehtävänantoa, tehtäväpohjaa ja malliratkaisua sekä testaukseen käytettäviä syötteitä ja tulosteita. Arvioi niiden selkeyttä, vaikeutta ja sitä, kuinka hyvin ne vastaavat ohjeistukseensa.
Voit vaihtaa näkymää tehtäväpohjan ja mallivastauksen välillä painamalla lähdekoodin yläpalkin painikkeita. Palautteenannon avuksi on annettu väittämiä. Voit valita kuinka samaa mieltä niiden kanssa olet painamalla hymiöitä. Annathan myös sanallista palautetta sille varattuun kenttään! Lisää vielä tehtävää mielestäsi kuvaavia tageja ja paina Lähetä.
Anna arvio kummallekin vertaispalautetehtävälle ja lopuksi vielä omallesi.
Muista olla reilu ja ystävällinen. Hyvä palaute on rehellistä, mutta kannustavaa!
Voit halutessasi ladata arvioitavan tehtävän tehtäväpohjan ja malliratkaisun koneellesi, ja testata niiden käyttöä. Molemmat tulevat ZIP-paketeissa, jolloin sinun täytyy purkaa ne, ennen kuin voit avata ne NetBeansissä.