Nädal 6
Pärilus. Meetodite ülekatmine. Klass Object.
Teemad
Pärilus. Meetodite ülekatmine. Klass Object
. Polümorfism.
Pärast selle nädala läbimist üliõpilane
- oskab luua alamklasse;
- oskab kasutada ülemklassi meetodeid;
- oskab katta üle ülemklassi meetodeid;
- oskab kasutada võtmesõna
super
; - tunneb klasside hierarhiat ja teab klassi
Object
kohta selles; - on tuttav polümorfismi ja dünaamilise seostamisega.
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. Näiteks märkimaks, et klass SorteeritudTabel
on klassi Tabel
alamklass, tuleb klassi nime järele lisada võtmesõna extends
ja ülemklassi nimi:
class Tabel { ... } class SorteeritudTabel extends Tabel { ... }
Kui klass peab korraga pärima ja liideseid realiseerima, siis tuleb märkida enne ülemklass ja seejärel realiseeritavad liidesed: class SorteeritudTabel extends Tabel implements Kuvatav { .. }
Päriluse kasutamiseks loo uus klass nagu tavaliselt, seejärel lisa klassi nime järele märksõna extends ja ülemklassi nimi. Kui ülemklass on märkimata, siis kasutatakse vaikimisi java.lang.Object
.
Järgnevalt on seletatud päriluse kasutamist mõndade pangandusega seotud klasside näitel. Näite aluseks on klass Klient
.
public class Klient { private String isikukood; private double saldo; private List<String> tehingud = new ArrayList<>(); public Klient(String isikukood, double saldo) { this.isikukood = isikukood; this.saldo = saldo; } public void teostaÜlekanne(String kontole, double summa) { tehingud.add("ülekanne kontole " + kontole + ", summa " + summa); saldo -= summa; } public double arvutaTehinguTasud() { return 0.1 * tehingud.size(); } public String toString() { return "klient " + isikukood + ", saldo " + saldo; } }
Pangad teevad tihti klientidele eripakkumisi. Pank võib määrata kuldkliendile isikliku kliendihalduri ja teha soodustusi tehingutasude osas. Sellise kliendi programmis esitamiseks tuleks teha uus klass, mis on väga sarnane olemasolevale Klient klassile - kuldkliendil oleks kõik kliendi väljad ja meetodid, aga lisaks ka kliendihalduri väli ja muudetud kehaga tehingutasude meetod.
Lihtne lahendus oleks teha Klient
klassist copy-paste klass Kuldklient
ja teha seal vajalikud muudatused. Tegelikult ei oleks see kuigi hea lahendus, sest kui Kliendi klass peaks kunagi muutuma, siis tuleb teha samad muudatused ka kuldkliendi ja teiste eripakkumiste jaoks tehtud copy-paste klassides. Lisaks peab tegema klientide ja kuldklientide hoidmise jaoks erinevad listid, sest Klient
ja Kuldklient
on erinevad tüübid.
Parem lahendus oleks panna Kuldklient
klass pärima klassi Klient
omadused:
public class Kuldklient extends Klient { private String kliendiHaldur; public Kuldklient(String isikukood, double saldo, String kliendiHaldur) { super(isikukood, saldo); // Klient konstruktori käivitamine this.kliendiHaldur = kliendiHaldur; } }
Tänu pärilusele sisaldab alamklass Kuldklient
kõiki oma ülemklassi Klient
väljasid ja meetodeid - neid pole vaja uuesti välja kirjutada. Alamklass peab defineerima ainult need väljad ja meetodid, mida ülemklassis pole. Ülemklassist päritud väljad tuleb siiski väärtustada ja selle jaoks tuleb käivitada ülemklassis defineeritud konstruktor (mõnikord saab kasutada ka set-meetodeid).
Kompilaator nõuab, et ülemklassi konstruktori käivitamine toimuks alamklassi iga konstruktori alguses. Ülemklassi konstruktori käivitamiseks tuleb kasutada võtmesõna super
ja sulgudesse tuleb märkida ülemklassi konstruktori argumendid. Kui ülemklassil on mitu konstruktorit, siis valitakse konstruktor parameetrite arvu ja tüüpide järgi. Kui alamklassi konstruktoris ülemklassi konstruktori kutsumine ära jätta, siis kutsutakse automaatselt ülemklassi ilma parameetriteta konstruktorit (kui seda ei eksisteeri, tekib kompileerimisviga).
IDE abil saab sobilikud konstruktorid automaatselt genereerida. IntelliJ puhul kasuta Code-menüüst Generate valikut.
Tasub ära märkida, et loodud alamklass pärib kõik ülemklassi omadused, aga alamklassist saab otse kasutada ainult neid väljasid ja meetodeid, millel ei ole private
piiritlejat. Ülemklassi privaatsetele väljadele ligi pääsemiseks tuleb lisada ülemklassi vajalikud get-meetodid või korraldada programmi töö nii, et alamklassidest pole privaatsetele väljadele vaja ligi pääseda.
Ülesanne 1
Looge klassid Klient
ja Kuldklient
vastavalt ülaltoodud programmilõikudele. Looge klassis TestPank
mitmeid klassi Klient
ja Kuldklient
isendeid ja katsetage nendega isendimeetodeid teostaÜlekanne
ja arvutaTehinguTasud
. Lisage get- ja set-meetodid, millega saab vaadata ja muuta kuldkliendi haldurit. Veenduge, et kliendihalduri meetodid toimivad ainult Kuldkliendi isendite peal.
Enesekontroll
Meetodid. Ülekatmine
Kuldkliendi klass sai loodud selleks, et saaks kuldkliendile määrata kliendihalduri ja pakkuda tehingutasude soodustusi. Pärilus on siiani aidanud väljade ja meetodite dubleerimist vältida, aga ülemklassist päritud arvutaTehinguTasud
meetodi tõttu peab kuldklient jätkuvalt tavakliendi hindu kasutama.
Soodustuste pakkumiseks on vaja Kuldklient
klassis muuta päritud arvutaTehinguTasud
meetodi käitumist. Selle jaoks tuleb Kuldklient
klassis lihtsalt arvutaTehinguTasud
meetod uuesti defineerida ja määrata sellele sobilik keha. Kui alamklassis defineerida sama signatuuriga meetod, mis ülemklassis juba olemas on, siis alamklassi definitsioon asendab ülemklassi definitsiooni. Seda kutsutakse meetodi ülekatmiseks (overriding).
public class Kuldklient extends Klient { private String kliendiHaldur; public Kuldklient(String isikukood, double saldo, String kliendiHaldur) { super(isikukood, saldo); this.kliendiHaldur = kliendiHaldur; } @Override public double arvutaTehinguTasud() { return 10.0; // alati sama teenustasu sõltumata tehingute arvust } }
Üle katta saab kõiki ülemklassi meetodeid, mis pole staatilised (static) ega privaatsed (private). Üle kaetud meetodite ette on hea mõte lisada @Override
annotatsioon. Annotatsioon ei mõjuta mitte kuidagi programmi käitumist - see on kõigest vihje kompilaatorile, et programmeerija eesmärk oli meetod üle katta. Kui kompilaator avastab, et ülemklassis pole samasuguse signatuuriga meetodit, keeldub ta koodi kompileerimast.
Ülesanne 2
Lisage Klient
klassi uus privaatne double
-tüüpi väli aktsiaPortfelliVäärtus
. Muutke Klient
konstruktorit, et sellega saaks välja väärtustada (muutma peab ka KuldKlient
konstruktorit, et vajalikud andmed ülemklassi konstruktorile edasi anda). Lisage Klient
klassi meetod arvutaPortfelliHaldustasu
, mis tagastab 0.5% portfelli väärtusest. Katke KuldKlient
klassis arvutaPortfelliHaldustasu
üle nii, et kuldkliendilt ei küsita esimese 50000€ eest hooldustasu. Vajadusel lisa portfelli väärtusele get-meetod. Katseta haldustasu arvutamist erinevate klient ja kuldklient isendite peal.
Klass Object
Nagu öeldud, on kõigi teiste klasside (kas otseseks või kaudseks) ü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 Klient
, 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; // loodetavasti mäletad, et == võrdleb kahe objekti identiteete }
Aga näiteks klassi String
puhul on see nii üle kaetud, et võrreldakse hoopis sõnede sisu.
Polümorfism
Tuletage meelde eelmise nädala materjali liideste polümorfismi. Kui klass realiseeris liidese, siis sai selle klassi isendeid kasutada igal pool, kus oodati liidese tüüpi objekti. Päriluse puhul toimib sarnane loogika: iga alamklassi isendit saab kasutada seal, kus on oodatud tema ülemklassi tüüpi objekti. Samas iga ülemklassi isend ei ole tema alamklassi tüüpi. Näiteks iga Kuldklient
on ühilduv Klient
ja Object
tüüpidega. Samas iga Klient
ei ole Kuldklient
ja iga Object
ei ole Klient
(ega Kuldklient
). Klassil saab ju olla mitu alamklassi, nagu liidesel saab olla mitu realisatsiooni.
Mõned näited:
static void kasutaKlienti(Klient k) { ... } static void kasutaObjekti(Object o) { ... } Kuldklient kuld = new Kuldklient(...); Klient tava = kuld; // toimib, sest Kuldklient on Kliendi alamklass Object obj = kuld; // toimib, sest Klient on Object alamklass kasutaKlienti(kuld); // OK kasutaObjekti(new Klient(...)); // OK, siia sobib kõik peale primitiivide List<Klient> kliendid = new ArrayList<>(); kliendid.add(tava); kliendid.add(kuld); // Kuldklient on ka Klient kliendid.add(obj); // kompilaator kurdab: iga Object ei ole Klient Kuldklient teineKuld = tava; // kompilaator kurdab: iga Klient ei ole Kuldklient
Dünaamiline seostamine
Loome klassi KampaaniaKuldklient
, mis oleks klassi Kuldklient
alamklass. Teeme hästi triviaalse klassi ja loome sellest isendi.
public class KampaaniaKuldklient extends Kuldklient { public KampaaniaKuldklient(String isikukood, double saldo, String kliendiHaldur, double aktsiaPortfelliVäärtus) { super(isikukood, saldo, kliendiHaldur, aktsiaPortfelliVäärtus); } } KampaaniaKuldklient k = new KampaaniaKuldklient(1, 100, "andrus", 3000);
Kui nüüd kasutada meetodit k.toString()
, siis näeme, et rakendus klassis Klient
kirjeldatud toString
meetod. Kuna klassis KampaaniaKuldklient
pole meetodit toString
üle kaetud, siis asutakse seda otsima tema vahetust ülemklassist Kuldklient
. Kui klassis Kuldklient
see meetod oleks ilmutatult olemas (teeme hiljem ülesandes 3), siis kasutataks seda. Kuna aga seal pole, siis otsitakse klassist Klient
, mis on klassi Kuldklient
vahetuks ülemklassiks. Tegemist on dünaamilise seostamisega (dynamic binding).
Tähtis on mõista, et dünaamiline seostamine toimib sõltumata muutuja tüübist - tähtis on vaid see, mis klassi isendiga tegu on. Iga objekt mäletab täpselt, mis klassi isend ta on (see fikseeritakse isendi loomisel) ja teab sellest tulenevalt, kust ülekaetud meetodeid otsida.
KampaaniaKuldklient k = new KampaaniaKuldklient(1, 100, "andrus", 3000); k.toString(); // käivitub meetod toString klassist Kuldklient Object o = new KampaaniaKuldklient(2, 50, "jaana", 4000); o.toString(); // hoolimata muutuja tüübist on tegu KampaaniaKuldklient isendiga. käivitub meetod toString klassist Kuldklient
Ülekaetud meetoditest
Eelnevalt kirjeldasime meetodite üle katmist - alamklassi meetod "asendab" ülemklassist päritud meetodi, kui sellel on ülemklassi meetodiga sama signatuur. Dünaamiline seostamine selgitab, kuidas see täpsemalt toimib. Tegelikult ülekaetud meetodit ei asendata täielikult. See on ülemklassis jätkuvalt olemas, aga seda pole võimalik alamklassi tüüpi isendi kaudu käivitada. Dünaamilise seostamise tõttu hakatakse objektil meetodit kutsudes seda kõigepealt otsima objekti klassist ja alles seejärel ülemklassidest. Niimoodi leitakse ülekattev meetod alati enne ülemklassis asuvat ülekaetud meetodit.
Mõnikord oleks siiski kasulik käivitada hoopis ülemklassis asuv ülekaetud meetod. See on võimalik võtmesõna super
abil. Näiteks kuldklient võib reklaamida säästetud teenustasude summat:
// class Kuldklient public double säästetudTehinguTasud() { // käivitab Kuldklient klassis oleva meetodi double soodusTasud = arvutaTehinguTasud(); // käivitab Klient klassis oleva meetodi double tavakliendiTasud = super.arvutaTehinguTasud(); return tavakliendiTasud - soodusTasud; }
Võtmesõna super
kasutamine muudab tavapärase dünaamilise seostamise loogikat: käivitatava meetodi otsimist alustatakse objekti ülemklassist.
Enesekontroll
Ülesanne 3
Katke klassis Kuldklient
üle meetod toString
nii, et seal kajastuks ka kliendihalduri nimi. Klient klassis olevat toString loogikat ei peaks dubleerima.
Ülesanne 4 (kontroll)
Koostage enda valikul komplekt klasse (vähemalt 5 klassi), mis oleksid ülemklasside-alamklasside hierarhilises struktuuris, milles on vähemasti 3 taset. Mõned võimalikud teemaringid 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 klassidega ülesandeid, mis nõuaksid
- meetodite ülekatmist,
- dünaamilist seostamist,
- polümorfismi,
- ja muu ülaltoodu rakendamist.
Lisage programmi kommentaarid sinna, kus meetodite ülekatmist, dünaamilist seostamist, polümorfismi vms on kasutatud (iga asja kohta kommentaar vähemalt ühe koha peale).
Joonistage paberile (või arvutifaili) koostatud klasside hierarhia puustruktuur, mis sisaldab ka klassi Object
.
Kui olete juba hulk aega proovinud ülesannet iseseisvalt lahendada ja see ikka ei õnnestu, siis võib-olla saate abi murelahendajalt
. Püütud on tüüpilisemaid probleemseid kohti selgitada ja anda vihjeid.
Ülesanne 5 (kontroll)
Klõpsa siia ülesande eesmärkide nägemiseks
Ülesande põhieesmärgid on harjutada:
- ülem- ja alamklasside loomist;
- polümorfismi kasutamist;
- dünaamilise seostamise kasutamist;
- ülemklassi meetodite kasutamist;
- meetodite ülekatmist.
Üks autoteenindus palub arendada autoremondi haldussüsteemi. Autoteenindus tegeleb sõidu-, veo- ja luksusautode parandamisega. Süsteemi koostamisel tuleb arvestada sellega, et iga autoliigi puhul kehtib oma hinnakiri.
Koosta klass Auto
, millel on privaatsed isendiväljad omaniku nime (String
) ja automudeli (String
) jaoks ning privaatne isendiväli, mis määrab, kas tegemist on elektriautoga (boolean
). Lisaks on klassis:
- konstruktor isendiväljade väärtustamiseks;
double
-tüüpi isendimeetodarvutaParanduseMaksumus
, millel on üksdouble
-tüüpi parameeter auto parandamisele kulutatud aja jaoks (tundides). Meetod peab tagastama remondi maksumuse, eeldusel, et töötunni hind elektriauto korral on 36 eurot ja muudel juhtudel 40 eurot (vt näide 1);- parameetriteta
String
-tüüpi isendimeetodautoliik
, mis tagastab sõnaSõiduauto
; toString
meetod, mis tagastab autoliigi (kasutades eelmist meetodit) koos automudeli ja omanikuga (nt kujul:Sõiduauto. Mudel: Audi A4 Avant; omanik: Peeter Paanika
).
Klõpsa siia näite 1 nägemiseks
Auto auto = new Auto("Peeter Paanika", "Audi A4 Avant", false); System.out.println(auto.arvutaParanduseMaksumus(2.5)); // Väljastatakse: 100.0
Koosta klassi Auto
alamklass Veoauto
, millel on:
- privaatne
boolean
-tüüpi isendiväli, mille väärtus näitab, kas veoauto omanik on füüsiline isik (välja väärtusekstrue
) või mitte; - konstruktor isendiväljade väärtustamiseks (kokku 4 parameetrit);
- ülekaetud isendimeetod
arvutaParanduseMaksumus
. Meetod peab tagastama kahekordse või kolmekordse ülemklassi samanimelise meetodi poolt arvutatud väärtuse vastavalt sellele, kas omanik on füüsiline isik või ettevõte (vt näide 2); - ülekaetud isendimeetod
autoliik
, mis tagastab sõnaVeoauto
.
Klõpsa siia näite 2 nägemiseks
Veoauto veoauto = new Veoauto("Mu firma", "Volvo", false, false); System.out.println(veoauto.arvutaParanduseMaksumus(1)); // Väljastatakse: 120.0
Koosta klassi Auto
alamklass Luksusauto
, millel on:
- privaatne
int
-tüüpi isendiväli tootmisaasta jaoks; - konstruktor isendiväljade väärtustamiseks;
- ülekaetud isendimeetod
arvutaParanduseMaksumus
. Meetod peab tagastama 15-kordse või 10-kordse ülemklassi samanimelise meetodi poolt arvutatud väärtuse vastavalt sellele, kas auto on vanem kui 70 aastat või mitte; - ülekaetud meetod
autoliik
, mis tagastab sõnaLuksusauto
.
Koosta klassi Luksusauto
alamklass Limusiin
, millel on:
- konstruktor ülemklassi isendiväljade väärtustamiseks;
- ülekaetud isendimeetod
arvutaParanduseMaksumus
. Meetod peab tagastama ülemklassi samanimelise meetodi 1,5-kordse väärtuse; - ülekaetud meetod
autoliik
, mis tagastab sõnaLimusiin
.
Koosta klass Autoteenindus
, millel on:
- privaatne
int
-tüüpi isendiväli parandatud autode arvu jaoks; - privaatne
double
-tüüpi isendiväli autoteeninduse teenitud tulu jaoks; void
-tüüpi isendimeetodparanda
, millel on 2 parameetrit: auto (Auto
-tüüpi), mida parandatakse, ja remondile kulutatud aeg tundides (double
-tüüpi). Meetod peab väljastama ekraanile info auto kohta koos remondi hinnaga ning vastavalt suurendama autoteeninduse parandatud autode ja tulu isendiväljade väärtusi (vt näide 3);toString
meetod, mis tagastab parandatud autode arvu koos teenitud tulu suurusega (loetaval kujul).
Klõpsa siia näite 3 nägemiseks
Autoteenindus autoteenindus = new Autoteenindus(); autoteenindus.paranda(auto, 2.5); autoteenindus.paranda(veoauto, 1); System.out.println(autoteenindus);
Ekraanile väljastatakse:
Sõiduauto. Mudel: Audi A4 Avant; omanik: Peeter Paanika — 100.0 Veoauto. Mudel: Volvo; omanik: Mu firma — 120.0 Parandatud autosid: 2, tulu: 220.0
Koosta peaklass AutodeParandamine
, kus peameetodis:
- luuakse autoteenindus ja vähemalt üks auto igast klassist (
Auto
,Veoauto
,Luksusauto
,Limusiin
); - kõiki autosid parandatakse loodud autoteeninduses, kusjuures remondile kulutatud aeg tuleb iga auto jaoks genereerida juhuslikult täisarvude poollõigust
[1, 21)
ja jagada seda ujukomaarvuga2.0
.
Vihje: juhuslike arvude genereerimiseks saab kasutada klassiRandom
isendit; - väljastatakse ekraanile info autoteeninduse kohta.
Kui olete juba hulk aega proovinud ülesannet iseseisvalt lahendada ja see ikka ei õnnestu, siis võib-olla saate abi murelahendajalt
. Püütud on tüüpilisemaid probleemseid kohti selgitada ja anda vihjeid.
Abstraktsed klassid
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 - 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 klasse nimetatakse abstraktseteks. Abstraktne klass on mitmes mõttes sarnane liidestega - võibki mõelda, et liides on abstraktse klassi erijuht, kus kõik klassi meetodid on abstraktsed.
Abstraktsel meetodil on järgmine üldkuju: piiritlejad abstract tagastustüüp meetodiNimi(parameetrite loetelu);
Kui klass sisaldab vähemalt ühte abstraktset meetodit, tuleb see varustada võtmesõnaga abstract. Abstraktsest klassist (nagu ka liidesest) 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.
Kui abstraktne klass realiseerib liideseid, siis võib osad (või kõik) liidese meetodid realiseerimata jätta. Sellisel juhul käituvad realiseerimata liidese meetodid nagu abstraktsed meetodid - abstraktse klassi alamklass peab need realiseerima või olema ka ise abstraktne klass.
Polümorfismi reeglite järgi saab kasutada abstraktset klassi muutujate tüübina. See annab võimaluse näiteks kirjeldada seda tüüpi elementidega massiivi, elemente ise aga luua alamklasside konstruktorite abil. Näide abstraktsest klassist:
class Isik { private String eesnimi; private String perenimi; Isik(String eesnimi, String perenimi) { this.eesnimi = eesnimi; this.perenimi = perenimi; } } abstract class IsikuFailistLugeja { protected abstract Isik ridaIsikuks(String rida); public List<Isik> loeIsikud(File sisend) throws Exception { // Files.readAllLines saab kasutada, et kiirelt etteantud failist kõik read listi lugeda List<String> read = Files.readAllLines(sisend.toPath(), StandardCharsets.UTF_8); List<Isik> isikud = new ArrayList<>(); for (String rida : read) { isikud.add(ridaIsikuks(rida)); } return isikud; } } class EesnimiPerenimiLugeja extends IsikuFailistLugeja { protected Isik ridaIsikuks(String rida) { String[] nimeOsad = rida.split(" "); // Malle Mallikas return new Isik(nimeOsad[0], nimeOsad[1]); } } class PerenimiEesnimiLugeja extends IsikuFailistLugeja { protected Isik ridaIsikuks(String rida) { String[] nimeOsad = rida.split(", "); // Mallikas, Malle return new Isik(nimeOsad[1], nimeOsad[0]); } } class DemoAbstract { public static void main(String[] args) throws Exception { EesnimiPerenimiLugeja epLugeja = new EesnimiPerenimiLugeja(); List<Isik> nimed = epLugeja.loeIsikud(new File("nimed.txt")); IsikuFailistLugeja teineLugeja = new PerenimiEesnimiLugeja(); List<Isik> ametlikudNimed = teineLugeja.loeIsikud(new File("ametlikud-nimed.txt")); // IsikuFailistLugeja lugeja = new IsikuFailistLugeja(); // viga! } }
Näeme, et kuigi klassist IsikuFailistLugeja
ei ole võimalik luua isendit tema enda konstruktoriga, on aga võimalik pöörduda tema meetodite (ja ka konstruktori) poole alamklassi isendi abil.
Näide abstraktsest klassist on java.net.URLConnection
. Javas on lihtne erinevatele aadressidele ühendusi luua: new URL("http://ut.ee").openConnection()
ja new URL("https://google.com").openConnection()
loovad vastavatele aadressidele ühendused. Java dokumentatsioonist näeme, et openConnection
tagastab objekti tüübiga java.net.URLConnection
. Tegemist on abstraktse klassiga, mille kaudu saab etteantud aadressile andmeid saata või saabuvaid andmeid vastu võtta. Ainus abstraktne meetod selles klassis on connect
, mis määrab selle, kuidas ühendus avada. See meetod realiseeritakse erinevates alamklassides: HttpURLConnection
, HttpsURLConnection
, jne.
Enesekontroll
Ülesanne 6 (kontroll)
Klõpsa siia ülesande eesmärkide nägemiseks
Ülesande põhieesmärgid on harjutada:
- abstraktse klassi loomist;
- polümorfismi kasutamist;
- dünaamilise seostamise kasutamist;
- liidese kasutamist.
Pagarikojas “GLaDOSil külas” küpsetati hulk erinevaid kooke: ümmargusi, ristkülikukujulisi ja kolmnurkseid. Info kõigi valmistatud kookide kohta on failis koogid.txt
.
Näide faili võimalikust sisust:
Laimitort; 2023-03-10; 0.04; 20 Kirsitort; 2023-03-09; 0.06; 20; 25 Sidrunibeseekook; 2023-03-11; 0.07; 20; 20; 30 ; 2023-03-12; 0.07; 25 Mangotort; 2023-03-06; 0.05; 15 Kohvitort; 2023-03-10; 0.055; 22; 22
Iga faili rida koosneb vähemalt neljast osast, mis on omavahel eraldatud semikooloni ja tühikuga. Esimesed kolm on koogi nimetus (võib ka tühi sõne olla), “parim enne” kuupäev (kujul AAAA-KK-PP
) ja koogi ruutsentimeetri hind (eurodes). Nendele järgnevad koogi mõõtmed sentimeetrites (üks kuni kolm suurust). Suuruste arv sõltub koogi kujust:
- ümmargusel koogil on vaid üks suurus – koogi läbimõõt;
- ristkülikukujulise koogi mõõtmed on koogi laius ja pikkus;
- kolmnurkse koogi puhul on mõõtmed külgede pikkused.
Pagarikoja töötajad tahavad failis olevate kookide hindu teada saada. Lisaks sellele on vaja järjestada kooke “parim enne” kuupäeva järgi, et kookide müümisel vältida müügitähtaja ületamist.
Koosta abstraktne klass Kook
, millel on privaatsed isendiväljad koogi nimetuse (String
), kuupäeva “parim enne” (LocalDate
) ja ruutsentimeetri hinna (double
) jaoks. Klassis peavad olema:
- konstruktor isendiväljade väärtustamiseks. Kui koogi nimetus oli tühi sõne, siis omistada nimetuse isendiväljale väärtus
The cake is a lie
(lisainfo); - parameetriteta abstraktne
double
-tüüpi isendimeetodpindala
; - parameetriteta
double
-tüüpi isendimeetodkoogiHind
, mis tagastab koogi hinna, kasutades meetoditpindala
ja ruutsentimeetri hinda. Koogi hind peab olema ümardatud kahe komakohani; toString
meetod, mis tagastab loetaval kujul koogi nimetuse, koogi hinna ja kuupäeva “parim enne”.
Klass Kook
peab realiseerima liidest Comparable<Kook>
. Võrrelda tuleb kuupäeva “parim enne” põhjal põhimõtte “mida hilisem kuupäev, seda “suurem” see on” järgi. Vihje: klassis LocalDate
on olemas meetod compareTo
.
Koosta klassi Kook
mitteabstraktne alamklass ÜmmarguneKook
. Klassis peab olema privaatne double
-tüüpi isendiväli läbimõõdu jaoks ning konstruktor isendiväljade väärtustamiseks (kokku 4 parameetrit). Realiseeri meetod pindala
järgmise valemi järgi: S = π * r²
.
Klõpsa siia näite nägemiseks
ÜmmarguneKook kook = new ÜmmarguneKook("Laimitort", LocalDate.parse("2023-03-10"), 0.04, 20); System.out.println(kook.pindala()); // Väljastatakse: 314.1592653589793 System.out.println(kook.koogiHind()); // Väljastatakse: 12.57
Koosta klassi Kook
mitteabstraktne alamklass RistkülikukujulineKook
. Klassis peavad olema privaatsed double
-tüüpi isendiväljad laiuse ja pikkuse jaoks ning konstruktor isendiväljade väärtustamiseks. Realiseeri meetod pindala
järgmise valemi järgi: S = a * b
.
Koosta klassi Kook
mitteabstraktne alamklass KolmnurkneKook
. Klassis peavad olema privaatsed double
-tüüpi isendiväljad külgede pikkuste jaoks ning konstruktor isendiväljade väärtustamiseks. Realiseeri meetod pindala
Heroni valemi järgi.
Koosta peaklass Pagarikoda
. Klassis peavad olema:
- staatiline abimeetod
loeKoogid
, mille parameeter on faili nimi ning mis tagastab kookide listi (List<Kook>
).
Vihje: sõne põhjal klassi LocalDate isendi loomisel on abiks meetodLocalDate.parse
.
Nõuanne: sõne põhjal koogi loomine on mõistlik realiseerida eraldi staatilise meetodina, kuid see pole kohustuslik. - peameetod, kus
- luuakse kookide list faili
koogid.txt
põhjal (eelneva abimeetodi abil); - järjestatakse mittekahanevalt listi elemente (Vihje: meetod
Collections.sort
); - väljastatakse eraldi ridadele listis olevad koogid ekraanile.
- luuakse kookide list faili
Näide peameetodi tööst ülaltoodud faili koogid.txt
korral:
Mangotort — 8.84 eurot — parim enne 2023-03-06 Kirsitort — 30.0 eurot — parim enne 2023-03-09 Laimitort — 12.57 eurot — parim enne 2023-03-10 Kohvitort — 26.62 eurot — parim enne 2023-03-10 Sidrunibeseekook — 13.89 eurot — parim enne 2023-03-11 The cake is a lie — 34.36 eurot — parim enne 2023-03-12
Kui olete juba hulk aega proovinud ülesannet iseseisvalt lahendada ja see ikka ei õnnestu, siis võib-olla saate abi murelahendajalt
. Püütud on tüüpilisemaid probleemseid kohti selgitada ja anda vihjeid.
Ülesanne 7
Looge klass KliendiAndmebaas
, kus saab konstruktori parameetriga kliendid määrata (List<Klient>
tüüpi isendiväli). Lisage meetod getKliendid
, mis tagastab kõik andmebaasis olevad kliendid. Lisage meetod väljastaAruanne
, mis väljastab kõigi andmebaasis olevate klientide andmed.
Lisage Klient
klassi saldo
väljale get-meetod. Looge KliendiAndmebaas
klassile alamklass RikasteKlientideAndmebaas
. Alamklassi väljastaAruanne
meetod peaks väljastama ainult selliste klientide andmed, kelle saldo on üle 10000. Proovige lahendada ülesanne nii, et väljastaAruanne
meetodi ülekatmise asemel kaetakse üle ainult getKliendid
meetod.
Ülesanne 8
Klientide tehingud salvestati algselt lihtsuse nimel ühe stringina. Tagantjärele oleks olnud parem teha nende jaoks Tehing
klass, kus kõik andmetükid eraldi hoitakse. Nüüd nõuavad kliendid palju erinevaid kontoväljavõtteid ja tuleb hakata tehinguid uuesti tükkideks jaotama.
Lisage Klient
klassi tehingud
väljale get-meetod. Looge abstraktne klass Väljavõte
, kus saab konstruktori parameetriga kliendi määrata. Lisage abstraktne meetod lisaVäljavõttesse
, millel on parameetrid saajaKonto
(String) ja summa
(double). Lisage void tüüpi parameetriteta meetod arvuta
, mis käib kõik kliendi tehingud läbi ja kutsub iga tehingu infoga lisaVäljavõttesse
meetodit.
Looge Väljavõte
alamklass ÜldVäljavõte
. ÜldVäljavõte
klass peab leidma kõigi kliendi tehtud tehingute kogusumma. Summa arvutamine toimub siis, kui objekti peal kutsutakse arvuta
meetodit. Tulemuse teada saamiseks peab ÜldVäljavõte
sisaldama parameetriteta double tüüpi meetodit getSumma
.
Looge ÜldVäljavõte
alamklass TäpneVäljavõte
. TäpneVäljavõte
konstruktoris tuleb määrata String tüüpi kontonumber
(lisaks uuritavale kliendile). TäpneVäljavõte
töötab nagu ÜldVäljavõte
, aga ignoreerib kõiki tehinguid, mis pole määratud kontonumbriga seotud.
Vihje: arvuta
meetodit ei peaks üle katma. Lisa Väljavõte
alamklassidesse vajalikud isendiväljad.
@Override
Kui olete IntelliJ-l palunud liideses kirjeldatud meetodi mõnda klassi automaatselt genereerida, siis võis meetodi päise kohale ilmuda tekst @Override
. Tegemist on annotatsiooniga
, mis toonitab programmi lugejale ja Java kompilaatorile, et see meetod katab üle ülemklassi meetodi või realiseerib mõne liidese meetodi.
Kuigi Java seda ei nõua, on @Override
annotatsiooni kasutamine siiski väga soovitatav. Esiteks hõlbustab see koodi lugemist, kuna lugeja saab meetodi otstarbest kergemini aimu, ilma ülemklassi või liidese definitsiooni uurimata.
Teiseks, see annotatsioon aitab vigu vältida. Oletame, et ülemklassis on meetod teeMidagi
, mille tahetakse alamklassis üle katta, aga kogemata kirjutati alamklassis meetodi nimeks teeMigadi
. Java arvab, et tahetigi defineerida täiesti uus meetod ja viga võib jupiks ajaks peitu jääda. Kui aga on harjumus annoteerida ülekaetud meetodid alati @Override
'iga, saab kompilaator kohe hoiatada, et ülemklassis pole sellist meetodit.
Java juhib tähelepanu küll @Override
annotatsiooni vigasele kasutamisele, aga ei märgi ära juhtumeid, kus seda pole kasutatud aga võiks. Õnneks saate seadistada oma IDE märku andma, kui kuskil oleks hea mõte @Override
kasutada. Soovitame vajaliku seadistuse kohe ära teha, kasutades seda juhendit.
Veel
- Palun õppige korralikult 1. kontrolltööks, mis põhineb kuue esimese nädala 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 praktikumide näidisülesanded, mis veel tegemata on.
- 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.