Pakolliset tehtävät on merkitty harmaalla taustavärillä. Pakollisuus tarkoittaa, että kyseiset tehtävät ovat erityisen oleellisia ja niiden tekeminen on hyvin suositeltavaa. Jos joskus jokin "pakollinen" tehtävä jää tekemättä, kurssi ei kuitenkaan kaadu siihen.
Teemme ohjelman, jossa käyttäjä voi liikutella näppäimistön avulla ruudulle piirrettyjä kuvioita.
Aluksi tehdään muutama luokka jolla kuvioita hallitaan. Tehtävän osassa 1.3 päästään kuvioita piirtämään ruudulle.
Tee abstrakti luokka Kuvio
. Kuviolla on
attribuutit x
ja y
, jotka kertovat kuvion
sijainnin ruudulla sekä metodi void siirra(int dx, int
dy)
jonka avulla kuvion sijainti siirtyy parametrina olevien
kordinaattisiirtymien verran. Esim. jos sijainti aluksi on
(100,100), niin kutsun siirra(10,-50)
jälkeen sijainti
on (110, 50). Luokan konstruktori asettaa kuviolle
alkusijainnin.
Luokalla on myös abstrakti metodi public
abstract void piirra(Graphics g);
jolla kuvio piirretään
ruudulle. Kuvion piirtämismetodia tulee kutsumaan piirtoalustana
käytettävän JPanel:in paint()
-metodi. Piirtoalusta tehdään kohdassa 1.3
Tee luokka Ympyra
joka perii Kuvion. Ympyrällä on halkaisija
jonka arvo asetetaan konstruktorissa. Konstruktorissa asetetaan myös alkuperäinen sijainti. Ympyra määrittelee metodin piirra
asiaan kuuluvalla tavalla, eli kutsumalla parametrina saamansa Graphics
-olion sopivaa metodia.
Kertaa monisteen lukuja 12 ja 16 jos et muista mitä kaikkea abstraktin luokan perimiseen liittyy.
Käytetään aluksi seuraavaa pääohjelmaa:
public static void main(String[] args) { Kuvio kuvio = new Ympyra(50, 50, 30); Piirtoalusta piirtoalusta = new Piirtoalusta(kuvio); JFrame ikkuna = new JFrame(); Container container = ikkuna.getContentPane(); container.add(piirtoalusta); ikkuna.setSize(480, 360); ikkuna.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); ikkuna.setVisible(true); }
Luo luokka Piirtoalusta
joka perii luokan JPanel
, mallia voit ottaa materiaalin luvusta 20.3. Piirtoalusta saa konstruktorin parametrina Kuvio
-tyyppisen olion. Tee Piirtoalustalle paint(Graphics g)
-metodi, jossa se pyytää konstruktiorin välityksellä saamaansa kuvioa piirtämään itsensä, eli kutsuu kuvion piirra
-metodia.
Testaa että ruudulle piirtyy ympyrä.
Laajenna piirtoalustaa siten, että kuvioa voi liikutella nuolinäppäinten avulla. Materiaalin luvusta 20.4 ja viime viikon tehtävästä 2 on tässä apua.
Peri luokasta Kuvio
luokat Nelio
ja Laatikko
. Neliölle asetetaan konstruktorissa alkusijainnin lisäksi sivun pituus. Laatikolla on korkeus ja leveys.
Varmista, että neliöt ja laatikot piirtyvät ja liikkuvat oikein Piirtoalustalla.
Peri luokasta Kuvio
luokka KoosteKuvio
. Koostekuvio sisältää joukon muita kuvioita jotka se tallettaa ArrayList:iin. Koostekuviolla on metodi public void liita(Kuvio k)
jonka avulla koostekuvioon voi liittää kuvio-olion. Koostekuviolla ei ole omaa sijaintia. Koostekuvio piirtää itsensä pyytämällä osiaan piirtämään itsensä, koostekuvion siirtyminen tapahtuu samoin.
Testaa että koostekuviosi piirtyy ja siirtyy oikein, esim. seuraavan koostekuvion avulla:
Kuvio y1 = new Ympyra(90, 100, 50); Kuvio y2 = new Ympyra(95, 105, 40); KoosteKuvio takaRengas = new KoosteKuvio(); takaRengas.liita(y1); takaRengas.liita(y2); Piirotalusta piirtoalusta = new Piirotalusta(takaRengas); // ...
Linja-autolla on kaksi rengasta. Molemmat ovat kahdesta ympyrästä muodostuvia koostekuvioita. Linja-autolla on myös runko ja ainakin yksi ikkuna. Linja-auto on siis koostekuvio joka koostuu useasta kuviosta joista osa on koostekuvioita. Luo linja-auto ja liikuttele sitä ruudulla.
Huomaa miten olioiden vastuut jakautuvat tehtävässä. Jokainen Kuvio on vastuussa itsensä piirtämisestä ja siirtämisestä. Yksinkertaiset kuviot siirtyvät kaikki samalla tavalla. Jokaisen yksinkertaisen kuvion on itse hoidettava piirtymisestään. KoosteKuvio siirtää itsensä pyytämällä osiaan siirtymään, samoin hoituu koostekuvion piirtyminen. Piirtoalusta tuntee Kuvio-olion joka siis voi olla mikä tahansa yksinkertainen kuvio tai koostekuvio, kaikki piirretään ja siirretään samalla tavalla. Piirtoalusta siis toimii samalla tavalla kuvan oikeasta tyypistä huolimatta, piirtoalustan ei tarvitse tietää kuvion yksityiskohdista mitään. Kun piirtoalusta kutsuu kuvion metodia piirra
tai siirra
polymorfismin ansiosta kutsutuksi tulee kuvion todellista tyyppiä vastaava metodi.
Huomionarvoista tehtävässä on se, että KoosteKuvio voi sisältää mitä tahansa Kuvio-olioita, siis myös koostekuvioita! Luokkarakenne mahdollistaakin mielivaltaisen monimutkaisen kuvion muodostamisen ja kuvion siirtely ja piirtäminen tapahtuu aina täsmälleen samalla tavalla.
Luokkarakennetta on myös helppo laajentaa, esim. perimällä Kuvio-luokasta uusia kuviotyyppejä: kolmio, piste, viiva, ym... KoosteKuvio toimii ilman muutoksia myös uusien kuviotyyppien kanssa, samoin piirtoalusta.
Monimutkaisen kuvion, kuten linja-auton tekeminen vaatii monia koodirivejä ja koodi uhkaa muuttua ikäväksi luettavuudeltaan. Kuvion rakentaminen kannattaakin siirtää erillisen luokan vastuulle. Tee luokka Tehdas
, jolla on metodi public static Kuvio teeLinjaAuto(int x, int y)
, joka muodostaa ja palauttaa linja-autoa vastaavan kuvion, jonka vasen ylänurkka on parametrien määräämässä pisteessä.
Tehdasta käytetään seuraavalla tavalla:
Kuvio linjaAuto = Tehdas.teeLinjaAuto(50, 100); Piirotalusta piirtoalusta = new Piirotalusta(linjaAuto);
Voit tehdä tehtaaseesi myös muita metodeja, esim. public static Kuvio teeKaksiOvinenHenkiloAuto(int x, int y)
. Tämän tyylisiä "oliotehtaita" käytetään olio-ohjelmoiniissa hyvin yleisesti. Viikon 4 tehtävän 6.3 luokka Iteraattirit
toimi itseasiassa täysin samaa periaatetta noudattaen, eli se tarjosi metodeinaan "tehtaita", joiden avulla pystyttiin luomaan erilaisia Iterable
-rajapinnan toteuttavia olioita.
Viime viikolla teimme vaalikoneen rungon. Nyt jatkamme ohjelman tekemistä. Jos et ehtinyt tekemään tehtävää viime viikolla, niin voit ottaa pohjaksesi täältä löytyvän version. Tehtävät 1.1. ja 1.2. kannattaa tehdä samanaikaisesti.
Toistaiseksi vaalikoneen toimintalogiikka on vähintäänkin arvelluttavaa, sillä ehdokkaan valintalogiikka nojaa täysin tietokoneen kykyyn lukea käyttäjän ajatuksia. Lisätään vaalikoneeseen liukurivalitsin, jonka avulla käyttäjä voi kertoa vaalikoneelle poliittisen kantansa. Tavoitteena on tehdä seuraavan kuvan kaltainen ohjelma.
Tarkoituksena on luoda komponentti, jota voidaan käyttää samaan tapaan kuin
jo ennestään käytettyjä Swingin komponentteja (esimerkiksi JLabel
).
Swingin JPanel
sopii piirtämisen lisäksi myös tämänkaltaiseen tarkoitukseen. Siihen
voidaan lisätä komponentteja lähes vastaavaan tapaan kuin JFrame
en
käyttämällä JPanel
in add()
-metodia.
Vaalikoneeseen luodaan Valitsin
-komponentti periyttämällä
se JPanel
-luokasta. Siihen on tarkoitus asetella
yksi liukurivalitsin (JSlider
)
sekä valintaa kuvaava tekstikenttä (JLabel
).
Luokka ohjelmoidaan seuraavaan luokkarunkoon.
public class Valitsin extends JPanel { /* Tähän olion kentät, eli JSliderin ja JLabelin ilmentymät */ /* Konstruktori luo JSliderin sekä JLabelin ja asettelee ne perittyyn JPaneliin. */ public Valitsin(String viesti) { super(new BorderLayout()); /* Loput konstruktorista */ } /* Metodi palauttaa JSliderin arvon normalisoituna se välille 0..1. */ public double arvo() { /* Kutsu JSliderin metodia getValue() ja normalisoi * se välille [0...1] metodien getMaximum() ja getMinimum() avulla */ } }
Edellisen viikon materiaalissa käytettiin esimerkkinä BorderLayout
ia.
Jotta sen käyttäminen onnistuisi JPanel
in tapauksessa, pitää
JPanelille antaa konstruktorissa BorderLayout
in ilmentymä.
Tutustu JSlider
-luokan
dokumentaatioon selvittääksesi millaisia konstruktoreita
JSlider
illa on ja mikä niistä on sopivin tähän tapaukseen.
Toteuta ylläolevaan luokkarunkoon Valitsin
-luokka ja lisää
sille sopivat import
it sekä pakkausmääreet.
Tässä tehtävässä lisätään Valitsin
-luokan ilmentymä
vaalikoneen pääikkunaan.
Lisäksi muutetaan EhdokasValitsin
-luokkaa siten,
että se huomioi lisätyssä valitsimessa asetetun mielipiteen.
Oletusarvoisesti JFrame-luokassa (siten myös Vaalikoneessa!) käytettävä
BorderLayout
ei oikein
taivu usean päällekkäin asetellun komponentin näyttämiseen.
Javassa on useita erilaisia mahdollisuuksia vaikuttaa elementtien sijoitteluun
Swingin säiliöissä. Jos tahdot tutustua erilaisiin sijoittelutapoihin
Swingissä, niin
tämän linkin takaa löytyy kattava opas. Eräs sopiva layout on
javax.swing.BoxLayout
ia. BoxLayout
ia käytetään
hiukan eri tavalla kuin BorderLayout
ia:
// Konstruktorissa pitää asettaa käytössä oleva säiliö BoxLayoutille: Container pane = getContentPane(); BoxLayout boxlayout = new BoxLayout(pane, BoxLayout.Y_AXIS); pane.setLayout(boxlayout); // Lisättäessä objekteja ne voidaan tasata seuraavasti JLabel tekstikentta = new JLabel("aloitusteksti"); tekstikentta.setAlignmentX(Component.CENTER_ALIGNMENT); pane.add(tekstikentta);
Asettele vaalikoneeseen muutama Valitsin
-komponentti. Käytä
haluamaasi Layout
-luokkaa asetellaksesi valitsimet järkevästi.
Muuta myös EhdokasValitsin
-luokkaa ottamaan valitsimesta
saadut arvot huomioon.
Tee luokka Ehdokas
, joka tallettaa ehdokkaan nimen ja
poliittiset mielipiteet. Sopiva talletusrakenne ehdokkaan mielipiteiden
talletukseen on HashMap<String, Double>
. Mielipiteitä
voidaan etsiä niiden merkkijono-esitysten perusteella. Mahdollinen
tapa käyttää Ehdokas
-luokkaa olisi seuraavanlainen:
Ehdokas aku = new Ehdokas("Aku Ankka"); aku.lisaaMielipide("Perustulo", 0.99); aku.lisaaMielipide("Eläkeikää korotettava", 0.11); System.out.println(aku);
Tällöin ohjelma voisi tulostaa:
Aku Ankka Perustulo: 0.99 Eläkeikää korotettava: 0.11
Lisää EhdokasValitsin
-luokkaan metodi
void lisaaEhdokas(Ehdokas e)
, jonka avulla ehdokasvalitsimeen
voidaan tallettaa ehdokkaita. Ehdokkaiden tallettamiseen voit käyttää
haluamaasi tapaa.
Kuormita ehdokasvalitsimen annaEhdokas()
-metodi siten, että se
ottaa nyt parametrinaan tyyppiä HashMap<String, Double>
olevan olion, jonka avulla valitaan ehdokas, jonka mielipiteet ovat lähimpänä
annettussa HashMapissa olevia mielipiteitä. Ehdokasvalitsinta voitaisiin
käyttää seuraavaan tapaan:
EhdokasValitsin eValitsin = new EhdokasValitsin(); eValitsin.lisaaEhdokas(aku); eValitsin.lisaaEhdokas(roope); HashMap<String, Double > mielipiteet = new HashMap<String, Double >(); mielipiteet.put("Perustulo", 0.5); mielipiteet.put("Eläkeikää korotettava", 0.7); System.out.println(eValitsin.annaEhdokas(mielipiteet));
Jolloin ohjelma voisi tulostaa:
Roope Ankka Perustulo: 0.01 Eläkeikää korotettava: 0.99
Sopivin ehdokas kannattaa etsiä laskemalla "etäisyys" mielipiteiden välillä. Tällöin etsittäessä sopivinta ehdokasta voidaan laskea ero kaikkiin valitsimeen talletettuihin mielipiteisiin ja sen jälkeen palauttaa ehdokas, jonka mielipite on lähimpänä parametrina annettua. Eräs mahdollinen etäisyys mielipiteille olisi mielipiteiden erotusten itseisarvojen summa, joka olisi esimerkissämme.
Roope Ankka: (0.5 - 0.01) + (0.99 - 0.7) = 0.49 + 0.29 = 0.78
Aku Ankka: (0.99 - 0.5) + (0.7 - 0.11) = 0.49 + 0.59 = 1.08
Esimerkeissä laskettiin ensin erotus perustuloa koskevista mielipiteistä ja sen jälkeen eläkeiän nostoa koskevista mielipiteistä.
Nyt mielipiteiden etäisyyden laskeminen on hiukan kankeahkoa. Informaatioteoriassa on käytössä Kullback-Leibler divergenssi, joka määrittelee eräänlaisen etäisyyden kahden todennäköisyysjakauman välille. Jos P ja Q ovat todennäköisyysjakaumia, niin niiden Kullback-Leibler divergenssi lasketaan kaavalla:
Käytä Kullback-Leibler divergenssiä mielipiteiden erotusten laskemiseen. Tällöin sinun pitää normalisoida mielipiteen kohdat siten, että niiden summa on 1. Huomaa, että jos jokin mielipide on arvoltaan 0, tulee KL-divergenssin arvoksi äärettömyys. Näistä kannattaa myös hommautua eroon jollain sopivalla tavalla.
Aseta käyttöliittymäsi käyttämään uutta EhdokasValitsimen
annaEhdokas
-metodia. Pystyäksesi käyttämään sitä mahdollisimman
helposti kannattaa lisätä Valitsin
-luokkaan sopiva apumetodi
mielipiteen tekstimuotoisen kuvauksen kyselemiseen.
Samalla kannattaa tallettaa kaikki Vaalikone
-luokan valitsimet
johonkin sopivaan säiliöön (esimerkiksi ArrayList
), jolloin
niiden läpikäyminen on helppoa. Tämän jälkeen "Anna ehdokas"-napin
painaminen johtaa siihen, että Valitsimiin talletetut arvot talletetaan
HashMap
piin ja syötetään sen jälkeen toimintalogiikasta
vastaavalle EhdokasValitsin
-luokalle, luokalle joka
palauttaa sopivimman ehdokkaan.
Tälläisenään vaalikone on vielä hiukan vaivalloinen käyttää, sillä
jokainen vaalikoneessa oleva ehdokas on määriteltävä Java-koodina.
Tee EhdokasValitsin
-luokkaan metodit
void alustaTiedostosta(String tiedostonNimi)
ja
TreeSet<String> mielipiteet()
. Ensimmäinen
metodi lukee ehdokkaat ja heidän mielipiteensä tiedostosta.
Tiedosto voi näyttää seuraavalta:
Roope Ankka Perustulo 0.01 Eläkeikää korotettava 0.99 Aku Ankka Perustulo 0.99 Eläkeikää korotettava 0.11 Pelle Peloton Perustulo 0.77 Eläkeikää korotettava 0.33
Halutessasi voit muokata tiedoston muotoilua helpommin käsiteltävään muotoon.
Jälkimmäinen metodi mielipiteet
palauttaa joukon, jossa on
lueteltuna kaikki tiedostossa esiintyneet mielipiteet.
TreeSet
on eräs Javan valmis toteutus joukolle. Sen etuna
tavalliseen listaan on se, että siihen ei tallennu duplikaatteja.
Sitä käytetään seuraavaan tapaan:
TreeSet<String> joukko = new TreeSet<String>(); joukko.add("eka"); joukko.add("toka"); joukko.add("kolmas"); joukko.add("toka"); joukko.add("eka"); for(String sana : joukko) { System.out.println(sana); }
Ohjelma tulostaa:
eka kolmas toka
Huomaa tulostuksesta, että TreeSet
tallettaa sen
alkiot järjestykseen eikä salli duplikaatteja.
Muokkaa Vaalikone
-luokkaa siten, että se kysyy
EhdokasValitsin
-luokalta mitä mielipiteitä ehdokkailla
on, ja luo sitten näitä mielipiteitä vastaavat valitsimet.
Mielipiteet voidaan kysyä käyttämällä ehdokasvalitsimen
mielipiteet()
-metodia.
Tämän muutoksen jälkeen
vaalikoneen pitäisi osata toimia täysin sille annetun ehdokaslistauksen
perusteella. Kokeile ohjelmaasi listaamalla ehdokkaille useita mielipiteitä.
Kokeile myös miten ohjelmasi käyttäytyy, jos jollain ehdokkaalla ei ole
kaikkia mahdolisia mielipiteitä.
Matopeli on tullut tutuksi mm. Nokian kännyköistä. Puolassa opiskelijat ovat muuttaneet kerrostalon matopeliksi:
Matopelin ruudukon tallentamiseen soveltuu hyvin kaksiulotteinen taulukko. Matopeliin sopiva kaksiulotteinen taulukko voidaan määritellä seuraavasti:
int[][] ruudukko = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
Tässä luku 0 tarkoittaa tyhjää ruutua,
luku 1 tarkoittaa madon osaa ja
luku 2 tarkoittaa aarretta.
Taulukon lukuihin voi viitata merkinnällä
ruudukko[rivi][sarake]
.
Esimerkiksi ruudukko[3][1]
tarkoittaa rivin 3 saraketta 1,
joka sisältää tässä ruudukossa aarteen (luku 2).
Tässä taulukon korkeus on 10 alkiota ja leveys 15 alkiota.
Nämä tiedot saadaan myös kirjoittamalla
ruudukko.length
(korkeus) ja
ruudukko[0].length
(leveys).
Tee matopelin ensimmäinen versio,
jossa paint
-metodi piirtää näkyviin
peliruudukon taulukon ruudukko
sisällön mukaisesti.
Ota mallia viime viikon nopeuspelin pohjasta.
Seuraavat komennot ovat hyödyllisiä:
g.setColor(Color.BLUE); // värin vaihtaminen
g.drawRect(x, y, leveys, korkeus); // suorakulmion ääriviivat
g.fillRect(x, y, leveys, korkeus); // täytetty suorakulmio
Pelin tulisi nyt näyttää suunnilleen tällaiselta:
Huom! Sinun ei tarvitse muokata paint
-metodia
tulevissa tehtävissä, vaan riittää,
että se piirtää ruudukon sisällön.
Pelin toiminnallisuus tulee muihin metodeihin,
jotka eivät ole yhteydessä kuvan piirtämiseen.
Madon tulisi liikkua niin, että joka hetkellä madon pää siirtyy ruudun edemmäs ja madon hännästä poistuu yksi ruutu. Tällaisen liikkumisen toteuttamiseen on kuin luotu tietorakenne jono.
Matoon kuuluvat ruudut on näppärää tallentaa seuraavanlaisiin olioihin:
public class Kohta { public int y; public int x; public Kohta(int y, int x) { this.y = y; this.x = x; } }
Matoa vastaavan jonon voi tallentaa esimerkiksi
javan LinkedList
-rakenteeseen.
Määrittely on tällöin seuraava:
LinkedList<Kohta> mato = new LinkedList<Kohta>();
Alussa madon ruudut voidaan siirtää jonoon seuraavasti:
mato.addFirst(new Kohta(6, 7)); mato.addFirst(new Kohta(5, 7)); mato.addFirst(new Kohta(4, 7)); mato.addFirst(new Kohta(3, 7));
Nyt jonon sisältö on seuraava:
(3, 7) -> (4, 7) -> (5, 7) -> (6, 7)
Seuraavat komennot siirtävät matoa askeleen ylöspäin:
// hännän poistaminen Kohta vanha = mato.removeLast(); // uuden ruudun lisääminen: mato.addFirst(new Kohta(2, 7));
Nyt jonon sisältö on seuraava:
(2, 7) -> (3, 7) -> (4, 7) -> (5, 7)
Toteuta peliin ajastimen ja jonon avulla
madon liikkuminen. Aluksi riittää,
että mato liikkuu koko ajan ylöspäin.
Suunnittele koodi niin,
että jos madon pää törmää yläreunaan,
se siirtyy sieltä alareunaan.
Muista tehdä myös vastaavat muutokset
taulukkoon ruudukko
,
jotta pelin tilanne päivittyy oikein.
Lisää peliin näppäimistön käsittely ja toteuta muutkin madon liikkumissuunnat (vasemmalle, alas ja oikealle). Huomaa, että mato ei voi tehdä täyskäännöstä: jos mato liikkuu ylöspäin, se voi kääntyä vasemmalle ja oikealle mutta ei alaspäin.
Pelin tulisi päättyä, jos mato törmää itseensä. Toteuta peliin tämä ominaisuus.
Lisää peliin myös aarteiden käsittely: jos mato törmää aarteeseen, mato pidentyy ja uusi aarre arvotaan sellaiseen ruudukon kohtaan, joka on tällä hetkellä tyhjänä.
Lisää peliin pistelaskuri (aina kun mato syö aarteen, pisteet karttuvat) sekä ennätyslista.
Muuta peli kaksinpeliksi: matoja onkin kaksi. Valitse pelaajille sopivat näppäimet.
Toteuta peliin vielä jokin oma ominaisuus. Tällainen voisi olla esimerkiksi seinä (jos mato törmää seinään, peli päättyy), teleportti (mato siirtyy sen kautta toiseen kohtaan ruudukossa) tai bonusaarteet.
Javassa metodi voi palautaa vain yhden arvon. Toisinaan olisi kätevää, jos metodi voisi palauttaa vaikka kaksi arvoa ilman, että näiden säilyttämiseen pitää määritellä uutta luokkaa.
Tee geneerinen luokka Pari<T1, T2>
.
Pariluokka ottaa siis kaksi tyyppiparametria, T1
ja T2
.
Esimerkiksi kokonaisluvun ja merkkijonon sisältävän parin tyyppi olisi
Pari<Integer, String>
.
Parissa tulisi olla kaksi public-kenttää: eka
ja
toka
. Tee parille myös kaksiparametrinen konstruktori,
joka alustaa nämä kentät.
Seuraavan koodinpätkän pitäisi toimia:
Pari<String, Integer> nimiJaIka = new Pari<String, Integer>("Ville", 5); nimiJaIka.toka += 1; System.out.println(nimiJaIka.eka); System.out.println(nimiJaIka.toka);
Huomaa, että tyyppiparametreina ei valitettavasti
voi käyttää alkeistyyppejä int
, boolean
, ...
vaan on käytettävä niiden olioversioita Integer
,
Boolean
, ...
Tee pariluokkaan seuraavat metodit:
luo
,
joka ottaa kaksi parametria ja luo niistä parin.
kaannettyna
, joka palauttaa parista kopion,
jossa eka ja toka ovat toisin päin.
Pari<String, Integer> nimiJaIka = Pari.luo("Ville", 5); Pari<Integer, String> ikaJaNimi = nimiJaIka.kaannettyna();
Joskus olisi kätevää voida välittää metodeja parametreina.
Javassa tämä ei kuitenkaan (ainakaan vielä) onnistu suoraan,
vaan parametrina on annettava rajapinta, joka vaatii sopivan metodin.
Esimerkki tästä on käyttöliittymien nappuloille välitettävä
ActionListener
, jossa on actionPerformed
-metodi.
Tehdään nyt yleispätevä abstraktio yksiparametriselle funktiolle:
public interface Funktio<X, Y> { public Y laske(X x); }
Javassa voi määritellä rajapinnan toteutuksen suoraan kirjoittamalla
new Rajapinta() { [...metodien toteutukset...] }
.
Tämä on kätevää funktioiden määrittelyssä:
Funktio<Integer, Integer> kaksinkertaista = new Funktio<Integer, Integer>() { public Integer laske(Integer x) { return 2*x; } }; System.out.println(kaksinkertaista.laske(4));
Toteuta harjoituksen vuoksi pari funktiota.
Tee luokka Funktiot
ja luo sinne staattinen metodi
yhdista
, joka ottaa kaksi funktiota f
ja
g
ja palauttaa niiden yhdisteen h
,
jolle pätee h(x) = g(f(x))
.
Metodisi tulisi toimia kaikilla yhteensopivilla funktiotyypeillä.
Esimerkiksi seuraavan pitäisi toimia:
Funktio<Integer, String> merkkijonoksi = new Funktio<Integer, String>() { public String laske(Integer x) { return ""+x; } }; Funktio<String, Character> viimeinenMerkki = new Funktio<String, Character>() { public Character laske(String x) { return x.charAt(x.length()-1); } }; Funktio<Integer, Character> viimeinenNumero = Funktiot.yhdista(merkkijonoksi, viimeinenMerkki); System.out.println(viimeinenNumero.laske(12345));
Tyyppiä Funktio<Double, Double>
olevalla funktiolla
voidaan esittää lukiomatematiikassa käsiteltyjä tavallisia reaalilukufunktioita,
joiden kuvaajia piirrettiin ahkerasti.
Tee luokka Piirtopinta extends JPanel
, joka piirtää siihen
lisätyt funktiot koordinaatistoon.
Tee piirtopintaan metodi
public void lisaaFunktio(Funktio<Double, Double> f, Color vari)
,
jolla piirrettäviä funktioita lisätään.
Piirrä käyrät väliltä -5 ≤ x ≤ 5, -5 ≤ y ≤ 5.
Käyrät voi piirtää piste pisteeltä esimerkiksi piirtämällä paljon pieniä
ympyröitä komennolla g.drawOval(x, y, 1, 1)
.
Piirtämisessä täytyy huomioida seuraavaa:
this.getWidth()
kertaa this.getHeight()
pikseliä,
ja piirtokomennot käyttävät tätä koordinaatistoa.
Joudut siirtämään funktioiden arvot ikkunan koordinaatistoon.
Koordinaatistomuunnoksen idea kannattaa miettä ennen koodausta rauhassa vaikka kynällä ja paperilla.
Testiohjelma:
Funktio<Double, Double> sini = new Funktio<Double, Double>() { public Double laske(Double x) { return Math.sin(x); } }; Funktio<Double, Double> kosini = new Funktio<Double, Double>() { public Double laske(Double x) { return Math.cos(x); } }; Funktio<Double, Double> sincos = Funktiot.yhdista(sini, kosini); Funktio<Double, Double> ylospainAukeavaParaabeli = new Funktio<Double, Double>() { public Double laske(Double x) { return x*x; } }; Funktio<Double, Double> polynomi = new Funktio<Double, Double>() { public Double laske(Double x) { return 3*x*x*x - 5*x*x + 1; } }; Piirtopinta pinta = new Piirtopinta(); pinta.lisaaFunktio(sini, Color.RED); pinta.lisaaFunktio(kosini, Color.BLUE); pinta.lisaaFunktio(sincos, Color.GRAY); pinta.lisaaFunktio(ylospainAukeavaParaabeli, Color.GREEN); pinta.lisaaFunktio(polynomi, Color.MAGENTA); JFrame ikkuna = new JFrame(); ikkuna.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); ikkuna.getContentPane().add(pinta); ikkuna.setSize(640, 640); ikkuna.setVisible(true);
Esimerkkikuva:
P.S. Keksitkö, miten voit tehdä metodin
Funktio<Double, Double> derivaatta(Funktio<Double, Double> f)
,
joka palauttaa f
:n derivaattafunktion approksimaation?
Entä integraalifunktion?
Säännöllinen lauseke määrittelee tiiviissä muodossa joukon merkkijonoja. Tarkastellaan esimerkiksi tehtävää, jossa täytyy tarkistaa, onko käyttäjän antama opiskelijanumero oikeanmuotoinen (alussa on numerot 01 ja niiden jälkeen 7 numeroa väliltä 0–9). Miten tekisit tämän Javalla?
Säännöllistä lauseketta käyttämällä tarkistus on hyvin helppo.
Merkkijonon metodilla matches
voi tarkistaa,
vastaako merkkijonon muoto tiettyä säännöllistä lauseketta.
Opiskelijanumeron tapauksessa sopiva säännöllinen lauseke on
"01[0-9]{7}"
, jolloin koodi näyttää seuraavalta:
System.out.print("Anna opiskelijanumero: "); String numero = lukija.nextLine(); if (numero.matches("01[0-9]{7}")) { System.out.println("Muoto on oikea."); } else { System.out.println("Muoto ei ole oikea."); }
Tavallisimmat säännöllisten lausekkeiden merkinnät ovat seuraavat:
Vaihtoehdot
Pystyviiva tarkoittaa,
että säännöllisen lausekkeen osat
ovat vaihtoehtoisia.
Esimerkiksi lauseke
00|111|0000
määrittelee merkkijonot
00
,
111
ja
0000
.
Sulut
Sulkujen avulla voi määrittää,
mihin säännöllisen lausekkeen
osaan muut merkinnät vaikuttavat.
Esimerkiksi lauseke
00(0|1)
määrittelee merkkijonot
000
ja 001
.
Toistot
Käytössä ovat seuraavat toistomerkinnät:
* |
toisto 0... kertaa |
+ |
toisto 1... kertaa |
? |
toisto 0 tai 1 kertaa |
{a} |
toisto a kertaa |
{a,b} |
toisto a ...b kertaa |
{a,} |
toisto a ... kertaa |
Esimerkiksi lauseke
(01)*
määrittelee tyhjän merkkijonon
sekä merkkijonot
01
,
0101
,
010101
,
01010101
, jne.
Vastaavasti lauseke
110?110?
määrittelee merkkijonot
1111
,
11011
,
11110
ja
110110
.
Merkkiryhmät
Merkkiryhmän avulla voi määritellä lyhyesti joukon merkkejä.
Merkit kirjoitetaan hakasulkujen sisään,
ja merkkivälin voi määrittää viivan avulla.
Esimerkiksi merkintä
[145]
tarkoittaa samaa kuin
(1|4|5)
ja merkintä
[2-46-9]
tarkoittaa samaa kuin
(2|3|4|6|7|8|9)
.
Tee säännöllisen lausekkeen avulla ohjelma, joka tarkistaa, onko merkkijono viikonpäivän lyhenne (ma, ti, ke, to, pe, la tai su).
Esimerkkitulostuksia:
Anna merkkijono: ti Muoto on oikea.
Anna merkkijono: abc Muoto ei ole oikea.
Tee säännöllisen lausekkeen avulla ohjelma, joka tarkistaa, ovatko merkkijonon kaikki merkit vokaaleja.
Esimerkkitulostuksia:
Anna merkkijono: aie Muoto on oikea.
Anna merkkijono: ane Muoto ei ole oikea.
Tee säännöllisen lausekkeen avulla ohjelma,
joka tarkistaa, onko merkkijono muotoa
tt:mm:ss
oleva kellonaika
(tunnit, minuutit ja sekunnit kaksinumeroisina).
Esimerkkitulostuksia:
Anna merkkijono: 17:23:05 Muoto on oikea.
Anna merkkijono: abc Muoto ei ole oikea.
Anna merkkijono: 33:33:33 Muoto ei ole oikea.
Säännöllinen lauseke "[0-9]*[02468]"
tunnistaa 2:lla jaolliset ei-negatiiviset kokonaisluvut ja
lauseke "[0-9]*[05]"
tunnistaa
5:llä jaolliset ei-negatiiviset kokonaisluvut.
Näissä tapauksissa säännöllisen lausekkeen tekeminen on helppoa, koska luvun viimeinen numero paljastaa 2:lla tai 5:llä jaollisuuden. Millä tahansa luvulla jaollisuuden voi tarkistaa säännöllisellä lausekkeella, mutta joskus tämä on paljon vaikeampaa.
Suunnittele säännöllinen lauseke, joka tunnistaa 3:lla jaolliset ei-negatiiviset kokonaisluvut.
Huom! Tämä tehtävä on vaikea mutta opettavainen!
Demo on liikkuvaa grafiikka sisältävä tietokoneohjelma, jonka avulla ohjelmoija voi esitellä taitojaan.
Demo voi olla vaikkapa tällainen:
Toteuta Javalla mahdollisimman hieno demo.
Anna kurssista palautetta seuraavalla sivulla: