Oppimateriaali perustuu osittain Arto Wiklan vanhaan ohjelmointisivustoon. Materiaalin copyright © Arto Wikla. Materiaalia saa vapaasti käyttää itseopiskeluun. Muu käyttö vaatii luvan.

VIII Periytyminen

(Muutettu viimeksi 15.11.2010, sivu perustettu 9.11.2010. Arto Wikla)

Periytyminen (inheritance) on luokan piirteiden - kenttien ja metodien - siirtymistä toiselle luokalle. Saava luokka on aliluokka (subclass), antava luokka on yliluokka (superclass). Aliluokkaa kutsutaan joskus myös laajennetuksi (extended) tai johdetuksi (derived) luokaksi.

Idea

Luokan laatiminen voidaan nähdä mallintamisena, jossa luokka mallintaa jotakin yleiskäsitettä. Esimerkiksi luokka Elain voisi olla eläimen malli, jossa on määritelty kaikille eläimille yhteisiä ominaisuuksia (paino, ikä, ...) ja toimintoja (syöminen, lisääntyminen, ...):

public class Elain {
  private int paino;
  ...
  public Elain(...) { ...
  ...
  public void syo(...) { ...
  ...
}

Luokan ilmentymä on jokin erityinen olio, joka kuuluu tuohon luokkaan. Esimerkiksi erityinen kissaolio nimeltään Missu voisi kuulua luokkaan Elain. Oliolla Missu on tällöin jokin erityinen paino, joka luultavasti muuttuu, kun Missu syö, jne...

Luokalle voidaan ohjelmoida aliluokkia (subclass), jotka erikoistavat ja rajaavat luokkaa: aliluokka perii (inherit) yliluokkansa (superclass) ominaisuudet ja toiminnot sekä täydentää ja muokkaa ominaisuuksia erityisemmiksi.

Luokalla Elain voisi olla aliluokka Kissa, joka täydentää luokan Elain ominaisuuksia erityisillä kissaominaisuuksilla, vaikkapa naukumistiheydellä ja toimintoja vaikkapa kehräämisellä:

public class Kissa extends Elain {
  ...
  private int naukumistiheys;
  ...
  public Kissa(...) { ...
  ...
  public void kehraa(...) { ...
  ...
}

Luokkamäärittelyn otsikon ilmaus extends tarkoittaa, että luokka Kissa perii kaikki luokan Elain ominaisuudet – attribuutit ja aksessorit. Erityisesti on syytä huomata, että niitä ei siis tarvitse ohjelmoida uudelleen, ei edes "copy-pasteta"!

Riittää ohjelmoida ainoastaan erityiset kissamaiset ominaisuudet luokkaan Kissa.

Kun Missu nyt luodaankin luokan Kissan ilmentymäksi, sillä on sekä Elaimen että Kissan määräämät ominaisuudet. Eläimenä Missu syö, Kissana se kehrää, jne.

Aliluokalla voi itselläänkin olla aliluokkia. Näin periytymisellä voidaan rakentaa puumainen luokkien hierarkia. (Tällaista periytymismekanismia kutsutaan yksittäisperiytymiseksi. Jotkin kielet (esim. C++) sallivat ominaisuuksien perimisen useammasta luokasta, ns. moniperiytymisen.

Javassa varsinainen moniperiytyminen ei ole luvallista, mutta ns. rajapintaluokka (interface) tarjoaa eräitä moniperinnän tekniikoita.

Luokka Object on kaikkien luokkien yliluokka, periytymispuun juuri. Koska kaikki luokat ovat Object-luokan välittömiä tai välillisiä aliluokkia, kaikki luokat perivät luokan Object ominaisuudet. Yksi tällainen on se jo kovin tutuksi tullut toString-metodi. Juuri tästä syystä kaikille oliolle on luvallista soveltaa toString-aksessoria, vaikka sitä ei itse olisikaan olion luokkaan ohjelmoitu! Tosin tuo peritty merkkiesitys harvoin riittää...

Luokan ilmentymällä, oliolla, on siis oman luokan määrittelemien ominaisuuksien lisäksi kaikkien oman luokan yliluokkien ominaisuudet! Kun luokkien hierarkia hahmottuu puuna, olion ominaisuudet voi nähdä kokoelmana piirteitä, jotka on saatu kaikista luokista matkalla Object-luokasta omaan luokkaan. (Periytymismekanismissa on välineitä, joilla jotkin piirteet voidaan peittää tai syrjäyttää eli korvata! Niistä pian lisää.)

Periytymismekanismia voidaan käyttää kahdella erilaisella tavalla ohjelmointityön jäsentämisessä (Kai Koskimies, Pieni oliokirja, 1997):

  1. Käytännöllinen ohjelmointitekniikka:
    Periytymisellä voidaan toteuttaa aiemmin ohjelmoitujen välineiden uudelleenkäyttöä: välineitä täydennetään ja erityistetään uusien tarpeiden mukaisiksi. Yleisemmin voidaan puhua ns. inkrementaalisesta ohjelmankehityksestä: olemassa olevaa ohjelmistoa laajennetaan uusilla komponenteilla ilman, että joudutaan koskemaan jo toteutettuihin ohjelmiin. (Luokka ei koskaan tiedä mitään aliluokistaan!)

  2. Käsitteellinen mallintaminen:
    Ongelmamaailmaa mallinnetaan peritymisen käsittein: yliluokka-aliluokka -suhde vastaa yleinen-erityinen -suhdetta. Eläin on kissan yleistys, lehmä on naudan erikoistapaus, jne.

Sormiharjoitus

Laaditaan luokka Yli, jossa on yksi kenttä settereineen ja gettereineen sekä taidokas toString:

public class Yli {
  private int a;

  public Yli() {
    a = 10;
  }

  public void setA(int a) {this.a = a;}
  public int  getA() {return a;}

  public String toString() {
    return "Hei, olen Yli-olio ja a = " + a;
  }
}

Ohjelmoidaan sitten Ylille aliluokka Ali, joka täydentää perimiään ominaisuuksia uudella kentällä settereineen ja gettereineen sekä omalla toString-metodilla:

public class Ali extends Yli {
  private int b;

  public Ali() {
    b = 100;
  }

  public void setB(int b) {this.b = b;}
  public int  getB() {return b;}

  public String toString() {
    return "Hei, olen Ali-olio ja b = " + b;
  }
}

Ja eikun tuotantoon:

public class YliAliKokeilua { 
  public static void main(String[] args) {

    Yli x = new Yli();
    System.out.println(x); // Hei, olen Yli-olio ja a = 10
    x.setA(123);
    System.out.println(x); // Hei, olen Yli-olio ja a = 123

    Ali y = new Ali();
    System.out.println(y); // Hei, olen Ali-olio ja b = 100
    y.setB(321);
    System.out.println(y); // Hei, olen Ali-olio ja b = 321

    System.out.println("Vaan tiedänpä myös kentän a: " + y.getA());
                           // Vaan tiedänpä myös kentän a: 10

    y.setA(999);
    System.out.println("Ja saanpa myös muutettua sitä! Nyt a = " + y.getA());
                           // Ja saanpa myös muutettua sitä! Nyt a = 999
  }
}

Esimerkki simuloidaan huolellisesti luennolla!

KuulaskuriPlus – kuukausilla on myös nimet

Luvussa III Oliot ja kapselointi, luokka olion mallina ensimmäinen esimerkki kapseloinnista (ja miksei samalla myös mallinnuksesta) oli luokka Kuulaskuri:

Tuo kuulaskuri ei tiennyt mitään kuukausien nimistä. Puute halutaan korjata. Paremman kuulaskurin API sisältää konstruktorin lisäksi myös toString-metodin, joka tuntee kuukaudet:

Uusi luokka KuulaskuriPlus voitaisiin toki ohjelmoida alusta alkaen uudelleen. Työtä voisi ehkä säästää cut-and-paste-tekniikalla? Mutta miksi ihmeessä tehdä turhaa työtä, kun vanha Kuulaskuri osaa jo yksinkertaisen kuulaskurin hommat?

Toteutetaan siis KuulaskuriPlus tavallisen Kuulaskurin aliluokkana:

public class KuulaskuriPlus extends Kuulaskuri {

  private static String[] kuunNimet = {     // kaikille olioille sama!
    "tammi", "helmi", "maalis", "huhti", "touko", "kesä",
    "heinä", "elo", "syys", "loka", "marras", "joulu"};

  public KuulaskuriPlus() { }  // Ei mitään lisättävää...

  public String toString() {
    return kuunNimet[this.moneskoKuu()-1] + "kuu"; 
  }
}

Käyttöesimerkki:

public class KuulaskuriPlusKokeilu {
  public static void main(String[] args) {

    // KuulaskuriPlus-olioita voi käyttää tuttuun tapaan kuin Kuulaskureita.
    // Itse asiassa ne myös ovat (ja ovat myös) Kuulaskureita!

    KuulaskuriPlus minun = new KuulaskuriPlus();
    KuulaskuriPlus sinun = new KuulaskuriPlus();

    System.out.println(minun.moneskoKuu() + ", " + sinun.moneskoKuu() );
    minun.seuraavaKuu();
    minun.seuraavaKuu();
    System.out.println(minun.moneskoKuu() + ", " + sinun.moneskoKuu() );

    for (int i=0; i < 20; ++i)
      sinun.seuraavaKuu();
    System.out.println(minun.moneskoKuu() + ", " + sinun.moneskoKuu() );

    // Mutta nyt käytettävissä on myös oman luokan lisäys:   

    System.out.println(minun);  // maaliskuu
    System.out.println(sinun);  // syyskuu         
  }
}

KuulaskuriPlusPlus – vuosiluvutkin mukaan...

Eikö mikään riitä... Nyt kuulaskurin halutaan tietävän myös vuosiluvun ja jopa osaavan kasvattaa vuosilukua, kun joulukuusta siirrytään tammikuuhun. API-täydennykset:

Tämäkin luokka voitaisiin toki ohjelmoida alusta alkaen uudelleen. Ja nytkin työtä voisi ehkä vähän säästää cut-and-paste-tekniikalla. Mutta miksi ihmeessä nytkään tehdä turhaa työtä, kun juuri ohjelmoitu KuulaskuriPlus osaa jo merkttävän osan KuulaskuriPlusPlusilta vaadituista taidoista?

Toteutetaan siis KuulaskuriPlusPlus KuulaskuriPlus-luokan aliluokkana.

Nyt matkassa on muutama lisähaaste:

  1. Tarvitaan uusi ilmentymämuuttuja – jokaiselle oliolle oma kenttä – vuosiluvun esittämistä varten.
  2. Luokasta Kuulaskuri perittävä aksessori seuraavaKuu ei osaa enää kaikkea vaadittua: vuosiluvun kasvattamista vuoden vaihtuessa.
  3. Luokasta KuulaskuriPlus peritty toString tuntee kyllä kuukausien nimet, mutta vuosiluvusta sillä ei ole aavistustakaan.

Ensimmäiseen haasteeseen on helppo vastata: senkun vaan ohjelmoidaan luokkaan kenttä vuosilukua varten ja konstruktori asettakoon tuolle kentälle alkuarvon. Ja getteri mikaVuosi palauttakoon arvonaan tuon kentän sisällön.

Toinen haaste on hankalampi. Jotenkin pitäisi osata opettaa peritty aksessori seuraavaKuu kasvattamaan vuosilukua aina vuoden vaihtuessa. Ratkaisu löytyy perityn metodin syrjäyttämisestä eli korvaamisesta (override): aliluokkaan ohjelmoidaan aksessori, jonka otsikko – parametrit mukaanlukien! – on samanlainen kuin perityllä metodilla. Tällöin oliolle sovelletaan juuri tätä oman luokan versiota aksessorista.

Vaan tästäpä aiheutuu toinen ongelma: kun ohjelmoidaan oma seuraavaKuu-metodi luokkaan KuulaskuriPlusPlus, peritty kenttä kuu ei ole käytettävissä, koska se on määritelty luokassa Kuulaskuri private-määreellä.

Kuten kaikki muistavat, private-määritellyt asiat näkyvät vain, ihan konkreettisesti, kyseessä olevan luokan alku- ja loppuaaltosulkeiden väliseen ohjelmaan. Eivät koskaan minnekään muualle, eivät siis erityisesti edes aliluokkaan.

Löytyypä onneksi toiseenkin ongelmaan ratkaisu: Ilmauksella super voidaan viitata perittyyn metodiin (tai myös kenttään), joka on syrjäytetty samanlaisella aliluokassa. Toisin sanoen vaikkapa uuden seuraavaKuu-metodin ohjelmoinnissa voidaan kutsua perittyä samannimistä metodia ilmauksella super.seuraavaKuu().

Juuri samalla tekniikalla ratkaistaan myös noista edellä luetelluista haasteista kolmas: Uutta toStringiä ohjelmoidessa perittyä samannimistä voi käyttää hyväksi!

Ja viimeinkin toteuttamaan KuulaskuriPlusPlus-luokkaa:

public class KuulaskuriPlusPlus extends KuulaskuriPlus {

  private int vuosi;

  public KuulaskuriPlusPlus(int vuosi) {
    this.vuosi = vuosi;
  }

  public int mikaVuosi() {
    return this.vuosi;
  }

  public void seuraavaKuu() {  // Syrjäyttää perityn!
    super.seuraavaKuu();       // Kasvatetaan kuukautta.
    if (moneskoKuu() == 1)     // Syrjäyttämättömiä perittyjä aksessoreita
      vuosi = vuosi + 1;       // voi kutsua sellaisinaan suoraan.
  }

  public String toString() {   // Syrjäyttää perityn!
    return super.toString() + ", " + vuosi;
  }
}

Käyttöesimerkki:

public class KuulaskuriPlusPlusKokeilu {
  public static void main(String[] args) {

    // KuulaskuriPlusPlus-olioita voi käyttää kuin KuulaskuriPlus-olioita.
    // Itse asiassa ne myös ovat (ja ovat myös) KuulaskuriPlus-olioita!

    // KuulaskuriPlusPlus-olioita voi käyttää kuin Kuulaskureita.
    // Itse asiassa ne myös ovat (ja ovat myös) Kuulaskureita!

    KuulaskuriPlusPlus minun = new KuulaskuriPlusPlus(1000);
    KuulaskuriPlusPlus sinun = new KuulaskuriPlusPlus(2000);

    System.out.println(minun.moneskoKuu() + ", " + sinun.moneskoKuu() );
    minun.seuraavaKuu();
    minun.seuraavaKuu();
    System.out.println(minun.moneskoKuu() + ", " + sinun.moneskoKuu() );

    for (int i=0; i< 20; ++i)
      sinun.seuraavaKuu();
    System.out.println(minun.moneskoKuu() + ", " + sinun.moneskoKuu() );

    // ... ja kuin KuulaskuriPlusPlus-olioita:
    // Itse asiassa ne myös ovat (ja ovat myös) KuulaskuriPlus-olioita!

    System.out.println(minun);  // maaliskuu, 1000
    System.out.println(sinun);  // syyskuu, 2001

    // Ja tietenkin ne muutenkin ovat ihan aitoja KuulaskuriPlusPlus-olioita:

    for (int i=0; i< 20000; ++i)  // 20000 kuukautta eteenpäin!
      sinun.seuraavaKuu();

    System.out.println(sinun);  // toukokuu, 3668
  }
}

Piste ja värillinen piste

Vielä toinen esimerkki. Määritellään luokka Piste seuraavasti:

public class Piste {

//-- kentät:
  private int x, y;  // vain omaan käyttöön!

//-- konstruktorit:
  public Piste() {x=0; y=0;}
  public Piste(int x, int y) {this.x = x; this.y = y;}

//-- metodit:
  public String toString() {  // syrjäyttää Object-luokan metodin!
    return "("+x+","+y+")";
  }
  public void siirry(int dx, int dy) {
    x += dx; y += dy;
  }
} 

Käyttö tuttuun tapaan:

Piste a = new Piste();
Piste b = new Piste(7, 14);

System.out.println(a);   // (0,0)
System.out.println(b);   // (7,14)

a.siirry(2, 3);
b.siirry(4, 5);

System.out.println(a);   // (2,3)
System.out.println(b);   // (11,19)

Jos halutaan toteuttaa värillinen piste, jolla on pisteen ominaisuuksien lisäksi väri ja väriin liittyviä operaatioita, on luontevaa toteuttaa värillinen piste pisteen aliluokkana.

Tämän voi nähdä pelkästään ohjelmointiteknisenä menettelynä: uudelleenkäytetään jo ohjelmoidut pisteen määrittelyt.

Toisaalta asian voi nähdä myös teoreettisemmin mallinnuksena: Värillinen piste on pisteen erikoistapaus. Se on ns. "IsA"-relaatiossa pisteeseen, so. jokainen värillinen piste on piste, joten kaikki, mitä voidaan tehdä pisteelle, voidaan tehdä myös värilliselle pisteelle. Päivastainen ei ole mahdollista, koska värillinen piste sisältää joitakin uusia ominaisuuksia, joita pisteellä ei ole.

Ohjelmoidaan luokka VariPiste seuraavasti:

public class VariPiste extends Piste {

  //-- lisäkenttä:
  private int vari;  // vain omaan käyttöön

  //-- konstruktorit:
  public VariPiste() {super();}  //  super() selviää heti kohta...!

  public VariPiste(int vari) {this.vari = vari;}

  public VariPiste(int x, int y, int vari) {
    super(x, y); this.vari = vari;  //  super(...) selviää heti kohta...!
  }

  //-- lisämetodi:
  public void uusiVari(int vari) {
    this.vari = vari;
  }

  //-- syrjäyttävä metodi ("overriding"):      // syrjäyttää Piste-luokasta perityn
  public String toString() {                // toString-metodin!
    return super.toString()+" väri: "+vari; // super selviää heti kohta...!
  }
} 

Käyttöesimerkki:

VariPiste c = new VariPiste();
VariPiste d = new VariPiste(7);
VariPiste e = new VariPiste(4,5,9);

System.out.println(c);  // (0,0) väri: 0
System.out.println(d);  // (0,0) väri: 7
System.out.println(e);  // (4,5) väri: 9

e.siirry(1, 1);         // yliluokan operaation käyttö!
System.out.println(e);  // (5,6) väri: 9

e.uusiVari(14);         // oman luokan lisäoperaation käyttö
System.out.println(e);  // (5,6) väri: 14

Konstruktorit ja periytyminen

Yliluokan konstruktorit eivät periydy aliluokalle!

Kun aliluokan ilmentymä luodaan, tapahtuu seuraavaa ("super(...)" on Javan ilmaus yliluokan konstruktorin kutsulle):

  1. [Valitaan mahdollisesti kuormitetuista konstruktoreista se, jonka muodolliset parametrit vastaavat todellisia parametreja. Tämä on itse asiassa tehty jo käännösaikana.]
  2. Varataan muistista tila luotavan olion kaikille kentille, niin olion omassa luokassa määritellyille kuin perityillekin. Kentät saavat tyyppinsä mukaisen oletusalkuarvon.
  3. Konstruktorin algoritmin ensi toimena aina kutsutaan yliluokan konstruktoria oli kutsu kirjoitettu näkyviin tai ei:
  4. Jos ilmentymämuuttujien määrittelyihin on kirjoitettu alkuarvojen asetuksia, arvot asetetaan muuttujiin.
  5. Vasta kaiken tämän jälkeen suoritetaan itse ohjelmoitu osuus konstruktorin algoritmista.

Huom: On järkevää ohjelmoida siten, että luotavan olion kaikkien kenttien asetukset kirjoitetaan aina näkyviin konstruktoreihin! Jopa oletusalkuarvot on syytä asettaa uudelleen. Miksi?

Tarkastellaan vielä edellistä esimerkkiä:

public class VariPiste extends Piste {
  //-- lisäkenttä:
  private int vari;  // vain omaan käyttöön

  //-- konstruktorit:
  public VariPiste() {super();}  // Pelkkä VariPiste() { } tarkoittaa samaa!
  ...

Jos VariPiste ei määrittelisi parametritonta konstruktoria, oliota ei voisi myöskään luoda parametreitta, koska luokalla on automaattisesti parametriton oletuskonstruktori vain, jos muita konstruktoreita ei ole ohjelmoitu!

Koska yliluokan parametritonta konstruktoria kutsutaan joka tapauksessa automaattisesti, määrittely:

  public VariPiste() { }
riittäisi. Piste käydään asettamassa (0,0):ksi, väri tulee oletusalkuarvon takia 0:ksi.

  public VariPiste(int vari) {  // Kutsuu ensin automaattisesti
    this.vari = vari;           // super(); !
  }

Tässä käydään automaattisesti asettamassa piste (0,0):ksi ennen värin asettamista. Ilmausta this on käytetty vain mukavuudenhalusta: näin muodollinen parametri saa olla samanniminen kuin asetettava kenttä. (this on – kuten jo aiemmin nähtiin – olion kenttä, joka viittaa olioon itseensä!)

  public VariPiste(int x, int y, int vari) {
    super(x, y); this.vari = vari;
  }

VariPisteen kolmiparametrinen konstruktori asettaa ensin pisteen koordinaatit Piste-luokan kaksiparametrisella konstruktorilla.

Huom: Piste-luokan private-kentät x ja y eivät näy VariPisteelle. Konstruktorilla ne silti voidaan käydä asettamassa. Aksessoreilla niitä päästään käsittelemään muutenkin.

Huom: (yleisemmin): Mikään yliluokassa private-määreellä määritelty kenttä tai metodi ei näy minnekään luokkamäärittelyn ulkopuolelle, ei edes aliluokkaan. Perittyjä private-kenttiä pääsee aliluokassakin käsittelemään vain perityillä aksessoreilla, ei mielin määrin. Siispä kapselointi kapseloi yliluokan yksityisen kaluston myös aliluokalta!

Jos luokkaan ei ohjelmoida lainkaan omia konstruktoreita, luokka saa automaattisesti parametrittoman oletuskonstruktorin, joka puolestaan ei muuta tee kuin kutsuu yliluokan parametritonta konstruktoria.

Laaditaan VariPisteelle sisarus NimiVariPiste:

public class NimiVariPiste extends Piste {

  private String vari="EiVäriä";

   public void uusiVari(String vari) {
     this.vari = vari;
   }

   public String toString() {
     return super.toString()+" väri: "+vari;
  }
}

Käyttöesimerkki:

Piste a = new Piste();
VariPiste b = new VariPiste(1,2,3);
NimiVariPiste c = new NimiVariPiste();

System.out.println(a); // (0,0)
System.out.println(b); // (1,2) väri: 3
System.out.println(c); // (0,0) väri: EiVäriä

c.uusiVari("Vihreä");
System.out.println(c); // (0,0) väri: Vihreä

c.siirry(7,8);
System.out.println(c); // (0,0) väri: Vihreä

Perityn metodin syrjäyttäminen

Sen lisäksi, että aliluokka voi täydentää perimiään ominaisuuksia uusilla ominaisuuksilla, aliluokka voi myös antaa perimilleen ominaisuuksille uusia merkityksiä. Tästä nähtiin edellä jo esimerkkeinä muutama toString-metodi ja KuulaskuriPlusPlus-luokan antama uusi toiminnallisuus seuraavaKuu-metodille.

Olemme jo aiemmin tutustuneet metodien kuormittamiseen (overloading): samannimiset eriparametriset metodit ovat luvallisia samassa näkyvyysalueessa. Tässäkin luvussa konstruktoreita on kovasti kuormitettu.

Kuormittaminen ei rajoitu vain yhden luokkamäärittelyn sisään, vaan aliluokkakin voi vallan mainiosti täydentää perittyjä ominaisuuksia lisäämällä metodeita, jotka kuormittavat perittyjä metodeita.

Metodin syrjäyttäminen eli korvaaminen (overriding, "ylikirjoittaminen")) tarkoittaa puolestaan perityn metodin syrjäyttämistä uudella samantyyppisellä, samannimisellä ja samaparametrisella metodilla.

Kun saman luokan muut metodit kutsuvat tällaista metodia, kutsu kohdistuu juuri tähän syrjäyttävään eli korvaavaan metodiin. Niinpä esimerkiksi, jos toString kutsuisi toStringiä sellaisenaan, kyse olisi rekursiivisesta kutsusta!

Syrjäytettyyn perittyyn metodiin viitataan käyttämällä ilmausta super, joka tarkoittaa "minun yliluokkaani".

Edellä luokka Piste määritteli:

  public String toString() {
    return "("+x+","+y+")";
  }

ja VariPiste:

  public String toString() { 
    return super.toString()+" väri: " + vari;
  }

Tässä siis VariPiste antaa uuden merkityksen metodille toString. Itse asiassa myös Piste on antanut tälle metodille uuden merkityksen: kaikkien muiden luokkien yliluokka Object jo määrittelee metodille toString erään merkityksen! VariPiste myös käyttää hyväkseen Piste-yliluokkansa toString-metodia.

Syrjäyttävän metodin valinta liittyy dynaamisesti (eli ohjelman suoritusaikana!) olion tyyppiin, ei muuttujan tyyppiin! Tätä kutsutaan polymorfismiksi eli monimuotoisuudeksi. Esimerkki:

VariPiste a = new VariPiste(7);
Piste b;
Object c;

b = a;
c = a;

System.out.println(a);  // (0,0) väri: 7
System.out.println(b);  // (0,0) väri: 7
System.out.println(c);  // (0,0) väri: 7

Opittavaa: Tässä siis muuttujan tyypistä riippumatta valitaan joka tilanteessa VariPiste-luokan toString-metodi, koska kaikkien kolmen muuttujan arvona on VariPiste-olio!

Taustaa ja lisätietoa: Kääntäjä on tarkistanut, että muuttujille b (tyyppiä Piste) ja c (tyyppiä Object) on käytettävissä toString()-metodi (println tarvitsee sitä!) ja generoinut tuon metodin kutsun. Suoritusaikana tulkki havaitsee b:n ja c:n arvona olevan olion tyypiksi VariPiste ja kutsuu toString()-metodia olion tyypin perusteella, ei muuttujan tyypin perusteella. Polymorfismi siis perustuu syrjäytettyjen metodien ketjuun perintähierarkiassa ja siihen, että ohjelman suoritusaikana ketjusta valitaan olion omaa luokkaa lähinnä oleva versio toisiaan syrjäyttävistä metodeista.

Perityn kentän peittäminen

Yliluokan kentän voi peittää (hide) näkyvistä, kun aliluokassa määrittelee peritylle kenttänimelle uuden merkityksen. Jos edellä nähtyjen esimerkkien tapaan ohjelmoidaan luokan kentät yksityisinä ja käytetään niitä vain aksessoreiden kautta, kentän peittämismahdollisuudesta saatava ilo on lähinnä siinä, että omia kenttiä nimetessä ei tarvitse vältellä perittyjen kenttien nimien käyttämistä. (Tai runollisemmin: Kun ostaa hienon luokan ja erikoistaa siitä aliluokkana oman sovelluksen, ei tarvitse saada pitkää luetteloa kaikista perityistä tunnuksista, joita ei saa käyttää... ;-)

this ja super, this(), super(), protected, final

Ilmaukset this ja super ovat olioarvoisia vakioita, final-muuttujia, jotka ovat automaattisesti käytettävissä jokaisen luokan konstruktoreissa, ilmentymämetodeissa, ilmentymämuuttujien alustuslausekkeissa, ...

Nämä konstruktorin kutsut ovat sikäli erikoisia, että ne johtavat vain kutsutun konstruktorin algoritmin suorittamiseen, eivät uuden olion luontiin. Tavallinen konstruktorin kutsu new-ilmauksella luo aina väistämättä uuden olion.

Kutsumalla this(...)-this ilmauksen avulla toisia konstruktoreita voidaan välttyä koodin toistamiselta toisiaan kuormittavissa konstruktoreissa.

Kutsu super(...) puolestaan tarjoaa mahdollisuuden valita jokin muu kuin yliluokan parametriton konstruktori suoritettavaksi. Tällaisella kutsulla tyypillisesti alustetaan perittyjä yksityisiä (private) kenttiä. Tilanteesta nähtiin esimerkkejä edellä.

Huom: Peräti itse Java-spesifikaatio sanoo, ettei yksityisiä (private) kenttiä peritä aliluokassa. Mielestäni siellä sanotaan "väärin"! Kirjoittajat tarkoittanevat, ettei aliluokkaa ohjelmoitaessa ole pääsyä perittyihin yksityisiin kenttiin? Mielestäni kuitenkin se, että aliluokan ilmentymät väistämättä sisältävät kaikki luokan ominaisuudet - niin perityt yksityiset kentät kuin kaiken muunkin - oikeuttaa sanomaan, että myöskin private-kentät periytyvät!

Javassa voidaan antaa kentille, metodeille, konstruktoreille, luokille, ..., neljä erilaista näkyvyystasoa:

Määrellä final tehdään muuttujasta tai muodollisesta parametrista vakio, ts. muuttujan arvoa ei voi muuttaa.

Java-kielessä - monista muista kielistä poiketen - vakiolle voi kerran sijoittaa arvon! Tuo sijoitus on tietenkin tavallisesti muuttujan määrittelyn yhteydessä. Metodin muuttujalle se voidaan tehdä määrittelyn jälkeenkin metodin algoritmissa, ilmentymämuuttujalle konstruktorissa ja luokkamuuttujalle ns. staattisessa alustuslohkossa.

Jos metodi on final, sitä ei voi aliluokissa syrjäyttää. Kun luokka määritellään final-määreellä, luokalle ei voi tehdä aliluokkia.