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
- luua ja kasutada abstraktset klassi;
- kasutada ajaarvamist;
- luua ja kasutada liidest;
- võrrelda objekte, vajadusel luua
võrdlemiskriteeriume;
- kasutada mähisklasse.
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. Nende ülesannete
lahenduste lõpptulemustest moodustubki selle praktikumi kontrollülesande lahendus.
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 (kontroll)
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 Mobiiltelefon. Klassis 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 üheksa tulema, siis see on sellest, et tegevus toimub oktoobris.
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 (kontroll)
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 (kontroll)
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.
Ülesanne 6
Plaadipood
haldab andmebaasi muusika- ja filmiplaatidest. Koostage klassid nii, et
komplekt vastaks järgmisele skeemile. Klassi juurest
viib pideva joonega nool tema ülemklassi juurde, kriipsjoonega nool viib aga liidese juurde, mida antud klass peab realiseerima. Abstraktse klassi nimi on kirjutatud kaldkirjas.

Täidetud
peavad
olema
järgmised
tingimused.
- Abstraktne
klass
Plaat
sisaldab
privaatset
int-tüüpi
isendivälja
plaadi väljaandmise
aasta ja privaatset double-tüüpi isendivälja plaadi hinna
jaoks. Lisaks on vastavad get-meetodid. Samuti on klassis abstraktne String-tüüpi
meetod
tagastaInfo
salvestise
info
tagastamiseks. Ei ole
vaja muretseda, et meetod compareTo ei ole klassis Plaat realiseeritud - seda meetodit
võib välja kutsuda ka abstraktse klassi Plaat alamklassides Muusikaplaat ja Filmiplaat.
- Klass
Muusikaplaat
on
klassi
Plaat alamklass.
Klassis
on
eraldi privaatsed String-tüüpi
isendiväljad
muusikaplaadi
artisti ja plaadi nime jaoks. Meetod tagastaInfo realiseeritakse klassis Muusikaplaat
nii, et
see
tagastab
muusikaplaadi
artisti nime, plaadi nime, väljaandmise aasta ja hinna. Lisaks klassis
realiseeritakse compareTo
meetod nii, et võrdlemine toimub artisti nime alusel tähestikuliselt. Meetodis
kasutada klassi String meetodit compareTo.
-
Klass
Filmiplaat
on
klassi
Plaat
alamklass.
Selles
on
eraldi privaatsed String-tüüpi
isendiväljad
filmi režissööri ja
filmi pealkirja jaoks. Meetod tagastaInfo realiseeritakse klassis Filmiplaat
nii, et
see
tagastab
filmiplaadi
režissööri nime, filmi pealkirja, väljaandmise aasta ja hinna. Lisaks
realiseeritakse klassis compareTo meetod nii, et võrdlemine toimub filmi nime põhjal
tähestikuliselt. Meetodis kasutada klassi String meetodit compareTo.
- Peaklassis
- luua
kaks
filmiplaati ja vőrrelda neid omavahel;
- luua tehtud muusikaplaatidest massiiv (Muusikaplaat[]);
- sorteerida muusikaplaadid vastavalt meetodis compareTo kirjeldatud järjekorrale;
- väljastada muusikaplaatide andmed ekraanile (jällegi järjekorras, mis on määratud meetodis compareTo).
Veel
- Palun õppige korralikult 1.
kontrolltööks, mis põhineb kuue esimese
praktikumi materjalidel.
- Ei piisa sellest, et loete läbi ja nagu saaksite
aru ning loodate, et küll kontrolltöö ajal
jõuate vaadata!
- Palun lahendage ülesanded-näited
läbi. Proovige programme mõnevõrra muuta.
- Lahendage ise läbi kontrolltöö näidisülesanded, mis on 6. praktikumi harjutusülesanded.
- Kontrolltöö on suurepärane
võimalus oma oskusi ja teadmisi näidata! Koguge
neid siis enne kontrolltööd.
- Kontrolltöös tuleb lahendus esitada
java-failidena. Mõistlik oleks failid enne esitamist
pakkida Eclipse'i abiga: File-menüü Export
--> General --> Archive file --> zip