ComiaTip Duck Typing Java


Duck Typing Java

Kirjoittanut Hannu Julkaistu 21.10.2009

Joskus tuntuu että Javaa käytetään ikään kuin se olisi dynaaminen kieli. Paikoin hävitetään tieto olion tyypistä välittämällä se Object:ina, ja sitten castataan se sen luokan olioksi, joka sen oletetaan olevan ajonaikana. Toisaalla taas määritellään samasta yläluokasta haarautuneisiin alaluokkiin saman nimisiä identtisesti toimivia metodeja, jotka eivät periydy samasta yläluokasta (tai interface:sta). Esimerkiksi Swing:istä löytyy esimerkkejä molemmista ongelmista.

Kiinteästi tyypitetyissä kielissä kuten Java on kuintenkin hyvät puolensa.

  • Kääntäjä löytää heti koodin järjettomimmät virheet,
  • metodille annettavien parametrien tyyppistä ei ole (suurimman osan aikaa) mitään epäselvyyttä,
  • aina läsnäoleva tieto tyypistä toimii kuin itsestään generoituvana dokumentaationa.

Mutta. Kun ajonaikaisten tyyppivirheiden mahdollisuuksien koodaamista ei kerran voi kokonaan välttää, niin olisi joskus mukava vain luottaa siihen että oliolla on ajonaikana siltä tarvittu metodi, oli olion tyyppi mikä tahansa.

Dynaamisissa kielissä kuten Python käytetään kiinteiden tyyppien sijaan ns. Duck Typing tyypitystä. Siinä Kaikki mikä kävelee ja vaakkuu kuin ankka ON riittävästi ankan kaltainen. Toisin sanoen, sillä mistä oliolla olevat metodit ovat peräisin ei ole mitään väliä. Ainoa vaatimus on että oliolla on kutsuttu metodi ajonaikana sillä hetkellä kun sitä kutsutaan. Vaikka Java onkin kiinteästi tyypitetty kieli, voi siinäkin metodeita kutsua tietämättä mistä ne ovat olioon tulleet. Tarvitsee vain tietää metodin nimi ja sen parametrien tyypit.

public class Duck { public void fly(){ System.out.println("Duck."); } } public class Swallow { public void fly(){ System.out.println("Unladen swallow."); } } import java.util.Random; public class Main { Random rand = new Random(); //satunnaisesti Duck tai Swallow olio private Object getAnyBird() { if (rand.nextBoolean()) return new Duck(); else return new Swallow(); } public Main() throws Exception { for (int i = 0; i < 10; ++i) { Object aBird = getAnyBird(); aBird.getClass().getMethod("fly").invoke(aBird); } } }

Ylläolevassa esimerkissä on kaksi lintua kuvaavaa luokkaa joilla on metodi fly(). Järjellisesti toteutetussa systeemissä luokat tietysti toteuttaisivat saman rajapinnan, mutta kaikkea ei aina pääse itse korjaamaan. Käyttämällä instanceof operaattoria ja tyyppimuunnosta (casting) voisi myös ratkaista tämän ongelman, mutta siitä seuraisi käytännössä helposti tolkuton määrä koodia.

aBird.getClass().getMethod("fly").invoke(aBird);

Tämä esimerkin rivi on ainoa mitä tarvitaan fly() metodin kutsumiseen oli aBird minkä luokan olio tahansa, jolla itsellään tai millä tahansa sen yläluokalla on metodi fly(). Ensin saadaan olion luokkaoliolta halutun niminen metodi ja sitten vain kutsutaan ko. metodia oliosta aBird.