Arvutiteaduse instituut
  1. Kursused
  2. 2022/23 kevad
  3. Objektorienteeritud programmeerimine (LTAT.03.003)
EN
Logi sisse
Tähelepanu! Tehnilise tõrke tõttu on hetkel kättesaadavad vaid 2020.a. ja hilisemad üles laetud failid ja kevadsemestri kursused. Rikke kõrvaldamisega tegeletakse.

Objektorienteeritud programmeerimine 2022/23 kevad

  • Kodutööd ja praktikumid
  • Loengud
  • Kursuse korraldus
  • IDE juhendid
  • Silumisest
  • Edasijõudnute rühm

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.

Meeldetuletus: automaatkontroll

Ka seekord tuleb koduülesannetele automaatkontroll, seepärast tehke palun kõik klassid vaikepaketti ning Eclipse'i kasutamise korral kontrollige üle projekti kodeering. Täpsemalt lugege 4. nädala materjali algusest.

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. Eclipse kasutades saab ülemklassi määrata ka klassi loomise dialoogi Superclass lahtris.

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. Eclipse puhul saab Source-menüüst kasutada nii Generate Constructors using Fields ... kui ka Generate Constructors from Superclass ....

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:

  1. konstruktor isendiväljade väärtustamiseks;
  2. double-tüüpi isendimeetod arvutaParanduseMaksumus, millel on üks double-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);
  3. parameetriteta String-tüüpi isendimeetod autoliik, mis tagastab sõna Sõiduauto;
  4. 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:

  1. privaatne boolean-tüüpi isendiväli, mille väärtus näitab, kas veoauto omanik on füüsiline isik (välja väärtuseks true) või mitte;
  2. konstruktor isendiväljade väärtustamiseks (kokku 4 parameetrit);
  3. ü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);
  4. ülekaetud isendimeetod autoliik, mis tagastab sõna Veoauto.
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:

  1. privaatne int-tüüpi isendiväli tootmisaasta jaoks;
  2. konstruktor isendiväljade väärtustamiseks;
  3. ü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;
  4. ülekaetud meetod autoliik, mis tagastab sõna Luksusauto.

Koosta klassi Luksusauto alamklass Limusiin, millel on:

  1. konstruktor ülemklassi isendiväljade väärtustamiseks;
  2. ülekaetud isendimeetod arvutaParanduseMaksumus. Meetod peab tagastama ülemklassi samanimelise meetodi 1,5-kordse väärtuse;
  3. ülekaetud meetod autoliik, mis tagastab sõna Limusiin.

Koosta klass Autoteenindus, millel on:

  1. privaatne int-tüüpi isendiväli parandatud autode arvu jaoks;
  2. privaatne double-tüüpi isendiväli autoteeninduse teenitud tulu jaoks;
  3. void-tüüpi isendimeetod paranda, 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);
  4. 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:

  1. luuakse autoteenindus ja vähemalt üks auto igast klassist (Auto, Veoauto, Luksusauto, Limusiin);
  2. 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 ujukomaarvuga 2.0.
    Vihje: juhuslike arvude genereerimiseks saab kasutada klassi Random isendit;
  3. 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:

  1. 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);
  2. parameetriteta abstraktne double-tüüpi isendimeetod pindala;
  3. parameetriteta double-tüüpi isendimeetod koogiHind, mis tagastab koogi hinna, kasutades meetodit pindala ja ruutsentimeetri hinda. Koogi hind peab olema ümardatud kahe komakohani;
  4. 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:

  1. 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 meetod LocalDate.parse.
    Nõuanne: sõne põhjal koogi loomine on mõistlik realiseerida eraldi staatilise meetodina, kuid see pole kohustuslik.
  2. peameetod, kus
    1. luuakse kookide list faili koogid.txt põhjal (eelneva abimeetodi abil);
    2. järjestatakse mittekahanevalt listi elemente (Vihje: meetod Collections.sort);
    3. väljastatakse eraldi ridadele listis olevad koogid ekraanile.

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 Eclipse'il või 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.
  • Arvutiteaduse instituut
  • Loodus- ja täppisteaduste valdkond
  • Tartu Ülikool
Tehniliste probleemide või küsimuste korral kirjuta:

Kursuse sisu ja korralduslike küsimustega pöörduge kursuse korraldajate poole.
Õppematerjalide varalised autoriõigused kuuluvad Tartu Ülikoolile. Õppematerjalide kasutamine on lubatud autoriõiguse seaduses ettenähtud teose vaba kasutamise eesmärkidel ja tingimustel. Õppematerjalide kasutamisel on kasutaja kohustatud viitama õppematerjalide autorile.
Õppematerjalide kasutamine muudel eesmärkidel on lubatud ainult Tartu Ülikooli eelneval kirjalikul nõusolekul.