6. praktikum (Abstraktsed klassid ja liidesed. Mähisklassid.)


Teemad

Abstraktsed klassid. Kalendriklassid. Liidesed. Liides Comparable. Mähisklassid.

Pärast selle praktikumi läbimist oskab üliõpilane
Alamklasside-ülemklasside hierarhias on alamklassid järjest spetsiifilisemad, ülemklassid aga vastavalt üldisemad. Mõnikord on mõistlik korraldada, et ülemklass on sedavõrd üldine, et näiteks kõigi meetodite sisu ei täpsustatagi. Selliseid klasse nimetatakse abstraktseteks ja nende loomiseks on erivõimalused, mida käesolevas praktikumis käsitlemegi. Veel on vaatluse all liidesed, mis võimaldavad teatud mõttes üle saada sellest, et Javas saab igal klassil olla vaid üks otsene ülemklass. Mähisklassid võimaldavad algtüüpi väärtusega "objektilisemalt" tegutseda.

Järgnevas materjalis moodustavad ülesanded 1, 3 ja 4 teatud terviku, mille osana tuleb luua peaklass, kus siis järjest loodud klasside isendite tegevust testida saab.

Abstraktsed klassid

Andmete mitmekordse kirjeldamise vältimiseks on mõnikord mõistlik luua ülemklass, kus mõned meetodid koosnevad vaid nö. laiendatud signatuuridest (meetodi nimi koos formaalsete parameetritega ja nende tüüpidega, piiritlejad ja tagastustüüp) ning sisu jäetakse alamklasside täpsustada. Selliseid meetodeid nimetatakse abstraktseteks. Abstraktsel meetodil on järgmine üldkuju:

abstract piiritlejad tagastustüüp meetodiNimi(parameetrite loetelu);

Seega meetodi keha puudub.

Kui klass sisaldab vähemalt ühte abstraktset meetodit, tuleb see varustada võtmesõnaga abstract ja seda klassi nimetatakse abstraktseks klassiks. Abstraktsest klassist ei saa luua isendeid. Abstraktse klassi iga alamklass on ise abstraktne või realiseerib ülemklasside kõik abstraktsed meetodid. Abstraktseks ei saa kuulutada konstruktorit ega staatilist meetodit.

Abstraktseid klasse saab kasutada muutujate tüübina. See annab võimaluse näiteks kirjeldada seda tüüpi elementidega järjendit, elemente ise aga luua alamklasside konstruktorite abil. Näide abstraktsest klassist:

abstract class A {


  A() {
    System.out.println("Klassi A isendi loomine."); 
  }


  abstract void esimeneTeade();

  void teineTeade() {
    System.out.println("See on klass A.");
  }
}

class B extends A {

  void esimeneTeade() {
    System.out.println("See on klass B.");
  }
}

class DemoAbstract {

  public static void main(String[] args) {
    B b = new B();
    b.esimeneTeade();
    b.teineTeade();
    // A a = new A(); viga!
    A a = new B();
  }
}


Näeme, et kuigi klassist A ei ole võimalik luua isendit tema enda konstruktoriga, on aga võimalik pöörduda tema meetodite ja ka konstruktori poole alamklassi isendi abil.


Ülesanne 1

Koostada abstraktne klass Telefon, milles on privaatsed isendiväljad telefoninumbri (int) ja helina (sõne) jaoks ning vastavad get-meetodid nende teadasaamiseks. Samuti peab olema abstraktne meetod tähtisInfo(), mis kaetakse alamklassides nii, et paiksete telefonide puhul tagastatakse aadress, liikuvate puhul aga omanik. Klassis on ka konstruktor. 

Koostada klassi
Telefon (mitteabstraktne) alamklass Lauatelefon. Abstraktse klassi alamklass saab olla mitteabstraktne ainult siis, kui ta realiseerib ülemklasside kõik abstraktsed meetodid. Klassis peavad olema privaatne isendiväli aadressi (String) jaoks ja vastav get-meetod. Konstruktoreid peab olema vähemalt kaks. Meetod viimasedNumbrid(int n)tagastab telefoninumbri n viimast numbrit ühe arvuna. (Selleks võib näiteks leida jäägi 10n-ga jagamisel.)

Koostada klassi Telefon (mitteabstraktne) alamklass MobiiltelefonKlassis peavad olema privaatsed isendiväljad omaniku (String) ja pildistamisvõimaluse (boolean) jaoks, vastavad piilumeetodid ja vähemalt kaks erinevat konstruktorit.

Koostada ka peaklass, mis leiab kasutust ka ülesannete 3 ja 4 korral.

Abstraktse klassi näide: Calendar

Abstraktseid klasse leidub ka Java APIs, näitena vaatleme klassi java.util.Calendar, mis on mõeldud ajaga seotud tegevusteks. Klassi Calendar abil saame aja puhul eraldi välja tuua infot, näiteks aastat, kuud, tunde, minuteid, sekundeid. (Mõningaid selliseid meetodeid on olemas ka klassis Date, aga need on kuulutatud ebasoovitavateks (deprecated) ja soovitatud ongi kasutada klassi Calendar.)

Klass Calendar on abstraktne klass tänu abstraktsetele meetoditele, näiteks add, computeTime, roll. Abstraktse klassi isendit pole võimalik luua selle klassi enda konstruktoriga. Küll aga on võimalik kasutada mitteabstraktset alamklassi, antud juhul klassi GregorianCalendar.

java.util.Calendar kalender1 = new java.util.GregorianCalendar(); 
System.out.println(kalender1);

On ka võimalus kasutada klassi Calendar staatilist meetodit getInstance, mis samuti tekitab Calendar-tüüpi objekti, mille väljad täidetakse hetkel kehtiva kuupäeva ja kellaajaga.

java.util.Calendar kalender2 = java.util.Calendar.getInstance(); 

System.out.println(kalender2);

Nagu näeme, on see kuju, mida meetodi toString varjatud abiga serveeritakse, küllaltki mitmekesine. Üks olulisemaid näidatud suurusi on time, mis näitab, mitu millisekundit on kulunud alates 1970. aasta 1. jaanuari  algusest. Kui vaadata klassi Calendar väljasid, siis näeme nende hulgas mõningaid isendiväljasid ja suurt hulka konstantseid klassiväljasid (piiritlejatega static ja final). Konstandid on tegelikult täisarvud, mille väärtused on toodud tabelis ja millest mitmeid saab kasutada näiteks get-meetodi argumendina. Järgnevad kaks rida on samaväärsed, sest MONTH väärtus ongi 2. Kui teil juhtub aga nüüd ekraanilegi kaks tulema, siis see on sellest, et tegevus toimub märtsis.

System.out.println(kalender1.get(java.util.Calendar.MONTH));
System.out.println(kalender1.get(2));

Ülesanne 2

Koostada programm, mis küsib kasutajalt sünnipäeva ja teatab, kui vana kasutaja on (täis)aastates ja ka sekundites.

Liidesed

Peale klasside võib Javas defineerida ka liideseid, mis on suhteliselt klassitaolised konstruktisoonid. Liides võimaldab määrata, mida klass peab tegema, jättes täpsustamata, kuidas seda teha. Süntaktiliselt on liidesed sarnased selliste klassidega, mille kõik meetodid on abstraktsed. Liides sisaldab ainult konstante ja meetodeid. Kui liides on loodud, on võimalik seda realiseerida (varustada sisuga) paljudes klassides. Seda näitab vastavas klassis võtmesõna implements. Üks klass võib realiseerida suvalise arvu liideseid, sellisel juhul eraldatakse realiseeritavate liideste nimed komadega. (Siin on oluline erinevus pärilusest, kuna üks klass ei saa olla rohkema kui ühe klassi vahetu alamklass. Sellest Java nõudest "mööda hiilimine" ongi liidese üks olulisi kasutusalasid.) Liidest realiseerivas klassis peavad realiseeritavate meetodite signatuurid olema täpselt samad, mis on liideses. Tuleb realiseerida kõik liideses sisalduvad meetodid või kuulutada klass abstraktseks. Selles klassis võib olla ka liideses kirjeldamata meetodeid.

Liideseid saab Javas teha sarnaselt klassidega (New --> Interface).

Näide liidese kasutamise kohta (liideste nimed on tavaliselt omadussõnad või nimisõnad, klasside nimed on tavaliselt nimisõnad):

interface Helistav {
  void helista(int nr);
}

class Klient implements Helistav {

  public void helista(int nr) {
       System.out.println("Helistatud numbril " + nr);
  }

  void helistaVeel() {
    System.out.println("Klassid, mis kasutavad liideseid" +
                    ", võivad sisaldada teisi meetodeid.");
  }
}


Liidese kõik meetodid on vaikimisi avalikud (public), nende realiseerimisel tuleb aga vastavat piiritlejat (public) ilmutatult kasutada. Liidest võib kasutada tüübina. Sel juhul hoitakse muutujas viidet isendile, mis realiseerib liidese.

class TestiLiidest {

    public static void main(String[] args) {
        Helistav c = new Klient();
        c.helista(4646);
    }
}


Oluline on siin, et muutuja c on tüüpi Helistav, mis on liides. Muutuja c abil saab pöörduda vaid nende meetodite poole, mis on loetletud liideses Helistav. Olgu meil veel üks liidese Helistav realisatsioon:

class TeineKlient implements Helistav {

    public void helista(int nr) {
        System.out.println("Helista teine versioon.");
        System.out.println("Number ruudus = " + (nr * nr) + ".");
    }
}


Liidesetüüpi muutuja on sõltumatu liidest tegelikult realiseerivast klassist.

class TestiTeist {

    public static void main(String[] args) {
        Helistav c = new Klient();
        TeineKlient d = new TeineKlient();
        c.helista(444);
        c = d; // c viitab nüüd klassi TeineKlient isendile
        c.helista(4);
    }
}


Kui klass realiseerib (implements) liidest, kuid ei  realiseeri siiski kõiki liidese meetodeid, siis tuleb klass deklareerida abstraktseks.

abstract class Pooleli implements Helistav {

    int a;
    int b;

    void naita() {
        System.out.println(a + " " + b);
    }

  // ...

}


Ülesanne 3

Koostada liides Ajanäitaja, mis sisaldab meetodit näitaAega().  Meetod on mõeldud hetkel kehtiva kellaaja sõnena tagastamiseks. Tagada, et klass Mobiiltelefon realiseerib liidese Ajanäitaja. Meetodis näitaAega võiks hetkeaja saamiseks kasutada klassi Calendar abi. Liides(t)e realiseerimine tuleb klassi päises märkida pärast ülemklassi märkimist (class B extends A implements C).

Liidese näide: Comparable

Java APIs on lisaks klassidele ka liideseid. Vaatame natuke põhjalikumalt liidest java.lang.Comparable, mis annab meile võimaluse võrrelda omavahel seda liidest realiseerivate klasside isendeid. Võrdlemise sooritab meetod compareTo. Kui klass A realiseerib liidese Comparable, siis annab see näiteks võimaluse Java vahenditega sortida jada, mis koosneb klassi A isenditest. See, mille alusel toimub isendite võrdlemine, sõltub ainult meetodi compareTo kirjeldusest klassis A.
Meetodi compareTo realisatsioon peab kindlaks tegema, milline on antud objekt, võrreldes parameetrina antud objektiga. Meetod tagastab negatiivse arvu, arvu 0 või positiivse arvu vastavalt sellele, kas antud objekt on parameetriks antud objektist väiksem, temaga võrdväärne või temast suurem.

Vaatleme uuesti varasemas kirjeldatud klassi Isik. Oletame, et me võrdleme isikuid pikkuse järgi. Modifitseerime klassi selliselt, et see realiseeriks liidest Comparable ja realiseerime meetodi compareTo, mis annab täisarvulise tulemuse.

class Isik implements Comparable {

  private String nimi;   // isendiväli isiku nime jaoks
  private double pikkus; // isendiväli isiku pikkuse jaoks

  Isik(String isikuNimi, double isikuPikkus) {
    nimi = isikuNimi;
    pikkus = isikuPikkus;
  }

  double getPikkus() {
    return pikkus;
  }

  public int compareTo(Object o) {
    if (this.getPikkus() > ((Isik) o).getPikkus()) {
      return 1;
    } else if (this.getPikkus() < ((Isik) o).getPikkus()) {
        return -1;
    } else {
        return 0;
    }
  }

  public String toString() {
    return nimi + " (pikkusega " + pikkus + ") ";
  }
}

class VordleIsik {

  public static void main(String[] args) {
    Isik a = new Isik("Juhan Juurikas", 1.99);
    Isik b = new Isik("Madli Mallikas", 1.55);
    System.out.print("Isik " + a +
            " on võrreldes isikuga " + b);
    if (a.compareTo(b) > 0)
      System.out.println(" pikem.");
    else if (a.compareTo(b) < 0)
      System.out.println(" lühem.");
    else
      System.out.println(" sama pikk.");
  }
}


Kuna compareTo formaalne parameeter on tüüpi Object, siis on vajalik tüübiteisendus enne piilumeetodi getPikkus poole pöördumist.

Ülesanne edasijõudnutele

Selline programm kompileerub ja töötab. Kui aga vaadata Eclipse'is klassi Isik päist, siis võib seal märgata, et Comparable on õrnalt alla joonitud ja toodud on ka hoiatus: Comparable is a raw type. References to generic type Comparable should be parameterized

Nimelt on alates Java 5. versioonist võimalik kasutada geneerilist programmeerimist. Natuke räägiti sellest 6. loengus. Jätkame selle teemaga ka edaspidi, aga edasijõudnutel on soovitav juba praegu proovida see ülesanne lahendada vastavaid võimalusi kasutades. (Ehk siis nii, et see hoiatus kaoks.)

Abiks võivad olla järgmised materjalid: http://docs.oracle.com/javase/tutorial/java/generics/ ja http://www.java2s.com/Tutorial/Java/0200__Generics/Catalog0200__Generics.htm 


Ülesanne 4


Täiendada klassi Lauatelefon nii, et see realiseeriks liidese Comparable. Võrdlemisel võtta aluseks telefoninumbri kolmest viimasest numbrist koosnev arv. Selle leidmiseks kasutada meetodit viimasedNumbrid. Testklassis seejärel sorteerida lauatelefonide järjend meetodit java.util.Arrays.sort(Object[] o)kasutades. 

Mähisklassid

Selleks, et jõudlust paremal tasemel hoida, ei käsitleta algtüüpi suurusi Javas objektidena. Samas nõuavad paljud meetodid siiski argumentidena objekte. Sellisel puhul saame kasutada mähisklasse (wrapper class). Algtüüpidele vastavad mähisklassid on Boolean, Character, Double, Float, Byte, Short, Integer ja Long. Arvudele vastavad mähisklassid on abstraktse klassi Number alamklassid. Neis kõigis on meetodid doubleValue, floatValue, intValue, longValue, shortValue ja byteValue, mis tagastavad vastavat algtüüpi väärtuse. Kõik arvulised mähisklassid ja klass Character realiseerivad liidese Comparable (st. neis on meetod compareTo). Igas arvulises mähisklassis on üks konstruktor, mis nõuab argumendiks vastavat algtüüpi suurust ja teine, mis nõuab argumendiks sõnet. 

Integer intObjekt1 = new Integer(15);
Integer intObjekt2 = new Integer("17");
int arvint2 = intObjekt2.intValue();
double arvdouble2 = intObjekt2.doubleValue();
System.out.println(arvdouble2);
System.out.println(intObjekt1.compareTo(intObjekt2));

Viimase reaga väljastatud -1 tähendab, et intObjekt1 on väiksem kui intObjekt2.

Arvulistes mähisklassides on ka konstandid MAX_VALUE ja MIN_VALUE, mis kujutavad vastava algtüübi maksimaalseid ja minimaalseid väärtusi. Täisarvuliste tüüpide puhul on MIN_VALUE üldse minimaalne arv, Float ja Double korral minimaalne positiivne arv.

System.out.println(Double.MIN_VALUE);
System.out.println(Byte.MAX_VALUE);

Igas arvulises mähisklassis on staatiline meetod valueOf, mis loob uue objekti sõne põhjal.

Integer intObjekt3 = Integer.valueOf("19");

Sõne põhjal saab luua algtüüpi suurusi ka vastavate parsimismeetoditega, millele täisarvude puhul saab näidata ka arvusüsteemi aluse.

double arvdouble4 = Double.parseDouble("21");
int arvint4 = Integer.parseInt("1CE",16);
    

Ülesanne 5

Vaadake Java APIst mähisklasside infot ja uurige erinevaid meetodeid, konstante. Näiteks seda, kuidas on korraldatud töö lõpmatustega.