Nädal 3
Objektid. Klassid.
Teemad
Objektid ja klassid. Konstruktorid. Isendiloome. Muutujate ja meetodite nähtavus.
Pärast selle nädala läbimist oskab üliõpilane
- luua klassitüüpi objekte ehk isendeid;
- luua ning kasutada erinevaid konstruktoreid ja isendivälju;
- kasutada piiritlejaid;
- kasutada võtmesõna
this
; - luua ja kasutada erinevat tüüpi massiive;
- selgitada algtüübi ja viittüübi erinevusi.
Automaatkontrollist ja Java failide kodeeringust
Seekord on Moodle'is osade koduülesannete jaoks olemas automaatkontrollid, mis annavad lahenduse kohta esmase tagasiside. Lehekülje lõpus on selle kohta rohkem juttu, aga enne programmeerima hakkamist tuleks vaadata üle enda projekti seaded.
Automaatkontrolli puhul on mõistlik kokku leppida, et esitatavad Java failid on kindlas kodeeringus, siis ei teki kahetimõistetavust, kuidas failide baite tõlgendada. Selle kursuse automaattestid eeldavad, et failid on UTF-8 kodeeringus.
Automaatkontrolliga hästi läbisaamiseks palun kontrolli kohe praegu, et su IDE oleks seadistatud õiget kodeeringut kasutama. Juhend selle jaoks on siin.
Automaatkontrollist ja Java klasside pakettidest
Selleks, et automaatkontroll esitatud klassid kindlalt üles leiaks, peab see teadma, millistes pakettides klassid asuvad. Kuna selles kursuses pole veel pakettidest räägitud, siis on praegu kõige mõistlikum leppida kokku, et klassid asuvad vaikepaketis, st. vastavad Java failid asuvad otse koodi juurkaustas (enamasti on selle kausta nimi "src") ja failide alguses ei ole package
direktiivi.
Absoluutsed ja suhtelised failiteed. Jooksev kaust.
Kuna nüüd peate oma ülesanded tööle saama vähemalt kahes arvutis (teie enda arvuti ja Moodle'i testserver), on põhjust rääkida täpsemalt failidele viitamisest.
Failidele saab viidata kahel moel.
Kõige konkreetsem viitamisviis on absoluutne tee (ingl.k absolute path), nt. C:\Users\Frederico\projektid\OOP\nädal1\nimed.txt
või /home/frederico/projektid/OOP/nädal1/nimed.txt
. Sellise viitamisviisi puhul on alati selge, millist konkreetset faili mõeldakse. Seesama konkreetsus on aga ka selle viisi puuduseks -- mõnikord soovime failile viidata kaudsemalt. Näiteks kui meie programm vajab mingit nimede faili ja me soovime, et meie programmi saaks kasutada ka teises arvutis, ei ole mõistlik nõuda, et teises arvutis peaks olema kindlasti kaust C:\Users\Frederico
.
Lahendus on kasutada suhtelist failiteed (ingl.k relative path), nt. nimed.txt
või data\nimed.txt
või blaa/blaa/data/nimed.txt
. Sel juhul on viide failile antud mingi kausta suhtes, enamasti jooksva kausta (ingl.k current directory) suhtes. OP-süsteemi igal protsessil on konkreetsel hetkel mingi kaust valitud jooksvaks kaustaks, ja selles kaustas olevaid failidele ja alamkaustadele saab kergesti viidata suhtelise teega.
Kui öeldakse, et programm loeb midagi failist nimed.txt
, siis tuleb märgata, et tegemist on suhtelise teega ja et programmis tulebki kirjutada new File("nimed.txt")
vms. (mitte new File("C:\\Users\\Frederico\\projektid\\OOP\\nädal1\\nimed.txt")
). Sellise programmi testimisel tuleks korraldada nii, et fail nimed.txt
asuks programmi jooksvas kaustas. IntelliJ kaudu klassi käivitades on jooksvaks kaustaks vaikimisi projekti juurkaust, seega tuleks nimed.txt
salvestada projekti juurkausta (st. alamkausta src
kõrvale, mitte sisse). Käsurealt käivitades on jooksvaks kaustaks see kaust, kus ollakse java
käsu käivitamise hetkel.
Veidi massiividest
Enne, kui lähme nädala põhiosa juurde, meenutame pisut massiividega seonduvat (pikemalt oli eelmise nädala materjalis).
Paljude ülesannete puhul on mõistlik mingit hulka ühte tüüpi muutujad koos käsitleda. Näiteks kuue ujukomaarvu puhul võime kasutada massiivi, mille tekitame massiiviloomega:
double[] arvud = new double[6];
Nüüd on olemas muutujad arvud[0]
, arvud[1]
, arvud[2]
, arvud[3]
, arvud[4]
ja arvud[5]
. Vaikimisi on neil väärtuseks 0.0
. Uusi väärtusi saab omistada nagu ikka muutujatele väärtusi omistame, nt. arvud[3] = 4.6;
.
Massiivi elemendiga saame teha tehteid (nt. 34*arvud[3]
), võime neid ekraanile väljastada: System.out.println(arvud[3]);
Kui aga tahaksime kogu massiivi väljastada, siis rea System.out.println(arvud);
toimel tuleb ekraanile midagi sellist: [D@15b7986
. Muutuja arvud
väärtuseks ei ole massiiv ise vaid viit (ingl.k reference) massiivile. (Natuke pikemalt käsitleme seda temaatikat materjali lõpuosas.)
Kui tahame kõik massiivi elemendid ekraanile saada, siis on sobiv vahend for
-tsükkel. Näiteks programmilõik
for (int i = 0; i < arvud.length; i++) { System.out.println(arvud[i]); }
või ka
for (double elem : arvud) { System.out.println(elem); }
Massiivi suurus on fikseeritud, nii et elemente lisada, nii nagu Pythonis, pole võimalik. Kui on soovi pikkust programmi töö käigus vabalt muuta, elemente erinevatesse kohtadesse lisada ja neid eemaldada, siis selleks on Javas teised vahendid, mida õpime juba järgmisel nädalal.
Enne, kui läheme tänase materjali põhiosa juurde, meenutame veel eelmisel nädalal käsitletud käsurea argumente. Tegemist on sõnemassiiviga, mille elemendid saavad väärtused programmi käivitamisel ja mille nimi määratakse peameetodi formaalse parameetri nimena. Kuigi ka IDEst käivitamisel saab argumentide väärtusi määrata, on see ilmekam käsurealt käivitades pärast kompileerimist (nagu 1. praktikumis käsitletud):
java Klassinimi Tartu Riia
Aga nüüd klasside ja isendite juurde
Javas programmeerimine seisneb klasside koostamises. Klasse koostasime juba ka eelmistes praktikumides. Kuna need sisaldasid peameetodit (main
), siis võib neid nimetada peaklassiks ja neid sai käivitada. Samuti kasutasime klassi Math
-meetodeid. Nüüd asume koostama klasse, mis on kui uued andmetüübid, mida saab kasutada üsna analoogiliselt lihttüüpidega (int
, char
, ... ). Väga olulisel kohal on koostatud klasside isendite (objektide) loomine. Klassikirjelduses on oma koht andmetel (mis näitavad olekut) ja meetoditel (mis kirjeldavad käitumist). Andmeid kujutatakse muutujate abil. Kuna muutujad saavad väärtused konkreetsete isendite loomisel, siis nimetatakse neid isendiväljadeks (ka isendimuutujateks, inglise keeles fields).
Olgu meil klass Isik
, milles on kaks välja:
class Isik { String nimi; // isendiväli isiku nime jaoks double pikkus; // isendiväli isiku pikkuse jaoks }
Olemegi loonud uue andmetüübi, mis sisaldab kohta sõnele (nime jaoks) ja ujukomaarvule (pikkuse jaoks).
Isendi loomine võiks toimuda peaklassi (nt. TestIsik
) peameetodis (main
). (Nimi TestIsik ei tähenda siin, et tegemist oleks mingit eriliiki isikuga vaid klassiga, mis on mõeldud klassi Isik
isendite testimiseks.) Klassi isendeid luuakse käsuga new
:
class TestIsik { public static void main(String[] args) { Isik a = new Isik(); } }
Nüüd on loodud klassi Isik
isend (objekt) a
. Kuigi isend on loodud, on muutujad nimi
ja pikkus
väärtustamata (tegelikult on neil vaikeväärtused, vastavalt null
ja 0.0
). Lisades peaklassi rea System.out.println(a.nimi);
saaksime isendivälja väärtuse ekraanile.
Iga klass peab olema eraldi oma .java
failis ja faili nimi ja klassi nimi võiksid olla samad. S.t. et praegu teil peaks olema tehtud fail Isik.java, kus asub klass Isik
, ja fail TestIsik.java, kus asub klass TestIsik
.
Väärtustamiseks on mitu võimalust. Võib näiteks muuta klassi Isik
kirjeldust
class Isik { String nimi = "Toomas Indrek Elvis"; // isendiväli isiku nime jaoks double pikkus = 1.92; // isendiväli isiku pikkuse jaoks }
Sellisel juhul on aga see mure, et kõik loodud selle klassi isenditel on vastavatel väljadel täpselt samad väärtused. Teine võimalus oleks muuta väärtusi peaklassis. Näiteks
class TestIsik { public static void main(String[] args) { Isik a = new Isik(); a.pikkus = 2.03; System.out.println(a.pikkus); } }
Konstruktor
Eeltoodud väärtustamine pole paraku objektorienteeritud programmeerimise ideoloogiaga kooskõlas. Kui vähegi võimalik, peaks objekti väljad väärtustama hoopis isendi loomise käigus - niimoodi ei ole objekt kordagi vigases seisus, kus tal on osad andmed puudu. Selleks täiendame klassi konstruktoriga (erilise protseduuriga, mis rakendub isendiloome käigus; ingl.k constructor).
class Isik { String nimi; // isendiväli isiku nime jaoks double pikkus; // isendiväli isiku pikkuse jaoks // konstruktor Isik(String isikuNimi, double isikuPikkus) { nimi = isikuNimi; pikkus = isikuPikkus; } }
Nüüd saab isendeid luua järgmiselt:
Isik a = new Isik("Juhan Juurikas", 1.99); Isik b = new Isik("Madli Mallikas", 1.55);
Muutujaid a
ja b
käsitletakse siin kui tavalisi muutujaid, kuid nende tüübiks on Isik
. Neid nimetatakse viittüüpi muutujateks, sest nende väärtuseks on viit klassi isendile. (Täpsemalt räägime sellest materjali lõpuosas.)
Ülesanne 1
Olgu meil klassi Isik
kasutamiseks järgmine peaklass
class TestIsik { public static void main (String[] argv) { Isik a = new Isik("Juhan Juurikas", 1.99); System.out.println("Isik a on " + a); } }
Sisestage see programm ja käivitage. Milline on tulemus?
Tulemus ei ole hästi loetav, sest objekt a
ei kuulu Java standardobjektide hulka.
Meetod toString
Selleks, et Java suudaks programmeerija kirjeldatud objekte arusaadaval viisil ekraanile tuua, võib (kuid saab ka teisiti) klassi Isik
kirjeldusse lisada meetodi toString()
, mis määrab, kuidas antud objekt tekstina välja näeb. Näiteks klassi Isik
puhul võib see meetod olla järgmine:
public String toString() { return "(" + nimi + "; " + pikkus + ")"; }
Ülesanne 2
Täiendage klassi Isik
meetodiga toString()
. Testklassis looge mõni isik ja väljastage ekraanile nende andmed, näiteks järgmise rea abil:
System.out.println(a.toString());
Tegelikult on toString
mõneti ebatavaline meetod, mis rakendub automaatselt isendi sõneksteisendusel (nt. väljastamisel). Seega võime kasutada ka
System.out.println(a);
Meetod toString
on tegelikult kasutatav ka selliste klasside isendite puhul, kus seda meetodit eraldi kirja pandud polegi. Sellisel juhul toimib meetod toString
, mis on kirjeldatud ülemklassis. (Ülemklassid ja alamklassid on päriluse terminid. Pärilus on aga objektorienteeritud programmeerimise põhialuseid, millest lähemalt räägime juba varsti.) Kui ülemklassi ilmutatult märgitud pole, siis on selleks klass Object
. Klassi Object
defineeritud isendimeetod toString()
annabki selle mõneti ehk kummalisena tunduva @-märgiga kuju.
Enesekontroll
Piiritlejad private, protected ja public
Nagu ülalpool mainisime saab isendivälja väärtusi muuta testklassist
a.pikkus = 1.95; // isendi a väljale pikkus omistatakse 1.95
Isendiväljade otsekasutuse (nt. a.pikkus = 1.95
) saab keelata, kui nende kirjeldamisel kirjutada muutuja nime ette piiritleja private
. Näiteks
private String nimi; private double pikkus;
Muutujate ja meetodite nähtavust ning kasutust saabki reguleerida piiritlejatega public
, private
ning protected
. Neist public
määrab piiranguteta, private
klassisisese ning protected
klassi- ja selle alamklasside-sisese kasutusõiguse. Kui juurdepääsu määravat piiritlejat pole, siis vaikimisi on juurdepääs olemas sama paketi piires.
Get- ja set-meetodid
Tavaliselt väärtustatakse isendiväljad klassi konstruktorite ja meetodite abil. Privaatse isendivälja väärtuse tuvastamiseks on mõeldud nn. piilumeetod, mille peamiseks ülesandeks on tagastada vastava muutuja väärtus. Näiteks
public String getNimi() { return nimi; } public double getPikkus() { return pikkus; }
Analoogiliselt saab luua ka meetodid väärtuste muutmiseks
public void setNimi(String nimi) { this.nimi = nimi; } public void setPikkus(double pikkus) { this.pikkus = pikkus; }
Kuna neid meetodeid on sageli vaja, IDE oskab neid automaatselt lisada. IntelliJ puhul vali Code-menüüst Generate
.
Võimalusel tuleb siiski väljade väärtused juba konstruktoris seada. Väljade väärtuse hilisem muutmine set-meetoditega muudab koodi mõistmise keerulisemaks, sest klassile peale vaadates ei ole ilmne, mis hetkel väljad mingi konkreetse väärtuse on saanud (kui üldse).
Võtmesõna this
kasutatakse isendiväljadele viitamisel, kui konstruktori või meetodi formaalsete parameetrite nimed langevad kokku isendiväljade nimedega. Võtmesõna this
käitub nagu automaatselt tekitatud isendiväli, mis viitab alati objektile endale. See on kättesaadav kõigis objekti isendimeetodites (static meetodid pole objektiga seotud, seal see ei toimi).
Meetodeid saab kasutada näiteks testklassis koos muutujanimega nt. a.setNimi("Evelin");
.
Objektorienteeritud lähenemise üks olulisi mehhanisme on kapseldamine (encapsulation): objekti realisatsiooni detailid varjatakse ära. Objekti kasutaja ei tea, kuidas andmestruktuurid ja operatsioonid on realiseeritud, ainult objekt ise teab. Objekti andmestruktuuridele saab juurde ainult antud objekti operatsioonide kaudu. See võimaldab objekti sisemuse asendada uuega, ilma, et muus osas mingeid muutusi oleks vaja teha. Nt. getNimi()
tegelikult võib tagastada väärtuse, mis ühendab mitu isendivälja (return eesnimi + " " + perenimi
) väärtust. Samuti meetodis väärtuse muutmiseks võib esialgu kontrollida, kas uued andmed üldse sobivad (nt. setPikkus
ei pea olema nõus negatiivse parameetriga). Kõikidel isendiväljadel ei pea olema get
- ja/või set
-meetodeid. Mõned isendiväljad võivad olla ainult klassisisesed ja väljapoole ei pea neid näha olema (s.t. ei pea olema get
- ja set
-meetodeid). Samuti võib olla isendivälju, mida ei saagi muuta, näiteks seatakse väärtus konstruktoris ning hiljem polegi seda võimalik muuta (set
-meetodit ei olegi, get
-meetod võib olla).
Veel konstruktoritest
Eespool koostasime konstruktori, mis isendiloomel rakendamisel vajas kahte argumenti. Sama saab kirjutada ka võtmesõna this
abil.
public Isik(String nimi, double pikkus) { // isendiväljad nimi ja pikkus saavad väärtusteks // konstruktori parameetrite väärtused this.nimi = nimi; this.pikkus = pikkus; }
Ka konstrukorite kirjutamine IDEs mugavaks tehtud. IntelliJ puhul vali Code-menüüst Generate
. (On ka genereerimine ülemklassi abil, aga seda me käsitleme pärilusega koos.)
Konstruktorit võib käsitleda kui erilist meetodit kolme tunnusega:
- konstruktori nimi langeb kokku klassi nimega;
- konstruktori nime ette ei kirjutata tagastustüüpi;
- konstruktori poole pöördumine toimub käsuga
new
antud klassi isendi loomisel ja konstruktor tagastab viida loodud isendile.
Kui klassis pole kirjeldatud ühtegi konstruktorit, siis Java lisab sinna ilma argumentideta vaikekonstruktori. Materjali algupoolel just seda kasutasimegi: Isik a = new Isik();
.
Samamoodi (Isik a = new Isik()
) kasutatakse ka parameetriteta konstruktorit
public Isik() { nimi = "Nimetu"; pikkus = 0.0; }
Konstruktoreid võib klassis olla mitu, sel juhul on tegemist konstruktorite üledefineerimisega. Ühe ja sama klassi konstruktorid peavad üksteisest erinema signatuuri (formaalsete parameetrite arv ja nende tüübid) poolest. Samuti võib klassis olla sama nimega meetodeid, sellisel juhul peavad need erinema formaalsete parameetrite arvu ja/või nende tüüpide poolest.
Kahe konstruktoriga klass Isik
kirjelduse algusosa on järgmine:
public class Isik { private String nimi; private double pikkus; public Isik(String isikuNimi, double isikuPikkus) { nimi = isikuNimi; pikkus = isikuPikkus; } public Isik() { nimi = "Nimetu"; pikkus = 0.0; } // ERINEVAD MEETODID }
See, milline konstruktor täidetakse, määratakse argumentide arvu ja tüüpide järgi:
Isik d = new Isik(); Isik e = new Isik("Ülli Õpilane", 2.05);
Võtmesõna this
abil on võimalik ühe konstruktori sees pöörduda teise sama klassi konstruktori poole. Klassi Isik
viimase toodud versiooniga samaväärne klass:
public class Isik { private String nimi; private double pikkus; public Isik(String isikuNimi, double isikuPikkus) { nimi = isikuNimi; pikkus = isikuPikkus; } public Isik() { this("Nimetu", 0.0); } //ERINEVAD MEETODID }
Enesekontroll
Meetodid
Eelnev osa oli põhiliselt andmetest - kuidas saab isendiväljadele väärtusi anda, neid vaadata jms. Edasi vaatleme, kuidas saab objekti "õpetada käituma". Tegelikult juba eelmises lõigus olid käsitlemisel get-
ja set-
meetodid, samuti on meetod ka toString
. Meetodite üldine ideoloogia on sama, mis oli eelmistes praktikumides, kus küll veel polnud isendimeetodeid.
Muutujat või meetodit, mis otseselt ei seostu antud klassi isendiga, nimetatakse vastavalt klassimuutujaks või -meetodiks. (Meenutame nt. klassi Math
.) Eraldamaks neid meetodeid ja muutujaid otseselt isenditega seotutest, lisatakse nende kirjelduste ette piiritleja static
, samuti ei ole nende kasutamiseks vajalik klassi isendite olemasolu.
Isendimeetodid see-eest on seotud konkreetse objektiga. Selle tulemusena on isendimeetoditel ligipääs kõigile seotud objekti isendiväljadele, mida nad ka tihtipeale kasutavad. Näiteks sõne küljes olevaid meetodeid ei saa kasutada ilma konkreetset sõne täpsustamata.
Mäletatavasti peavad meetodil olema tagastustüüp, nimi ning parameetrite tüübid ja nimed (kui parameetreid üldse on). Püüame koostada meetodi, mis leiab isiku pikkuse järgi klassikalise tehnika suusakepi pikkuse sentimeetrites. Esimese hooga võiks arvata, et pikkus tuleks argumendina ette anda. Saaks tõesti ka nii, aga kuna pikkus on isendiväljal olemas, siis on mõistlik seda ära kasutada.
public int suusakepiPikkus() { return (int) Math.round(0.85 * pikkus * 100); }
Isendimeetodit saab väljaspool seda klassi kasutada vaid isendiga seotult, nt.
Isik e = new Isik("Ülli Õpilane", 2.05); // loob isendi System.out.println(e.suusakepiPikkus()); // kasutab isendi meetodit
Klassi isendimeetodite sees saab kutsuda sama klassi isendimeetodeid ilma isendit eraldi märkimata. Võib kirjutada lihtsalt meetodi väljakutse ja see käivitatakse automaatselt sama isendi (this) peal.
Ülesanne 3 (kontroll)
Täiendage nüüd klassi Isik
nii, et seal oleks vähemasti neli isendivälja (nimi, pikkus, isikukood (sõnena) ja mass). Klassis peab olema konstruktor, mis väljad väärtustab. Isendiväljadele peavad olema vastavad get
- ja set
-meetodid (isikukood seatakse konstruktoris ja hiljem seda muuta ei saa, pikkuse ja massi muutmisel tuleb kontrollida, kas uued andmed üldse sobivad). Samuti klassis on meetod toString
ning lisaks veel mõned meetodid (nt. kehamassiindeksi või suusa pikkuse arvutamiseks). Vähemasti üks meetod peaks vajama ka argumente. (Argumentideks peaksid olema lisaandmed, mitte isendiväljad.) Katsetage loodud meetodeid testklassis.
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.
Massiivid
Eelpool oli juttu nt. arvude massiividest. Massiivis on üht tüüpi elemendid. See tüüp võib olla aga ka viittüüp.
Näitena koostame klassi Raamat
:
public class Raamat { private String autor; private String pealkiri; public Raamat(String autor, String pealkiri) { this.autor = autor; this.pealkiri = pealkiri; } }
Koostage ka vastav testklass, milles võime luua selle klassi isendeid, nt.
Raamat kevade = new Raamat("Oskar Luts", "Kevade");
Klassi Raamat
isenditest massiivi loomine toimub järgnevalt:
Raamat[] riiul = new Raamat[100];
Muutuja riiul
sisaldab viitasid raamatutele, aga hetkel pole seal veel ühtegi (sisulist) viita. Võite proovida nt. väljastada
System.out.println(riiul[8]);
Paneme Kevade "riiulisse":
riiul[8] = kevade;
Proovige nüüd väljastada.
Lisage nüüd klassi Raamat
veel toString
meetod.
Proovime tekitada raamatuid "hulgi":
String autor = "Eduard Vilde"; for (int i = 0; i < riiul.length; i++) { riiul[i] = new Raamat(autor, "Kogutud teosed " + String.valueOf(i + 1)); } System.out.println("10. raamat riiulil on " + riiul[9] + ".");
Ülesanne 4 (kontroll)
Muutke klassi Raamat
nii, et raamatu loomisel peab autori nime asemel argumendiks andma Isik
-tüüpi objekti. Lisaks peab Raamat
autori nime asemel hoidma enda sees viidet Isik
objektile.
private String autor; --> private Isik autor;
Veendu, et peameetodi käivitamisel prinditakse korrektselt raamatu pealkiri ja autori info.
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.
Viittüüp vs algtüüp
Materjali viimases lõigus räägime mõnede näidete põhjal viittüübi (reference type) ja algtüübi (primitive type) erinevustest. Piirdume küllalt lihtsustatud käsitlusega, millest aga võiks piisata käesoleva kursuse jaoks.
Iga muutuja jaoks eraldatakse mälus koht, kus vastavat väärtust hoida. Kui me deklareerime muutuja, siis ütleme sellega, mis tüüpi väärtusega tegemist on. Kui muutuja on algtüüpi (byte
, short
, int
, long
, float
, double
, char
, boolean
), siis see väärtus on algtüüpi. Viittüüpi muutuja (kõik objektid, nt String, Scanner, kõik massiivid ja meie loodud klasside isendid) korral on aga väärtuseks viit vastavale objektile. Kogu aeg me sellele erinevusele mõtlema ei pea, küll aga teatud juhtudel on see väga oluline. Näiteks kui algtüüpi muutuja korral omistame muutujale väärtuseks teise muutuja väärtuse, siis tehakse väärtusest koopia ja kummaskis muutujas on koopia samast väärtusest. Viittüüpi muutuja korral on aga väärtuseks viit ja seetõttu tekib natuke teistsugune seos - koopia tehakse viidast, mitte objektist endast! Selle tulemusena saavad mitu muutujat viidata samale objektile. Niisiis programmilõigu
int arv1 = 1632; int arv2 = arv1; arv2 = 1802;
puhul saab arv2
esialgu väärtuseks muutuja arv1
väärtuse 1632 ja seejärel 1802. Muutuja arv1
enda väärtus ei muutu. Kui nüüd väljastame muutujate väärtused, siis saame
System.out.println("arv1 on: " + arv1); System.out.println("arv2 on: " + arv2);
Enne, kui proovite, püüdke tulemusi ennustada.
Viittüübi tutvustamiseks loome kõigepealt ühe väga lakoonilise klassi, kus on vaid üks int
-tüüpi isendiväli.
public class Arv { public int arv; }
Teeme nüüd samalaadsete väärtustamistega programmilõigu.
Arv viitarv1 = new Arv(); viitarv1.arv = 1632; Arv viitarv2 = new Arv(); viitarv2 = viitarv1; viitarv2.arv = 1802; System.out.println("viitarv1.arv on: " + viitarv1.arv); System.out.println("viitarv2.arv on: " + viitarv2.arv);
Enne, kui proovite, püüdke ka neid tulemusi ennustada.
(Näide on inspireeritud raamatust
, kus on palju huvitavat informatsiooni.)
Ka massiivid on käsitletavad viittüüpi objektidena. Näiteks
int[] arvud1 = {1632}; int[] arvud2 = arvud1; arvud2[0] = 1802; System.out.println("arvud1[0] on: " + arvud1[0]); System.out.println("arvud2[0] on: " + arvud2[0]);
Enne, kui proovite, püüdke ka neid tulemusi ennustada.
Kui on vaja massiive kopeerida, siis saaks seda teha
- elementhaaval kopeerides;
- kasutades klassi
System
meetoditarraycopy
; - kasutades klassi
Arrays
meetoditcopyof
.
Ülesanne 5
Katsetage eeltoodud näiteid ja kontrollige oma ennustuste paikapidavust. Võite proovida ka tehisaruga, et näha mis tema ennustab ja võrrelda enda ja tehisaru vastust.
Enesekontroll
Ülesanne 6 (kontroll)
Klõpsa siia ülesande eesmärkide nägemiseks
Ülesande põhieesmärgid on harjutada:
- klasside (koos isendiväljadega) ja konstruktorite (sh üledefineeritud) loomist;
- isendimeetodite (sh
get
-,set
-,toString
) loomist ja kasutamist; - erinevate andmetüüpide (sh iseseisvalt loodud klasside) kasutamist;
- tingimuslausete kasutamist.
Koosta klass Kohv
, millel on:
- privaatne
String
-tüüpi isendivälikohvisort
kohvisordi jaoks; - privaatne
double
-tüüpi isendivälihind
tassi kohvi hinna jaoks; - konstruktor isendiväljade väärtustamiseks;
- mõlema isendivälja jaoks
get
-meetodid; double
-tüüpi isendimeetodtassideMaksumus
, mille parameeter on tasside arvu (int
). Meetod peab tagastama kohvi tasside maksumuse.
Klõpsa siia näite nägemiseks
Kohv kohv = new Kohv("Supremo", 2.5); System.out.println("Tasside maksumus: " + kohv.tassideMaksumus(2)); // Väljastatakse: Tasside maksumus: 5.0
Koosta klass Programmeerija
, millel on:
- privaatne
String
-tüüpi isendiväliprogrammeerijaNimi
programmeerija nime jaoks; - privaatne
double
-tüüpi isendiväliriduKoodi
programmeerija tulemuslikkuse näitamiseks ehk mitu rida koodi päevas ta keskmiselt kirjutab; - privaatne
int
-tüüpi isendivälitasseKohvi
, mis kirjeldab, mitu tassi kohvi joob programmeerija päeva jooksul; - privaatne
Kohv
-tüüpi isendivälilemmikkohv
programmeerija lemmikkohvi jaoks; - konstruktor kõikide isendiväljade väärtustamiseks;
- konstruktor juhuks, kui programmeerija kohvi ei joo. Sellel konstruktoril peab olema vaid 2 parameetrit: programmeerija nimi ja koodiridade arv. Konstruktor peab väärtustama vaid vastavaid isendivälju, ülejäänud isendiväljad tuleb jätta väärtustamata;
- isendiväljade
tasseKohvi
jariduKoodi
jaoksget
- jaset
-meetodid; - parameetriteta
boolean
-tüüpi isendimeetodkasJoobKohvi
, mis tagastabtrue
kuilemmikkohv
polenull
, muidufalse
. Järgnevalt kasuta seda meetodit, kui on vaja kontrollida, kas programmeerija joob kohvi või mitte; - parameetriteta
double
-tüüpi isendimeetodkoodiTassiKohta
, mis tagastab, mitu rida koodi suudab programmeerija kirjutada, juues ühe tassi kohvi (valem:riduKoodi / tasseKohvi
). Kui programmeerija kohvi ei joo, väljastatakse ekraanile programmeerija nimi ja teade, et ta kohvi ei joo, ning tagastatakse-1
; toString
meetod, mille tagastus sõltub sellest, kas programmeerija joob kohvi või mitte.- Kui programmeerija joob kohvi, siis tagastab meetod:
- programmeerija nime,
- lemmikkohvi sordi nime,
- lemmikkohvi tassi hinna,
- rahasumma, mis igapäevaselt kulub kohvi joomisele (kasutades
tassideMaksumus
isendimeetodit), - meetodi
koodiTassiKohta
väärtuse.
- Muidu tagastatakse vaid programmeerija nimi koos teatega, et ta kohvi ei joo.
- Kui programmeerija joob kohvi, siis tagastab meetod:
Koosta peaklass nimega Kohvijoomine
, mille peameetodis luuakse kaks isendit klassist Kohv
ja kolm isendit klassist Programmeerija
(kusjuures üks neist kohvi ei joo). Seejärel:
- väljasta iga programmeerija kohta informatsiooni, kasutades meetodi
toString
tagastatud väärtust; - selgub, et programmeerijad jätsid töö viimasele hetkele ja peavad esitama oma projekti järgmisel hommikul. Tähtaja lähenedes kirjutavad nad rohkem koodi ning joovad rohkem kohvi. Suurenda vastavaid isendiväljade väärtusi, kasutades
get
- jaset
-meetodeid:- iga kohvijooja joob 3 tassi võrra rohkem kohvi kui tavaliselt ning kirjutab kaks korda rohkem koodiridu päevas;
- programmeerija, kes kohvi ei joo, kirjutab 100 rea võrra rohkem koodi, aga kohvi endiselt ei joo.
- väljasta iga programmeerija kohta informatsioon uuesti.
Klõpsa siia näite nägemiseks
Näide võimalikust peameetodi väljundist. Programmis oli loodud 3 programmeerijat:
Peeter. Kohvi ei joo Indrek. Lemmikkohv on Supremo hinnaga 2.5. Igapäevaselt kohvi joomisele kulub 12.5 eurot. Programmeerija tulemuslikkus on 120.0 rida/tass Sander. Lemmikkohv on Yirgacheffe hinnaga 3.0. Igapäevaselt kohvi joomisele kulub 6.0 eurot. Programmeerija tulemuslikkus on 350.0 rida/tass Tähtaeg läheneb Peeter. Kohvi ei joo Indrek. Lemmikkohv on Supremo hinnaga 2.5. Igapäevaselt kohvi joomisele kulub 20.0 eurot. Programmeerija tulemuslikkus on 150.0 rida/tass Sander. Lemmikkohv on Yirgacheffe hinnaga 3.0. Igapäevaselt kohvi joomisele kulub 15.0 eurot. Programmeerija tulemuslikkus on 280.0 rida/tass
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.
Lahenduste esitamine
Kuna seekord on kontrollülesannete lahendustele koostatud automaattestid, on ka lahenduste esitamise kord varasemast erinev. Java failid tuleb Moodle'isse esitada eraldi, mitte kokkupakituna.
Kursuse Moodle'i avalehelt jõuate esmalt ülesande kirjelduse juurde, kust saab liikuda edasi lehtedele "Esitamine" ja "Redigeerimine". Kuigi "Esitamine" tundub pealkirja järgi ülesannete esitamiseks õigem valik, on tegelikult mitut faili mugavam esitada lehel "Redigeerimine". Valige seal nuppu "Impordi" (kastikese kohal nool suunaga üles) otsige üles enda *.java failide kaust, märkige kõik failid, mida soovite esitada ja vajutage "OK" ning seejärel redaktori nuppu "Salvesta". Kui hiljem otsustate mingit faili uuendada, siis piisab vaid selle faili uuesti importimisest (või redaktoris muutmisest).
Kontrollimise alustamiseks vajutage redaktori menüüribal "Kontrolli" (☑). Seepeale peaks ekraani paremasse serva varsti ilmuma tagasiside või kompileerimise veateated.
Esitada tuleks järgmised failid:
- Isik.java
- Raamat.java
- Kohv.java
- Programmeerija.java
- Kohvijoomine.java
- valikuliste ülesannete lahendused, mille kohta soovite juhendajalt tagasisidet
Nagu ülalpool öeldud, peavad kõik nõutud klassid olema vaikepaketis, st. vastava java faili alguses ei tohi olla package
direktiivi.
Kui mõnes esitatud failis tekib kompileerimisviga, siis jääte paraku automaatsest tagasisidest ilma. Esitatud failid jõuavad praktikumijuhendajani sellegipoolest ja ta saab omapoolse tagasiside siiski anda.