5. praktikum (Pärilus ja polümorfism. Meetodite ülekatmine. Klass Object.)


Teemad

Pärilus. Meetodite ülekatmine. Klass Object.  Polümorfism. Tüübiteisendus.

Pärast selle praktikumi läbimist üliõpilane

Pärilus

Javas (ja ka mitmetes teistes keeltes) asuvad klassid teatud ülemklasside ja alamklasside hierarhias. Tahtes olemasolevale klassile lisada võimalusi ja omadusi (meetodid, väljad), kuid seda klassi ennast mitte muuta, võime luua uue klassi, milles kirjeldame lisatavad meetodid ja väljad. Niiviisi loodud klassi nimetame alamklassiks (subclass, ka child class, extended class, derived class) ja klassi, millest lähtusime, ülemklassiks (superclass, ka parent class, base class). Sellist tegevust võib üldjuhul korduvalt jätkata. Ülemklassi omadused kanduvad  üle tema alamklassidele. Vastavat mehhanismi nimetatakse päriluseks (inheritance). Pärilus on üks objektorienteeritud programmeerimise alustalasid, võimaldades loodud klasside taaskasutamist ja tekitades klasside hierarhilise struktuuri. Hierarhia juureks on klass java.lang.Object, mis on kõigi teiste klasside otseseks või kaudseks ülemklassiks.

Eelmistes praktikumides loodud klasside puhul me ei märkinud kuidagi eraldi, et tegemist on klassi Object alamklassidega, seda arvestati vaikimisi. Kui aga loome mõne teise klassi alamklassi, siis tuleb seda eraldi märkida. Märkimaks, et klass B on klassi A alamklass, tuleb klassi nime järele lisada võtmesõna extends ja ülemklassi nimi:

class A {
  . . .
}

class B extends A {
  . . .
}

Kui Eclipse'is luua uus klass (File - New - Class), siis saab ülemklassi määrata vastaval real Superclass. Vaikimisi on seal real java.lang.Object.

Alamklassis on tegelikult olemas kõik need ülemklassi muutujad ja meetodid, mille piiritlejaks ei ole private. Kolmandas praktikumis lõime klassi Isik, milles olid isendiväljad nime ja pikkuse jaoks ning hulk meetodeid. Püüame nüüd sellele klassile luua alamklassi Tudeng,  milles on (lisaks päritud isendiväljadele) sõnetüüpi isendiväli ülikooli märkimiseks.

public class Tudeng extends Isik {
    private String ülikool;
}


Lisame ka konstruktorid, mida klassi Tudeng isendi loomisel saab kasutada. Kui kasutame Eclipse'it, siis saame Source-menüüst kasutada nii Generate Constructors using Fields ... kui ka 
Generate Constructors from Superclass ...

Kui genereerida võimaluse
Generate Constructors using Fields ... abiga, siis saame näiteks:

public Tudeng(String ülikool) {
    super();
    this.ülikool = ülikool;
}


(Täpne kuju sõltub, milline valik ülemklassi konstruktori suhtes tehakse. Võimaluste järjekord, millest esimene on vaikimisi nähtav sõltub kontruktorite järjekorrast ülemklassis.)


Kui genereerida võimaluse Generate Constructors from Superclass ... abiga, siis saame näiteks:

public Tudeng() {
    super();
        // TODO Auto-generated constructor stub
}

või
 
public Tudeng(String nimi, double pikkus) {

    super(nimi, pikkus);
        // TODO Auto-generated constructor stub
}


Võtmesõnaga super saame pöörduda ülemklassi konstruktori poole. Tingimuseks on, et see käsk (kui ta on vajalik), asuks konstruktori esimesel real (kommentaariridu arvestamata). (Ka võtmesõnaga this sama klassi teise konstruktori väljakutsumine peab olema esimesel real.) Kui selliseid väljakutseid konstruktoris kirjas pole, siis toimub siiski pöördumine ülemklassi ilma argumentideta konstruktori poole.

Konstruktor, millele saaks ette anda nii nime, pikkuse, kui ülikooli, näeb välja selline.

public Tudeng(String nimi, double pikkus, String ülikool) {
    super(nimi, pikkus);
    this.ülikool = ülikool;
}

Nüüd saame näiteks klassis TestIsik klassi Tudeng isendeid luua.

Tudeng t1 = new Tudeng("Eveli Saue", 1.63, "Tartu Ülikool");
Tudeng t2 = new Tudeng("Karel Tammjärv", 1.93, "Tartu Ülikool");
Tudeng t3 = new Tudeng("Tallinna Tehnikaülikool");

Ülesanne 1

Looge klassi Isik alamklass Tudeng vastavalt ülaltoodud programmilõikudele. Looge klassis TestIsik mitmeid klassi Tudeng isendeid ja katsetage nendega isendimeetodit suusaKepiPikkus.

System.out.println(t1.suusaKepiPikkus());

Lisage get- ja
set-meetodid, millega saab vaadata ja muuta tudengi ülikooli.

Meetodid. Ülekatmine

Nagu nägime, saab alamklassi isend kasutada ülemklassi meetodit. Koostame aga nüüd uue meetodi, mis on vaid klassi Tudeng isenditele.    

char hinne (int punkte){
    if (punkte > 90) {
        return 'A';
        } else if (punkte > 80) {
            return 'B';
        } else if (punkte > 70) {
            return 'C';
        } else if (punkte > 60) {
             return 'D';
        } else if (punkte > 50) {
             return 'E';
        } else {
             return 'F';
        }
    
}



Katsetame seda klassis TestIsik.

System.out.println(t1.hinne(97));
System.out.println(a.hinne(87));

(Olgu a klassi Isik isend, loodud nt. Isik a = new Isik("Juhan Juurikas", 1.99);)

Näeme, et  mittetudengist isiku puhul ei õnnestu hinnet määrata.

Vahel on vaja ülemklassist päritud meetodi puhul, et see töötaks alamklassi puhul teistmoodi kui ülemklassis. Sellist sama signatuuriga meetodi uuestikirjeldamist alamklassis nimetatakse meetodi ülekatmiseks (overriding). Kui midagi muuta pole vaja, ei ole mõtet alamklassi seda meetodit uuesti kirjutadagi. Aga meie muudame meetodit nii, et see kirjutaks (lisaks suusakepi pikkuse tagastamisele) ekraanile "Olen tudeng!".

int suusaKepiPikkus(){
    System.out.println("Olen tudeng!");
    return (int) Math.round(0.85*pikkus*100);
}

Kui klassis Isik on isendivälja pikkus ees piiritleja private, siis alamklassi meetod seda nii kasutada ei saa. Kui panna piiritlejaks proteced (või public), siis saab.

Kui tahta ikkagi alamklassis kasutada seda meetodi varianti, mis on ülemklassis, tuleb kasutada võtmesõna super.

int suusaKeppidePikkus(){

    return 2*super.suusaKepiPikkus();
}

Ülesanne 2

Katke klassis Tudeng üle neid meetodeid, mille 3. praktikumis klassi Isik tegite. Proovige nende kasutamist. 

Klass Object

Nagu öeldud, on kõigi teiste klasside (kas otseselt või kaudselt) ülemklassiks java.lang.Object (vt. API). Päriluse tõttu on igas klassis olemas tema meetodid, nt. toString ja equals. Tahtes ülemklassis kirjeldatud meetodit panna alamklassis käituma teisiti, peame meetodi üle katma. Tegelikult katsime klassi Object meetodi toString  üle juba klassis Isik, aga siis ei nimetanud me seda tegevust nii. 

Meetod equals võrdleb, kas kaks objekti on võrdsed: objekt1.equals(objekt2). Vaikimisi toimib equals järgmiselt.

public boolean equals(Object obj) {
    return (this == obj)
}

Aga näiteks klassi String puhul on see nii üle kaetud, et võrreldakse hoopis sõnede sisu.

Ülesanne 3 

Katke klassis Tudeng üle meetod toString nii, et seal kajastuks tudengiks olemine.

Polümorfism

Iga alamklassi isend on ka ülemklassi isend, aga vastupidi mitte. Näiteks iga tudeng on isik, aga iga isik ei ole tudeng. (Seda nii meie programmi korral, aga ka elus laiemalt.) Igale poole, kus on nõutud ülemklassi isendid, sobivad ka alamklassi isendid. Seda omadust nimetatakse polümorfismiks.

Lisame klassi TestIsik staatilise meetodi, mille argument on klassi Object isend ja mis väljastamisel kasutab meetodit toString.

public static void väljasta(Object o){
    System.out.println(o.toString());
}

Selle meetodi argumendiks sobib ükskõik millist tüüpi objekt (on ju kõik ülejäänud klassid klassi Object alamklassid).

väljasta(a);
väljasta(t1);

Loome klassi Magistrant, mis oleks klassi Tudeng alamklass. Teeme hästi triviaalse klassi.

public class Magistrant extends Tudeng {

}

Klassi isendi saame luua vaikekonstruktoriga.

Magistrant m1 = new Magistrant();

Kui nüüd kasutada
meetodit väljasta selle argumendiga, siis näeme, et rakendus klassis Tudeng kirjeldatud toString meetod. Kuna klassis Magistrant pole meetodit toString üle kaetud, siis asutakse seda otsima tema vahetust ülemklassist. Kuna klassis Tudeng on see meetod ilmutatult olemas, siis kasutatakse seda. Kui poleks olnud, siis oleks otsitud klassist Isik, mis on klassi Tudeng vahetuks ülemklassiks jne. Tegemist on dünaamilise seostamisega (dynamic binding).

Tüübiteisendus

Varasemates praktikumides oleme algtüüpide korral vahel kasutanud tüübiteisendust (nt. int a = (int) Math.round(...); ). Tüübiteisendust (casting) saab kasutada ka objektide puhul. Näiteks saame luua Object-tüüpi muutuja luues klassi Magistrant isendi ning siis seda kasutada argumendina meetodi puhul, mis nõuabki argumendiks Object-tüüpi.

Object o = new Magistrant();
väljasta(o);

Tegemist on ilmutamata tüübiteisendusega (implicit casting), mis on lubatud, kuna iga klassi Magistrant isend on ka klassi Object isend.

Võiks tahta nüüd objekti o kasutada magistrandina ning teha väärtustus

Magistrant m3 = o;

Paraku see ei õnnestu. Kompilaatorile tuleb eraldi märku anda, et tõesti nii teha.

Magistrant m3 = (Magistrant) o;

Tegemist on ilmutatud tüübiteisendusega (explicit casting), mis on võimalik, kui tüübid on vastavalt klassihierarhias. Selleks, et teada saada, kas objekt on mingi klassi isend, saame kasutada võtmesõna instanceof, mis siis annab vastava tõeväärtuse.

if (o instanceof Magistrant) { ...}

Ülesanne 4

Koostada enda valikul komplekt klasse (vähemalt 5 klassi), mis oleksid ülemklasside-alamklasside hierarhilises struktuuris, milles on vähemasti 3 taset. Mõned võimalikud teemariingid oleksid nt. geomeetrilised kujundid, loomad, sõidukid, spordialad, toiduained. Aga julgesti võite käsitleda ka mingeid muid struktuure.

Mõelge välja ja lahendage seoses loodud klasside ülesandeid, mis nõuaksid
Joonistage paberile koostatud klasside hierarhia puustruktuur, mis sisaldab ka klassi Object.

Kodune ülesanne