Erinevus lehekülje "Java:Objektid" redaktsioonide vahel
216. rida: | 216. rida: | ||
Konstruktori definitsiooni puhul pange tähele, et '''tagastatavat andmetüüpi ei märgita'''. '''Meetodi nimi on täpselt sama mis klassil'''. | Konstruktori definitsiooni puhul pange tähele, et '''tagastatavat andmetüüpi ei märgita'''. '''Meetodi nimi on täpselt sama mis klassil'''. | ||
+ | |||
+ | == Objektide võrdlemine == | ||
+ | |||
+ | Kahe objektile viitava muutuja võrdlemisel kujul <code>if (std1 == std2)</code> kontrollitakse, '''kas mõlemad muutujad viitavad samasse kohta mälus'''. Ei võrrelda seda, mis mälus kirjas on. Ehk siis objektide sisu ei võrrelda. | ||
+ | |||
+ | <code>String</code> on Javas objekt. Selle võrdlemine käib üldiste objekti reeglite alusel. Kui me teeme kontrolli <code>if (str1 == str2)</code>, siis tulemus on <code>true</code> vaid juhul, kui muutujat viiatavad mälus samasse kohta (ehk siis sõned on mälus samas kohas). Java võimaldab sõne luua lihtsustatud kujul: | ||
+ | <source lang="java"> | ||
+ | String str1 = "tere"; | ||
+ | String str2 = "tere"; | ||
+ | </source> | ||
+ | |||
+ | Nagu eelnevalt oleme vaadanud, siis objektide loomisel tuleb kasutada võtmesõna <code>new</code>. Ülal väljatoodud näites seda tehtud ei ole. Kuna sõna andmetüüp on väga levinud, võimaldab Java kasutada n-ö lihtsustatud sõne loomist, kus <code>new</code> võtmesõna pole vaja märkida. Sellise kirjapildi puhul ei looda igakord uut sõne (objekti). Kui sama sisuga sõne on juba mälus olemas (näiteks aadressil 123), siis uue sõne loomisel (str2) puhul pannakse see viitama samasse kohta mälus (123). Kui nüüd teha võrdlus <code>if (str1 == str2)</code>, saab suure tõenäosusega tulemuseks <code>true</code>. | ||
+ | |||
+ | Sõne on võimalik luua ka selliselt: <code>String str3 = new String("tere");</code>. Sellisel juhul sunnitakse uue sõne jaoks mälus eraldi piirkonda eraldama. Kui sellist sõne võrrelda <code>==</code> võrdlusega, on tulemus <code>false</code> ehk siis algselt loodud "tere" ei paikne samas kohas mälus kui hiljem loodud <code>str3</code> viitab, kuigi sisu (ehk siis sõne ise) on täpselt sama. | ||
+ | |||
+ | Koodinäide: | ||
+ | <source lang="java"> | ||
+ | public class StringExample { | ||
+ | public static void main(String[] args) { | ||
+ | String str1 = "tere"; | ||
+ | String str2 = "tere"; | ||
+ | // str1 and str2 point to the same memory location | ||
+ | System.out.println(str1 == str2); | ||
+ | // and the contents are equal | ||
+ | System.out.println(str1.equals(str2)); | ||
+ | |||
+ | // enforce new object creation | ||
+ | String str3 = new String("tere"); | ||
+ | // now str3 is stored in a separate location in memory | ||
+ | System.out.println(str1 == str3); | ||
+ | // but the contents are still equal | ||
+ | System.out.println(str1.equals(str3)); | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
[[Category:Java juhend|Objektid]] | [[Category:Java juhend|Objektid]] |
Redaktsioon: 12. märts 2015, kell 00:13
Objekt-orienteeritud programmeerimine (OOP)
Objekt-orienteeritud programmeerimine (ingl object oriented programming, OOP) on programmeerimise viis, kus programmi vaadatakse kui klasside/objektide kogumit. Java on suures osas objekt-orienteeritud programmeerimiskeel (primitiivsed andmetüübid (int, double jne) ei ole objektid).
OOP on tehnika, mis võimaldab programmeerimist mugavamaks teha. Eriti kasulik suurte projektide tükeldamisel. OOP ei tee lahendusi kiiremaks.
OOP peamised tehnikad/eesmärgid:
- informatsiooni kapseldamine (encapsulation). Teised programmeerijad ei saa kasutada osasid minu funktsioone ega muuta osasid minu muutujaid. Informatsioon peidetakse nende eest ära.
- Modulaarsus (modularity). Koodi jagamine mooduliteks. Seotud kapseldamisega, kus kapseldatud programm viiakse vastavusse pärismaailmaga.
- Polümorfism (polymorphism). Sama nimega meetod võib erinevate andmetüüpide puhul käituda erinevalt.
- Pärimine (inheritance). Pärinevussuhted klasside vahel, alamklassid pärivad kõik ülemklassi omadused ja meetodid, lisaks võib alamklass lisada funktsionaalsust.
- Koodi taaskasutamine. Kirjutada valmismooduleid, mida hiljem saab taaskasutada.
OOP põhikontseptsioon
Klassid on kui terviklikud tarkvara komponendid, mida saab kergesti (taas)kasutada. Keskne mõiste OOP juures on objekt, mis on justkui komponent, millel on andmed (olek) ja funktsionaalsus (käitumine).
Objekti oleku kirjeldamiseks kasutatakse ka järgmisi termineid: atribuut (attribute), omadus (property), (isendi)väli ((instance) field), (isendi)muutuja ((instance) variable).
Objekti andmeid muudetakse objekti meetodite abil. Meetodi aktiveerimiseks saadetakse objektile teade (message).
Näiteks elektronposti nimekirja objekt, millel olek (andmed) on kõik selle nimekirja nimed ja aadressid. Kui saata sellele objektile teade (kutsuda välja funktsiooni), mis lisab nime, muudetakse olekut vastavalt (lisatakse uus nimi). Kui saata sellele objektile teade (kutsuda välja funktsioon), mis prindib objekti välja, prindib objekt välja kõik nimed ja aadressid.
OOP ideoloogia
OOP tehnika tähendab mõnda konkreetset probleemi lahendavate objektide disainimist. Programmi objektid võivad esindada päriselu objekte antud probleemi puhul. Mida paremini (täpsemini, selgemini) päriselu objektid on programmi objektidena esitatud, seda lihtsam ja selgem on nendega opereerida. Näiteks kui programm peab lahendama ülesannet, mis on seotud sõiduki ja selle juhiga, siis on mõistlik luua programmi poolel objektid sõiduki ja juhi (inimese) kohta.
OOP võimaldab (suurema) programmi puhul:
- koodi paremini struktureerida
- hoida koodi arusaadavust lihtsana
- teha hiljem täiendusi koodi lihtsamini
jne.
Klassid ja objekt
Klass defineerib ära objekti abstraktsed omadused. Klass on nagu šabloon, mis kirjeldab millegi olemust. Näiteks klass Koer koosneks kõikide koerte omadustest nagu tõug ja värv ning oskustest nagu haukumine ja istumine. Objekt on konkreetne koer ehk isend. Klass Koer defineerib ära kõikide koerte omadused, objekt Lotte aga konkreetse isendi/koera, millele võib vastata (ei pea tingimata) reaalne koer. OOP puhul võib öelda, et klass on objektide kirjeldus, klass kirjeldab ära, milliseid andmeid/oskusi võivad sellesse klassi kuuluvad isendid (ehk objektid) omada.
Programmis on üldjuhul üks klass Koer, aga mitu erinevat objekti. Kui programm näiteks kirjeldab koerte varjupaiga andmeid, siis iga koera kohta varjupaigas on programmis üks objekt. Klass on aga neil kõikidel ühine.
OOP puhul öeldakse objekti kohta tihti isend (instance) või olem.
Java puhul:
- staatiline muutuja (
public static int dogCount;
) on klassi muutuja. Kuna klass ise kirjeldab ära kõik koerad, siis ka see muutuja käib kõikide koerte kohta. Selles võib näiteks hoida, mitu koera on varjupaigas. Kui kuskil seda numbrit muudetakse, muutub see üle kogu programmi. - mitte-staatiline muutuja (
String name;
), öeldakse ka objekti muutuja, isendimuutuja jne, kirjeldab ära ühe konkreetse objekti (koera) andmed, antud juhul koera nime. See võib olla kõikidel koertel erinev. Kui muudetakse ühe objekti nime, siis see ei muuda teiste koerte nimesid.
Klassi kirjeldus kirjeldab ära oskused, mida antud klassi isendid (objektid) teha oskavad. Neid oskusi nimetatakse meetoditeks (sisuliselt on need funktsioonid, aga objektide puhul räägitakse meetoditest). Objekti meetod võimaldab konkreetsel isendil mingit tegevust läbi viia. Näiteks koera puhul võib meetod olla haugu, mis siis paneb haukuma konkreetselt selle koera, kelle juures see meetod välja kutsuti (kellele saadeti vastav teade "haugu").
Java puhul:
- staatiline meetod e funktsioon (
public static int getDogLimit()
), nimetatakse ka klassi funktsiooniks või klassi meetodiks, on funktsioon, mis ei sõltu konkreetsest isendist. Näiteks see informatsioon, mis koera varjupaika mahub, ei sõltu ühestki konkreetsest koerast. See on kogu programmi kohta ühine funktsioon. - mitte-staatiline meetod (
public void bark()
), öeldakse ka isendimeetod, objektimeetod, on seotud aga konkreetse objektiga. Seda meetodit või funktsiooni rakendatakse ainult ühele konkreetsele isendile (loomulikult saab ka kõikidele rakendada, kui see iga objekti juures eraldi välja kutsuda).
Vaatame natuke teistsugust koodinäidet: <source lang="java">
/**
* Describes a student which * has some test results. * */
public class Student { /** * The name of the student. */ public String name; /** * Unique ID over all the students. */ public int ID; /** * The results of three tests. */ public double test1, test2, test3;
/** * Calculates the average test result. * @return Average test result */ public double getAverage() { return (test1 + test2 + test3) / 3; }
/** * Static variable which holds the next unique id. * The value of this variable is usually student count. * This is the same for all the students. */ private static int nextUniqueID = 0;
/** * Gets a unique ID for the student. * The ID number itself is increased. * @return Unique id for the student. */ public static int getUniqueID() { nextUniqueID++; return nextUniqueID; } }
</source>
Eelnevas õpilase näites on nii staatilisi kui mitte-staatilisi muutujaid/meetodeid. Staatiline getUniqueID()
ei sõltu otseselt ühestki tudengist. Ta sõltub vaid sellest, mis numbrid on juba määratud (unikaalne number eeldab, et ühelgi teisel tudengil sellist numbrit ei ole). Kõige lihtsam viis unikaalset numbrit saada on iga uue tudengi puhul anda ühe võrra suurem number kui eelmisele anti. Kui meil ülikoolis on 100 õpilast, siis me ei pea ühegi poole neist pöörduma, et saada uus unikaalne id. Seepärast ongi see meetod staatiline, kuna ta on kogu programmi (näiteks kogu ülikooli peale) ühine.
Meetod getAverage()
on aga seotud konkreetse tudengiga. Kui ülikoolist küsida "anna keskmine hinne", siis tähendaks see midagi muud. Kuigi jah, ülikooli näite puhul võib väita, et tegelikult ülikool teab ka kõikide tudengite keskmisi hindeid. Jah, teoreetiliselt võiks see meetod olla staatiline, aga siis oleks selle sisu hoopis teine. Antud näite puhul on aga eeldatud, et hinnete info on seotud konkreetse tudengiga. Kui nüüd ühelt konkreetselt tudengilt küsida, mis ta keskmine hinne on, siis ta saab sellele vastata (võtab lihtsalt aritmeetilise keskmise oma hinnetest).
Objektide loomine
Klassi nimi defineerib ära uue andmetüübi. Kõik senised objektid, mis te seni kasutanud olete (String, ArrayList jne) on tegelikult samamoodi kuskil ära kirjeldatud nagu meie Student objekt eelnevalt näidatud klassis.
Seega, klassinimi on kasutatav andmetüübina. Näiteks võime luua muutuja, mille andmetüüp on Student: <source lang="java"> Student std; </source>
Javas muutuja deklareerimine (nagu eelnevalt näidatud) ei loo veel objekti. Üldisemalt:
- Javas ükski muutuja ei salvesta endas objekti.
- Muutuja hoiab vaid viidet objektile.
Kui kujutada ette, et andmed paiknevad suvaliselt mälus laiali. Näiteks ühe tudengi andmed hakkavad positsioonilt 100, teise tudengi andmed hakkavad positsioonilt 1190 jne. Muutuja std ei hoia mitte tudengi andmeid endas, vaid hoopis viidet mällu. Seega, lihtsustatult võib öelda näiteks, et std = 100
(tegelikult viide objektile ei ole 1:1 mäluaadress, Javal on endal vahel mingi vastavustabel mäluaadresside jaoks).
Seega, Javas muutuja, mille andmetüüp on objekt (ükskõik kas teie enda loodud või mõni Java sisseehitatud objekt), salvestab vaid viite mällu. Viite (reference) või pointeri (pointer) täpset väärtust programmeerija teadma ei pea. Objekti loomisel teeb Java vajalikud protseduurid (eraldab objektile mälu ja määrab vastava aadressi muutujasse).
Objekti loomine toimub võtmesõnaga new: <source lang="java"> std = new Student(); </source>
Ülaltoodud kood loob uue objekti tüübiga Student
, salvestab selle mällu ja paneb vastava (alguse) mäluaadressi std
muutujasse. Loodud muutuja std
on kasutatav nüüd objektina. Selleks, et objekti muutujaid või meetodeid välja kutsuda, saab kasutada loodud muutujat, näiteks std.name
.
Nullviit
On võimalik, et muutuja nagu std
, millel on andmetüübiks klass, ei viitagi objektile. Sellisel juhul hoiab std
null-viita või tühja viita (null reference). Javas kirjutatakse null-viit null
. Muutujale saab null-viida omistada selliselt:
<source lang="java">
std = null;
</source>
ning null-viida testimine käib selliselt:
<source lang="java">
if (std == null) {
</source>
Kui muutuja väärtus on null
, siis isendi muutujate või meetodite poole pöördumine ei ole lubatud. Näiteks kui std = null;
, siis std.name
pöördumine pole lubatud. Kui seda tehakse, annab programm null pointer exception (NullPointerException) vea.
Instants
Konkreetset isendit või objekti nimetatakse instantsiks (instance). Järgnevalt näide, kuidas luuakse mõned instantsid eelnevalt näidatud Student
andmetüübiga.
<source lang="java"> // Declare four variables of type Student Student std, std1, std2, std3; // Create a new object belonging to the class Student, // store a ref to that object in the var std. std = new Student(); std1 = new Student(); // Create a second Student object std2 = std1; // Copy the reference value in std1 into the variable std2. std3 = null; // Store a null reference in the variable std3. std.name = "John Smith"; // Set values of some // instance vars, getUniqueIdisa static method, // therefore is accessed from class Student // (not object instance like std) std.ID = Student.getUniqueID(); std1.name = "Mary Jones"; std1.ID = Student.getUniqueID(); // (Other instance variables have default initial values of zero.) </source>
Järgnevalt pildil on näha, kuidas antud näite puhul näeb välja olukord mälus:
Kui mõni muutuja viitab objektile, siis see on pildil tähistatud noolega. Nagu näha, std
, std1
ja std2
muutujad viitavad Student
tüüpi objektidele, kusjuures std1
ja std2
viitavad samale objektile! Kui muutujad viitavad samale objektile ja ühe kaudu muudetakse objektis mingit väärtust (näiteks nime), siis muutub see väärtust kõikide objektide jaoks. Ehk siis muutuja ise on lihtsalt viide mällu (n-ö aadress). Kui mitu muutujat viitavad samma kohta mälus, siis andmete muutmisel mälus näevad kõik muutujad seda muudatust. Kui teie kodune aadress (viit) on mitmel teie sõbral (muutuja) ning teie kodus (objekt, millele viidatakse) muutub seinavärv (näiteks üks sõpradest muudab selle ära sõbra_viide_teie_kodule.seinavärv = punane
), siis ükskõik, millise (teise) sõbra kaudu küsida teie kodu seinavärvi (prindi teise_sõbra_viide_teie_kodule.seinavärv
), on see muutunud (punaseks).
Tasub tähele panna, et String
on ka objekt. Seega tudengi nimi objektis on tegelikult omakorda viide kuskile järgmisesse kohta mälus.
Kui ühe mitteprimitiivse (ehk siis andmetüüp on mingi objekt) muutuja väärtuseks määratakse teine mitteprimitiivne muutuja (eelnevas koodis std2 = std1
), siis määratakse std2
väärtuseks täpselt sama viide ("mäluaadress") kui std1
muutujal oli. Tulemuseks hakkavad kaks muutujat viitama täpselt samale mälu piirkonnale. std1.name
ja std2.name
viitavad täpselt samale nimele. Kui ühe muutuja kaudu nime muuta, siis teise muutuja kaudu nime vaadates on ka see muutunud. std1.name
ja std2.name
on kaks erinevat viisi, kuidas samale mälu piirkonnale saab viidata.
Konstruktor
Üks erilist tüüpi meetod on konstruktor (constructor). See on meetod, mis käivitatakse juhul, kui uus instants luuakse. Vaikimisi on igal klassil see olemas. Isegi juhul, kui seda eraldi kirja pole pandud. Kui koodis pole oluline, et objekti loomisel midagi erilist tuleks teha, pole seda vaja ka eraldi defineerida. Olukorrad, kus konstruktorit oleks vaja eraldi kirjeldada:
- objekti loomisel oleks vaja kaasa anda mingid parameetrid
- objekti loomine on teatud välistele objektidele/klassidele keelatud
Näide, kus objekti loomisel antakse kaasa parameetrid. Igal tudengil on nimi. Iga kord objekti loomisel tuleb eraldi teha student.setName("...")
. Selle asemel, et eraldi real nimi lisada, teeme seda kohe objekti luues.
Student.java: <source lang="java"> public class Student { private String name;
public Student(String name) { this.name = name; } } </source>
Nüüd saab uut objekti luua selliselt: <source lang="java"> Student s = new Student("Reinuvader"); </source>
Kusjuures new Student();
(ilma argumendita) annab vea. Kui tahaks lubada mõlemad variandid, nii kohustusliku nimega kui ilma nimeta:
<source lang="java">
public static class Student {
private String name;
public Student() { }
public Student(String name) { this.name = name; } } </source>
Eelneva näite puhul kasutatakse overloading tehnikat. See tähendab, et meil on mitu funktsiooni sama nimega (antud juhul Student
), mille argumentide arv ja/või andmetüübid on erinevad. Kui koodis kutsutakse välja new Student();
, käivitub ilma argumentidega konstruktor. Kui koodis kutsutakse välja new Student("Koolipoiss");
, käivitub konstruktor, mis aktsepteerib sõne argumendina.
Konstruktori definitsiooni puhul pange tähele, et tagastatavat andmetüüpi ei märgita. Meetodi nimi on täpselt sama mis klassil.
Objektide võrdlemine
Kahe objektile viitava muutuja võrdlemisel kujul if (std1 == std2)
kontrollitakse, kas mõlemad muutujad viitavad samasse kohta mälus. Ei võrrelda seda, mis mälus kirjas on. Ehk siis objektide sisu ei võrrelda.
String
on Javas objekt. Selle võrdlemine käib üldiste objekti reeglite alusel. Kui me teeme kontrolli if (str1 == str2)
, siis tulemus on true
vaid juhul, kui muutujat viiatavad mälus samasse kohta (ehk siis sõned on mälus samas kohas). Java võimaldab sõne luua lihtsustatud kujul:
<source lang="java">
String str1 = "tere";
String str2 = "tere";
</source>
Nagu eelnevalt oleme vaadanud, siis objektide loomisel tuleb kasutada võtmesõna new
. Ülal väljatoodud näites seda tehtud ei ole. Kuna sõna andmetüüp on väga levinud, võimaldab Java kasutada n-ö lihtsustatud sõne loomist, kus new
võtmesõna pole vaja märkida. Sellise kirjapildi puhul ei looda igakord uut sõne (objekti). Kui sama sisuga sõne on juba mälus olemas (näiteks aadressil 123), siis uue sõne loomisel (str2) puhul pannakse see viitama samasse kohta mälus (123). Kui nüüd teha võrdlus if (str1 == str2)
, saab suure tõenäosusega tulemuseks true
.
Sõne on võimalik luua ka selliselt: String str3 = new String("tere");
. Sellisel juhul sunnitakse uue sõne jaoks mälus eraldi piirkonda eraldama. Kui sellist sõne võrrelda ==
võrdlusega, on tulemus false
ehk siis algselt loodud "tere" ei paikne samas kohas mälus kui hiljem loodud str3
viitab, kuigi sisu (ehk siis sõne ise) on täpselt sama.
Koodinäide: <source lang="java"> public class StringExample { public static void main(String[] args) { String str1 = "tere"; String str2 = "tere"; // str1 and str2 point to the same memory location System.out.println(str1 == str2); // and the contents are equal System.out.println(str1.equals(str2));
// enforce new object creation String str3 = new String("tere"); // now str3 is stored in a separate location in memory System.out.println(str1 == str3); // but the contents are still equal System.out.println(str1.equals(str3)); } } </source>