Muusta kuin vesisioista Janne Käki 8.12.2006
Metodin kuormittaminen (overloading) Samannimisestä metodista on määritelty samassa luokassa (tai samassa yli- ja aliluokkien jatkumossa) useita versioita, joista valitaan suoritettavaksi yksi sen perusteella, mitä parametreja metodikutsussa on annettu. Metodikutsu sidotaan suoritettavaan metodiin metodin nimen sekä parametrien tyyppien ja järjestyksen perusteella. (Sen sijaan esimerkiksi parametrimuuttujien nimillä ei ole mitään merkitystä. Ei myöskään sillä, minkä tyyppisiä arvoja metodin eri versiot palauttavat.) Metodikutsujen on oltava yksiselitteisiä, eli ei saa olla epäselvää, mikä vaihtoehtoisista tietynnimisen metodin versioista nyt suoritetaan: public void metodi(object o, String s) {... } public void metodi(string s, Object o) {... } metodi( nuuh, nuuh ); metodi( nuuh, (Object) nuuh );
Metodin korvaaminen (overriding) Aliluokka määrittelee yliluokassa määritellyn metodin toteutuksen kokonaan uudelleen. Korvaavan metodin puumerkki on täsmälleen sama kuin alkuperäisen, eli metodeilla on sama nimi, samanlainen parametrilista (tyypit ja järjestys) ja sama paluuarvon tyyppi. Voidaan merkitä aliluokkaan kirjoittamalla metodin yläpuolelle erityinen @Override-tägi. Ei pakollista, mutta parantaa luettavuutta. Metodin uusi versio EI saa muuttaa paluuarvon tyyppiä, rajata näkyvyyttä alkuperäistä suppeammaksi (laajentaa sen sijaan saa), heittää sellaisia poikkeuksia joita alkuperäinen ei määritellyt heittävänsä (sen sijaan uusi versio voi mainiosti olla heittämättä joitakin poikkeuksia joita alkuperäinen metodi heitti). Kun luokan A metodi x() on korvattu aliluokassa B metodilla x(), niin alkuperäistä metodia voi kutsua luokan B oliolle vain olio itse notaatiolla super.x(). Muut kutsut johtavat aina luokassa B määritellyn metodin suorittamiseen.
Metodien staattinen ja dynaaminen sidonta Otus eka = new PäheäOtus(); PäheäOtus toka = new PäheäOtus(); Olkoon PäheäOtus luokan Otus aliluokka. Otus-luokassa on määritelty tylsä metodi eksistoi(), jonka PäheäOtus on korvannut uudella päheämmällä versiolla. Lisäksi PäheäOtus-luokassa on täysin uusi, ennen näkemätön metodi kelaasunlaatuas(). PäheälleOtukselle, joka on sijoitettu Otus-tyyppiseen muuttujaan, voidaan kutsua ainoastaan Otus-luokan metodeja. Sanotaan, että kyseisen olion staattinen tyyppi on Otus. Vaikka olio siis osaisi myös kelata laatuaan, sitä ei voi tuon muuttujan kautta käskeä niin tekemään. Sopivalla tyyppimuunnoksella staattinen tyyppi voidaan kuitenkin saattaa sellaiseksi, että tuokin metodi on käytössä. eka :D :D Sen sijaan muuttujan staattinen tyyppi Otus antaa toki meille luvan kutsua oliollemme metodia eksistoi(). Tällöin metodikutsu sidotaan kuitenkin aina olion todelliseen, dynaamiseen tyyppiin, joka tässä tapauksessa on PäheäOtus. Hämmästykseksemme tylsässä Otuslaatikossa piileksinyt olio alkaakin siis eksistoida hyvin päheästi, kun käskytämme sitä tuollaisella metodikutsulla. Mitenkään emme pysty käskemään tuota oliota käyttäytymään siten kuin puhdas Otus käyttäytyisi, ellei se sitten itse päätä suorittaa tuota metodia kutsulla super.eksistoi(). toka Staattinen tyyppi siis sanelee sen, mitkä metodikutsut muuttujaan sijoitetulle oliolle ovat ylipäänsä luvallisia. Dynaaminen tyyppi (joka ei olion elinaikana muutu) taas määrää, mitä koodia oliolle tehtyjen metodikutsujen seurauksena todella suoritetaan.
Iteraattori Vanha tuttu iteroiva for-looppi... for (Olio o : kokoelma) { } System.out.println(o); Kokoelman on toteutettava rajapinta Iterable<T>, joka määrittelee metodin public Iterator<T> iterator()....toimii pinnan alla itse asiassa iteraattoriolion avulla. Sama looppi hieman toisin kirjoitettuna: Iterator<Olio> iter = kokoelma.iterator(); while (iter.hasnext()) { Olio o = iter.next(); System.out.println(o); } Myös Iterator<T> on itse asiassa rajapinta. Iteraattorin metodi next() palauttaa ja poistaa iteraattorista, ei iteroitavasta kokoelmasta järjestyksessä seuraavan elementin. Metodi hasnext() kertoo, vieläkö elementtejä on jäljellä.
Iteraattori Iteroimisessa on vaaransa. Metodi next() voi heittää poikkeuksia... NoSuchElementException, jos elementtejä ei enää ole. Vältettävissä huolellisella hasnext()-metodin käytöllä. ConcurrentModificationException, jos iteroitava kokoelma on muuttunut iteraattorin luomisen jälkeen. Uusia elementtejä ei siis voi kesken iteroinnin lisätä, ellei iterointia tämän jälkeen keskeytä. Elementtien poistaminen on luvallista iteroinnin lomassa vain iteraattoriolion metodilla remove(), joka poistaa viimeisimmän next()-metodin palauttaman elementin, myös iteroitavasta kokoelmasta. Iteraattori siis antaa luvan myös elementtien poistamiseen kokoelmasta (periaatteessa oma iteraattoriluokka voidaan kuitenkin toteuttaa myös niin, ettei remove()-metodi tee mitään). Aina ulkopuoliselle käyttäjälle ei haluta jättää tällaista mahdollisuutta, jolloin voi olla syytä harkita toisenlaisen rajapinnan tarjoamista kokoelman läpikäyntiin.
Suunnittelumallit (design patterns) Hyväksi havaittuja konsepteja siitä, millainen olioyhteisö soveltuu tietynlaisen ongelman ratkaisuun. (Kuvailevat yleensä muutamia oliota, joilla selkeä vastuunjako. Eivät sinänsä ota kantaa siihen, miten olioita kuvaavat luokat toteutetaan, eivätkä riipu tietystä ohjelmointikielestä.) MVC (Model-View-Controller): sovelluksen datamalli, sen esittäminen käyttäjälle sekä datamallin muokkaaminen on jaettu eri osien vastuulle. Observer: yksi olio ilmoittautuu tarkkailemaan muutoksia toisen olion tilassa. Tarkkailtava (observable) olio ilmoittaa, kun muutoksia tapahtuu. Singleton: luokka, josta voidaan luoda vain yksi olio. Luontimetodi piilotettu, ilmentymän luonti (ja luonnin jälkeen tuon ainoan ilmentymän hakeminen) tapahtuu jollakin staattisella metodilla, kuten getinstance(). Factory: luokka, jonka metodien avulla voidaan luoda usean muun luokan ilmentymiä. Paluuarvon tyyppi voi olla jokin rajapinta, jolloin käyttäjä voi luoda erilaisia olioita välittämättä niiden todellisesta luokasta. Composite: suurempi kokonaisuus rakennetaan tietynlaisista rakennuspalikkaolioista, jotka jälleen voivat edustaa jotakin palikkarajapintaa (ja näin ollen olla todelliselta luokaltaan hyvin erilaisia keskenään). ja onhan näitä vielä muitakin
Sovelluskehykset (software frameworks) Valmiiden luokkien (ja lopulta pakkausten) muodostamia kokonaisuuksia, joiden varaan voi rakentaa uusia ohjelmistoja, sekä käyttämällä valmiita komponentteja sellaisenaan että laatimalla niille tarpeen mukaan omia aliluokkia. Tutuin esimerkki sovelluskehyksestä: Swing. Usein sovelluskehysten luokkakokonaisuudet on suunniteltu hyvin, ja niissä nähdään monien suunnittelumallien soveltamista käytäntöön: MVC: jokaisen hiemankin monimutkaisemman Swing-komponentin perustana nämä kolme osaa. Observer: tapahtumankuuntelijat ja kuunneltavat komponentit. Singleton: tietyt Swing-sovelluksen hallintaan ja asetuksiin käytetyt luokat, joilla on aina vain yksi ilmentymä. Factory: esimerkiksi erilaisten reunusten (border) luonti. Composite: komponenttien lisääminen säiliöihin asettelijoiden avulla.
perintä Tulostusvirrat aggregaatio (toimiminen toisen luokan rakenteellisena osana) PrintWriter OutputStream Writer BufferedWriter FileOutputStream tavuvirrat FilterOutputStream OutputStreamWriter FileWriter merkkivirrat Korkeimman tason virroissa ns. decorator-suunnittelumalli. Virrat ovat perusmerkkivirta Writerin aliluokkia (eli lupaavat saman toiminnallisuuden), mutta niiden toiminta perustuu johonkin toiseen, matalamman tason merkkivirtaolioon. Ne lisäävät sen ympärille uutta toiminnallisuutta, koristeita. Nämä virrat luodaan siis aina jonkin olemassa olevan virran pohjalle.