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
Olemme kurssin ensimmäiseltä viikolta lähtien käyttäneet Scanner
-luokasta tehtyä oliota käyttäjän kirjoittaman syötteen lukemiseen.
// luodaan Scanner-olio, joka lukee näppäimistösyötettä Scanner lukija = new Scanner(System.in); // jatketaan syötteen lukemista kunnes käyttäjä syöttää // rivin "loppu" while (true) { String rivi = lukija.nextLine(); if (rivi.equals("loppu")) { break; } }
Yllä Scanner-luokan konstruktorille annetaan parametrina järjestelmän syötevirta, johon näppäinpanallukset ohjataan ohjelman tekstikäyttöliittymästä. Scanner voi saada konstruktorin parametrina myös muita olioita.
Tiedostot ovat tietokoneella sijaitsevia tietokokoelmia, jotka voivat sisältää vaikkapa kuvia tai musiikkia. Tutustutaan tässä tekstitiedostojen lukemiseen.
Myös tiedoston voi lukea Scanner-oliolla. Tällöin Scanner-oliolle annetaan konstruktorin parametrina File
-olio, joka kertoo tiedoston sijainnin tiedostojärjestelmässä. Tiedostoja lukiessa voidaan kohdata virhetilanne, joten tiedoston lukeminen vaatii erillisen "yrittämisen" (try) sekä mahdollisen virheen kiinnioton (catch). Palaamme mahdollisten virheiden käsittelyyn tarkemmin myöhemmin tällä kurssilla.
ArrayList<String> rivit = new ArrayList<>(); // luodaan lukija tiedoston lukemista varten try (Scanner lukija = new Scanner(new File("tiedosto.txt"))) { // luetaan kaikki tiedoston rivit while (lukija.hasNextLine()) { rivit.add(lukija.nextLine()); } } catch (Exception e) { System.out.println("Virhe: " + e.getMessage()); } // tee jotain luetuilla riveillä
Jos tiedosto löytyy ja sen lukeminen onnistuu, tulee ohjelman suorituksen lopussa tiedoston "tiedosto.txt" rivit olemaan listamuuttujassa rivit
. Jos taas tiedostoa ei löydy, tai sen lukeminen epäonnistuu, ilmoittaaa Java tästä virheviestillä. Alla eräs mahdollisuus:
Virhe: tiedosto.txt (No such file or directory)
Toteutimme neljännellä viikolla vieraslistaohjelman, missä käyttäjää pyydettiin syöttämään nimiä vieraslistalle. Kun nimet oli syötetty, käyttäjä syötti yksittäisiä nimiä joiden olemassaolo tarkitettiin vieraslistalta. Ohjelman toiminta oli seuraavanlainen:
Syötä nimiä vieraslistalle, tyhjä rivi lopettaa. Jack Bauer Jack Bower Jack Baur Jack Bowr Syötä nimiä, tyhjä rivi lopettaa. Chuck Norris Nimi ei ole listalla. Jack Baluer Nimi ei ole listalla. Jack Bauer Nimi on listalla. Jack Bower Nimi on listalla. Kiitos!
Muokataan tässä ohjelmaa siten, että toisin kuin aiemmin, nimet luetaan tiedostosta. Pääohjelmametodi tulee kokonaisuudessaan olemaan seuraavanlainen:
Scanner lukija = new Scanner(System.in); System.out.println("Minkä niminen tiedosto luetaan? "); String tiedosto = lukija.nextLine(); // kun toteutat metodit, voit testata toimintaa täällä ArrayList<String> lista = lueNimet(tiedosto); System.out.println(""); tarkistaNimet(lukija, lista); System.out.println("Kiitos!");
Toteuteta metodi lueNimet
, joka lukee nimet parametrina annetusta tiedostosta. Kun metodi on valmis, ohjelma toimii kuten aiempi vieraslistasovellus, mutta nimet luetaan tiedostosta -- nimiä ei siis tarvitse syöttää jokaisen käynnistyksen yhteydessä.
Kun olet valmis, kokeile vielä että ohjelma toimii halutusti tehtävänannon alussa olevalla koodilla.
Huom! Tehtäväpohjassa on mukana kaksi tiedostoa, nimet.txt
ja toiset-nimet.txt
, joiden sisällöt ovat seuravat. Älä muuta näiden tiedostojen sisältöä!
nimet.txt:
ada arto leena testi
toiset-nimet.txt:
leo jarmo alicia
Olemme toteuttaneet aiemmin kurssilla muutamaan otteeseen ohjelman, missä käyttäjä syöttää desibelimittauksia. Kun desibelimittaukset on syötetty, poistetaan luetuista luvuista virheelliset ja/tai turhat luvut. Lopulta kerrotaan lukujen keskiarvo, tai jos lukuja ei ole lainkaan, kerrotaan ettei mittaustuloksia ole.
Tässä tehtävässä toteutat metodin public static ArrayList<Integer> lueLuvut(String tiedosto)
, joka lukee luvut parametrina annetusta tiedostosta, ja palauttaa ne listalla. Kun metodi on toteutettu, voit kokeilla ohjelman toimintaa seuraavalla ohjelmakoodilla.
Scanner lukija = new Scanner(System.in); System.out.println("Minkä niminen tiedosto luetaan? "); String tiedosto = lukija.nextLine(); ArrayList<Integer> lista = lueLuvut(tiedosto); lista = valitseLuvutValilta(lista, 0, Integer.MAX_VALUE); if (lista.isEmpty()) { System.out.println("Ei lukuja."); } else { System.out.println("Lukujen keskiarvo: " + keskiarvo(lista)); }
Huom! Tehtäväpohjassa on mukana kaksi tiedostoa, mittaukset-1.txt
ja mittaukset-2.txt
, joiden sisällöt ovat seuravat. Älä muuta näiden tiedostojen sisältöä!
mittaukset-1.txt:
300 9 20 15
mittaukset-2.txt:
123 -5 12 67 -300 1902
Lähes kaikki verkkosivut, kuten tämäkin oppimateriaali, voidaan lukea tekstimuodossa ohjelmallista käsittelyä varten. Scanner-oliolle voi antaa konstruktorin parametrina oikeastaan lähes minkälaisen syötevirran tahansa. Alla olevassa esimerkissä luodaan URL-olio annetusta web-osoitteesta, pyydetään siihen liittyvää tietovirtaa, ja annetaan se juuri luotavalle Scanner-oliolle luettavaksi.
ArrayList<String> rivit = new ArrayList<>(); // luodaan lukija web-osoitteen lukemista varten try (Scanner lukija = new Scanner(new URL("http://www.cs.helsinki.fi/home/").openStream())) { // luetaan osoitteesta http://www.cs.helsinki.fi/home/ // saatava vastaus while (lukija.hasNextLine()) { rivit.add(lukija.nextLine()); } } catch (Exception e) { System.out.println("Virhe: " + e.getMessage()); } // tehdään jotain vastauksella
Web-selain on oikeastaan ohjelma siinä missä muutkin ohjelmat. Toisin kuin yllä toteutettu yksinkertainen web-sivun lataaja, web-selain osaa myös tulkita vastauksena tulevan HTML-muotoisen lähdekoodin.
Kerrataan hieman aiemmin oppimaamme, ja tutustutaan vielä muutamiin olioiden ominaisuuksiin.
Toteutetaan sovellus ostoslistan hallintaan. Ostoslistalle voi lisätä ostettavia asioita, tarkistaa kunkin ostettavan asian lukumäärän, sekä poistaa ostettavia asioita. Voit toteuttaa Ostoslista-olion sisäisen toiminnallisuuden esimerkiksi HashMap- tai ArrayList-tietorakenteen avulla.
Toteuta luokka Ostoslista
, jolla on parametriton konstruktori, ja joka sisältää listan String
-olioita. Toteuta luokalle metodit public void lisaa(String tuote)
, joka lisää yhden kappaleen tuotetta ostoslistalle, ja public int kappalemaara(String tuote)
, joka palauttaa tuotteen kappalemäärän listalta. Jos tuotetta ei ole ostoslistalla, tulee metodin kappalemaara
palauttaa arvo 0.
Luokkaa Ostoslista
ja luotavia metodeja tulee voida käyttää seuraavasti:
Ostoslista lista = new Ostoslista(); lista.lisaa("porkkana"); lista.lisaa("nauris"); System.out.println("Kauriita: " + lista.kappalemaara("kauris")); System.out.println("Nauriita: " + lista.kappalemaara("nauris"));
Kauriita: 0 Nauriita: 1
Toinen esimerkki
Ostoslista lista = new Ostoslista(); lista.lisaa("porkkana"); lista.lisaa("porkkana"); lista.lisaa("nauris"); lista.lisaa("porkkana"); System.out.println("Porkkanaa: " + lista.kappalemaara("porkkana") + " kpl"); System.out.println("Tomaatteja: " + lista.kappalemaara("tomaatti") + " kpl");
Porkkanaa: 3 kpl Tomaatteja: 0 kpl
Lisää luokalle Ostoslista
metodi public void poista(String tuote)
, joka poistaa yhden kappaleen annettua tuotetta. Metodin tulee toimia seuraavasti.
Ostoslista lista = new Ostoslista(); lista.lisaa("porkkana"); lista.lisaa("porkkana"); lista.lisaa("nauris"); lista.lisaa("porkkana"); System.out.println("Porkkanaa: " + lista.kappalemaara("porkkana") + " kpl"); System.out.println("Tomaatteja: " + lista.kappalemaara("tomaatti") + " kpl"); lista.poista("porkkana"); System.out.println(); System.out.println("Porkkanaa: " + lista.kappalemaara("porkkana") + " kpl"); System.out.println("Tomaatteja: " + lista.kappalemaara("tomaatti") + " kpl");
Porkkanaa: 3 kpl Tomaatteja: 0 kpl Porkkanaa: 2 kpl Tomaatteja: 0 kpl
Olemme nähneet metodeja jotka palauttavat totuusarvoja, lukuja, listoja ja merkkijonoja. On helppoa arvata, että metodi voi palauttaa minkä tahansa tyyppisen olion. Tehdään painovartijayhdistykselle metodi, jolla saadaan tietoon yhdistyksen suurimman painoindeksin omaava henkilö.
public class PainonvartijaYhdistys { // ... public Henkilo suurinPainoindeksinen() { // jos jasenlista on tyhjä, palautetaan null-viite if (this.jasenet.isEmpty()) { return null; } Henkilo painavinTahanAsti = this.jasenet.get(0); for (Henkilo henkilo : this.jasenet) { if (henkilo.painoIndeksi() > painavinTahanAsti.painoIndeksi()) { painavinTahanAsti = henkilo; } } return painavinTahanAsti; } }
Logiikaltaan edeltävä metodi toimii samaan tapaan kuin suurimman luvun etsiminen taulukosta. Käytössä on apumuuttuja painavinTahanAsti
joka laitetaan aluksi viittaamaan listan ensimmäiseen henkilöön. Sen jälkeen käydään lista läpi ja katsotaan tuleeko vastaan suuremman painoindeksin omaavia henkilöitä, jos tulee, niin otetaan viite talteen muuttujaan painavinTahanAsti
. Lopuksi palautetaan muuttujan arvo eli viite henkilöolioon.
Tehdään lisäys edelliseen pääohjelmaan. Pääohjelma ottaa vastaan metodin palauttaman viitteen muuttujaan painavin
.
public static void main(String[] args) { PainonvartijaYhdistys painonVartija = new PainonvartijaYhdistys("Kumpulan paino", 25); // .. Henkilo painavin = painonVartija.suurinPainoindeksinen(); System.out.print("suurin painoindeksinen jäsen: " + painavin.getNimi()); System.out.println(" painoindeksi " + String.format("%.2f", painavin.painoIndeksi())); }
Tulostuu:
suurin painoindeksinen jäsen: Petri painoindeksi 37,42
Edellisessä esimerkissä metodi palautti yhden painonVartija-olion sisältämistä Henkilo-olioista. On myös mahdollista, että metodi palauttaa kokonaan uuden olion. Seuraavassa yksinkertainen laskuri, jolla on metodi kloonaa
, jonka avulla laskurista voidaan tehdä klooni, eli uusi laskurio-olio, jolla on luomishetkellä sama arvo kuin kloonattavalla laskurilla:
public Laskuri { private int arvo; public Laskuri() { this(0); } public Laskuri(int alkuarvo) { this.arvo = alkuarvo; } public void kasvata() { this.arvo++; } public String toString() { return "arvo: " + arvo; } public Laskuri kloonaa() { // luodaan uusi laskuriolio, joka saa alkuarvokseen kloonattavan laskurin arvon Laskuri klooni = new Laskuri(this.arvo); // palautetaan klooni kutsujalle return klooni; } }
Seuraavassa käyttöesimerkki:
Laskuri laskuri = new Laskuri(); laskuri.kasvata(); laskuri.kasvata(); System.out.println(laskuri); // tulostuu 2 Laskuri klooni = laskuri.kloonaa(); System.out.println(laskuri); // tulostuu 2 System.out.println(klooni); // tulostuu 2 laskuri.kasvata(); laskuri.kasvata(); laskuri.kasvata(); laskuri.kasvata(); System.out.println(laskuri); // tulostuu 6 System.out.println(klooni); // tulostuu 2 klooni.kasvata(); System.out.println(laskuri); // tulostuu 6 System.out.println(klooni); // tulostuu 3
Kloonattavan ja kloonin arvo on siis kloonauksen tapahduttua sama. Kyseessä on kuitenkin kaksi erillistä olioa, eli jatkossa kun toista laskureista kasvatetaan, ei kasvatus vaikuta toisen arvoon millään tavalla.
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.
Toteuta metodi public void etene()
, joka siirtää päiväystä yhdellä päivällä. Tässä tehtävässä oletetaan, että jokaisessa kuukaudessa on 30 päivää. Huom! Sinun tulee tietyissä tilanteissa muuttaa kuukauden ja vuoden arvoa.
Toteuta metodi public void etene(int montakoPaivaa)
, joka siirtää päiväystä annetun päivien määrän verran. Käytä apuna edellisessä tehtävässä toteutettua metodia etene()
.
Lisätään Paivays
-olioon mahdollisuus edistää aikaa. Tee oliolle metodi Paivays paivienPaasta(int paivia)
, joka luo uuden Paivays
-olion, jonka päiväys on annetun päivien lukumäärän verran suurempi kuin oliolla, jolle sitä kutsuttiin. Voit edelleen olettaa, että jokaisessa kuukaudessa on 30 päivää. Huomaa, että vanhan päiväysolion on pysyttävä muuttumattomana!
Koska metodissa on luotava uusi olio, tulee rungon olla suunnilleen seuraavanlainen:
public Paivays paivienPaasta(int paivia) { Paivays uusiPaivays = new Paivays( ... ); // tehdään jotain... return uusiPaivays; }
Ohessa on esimerkki metodin toiminnasta.
public static void main(String[] args) { Paivays pvm = new Paivays(13, 2, 2015); System.out.println("Tarkistellun viikon perjantai on " + pvm); Paivays uusiPvm = pvm.paivienPaasta(7); for (int i = 1; i <= 7; ++i) { System.out.println("Perjantai " + i + " viikon kuluttua on " + uusiPvm); uusiPvm = uusiPvm.paivienPaasta(7); } 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.
Jatketaan luokan Päiväys laajentamista. Tämä tehtävä ei riipu edellisestä tehtävästä, saat tehtäväpohjan mukana Paivays-luokan jossa ei ole edellisen tehtävän lisäyksiä.
Lisää päiväykselle metodi public int erotusVuosissa(Paivays verrattava)
, jonka avulla saadaan selville päiväyksen ja verrattavan päiväyksen ero vuosissa. Huomioi seuraavat:
Seuraava pääohjelma demonstroi metodin käyttöä:
public class Paaohjelma { public static void main(String[] args) { Paivays eka = new Paivays(24, 12, 2009); Paivays toka = new Paivays(1, 1, 2011); Paivays kolmas = new Paivays(25, 12, 2010); System.out.println(toka + " ja " + eka + " ero vuosissa: " + toka.erotusVuosissa(eka)); System.out.println(kolmas + " ja " + eka + " ero vuosissa: " + kolmas.erotusVuosissa(eka)); System.out.println(toka + " ja " + kolmas + " ero vuosissa: " + toka.erotusVuosissa(kolmas)); } }
Tulos näyttää seuraavalta:
1.1.2011 ja 24.12.2009 ero vuosissa: 2 25.12.2010 ja 24.12.2009 ero vuosissa: 1 1.1.2011 ja 25.12.2010 ero vuosissa: 1
Vuosien laskenta ei edellisessä versiossa ollut vielä kovin tarkkaa. Esim. 1.1.2011 ja 25.12.2010 välillä ilmoitettiin olevan vuoden ero. Tarkennetaan metodin toiminta sellaiseksi, että se osaa laskea vuodet kunnolla. Laske erotukseen mukaan vain täydet vuodet. Eli vaikka päiväysten ero olisi 1 vuosi ja 364 päivää, ilmoittaa metodi eroksi vuoden.
Metodin tämänkin version tarvitsee toimia ainoastaan siten, että parametriksi annettava päivämäärä on aiempi kuin se päivämäärä jolle metodia kutsutaan.
Edellisen esimerkin tulos on nyt:
1.1.2011 ja 24.12.2009 ero vuosissa: 1 25.12.2010 ja 24.12.2009 ero vuosissa: 1 1.1.2011 ja 25.12.2010 ero vuosissa: 0
Laitetaan metodi toimimaan samoin riippumatta onko parametrina annettava päiväys myöhempi vai aiempi kuin päiväys mille metodia kutsutaan. Esimerkkipääohjelma:
public class Paaohjelma { public static void main(String[] args) { Paivays eka = new Paivays(24, 12, 2009); Paivays toka = new Paivays(1, 1, 2011); Paivays kolmas = new Paivays(25, 12, 2010); System.out.println(eka + " ja " + toka + " ero vuosissa: " + toka.erotusVuosissa(eka)); System.out.println(toka + " ja " + eka + " ero vuosissa: " + eka.erotusVuosissa(toka)); System.out.println(eka + " ja " + kolmas + " ero vuosissa: " + kolmas.erotusVuosissa(eka)); System.out.println(kolmas + " ja " + eka + " ero vuosissa: " + eka.erotusVuosissa(kolmas)); System.out.println(kolmas + " ja " + toka + " ero vuosissa: " + toka.erotusVuosissa(kolmas)); System.out.println(toka + " ja " + kolmas + " ero vuosissa: " + kolmas.erotusVuosissa(toka)); } }
24.12.2009 ja 1.1.2011 ero vuosissa: 1 1.1.2011 ja 24.12.2009 ero vuosissa: 1 24.12.2009 ja 25.12.2010 ero vuosissa: 1 25.12.2010 ja 24.12.2009 ero vuosissa: 1 1.1.2011 ja 25.12.2010 ero vuosissa: 0 25.12.2010 ja 1.1.2011 ero vuosissa: 0
Lisäsimme aiemmin henkilölle oliomuuttujaksi syntymäpäivän kertova Paivays-olio. Samalla huomattiin, että oliomuuttuja ika
kannattaa poistaa sillä iän pystyy laskemaan päiväyksen ja syntymäpäivän avulla.
Toteuta metodi ika
joka palauttaa henkilön iän.
Huom: edellisessä tehtävässä lisättiin luokalle Paivays
metodi public int erotusVuosissa(Paivays verrattava)
. Kannattaa kopioida metodi tässä tehtävässä olevaan luokkaan, se helpottaa tehtävän tekemistä oleellisesti!
import java.util.Calendar; public class Henkilo { private String nimi; private Paivays syntymaPaiva; public Henkilo(String nimi, int pp, int kk, int vv) { this.nimi = nimi; this.syntymaPaiva = new Paivays(pp, kk, vv); } public int ika() { // laske henkilön ikä syntymäpäivän ja tämän päivän perusteella // tämä päivä saadaan selville seuraavasti // Calendar.getInstance().get(Calendar.DATE); // Calendar.getInstance().get(Calendar.MONTH) + 1; // tammikuun numero on 0 joten lisätään 1 // Calendar.getInstance().get(Calendar.YEAR); return -1; } public String getNimi() { return this.nimi; } public String toString() { return this.nimi +", syntynyt "+ this.syntymaPaiva; } }
Voit testata Henkilöä seuraavalla pääohjelmalla. Lisää myös itsesi ohjelmaan ja varmista että ikäsi tulostuu oikein.
public class Main { public static void main(String[] args) { Henkilo verna = new Henkilo("Verna", 15, 1, 1993); Henkilo leo = new Henkilo("Leo", 1, 1, 1990); System.out.println(verna.getNimi() + " ikä " + verna.ika() + " vuotta"); System.out.println(leo.getNimi() + " ikä " + leo.ika() + " vuotta"); } }
Tulostus:
Verna ikä 22 vuotta Leo ikä 25 vuotta
Tee henkilölle metodi jonka avulla se vertaa ikäänsä parametrina annettuun henkilöön. Jos henkilö on vanhempi eli syntynyt aiemmin, palauttaa metodi true ja muuten false.
public class Henkilo { // ... public boolean vanhempiKuin(Henkilo verrattava) { // vertaa henklöiden ikiä käyttäen henkilöiden syntymäpäivää } }
Ja testaa laajennettua Henkilö-luokkaa esim. seuraavasti:
public class Main { public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka", 15, 2, 1983); Henkilo venla = new Henkilo("Venla", 22, 7, 2015); System.out.println(venla.getNimi() + " vanhempi kuin " + pekka.getNimi() + ": " + venla.vanhempiKuin(pekka)); System.out.println(pekka.getNimi() + " vanhempi kuin " + venla.getNimi() + ": " + pekka.vanhempiKuin(venla)); } }
Tulostus:
Venla vanhempi kuin Pekka: false Pekka vanhempi kuin Venla: true
Tee Henkilo-luokalle kaksi uutta konstruktoria:
public Henkilo(String nimi, Paivays syntymapaiva)
- jossa konstruktori käyttää annettua Paivays-oliota syntymäpäivänäpublic Henkilo(String nimi)
- jossa konstruktori määrittää syntymäpäiväksi tämänhetkisen päivänTestaa uusia konstruktoreja esim. seuraavasti:
public class Main { public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka", new Paivays(15, 2, 1983)); Henkilo iina = new Henkilo("Iina"); System.out.println(pekka); System.out.println(iina); } }
Esimerkkitulostus:
Pekka, syntynyt 15.2.1983 Iina, syntynyt 6.10.2015
Huom: jälkimmäinen rivi riippuu päivämäärästä, jolloin koodi ajetaan!
MUISTUTUS kun lisäät ohjelmaasi ArrayList:in, Scanner:in tai Random:in ei Java tunnista luokkaa ellet "importoi" sitä lisäämällä ohjelmatiedoston alkuun:
import java.util.ArrayList; // importoi ArrayListin import java.util.*; // importoi kaikki java.util:sissa olevat työkalut, mm. ArrayListin, Scannerin ja Randomin
Tehtävässä tehdään puhelinmuistio.
Tee ensin luokka Henkilo
Luokan pitää toimia seuraavan esimerkin osoittamalla tavalla:
public static void main(String[] args) { Henkilo pekka = new Henkilo("Pekka Mikkola", "040-123123"); System.out.println(pekka.haeNimi()); System.out.println(pekka.haeNumero()); System.out.println(pekka); pekka.vaihdaNumeroa("050-333444"); System.out.println(pekka); }
Tulostuu:
Pekka Mikkola 040-123123 Pekka Mikkola puh: 040-123123 Pekka Mikkola puh: 050-333444
Tee siis luokalle
public String toString()
, joka palauttaa henkilön merkkijonoesityksen (yo. esimerkin tapaan muotoiltuna)public String haeNimi()
, joka palauttaa nimenpublic String haeNumero()
, joka palauttaa puhelinnumeronpublic void vaihdaNumeroa(String uusiNumero)
, joka muuttaa henkilön puhelinnumeroaTee luokka Puhelinmuistio
joka tallettaa sisällään olevaan ArrayListiin Henkilo
-olioita. Tässä vaiheessa luokalle tehdään seuraavat metodit:
public void lisaa(String nimi, String numero)
luo Henkilo
-olion ja lisää sen puhelinmuistion ArrayListiin.public void tulostaKaikki()
, tulostaa puhelinmuistion sisällönEsimerkki muistion toiminnasta:
public static void main(String[] args) { Puhelinmuistio muistio = new Puhelinmuistio(); muistio.lisaa("Pekka Mikkola", "040-123123"); muistio.lisaa("Antti Laaksonen", "045-456123"); muistio.lisaa("Juhana Laurinharju", "050-222333"); muistio.tulostaKaikki(); }
Ohjelman tulostus oikein toteutetuilla luokilla on:
Pekka Mikkola puh: 040-123123 Antti Laaksonen puh: 045-456123 Juhana Laurinharju puh: 050-222333
Tehdään puhelinmuistiolle metodi public String haeNumero(String nimi)
, joka palauttaa parametrina annetun henkilön numeron. Jos henkilö ei ole muistiossa, palautetaan merkkijono "numero ei tiedossa". Esimerkki metodin toiminnasta:
public static void main(String[] args) { Puhelinmuistio muistio = new Puhelinmuistio(); muistio.lisaa("Pekka Mikkola", "040-123123"); muistio.lisaa("Antti Laaksonen", "045-456123"); muistio.lisaa("Juhana Laurinharju", "050-222333"); String numero = muistio.haeNumero("Pekka Mikkola"); System.out.println(numero); numero = muistio.haeNumero("Martti Tienari"); System.out.println(numero); }
Tulostuu:
040-123123 numero ei tiedossa
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.
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
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
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
Javan String-oliot ovat Raha-luokan olioiden tyyliin muuttumattomia, eli immutaabelena. Jos esim. merkkijonon perään katenoidaan eli liitetään +-operaatiolla uusi merkkijono, ei alkuperäistä merkkijonoa pidennetä vaan syntyy uusi merkkijono-olio:
String jono = "koe"; jono + "häntä"; System.out.println(jono); // koe
Merkkijonoa ei siis voi muuttaa, mutta voimme ottaa katenoimalla syntyvän uuden merkkijonon talteen vanhaan muuttujaan:
String jono = "koe"; jono = jono + "häntä"; // tai jono += "häntä"; System.out.println(jono); // koehäntä
Nyt siis muuttuja jono
viittaa uuteen merkkijono-olioon, joka luotiin yhdistämällä muuttujan aiemmin viittaama merkkijono "koe" ja merkkijono "häntä". Merkkijono-olioon "koe" ei enää viitata.
Olemme käyttäneet kurssin aikana lukemattomia kertoja ArrayList:ejä erilaisten olioiden säilömiseen. ArrayList on helppokäyttöinen sillä se tarjoaa paljon valmiita työvälineitä jotka helpottavat ohjelmoijan elämää: muun muassa automaattisen listan kasvatuksen, jonka ansioista listalta ei lopu tila kesken (ellei lista kasva niin suureksi että se käyttää loppuun ohjelmalle varatun muistimäärän).
ArrayListin helppokäyttöisyydesta huolimatta ohjelmissa on joskus tarvetta ArrayListin esi-isälle eli taulukolle.
Taulukko on olio, joka voidaan käsittää eräänlaisena järjestettynä lokerikkona arvoille. Taulukon pituus tai koko on lokerikon paikkojen lukumäärä, eli kuinka monta arvoa taulukkoon voi laittaa. Taulukon arvoja kutsutaan taulukon alkioiksi. ArrayLististä poiketen taulukon kokoa (eli sen alkioiden määrää) ei voi muuttaa, taulukon kasvattaminen vaatii siis aina uuden taulukon luomista ja vanhassa olevien alkioiden kopiointia uuteen.
Taulukon voi luoda kahdella eri tavalla. Tutustutaan ensin tapaan jossa taulukolle annetaan sisältö luomisen yhteydessä. Kolmen alkion kokonaislukutyyppinen taulukko määritellään seuraavasti:
int[] luvut = {100, 1, 42};
Taulukko-olion tyyppi merkitään int[]
, joka tarkoittaa taulukkoa, jonka alkiot ovat tyyppiä int
. Taulukko-olion nimi on esimerkissä luvut
ja se sisältää kolme lukua {100, 1, 42}
. Taulukko alustetaan lohkolla, jossa taulukkoon asetettavat arvot on esitelty pilkulla eriteltyinä.
Taulukon arvot voivat olla mitä tahansa aikaisemmin nähtyjä muuttujatyyppejä. Alla on esitelty ensin merkkijonoja sisältävä taulukko, jonka jälkeen esitellään liukulukuja sisältävä taulukko.
String[] merkkijonotaulukko = {"Matti P.", "Matti V."}; double[] liukulukutaulukko = {1.20, 3.14, 100.0, 0.6666666667};
Taulukon alkioihin viitataan indeksillä, joka on kokonaisluku. Indeksi kertoo alkion paikan taulukossa. Taulukon ensimmäinen alkio on paikassa nolla, seuraava kohdassa yksi ja niin edelleen. Taulukon tietyssä indeksissä olevaa arvoa tutkittaessa indeksi annetaan taulukko-olion nimen perään hakasulkeiden sisällä.
// indeksi 0 1 2 3 4 5 6 7 int[] luvut = {100, 1, 42, 23, 1, 1, 3200, 3201}; System.out.println(luvut[0]); // tulostaa luvun taulukon indeksistä 0, eli luvun 100 System.out.println(luvut[2]); // tulostaa luvun taulukon indeksistä 2, eli luvun 42
Yllä olevan taulukon koko (eli pituus) on 8.
Huomaat todennäköisesti että ArrayListin metodi get
käyttäytyy hyvin samalla tavalla kuin taulukon tietystä indeksistä haku. Taulukon kohdalla vain syntaksi, eli merkintätapa, on erilainen.
Yksittäisen arvon asettaminen taulukon tiettyyn paikkaan tapahtuu kuten arvon asetus tavalliseen muuttujaan, mutta taulukkoon asetettaessa paikka eli indeksi tulee kertoa. Indeksi kerrotaan hakasulkeiden sisällä.
int[] luvut = {100,1,42}; luvut[0] = 1; // asetetaan luku 1 indeksiin 0 luvut[1] = 101; // asetetaan luku 101 indeksiin 1 // luvut-taulukko on nyt {1,101,42}
Jos indeksillä osoitetaan taulukon ohi, eli alkioon jota ei ole olemassa, niin saadaan virheilmoitus ArrayIndexOutOfBoundsException, joka kertoo että indeksiä johon osoitimme ei ole olemassa. Taulukon ohi, eli indeksiin joka on pienempi kuin 0 tai suurempi tai yhtäsuuri kuin taulukon koko ei siis saa viitata.
Huomaamme, että taulukko on selvästi sukua ArrayList:ille. Aivan kuten listoilla, myös taulukossa alkiot ovat tietyssä paikassa!
Taulukko-olion koon saa selville kirjoittamalla koodiin taulukko.length
, huomaa että ilmauksessa ei tule käyttää sulkuja eli taulukko.length()
ei toimi!
Taulukon alkioiden läpikäynti on helppo toteuttaa while
-komennon avulla:
int[] luvut = {1, 8, 10, 3, 5}; int i = 0; while (i < luvut.length) { System.out.println(luvut[i]); i++; }
Esimerkissä käydään muuttujan i
avulla läpi indeksit 0, 1, 2, 3, ja 4, ja tulostetaan taulukon kussakin indeksissä olevan muuttujan arvo. Ensin siis tulostuu luvut[0]
, sitten luvut[1]
jne. Muuttujan i
kasvatus loppuu kun koko taulukko on käyty läpi, eli kun sen arvo on yhtäsuuri kuin taulukon pituus.
Taulukon läpikäynnissä ei ole aina todellista tarvetta taulukon indeksien luetteluun, vaan ainoa kiinnostava asia ovat taulukon arvot. Tällöin voidaan käyttää aiemmin tutuksi tullutta for-each-rakennetta arvojen läpikäyntiin. Nyt toistolauseen rungossa annetaan vain muuttujan nimi, johon kukin taulukon arvo asetetaan vuorollaan, ja taulukon nimi kaksoispisteellä erotettuna.
int[] luvut = {1,8,10,3,5}; for (int luku : luvut) { System.out.println(luku); }
String[] nimet = {"Juhana L.", "Matti P.", "Matti L.", "Pekka M."}; for (String nimi : nimet) { System.out.println(nimi); }
Huom: for-each-tyylisellä läpikäynnillä taulukon alkioihin ei voi asettaa arvoja! Seuraavaksi nähtävällä for-lauseen toisella muodolla sekin onnistuu.
Olemme toistaiseksi käyttäneet toistolauseissa whileä tai for-lauseen ns. "for-each"-muotoa. Toistolauseesta for on olemassa myös toinen muoto, joka on kätevä erityisesti taulukoiden käsittelyn yhteydessä. Seuraavassa tulostetaan for-toistolauseen avulla luvut 0, 1 ja 2:
for (int i = 0; i < 3; i++) { System.out.println(i); }
Esimerkin for toimii täsmälleen samalla tavalla kuin alla oleva while:
int i = 0; // toistossa käytettävän muuttujan alustus while (i < 3) { // toistoehto System.out.println(i); i++; // toistossa käytettävän muuttujan päivitys }
for-komento, kuten yllä esitelty for (int i = 0; i < 3; i++)
sisältää kolme osaa: looppimuuttujien alustus; toistoehto; looppimuuttujien päivitys:
i
lauseella int i=0
. Ensimmäinen osa suoritetaan vain kerran, juuri for:in suorituksen alussa.i < 3
. Toistoehdon voimassaolo tarkastetaan ennen jokaista for:in toistokertaa. Toistoehto toimii täsmälleen samoin kuin while:n toistoehto.i++
suoritetaan aina kertaalleen forin koodilohkon suorituksen jälkeen.for on hieman while:ä selkeämpi tapa toteuttaa toistoja joissa toistojen määrä perustuu esim. laskurin kasvatukseen. Taulukkojen läpikäynnissä tilanne on yleensä juuri tämä. Seuraavassa tulostetaan taulukon luvut
sisältö for:illa
int[] luvut = {1, 3, 5, 9, 17, 31, 57, 105}; for(int i = 3; i < 7; i++) { System.out.println(luvut[i]); }
Forilla voidaan aloittaa läpikäynti luonnollisesti muualtakin kuin nollasta ja läpikäynti voi edetä "ylhäältä alas". Esimerkiksi taulukon paikoissa 6, 5, 4, ja 3 olevat alkiot voidaan tulostaa seuraavasti:
int[] luvut = {1, 3, 5, 9, 17, 31, 57, 105}; for(int i = 6; i>2 ; i--) { System.out.println(luvut[i]); }
Kaikkien taulukon alkioiden läpikäynti for:in avulla onnistuu seuraavasti:
int[] luvut = {1, 8, 10, 3, 5}; for (int i = 0; i < luvut.length; i++) { System.out.println(luvut[i]); }
Huomaa, että toistoehdossa i < luvut.length
verrataan looppimuuttujan arvoa taulukolta kysyttyyn pituuteen. Ehtoa ei kannata missään tapauksessa "kovakoodata" tyyliin i < 5
sillä yleensä taulukon pituudesta ei ole etukäteen varmuutta.
Taulukkoja voidaan käyttää metodin parametrina aivan kuten muitakin olioita. Huomaa, että kuten kaikkien olioiden tapauksessa, metodi saa parametrina viitteen taulukkoon, eli kaikki metodissa tapahtuvat taulukon sisältöön vaikuttavat muutokset näkyvät myös pääohjelmassa.
public static void listaaAlkiot(int[] kokonaislukuTaulukko) { System.out.println("taulukon alkiot ovat: "); for(int luku : kokonaislukuTaulukko) { System.out.print(luku + " "); } System.out.println(""); } public static void main(String[] args) { int[] luvut = {1, 2, 3, 4, 5}; listaaAlkiot(luvut); }
Kuten jo tiedämme, parametrin nimi metodin sisällä voi olla aivan vapaasti valittu, nimen ei tarvitse missään tapauksessa olla sama kuin kutsuvassa. Edellä taulukkoa kutsutaan metodin sisällä nimellä kokonaislukuTaulukko
, metodin kutsuja taas näkee saman taulukon luvut
-nimisenä.
Huom: tämän tehtävän metodi samoin kuin muutamien seuraavien tehtävien taulukkoa käsittelevät metodit ovat viikkojen 2 ja 3 tapaan static
eli staattisia metodeja. Karkeasti ottaen tämä johtuu siitä, että metodi ei liity mihinkään olioon, vaan saa kaiken datan jota se käyttää eli tässä tapauksessa taulukon, parametrina. Palaamme seuraavalla viikolla tarkemmin aiheeseen staattiset vs. olioihin liittyvät metodit.
Tee metodi public static int laskeTaulukonLukujenSumma(int[] taulukko)
, joka palauttaa taulukossa olevien lukujen summan.
Ohjelman runko on seuraava:
public class Main { public static void main(String[] args) { // Tässä voit testata metodia int[] taulukko = {5, 1, 3, 4, 2}; System.out.println(laskeTaulukonLukujenSumma(taulukko)); } public static int laskeTaulukonLukujenSumma(int[] taulukko) { // Kirjoita koodia tänne return 0; } }
Ohjelman tulostus on seuraava:
15
Tee metodi public static void tulostaTyylikkaasti(int[] taulukko)
, joka tulostaa taulukossa olevat luvut tyylikkäästi. Lukujen väliin tulee pilkku ja välilyönti. Viimeisen luvun jälkeen ei pilkkua tule.
Ohjelman runko on seuraava:
public class Main { public static void main(String[] args) { // Tässä voit testata metodia int[] taulukko = {5, 1, 3, 4, 2}; tulostaTyylikkaasti(taulukko); } public static void tulostaTyylikkaasti(int[] taulukko) { // Kirjoita koodia tänne } }
Ohjelman tulostus on seuraava:
5, 1, 3, 4, 2
Jos taulukon koko ei ohjelmassa ole aina sama, eli se riippuu esim. käyttäjän syötteestä, ei äsken esitelty taulukon luontitapa kelpaa. Taulukko on mahdollista luoda myös siten, että sen koko määritellään muuttujan avulla:
int alkioita = 99; int[] taulukko = new int[alkioita];
Yllä luodaan int-tyyppinen taulukko, jossa on 99 paikkaa. Tällä vaihtoehtoisella tavalla taulukon luominen tapahtuu siis kuten muidenkin olioiden luominen, eli new
-komennolla. Komentoa new
seuraa taulukon sisältämien muuttujien tyyppi, ja hakasuluissa taulukon koko.
int alkioita = 99; int[] taulukko = new int[alkioita]; //luodaan muuttujan alkioita sisältämän arvon kokoinen taulukko if (taulukko.length == alkioita) { System.out.println("Taulukon pituus on " + alkioita); } else { System.out.println("Jotain epätodellista tapahtui. Taulukon pituus on eri kuin " + alkioita); }
Seuraavassa esimerkissä on ohjelma, joka kysyy käyttäjältä lukujen määrän ja joukon lukuja. Tämän jälkeen ohjelma tulostaa luvut uudestaan samassa järjestyksessä. Käyttäjän antamat luvut tallennetaan taulukkoon.
System.out.print("Kuinka monta lukua? "); int lukuja = Integer.parseInt(lukija.nextLine()); int[] luvut = new int[lukuja]; System.out.println("Anna luvut:"); for(int i = 0; i < lukuja; i++) { luvut[i] = Integer.parseInt(lukija.nextLine()); } System.out.println("Luvut uudestaan:"); for(int i = 0; i < lukuja; i++) { System.out.println(luvut[i]); }
Eräs ohjelman suorituskerta voisi olla seuraavanlainen:
Kuinka monta lukua? 4 Anna luvut: 4 8 2 1 Luvut uudestaan: 4 8 2 1
Koska metodit voivat palauttaa olioita, voivat ne palauttaa myös taulukkoja. Eräs merkkijonotaulukon palauttava metodi on seuraavannäköinen -- huomaa että taulukkoihin voi aivan hyvin siis laittaa myös olioita.
public static String[] annaMerkkijonoTaulukko() { String[] opet = new String[3]; opet[0] = "Bonus"; opet[1] = "Ihq"; opet[2] = "Lennon"; return opet; } public static void main(String[] args) { String[] opettajat = annaMerkkijonoTaulukko(); for (String opettaja : opettajat) { System.out.println(opettaja); } }
Tee metodi public static int[] kopioi(int[] taulukko)
joka luo kopion parametrina saadusta taulukosta. Vihje: koska metodin on luotava taulukosta kopio, tulee metodin sisällä luoda uusi taulukko ja kopioida vanhan taulukon sisältö uudelle taulukolle alkio alkiolta.
Seuraavassa esimerkki metodin käytöstä (koodissa myös Arrays-luokan tarjoama kätevä apuväline taulukon sisällön tulostamiseen):
public static void main(String[] args) { int[] alkuperainen = {1, 2, 3, 4}; int[] kopio = kopioi(alkuperainen); // muutetaan kopioa kopio[0] = 99; // tulostetaan molemmat System.out.println("alkup: " + Arrays.toString(alkuperainen)); System.out.println("kopio: " + Arrays.toString(kopio)); }
Kuten tulostuksesta huomaa, ei kopioon tehty muutos vaikuta alkuperäiseen:
alkup: [1, 2, 3, 4] kopio: [99, 2, 3, 4]
Tee metodi public static int[] kaanna(int[] taulukko)
joka luo käänteisessä järjestyksessä olevan kopion parametrinaan saamastaan taulukosta.
Eli jos parametrina on taulukko jossa esim. luvut 5, 6, 7 palauttaa metodi uuden taulukon jonka sisältönä luvut 7, 6, 5. Parametrina oleva taulukko ei saa muuttua.
Seuraavassa esimerkki metodin käytöstä:
public static void main(String[] args) { int[] alkuperainen = {1, 2, 3, 4}; int[] kaannetty = kaanna(alkuperainen); // tulostetaan molemmat System.out.println("alkup: " +Arrays.toString(alkuperainen)); System.out.println("käännetty: " +Arrays.toString(kaannetty)); }
Tulostuksesta pitäisi selvitä, että alkuperäinen taulukko on muuttumaton:
alkup: [1, 2, 3, 4] käännetty: [4, 3, 2, 1]
Kirjoita metodi public static void tulostaTaulukkoTahtina(int[] taulukko)
, joka tulostaa jokaista taulukossa olevaa lukua vastaavan pituisen rivin tähtiä.
Ohjelman runko on seuraava:
public class Main { public static void main(String[] args) { // Tässä voit testata metodia int[] taulukko = {5, 1, 3, 4, 2}; tulostaTaulukkoTahtina(taulukko); } public static void tulostaTaulukkoTahtina(int[] taulukko) { // Kirjoita tulostuskoodi tänne } }
Edellisen esimerkin mukaisella syötteellä ohjelman tulostus on seuraava:
***** * *** **** **
Eli koska taulukon nollannessa paikassa on luku 5, tulee ensimmäiselle riville 5 tähteä. Seuraavalla 1 tähti jne.
Luodaan ohjelma tähtitaivaan tulostamiseen. Tähtitaivaan tähtien määrä kerrotaan tiheyden avulla. Esimerkiksi jos tähtitaivaan tiheys on 0.2
, on noin 20% tähtitaivaasta peitettynä tähdillä. Pääset harjoittelemaan siis myös satunnaislukujen käyttöä.
Käytä tähtien tulostamiseen *
-merkkiä. Alla on esimerkki lopullisen Tahtitaivas
-luokan käytöstä ja käyttöä vastaavasti tulostuksesta.
Tahtitaivas tahtitaivas = new Tahtitaivas(0.1, 39, 10); tahtitaivas.tulosta(); System.out.println("Tähtiä: " + tahtitaivas.tahtiaViimeTulostuksessa()); System.out.println(""); tahtitaivas = new Tahtitaivas(0.2, 15, 6); tahtitaivas.tulosta(); System.out.println("Tähtiä: " + tahtitaivas.tahtiaViimeTulostuksessa());
* * * * * * * ** * * * * * * * * * * * * * * * * * * * ** * * * * * * Tähtiä: 36 * * * * * * * * * * * * * * * * * ** ** * Tähtiä: 22
Huom! tehtävissä kannattaa käyttää for
-lauseketta. Vaikka edellinen luku puhuukin sisäkkäisistä toistolauseista, tässä tehtävässä "sisempi" toisto piilotetaan metodin sisälle.
Luo luokka Tahtitaivas
, jolla on kolme oliomuuttujaa: tiheys (double
), leveys (int
), ja korkeus (int
). Luo luokalle myös kolme konstruktoria:
public Tahtitaivas(double tiheys)
Luo tähtitaivas-olion, jolla on parametrina annettu tiheys, leveys saa arvon 20
, korkeus saa arvon 10
.public Tahtitaivas(int leveys, int korkeus)
Luo tähtitaivas-olion, jolla on parametrina annetut leveys ja korkeus, tiheys saa arvon 0.1
.public Tahtitaivas(double tiheys, int leveys, int korkeus)
Luo tähtitaivas-olion, jolla on parametrina annetut tiheys, leveys ja korkeus.Lisää luokalle Tahtitaivas
metodi tulostaRivi
, joka tulostaa yhden rivin. Rivin leveyden määrää oliomuuttuja leveys
. Oliomuuttuja tiheys
kertoo todennäköisyyden tähdelle. Arvo jokaisen merkin kohdalla tulostetaanko tähti vai ei Random
-luokan nextDouble
-metodin avulla.
Testaa ohjelmaasi, esimerkkinä seuraava kutsu ja esimerkkitulostus.
Tahtitaivas tahtitaivas = new Tahtitaivas(0.1); tahtitaivas.tulostaRivi();
* * *
Luo Tahtitaivas
-luokalle metodi tulosta
, joka tulostaa koko tähtitaivaan. Käytä tässä hyödyksesi aiempaa tulostaRivi
-metodia.
Tahtitaivas tahtitaivas = new Tahtitaivas(8, 4); tahtitaivas.tulosta();
* * *
Lisää Tahtitaivas
-luokalle oliomuuttuja tahtiaViimeTulostuksessa (int
) ja metodi tahtiaViimeTulostuksessa()
, joka palauttaa viime tulostuksessa tulostuneiden tähtien lukumäärän. Toteuta ohjelmaasi tähtien laskeminen.
Tahtitaivas tahtitaivas = new Tahtitaivas(8, 4); tahtitaivas.tulosta(); System.out.println("Tähtiä: " + tahtitaivas.tahtiaViimeTulostuksessa()); System.out.println(""); tahtitaivas.tulosta(); System.out.println("Tähtiä: " + tahtitaivas.tahtiaViimeTulostuksessa());
* Tähtiä: 1 * * * Tähtiä: 3
Taulukon voi luoda myös kaksiulotteisena. Tämä on kätevää esimerkiksi silloin, jos haluamme ylläpitää sijainteja koordinaatistossa. Kaksiulotteisen taulukon, jossa on kaksi riviä ja kolme saraketta, tapahtuu seuraavasti:
int rivit = 2; int sarakkeet = 3; int[][] kaksiulotteinenTaulukko = new int[rivit][sarakkeet];
Yllä luomme taulukon, jonka jokainen rivi sisältää uuden taulukon, jossa on tietty määrä sarakkeita. Kaksiulotteisen taulukon läpikäynti onnistuu esimerkiksi kahden sisäkkäisen for-toistolauseen avulla seuraavasti:
int rivit = 2; int sarakkeet = 3; int[][] kaksiulotteinenTaulukko = new int[rivit][sarakkeet]; for (int y = 0; y < kaksiulotteinenTaulukko.length; y++) { for (int x = 0; x < kaksiulotteinenTaulukko[y].length; x++) { System.out.println("arvo kohdassa (" + x + ", " + y + "): " + kaksiulotteinenTaulukko[y][x]); } }
Ylläolevan ohjelman tulostus on seuraavan, sillä int-tyyppisiä muuttujia sisältävä taulukko alustaa solujen arvon oletuksena nollaksi.
arvo kohdassa (0, 0): 0 arvo kohdassa (1, 0): 0 arvo kohdassa (2, 0): 0 arvo kohdassa (0, 1): 0 arvo kohdassa (1, 1): 0 arvo kohdassa (2, 1): 0
Voimme muuttaa taulukon arvoja kuten ennenkin. Alla asetamme kahteen kohtaan uudet arvot.
int rivit = 2; int sarakkeet = 3; int[][] kaksiulotteinenTaulukko = new int[rivit][sarakkeet]; kaksiulotteinenTaulukko[0][1] = 4; kaksiulotteinenTaulukko[1][1] = 1; kaksiulotteinenTaulukko[1][0] = 8; for (int y = 0; y < kaksiulotteinenTaulukko.length; y++) { for (int x = 0; x < kaksiulotteinenTaulukko[y].length; x++) { System.out.println("arvo kohdassa (" + x + ", " + y + "): " + kaksiulotteinenTaulukko[y][x]); } }
Nyt tulostus näyttää seuraavalta:
arvo kohdassa (0, 0): 0 arvo kohdassa (1, 0): 4 arvo kohdassa (2, 0): 0 arvo kohdassa (0, 1): 8 arvo kohdassa (1, 1): 1 arvo kohdassa (2, 1): 0
Kaksiulotteinen taulukko on oikeastaan myös matriisi. Matriiseja käytetään muunmuassa tietokonegrafiikassa. Esimerkiksi kuviin yksittäiset pikselit (esimerkiksi kuvan punainen, sininen ja vihreä väri kohdassa (x, y)) esitetään usein matriisin avulla.
Taikaneliöt ovat kokonaisluvuista järjestettyjä neliöitä, joiden jokaisen rivin, sarakkeen ja lävistäjän summa on sama. Harjoitellaan taulukoiden käyttöä taikaneliöiden yhteydessä.
Ohjelmassa on annettu osittain toteutettu luokka Taikanelio
, jota voidaan käyttää lähtökohtana. Tehtävänäsi on ensin lisätä luokkaan toiminnallisuutta, jolla tarkistetaan onko neliö taikaneliö. Tämän jälkeen toteutat algoritmin taikaneliön luomiseen.
Luokassa Taikanelio on valmiina metodi public ArrayList<Integer> rivienSummat()
, joka palauttaa tyhjän ArrayList-olion. Muuta metodin toiminnallisuutta siten, että se palauttaa listan, jossa on jokaisen taikaneliön rivin summa.
Esimerkiksi seuraavanlaisella taikaneliöllä rivienSummat-metodin pitäisi palauttaa lista, jossa on luvut 15, 15, 15
.
8 1 6 3 5 7 4 9 2
Vaikka taikaneliö ei olisi "oikea" taikaneliö, tulee rivien summat silti palauttaa. Allaolevalla esimerkillä rivienSummat-metodin pitäisi palauttaa lista, jossa on luvut 6, 15, 24
.
1 2 3 4 5 6 7 8 9
Luokassa Taikanelio on valmiina metodi public ArrayList<Integer> sarakkeidenSummat()
, joka palauttaa tyhjän ArrayList-olion. Muuta metodin toiminnallisuutta siten, että se palauttaa listan, jossa on jokaisen taikaneliön sarakkeen summa.
Esimerkiksi seuraavanlaisella taikaneliöllä sarakkeidenSummat-metodin pitäisi palauttaa lista, jossa on luvut 15, 15, 15
.
8 1 6 3 5 7 4 9 2
Vaikka taikaneliö ei olisi "oikea" taikaneliö, tulee sarakkeiden summat silti palauttaa. Allaolevalla esimerkillä sarakkeidenSummat-metodin pitäisi palauttaa lista, jossa on luvut 12, 15, 18
.
1 2 3 4 5 6 7 8 9
Toteuta seuraavaksi metodi public ArrayList<Integer> lavistajienSummat()
, joka palauttaa listan, jossa on taikaneliön lävistäjien summat.
Esimerkiksi seuraavanlaisella taikaneliöllä lavistajienSummat-metodin pitäisi palauttaa lista, jossa on luvut 15, 15
(8 + 5 + 2) ja (4 + 5 + 6).
8 1 6 3 5 7 4 9 2
Vaikka taikaneliö ei olisi "oikea" taikaneliö, tulee lävistäjien summat silti palauttaa. Allaolevalla esimerkillä lavistajienSummat-metodin pitäisi palauttaa lista, jossa on luvut 15, 15
(1 + 5 + 9) ja (7 + 5 + 3).
1 2 3 4 5 6 7 8 9
Huom! Tämä tehtävä on melko visainen, kannattanee palauttaa edelliset osat ennen tämän aloitusta.
Taikaneliön pystyy myös luomaan. Tutustutaan Siamese method-menetelmään, jonka avulla voidaan luoda parittomien lukujen kokoisia taikaneliöitä.
Siamese method -algoritmi toimii siten, että numero yksi asetetaan ylimmän rivin keskimmäiseen sarakkeeseen. Tämän jälkeen siirrytään yksi ylös ja yksi oikealle ja asetetaan luku kaksi. Tämän jälkeen taas siirrytään yksi ylös ja yksi oikealle, ja asetetaan luku kolme jne.
Lukujen lisäämiseen liittyy kaksi sääntöä:
Toteuta parittomien taikalukujen luominen luokan Taikaneliotehdas
metodiin luoTaikanelio
. Metodin tarvitsee toimia vain tilanteissa, missä neliön leveys on pariton luku.