Osztaly alapok
Figyelem! A héten kikerül a Bíróra a 2. házi feladat!
A gyakorlat anyaga¶
Osztályok létrehozása¶
Ahogy korábban már tárgyaltuk, a konkrét objektum(csoportok) formai leírása lesz az osztály. Osztályokat kell létrehoznunk ahhoz, hogy majd létre tudjunk hozni a memóriában objektumokat. Osztályokkal olyan objektumokat formalizálunk, melyek azonos tulajdonságokkal és operációkkal rendelkeznek, mint például az emberek. Sok különböző ember létezik, de mégis rendelkeznek közös tulajdonságokkal: van név
és kor
tulajdonságuk, valamint mindenki tudja magáról, hogy férfi-e
. Készítsünk egy ilyen osztályt, melynek kulcsszava a class
. Általában osztályokat külön fájlokba készítünk, melynek neve megegyezik a létrehozni kívánt osztállyal, kiegészítve a .java
kiterjesztéssel. Tehát készítsük el az Ember.java
fájlt:
Az embereknek van néhány közös tulajdonságuk, amelyet UML-ben tulajdonságnak, attribútumnak hívtunk. Most a név
, kor
, valamint a férfi-e
tulajdonságot szeretnénk a programunkban használni. Ezek legyenek String
, int
és boolean
típusú változók.
koszon
metódust.
public class Ember {
String nev;
int kor;
boolean ferfi;
public void koszon(){
System.out.println("Szia! " + nev + " vagyok és " + kor + " éves, mellesleg " + (ferfi ? "férfi." : "nő."));
}
}
Az így elkészült osztályból objektumokat készítünk, ezt példányosításnak hívjuk. Minden egyes objektum pontosan egy osztály példánya, azonban egy osztályból számtalan objektumpéldány is létrehozható. Hozzuk létre Józsit a main függvényben a new
operátor segítségével.
Szia! null vagyok és 0 éves, mellesleg nő
. Nem éppen lett Józsi, még férfi sem igazán. Ennek az az oka, hogy az osztályban lévő adattagok nincsenek rendesen inicializálva, a típusokhoz tartozó default értéket vették fel (előző gyakorlat). Ahhoz, hogy ezt megfelelő módon megtehessük, konstruktort kell létrehoznunk.
Konstruktor¶
Ez a speciális függvény fogja létrehozni a példányokat (objektumokat) az osztályokból, beállítani az osztályban deklarált változókat a konkrét, adott objektumra jellemző értékekre. Ilyen alapból is létezik (hiszen az előbb mi sem írtunk ilyet), de mi paramétereseket is megadhatunk, ami segítségével gyorsan és könnyen tudjuk az adott objektumot inicializálni. Megjegyzendő, hogy ha mi készítünk bármilyen konstruktort, akkor a fordító nem készít egy default, paraméter nélküli konstruktort (ahogy azt tette korábban).
- A konstruktor nevének meg kell egyeznie az osztály nevével
- Visszatérési értéke/típusa nincs (nem is lehet!)
- Új objektum létrejöttekor fut le
/*
default konstruktor
Ez alapból létezik, nem muszáj kiírni, hacsak nem szánunk neki speciális szerepet.
*/
public Ember() {}
/*
paraméteres konstruktor
minden esetben létre kell hoznunk (ha szeretnénk paraméteres konstruktort).
*/
public Ember(String nev, int kor, boolean ferfi) {
this.nev = nev;
this.kor = kor;
this.ferfi = ferfi;
}
this
kulcsszó - az objektum saját magára tud hivatkozni vele, ennek akkor van gyakorlati haszna például, amikor egy metódusban szereplő paraméter neve megegyezik az osztályban deklarált adattag nevével, akkor valahogy meg kell tudnunk különböztetni, hogy melyik az objektum tulajdonsága, és melyik a paraméter.
A paraméteres konstruktorral könnyen, egy sorban létrehozhatjuk és értéket is adhatunk a példányoknak, ami energia- és helytakarékos módszer a paraméter nélkülihez képest.
Az így kiegészített osztály után hozzuk létre most már valóban Józsit.
public static void main(String[] args){
Ember jozsi = new Ember("Jozsi", 20, true);
jozsi.koszon();
}
A program kimenete: Szia! Jozsi vagyok és 20 éves, mellesleg férfi
.
Láthatóságok¶
A láthatóságok segítségével tudjuk szabályozni adattagok, metódusok elérését, ugyanis ezeket az objektumorientált paradigma értelmében korlátozni kell, kívülről csak és kizárólag ellenőrzött módon lehessen ezeket elérni, használni. Része az implementáció elrejtésének, azaz akár a programot, akár az osztályt anélkül használják kívülről a felhasználók, hogy pontosan ismernék annak működését.
Eddig ismert láthatóságok:
public
- mindenhonnan láthatóprivate
- csak maga az osztály látja- nem írtunk ki semmit - "friendly" láthatóság, csomagon belül
public
, csomagon kívülprivate
A későbbiekben majd megismerkedünk a 3. láthatósági módosítószóval is.
protected
- a csomag, az osztály és az azokból származtatott gyermekosztályok látják
Az UML diagramokon láthatunk attribútumok, valamint operációk előtt is néha plusz (+) és mínusz jeleket (-). A + jel azt jelöli, hogy az adott operáció, vagy tulajdonság az osztályon kívülről is használható ahhoz, hogy módosítsuk vele az objektum állapotát, Java esetén ez azt jelenti, hogy az adattag/metódus publikus láthatóságú. A - jel pedig azt jelenti, hogy csak az osztályon belüli használatra szánjuk az adott tulajdonságot/operációt. Java esetén ez lesz a privát láthatóság.
Getter/Setter¶
Az adattagok értékeinek lekérésére (getter), valamint inicializálás utáni módosítására (setter) használjuk. Ezek összefüggenek azzal, hogy egy objektum adatait csak ellenőrzött módon lehet lekérni, illetve módosítani, ezeken a függvényeken keresztül. Általában csak simán lekérésre, és beállításra használjuk, de ide tehetünk mindenféle ellenőrzéseket is például.
Ezekkel tulajdonképpen elrejtjük a változókat és akár még módosíthatunk is az értékeken lekéréskor vagy megadáskor (mondjuk setternek megadhatjuk, hogy ne hagyja 0-ra állítani a változó értékét (például egy osztás nevezőjét))
Hagyományos elnevezése: get + adattag neve
illetve set + adattag neve
.
//getter
public String getNev() {
return this.nev;
}
//setter
public void setNev(String nev) {
this.nev = nev;
}
//setter
public void setKor(int kor) {
if (kor > 0) {
this.kor = kor;
} else {
System.err.println("A kor csak pozitív szám lehet!");
}
}
Boolean értékek esetében a getter függvényt általában is + adattag neve
formában szoktuk elnevezni.
/*
Kiegészítés: boolean érték gettere
A boolean értékű gettert get kulcsszó helyett is-zel szokás jelölni.
*/
public boolean isFerfi(){
return this.ferfi;
}
Az IDE kedvesen segít ezek előállításában is (ez a ZH-n is használható):
- Eclipse esetén: a munkaterületen jobb klikk és Source > Generate Getters and Setters... menüpontban találjuk meg az ide kapcsolódó varázslót.
- IntelliJ IDEA esetén: a munkaterületen jobb klikk és Generate, vagy pedig egyszerűen nyomjuk le az Alt + Insert billentyűkombinációt, majd pedig válasszuk a Getter and Setter opciót.
toString metódus¶
Írassuk ki az elkészült jozsi
nevű, ember típusú objektumunkat:
A program kimenete valami hasonló: Ember@677327b6
.
Ez nem túl szerencsés, pláne nem olvasható.
Ezt megoldhatjuk úgy, hogy az Ember
osztály összes adattagját egyesével kiírogatjuk, ám ez macerás lehet, ha több helyen is szeretnénk ezt a kiíratást használni.
Ha minimális munkával szeretnénk emberi nyelvű leírószövegeket adni az objektumról minden esetben, amikor például átadjuk a kész objektumot a System.out.println
metódusnak, akkor felül kell definiálnunk a toString()
metódust.
Ez egy public
láthatóságú, String
-gel visszatérő metódus.
public String toString() {
return "Az ember neve: " + this.nev +
", aki " + this.kor + " eves" +
", ferfi=" + this.ferfi;
}
Ami ugyanolyan mintha egy sorba írtuk volna a return-t, csak így nem fut ki a sor a végtelenbe. Ezek után nincs más dolgunk, nézzük meg mi történik a következő sor hatására:
Ennek eredménye:Az ember neve: Jozsi, aki 20 eves, ferfi=true
Ha egy kicsit szépíteni szeretnénk még ezen is, akkor a ferfi
tulajdonságtól függően egy egy adott szöveget is kiírathatunk a háromoperandusú if-else (ternary operátor) használatával.
public String toString() {
return "Az ember neve: " + this.nev +
", aki " + this.kor + " eves" +
(this.ferfi ? "ferfi" : "no");
}
Ebben az esetben zárójelet is kell használnunk, különben a fordító a már létező szöveghez akarná hozzáfűzni a this.ferfi
tulajdonságot, és az így létrejövő Stringgel szeretné meghívni a ternary operátort, aminek nem lenne értelme, hiszen a ternary operátorhoz egy logikai értékre van szükség a kérdőjel bal oldalán.
Ennek eredménye: Az ember neve: Jozsi, aki 20 eves ferfi
Az IntelliJ Idea és az Eclipse is szívesen segít a toString
elkészítésében, hogy ezt a robotikus munkát ne kelljen kézzel
- IntelliJ Idea esetén: Code > Generate menüpont, majd pedig a felugró menüben toString(). A menüre kattintásokat kiküszöbölhetjük, ha a gyorsbillentyűjén keresztül hívjuk meg: Alt + Insert, majd toString().
- Eclipse esetén: A munkaterületen jobb klikk és Source > Generate toString()... menüpontban találjuk meg az ide kapcsolódó varázslót.
Ez pedig valami ilyesmit fog elkészíteni helyettünk
@Override
public String toString() {
return "Ember{" +
"nev='" + nev + '\'' +
", kor=" + kor +
", ferfi=" + ferfi +
'}';
}
A @Override
egyelőre számunkra nem érdekes.
Az elkészült toString ugyan nem a legszebb, de minden adatot könnyedén ki tudunk belőle olvasni, így bármilyen célra hasznos lehet.
Static¶
Akár adattagról, akár metódusról legyen szó, ha valami statikus, akkor azt az UML diagramon aláhúzással jelöljük.
Adattag¶
Ugyanazon a helyen tárolódik a memóriában, az összes példány esetében ugyanaz lesz az értékük, gyakorlatilag az osztály összes példánya osztozik rajtuk, példányosítás nélkül is hivatkozhatunk rájuk. Tulajdonképpen ezek az adattagok nem az objektumokhoz, hanem az osztályhoz tartoznak.
Gyakorlati jelentősége lehet például akkor, ha egy változóban szeretnénk letárolni egy olyan értéket - például a létrehozott objektumok darabszámát - amelyeknek azonosnak kell lenniük minden objektumpéldány esetén, sőt, akár példányosítás nélkül is értelmesek.
Hivatkozni rájuk Osztály.adattag
módon lehetséges, amennyiben látható az adattag az adott helyről.
Metódus¶
Ezek a metódusok gyakorlatilag nem az objektumokhoz, hanem az osztályhoz tartoznak. Objektum példányosítása nélkül is meghívhatóak.
Ezekre példát már sokat láttunk, ilyenek többek között a public static void main()
, Integer.parseInt()
metódusok.
Statikus metódusban csak statikus adattagok és a kapott paraméterek használhatóak, hiszen ezek nem kapcsolódnak egyetlen objektumhoz sem, azok létezése nélkül is meghívhatunk egy statikus metódust.
Ezek a metódusokat nem lehet felüldefiniálni, de erről a későbbiekben fogunk tanulni.
Final¶
Adattag¶
A final
adattag kezdeti értékét nem változtathatjuk meg a futás során. Ehhez persze adni kell kezdőértéket, vagy a létrehozás helyén (pl.: final int TOMEG = 10
) vagy pedig a konstruktorban. Ha a final értéken változtatni próbálnánk meg, akkor fordítási hibát kapunk. Próbáljuk ki a következőt.
Metódus¶
Ezeket a metódusokat a gyerekosztályban nem lehet felüldefiniálni. Erről a későbbiekben fogunk tanulni, de itt érdemes visszaemlékezni az UML-nél tanultakra.
Osztály¶
Nem lehet gyerekosztálya.
Konstans¶
Nincs rá külön kulcsszó, az eddigiekből merítve azonban könnyen kitalálható: valódi konstans létrehozása a static és final kulcsszavak együttes használatával, hiszen az így létrejövő változó: statikus lesz, bármely objektumpéldány esetén ugyanazt az egy konkrét adatot látjuk/módosítjuk; valamint a final miatt a kezdőérték nem változtatható meg.
Konstans változók nevét általában csupa nagybetűvel szoktuk írni, szóhatárnál aláhúzást teszünk.
Ennek elérése kívülről:Garbage collection - Szemétgyűjtés¶
Objektumok élettartama Java-ban: (élettartam = objektum létrejöttétől a memória felszabadításáig)
Amikor létrehozunk egy objektumot a new kulcsszóval, az objektum a heapen jön létre (ellentétben a primitív típusokkal).
A memória felszabadítása automatikus Javaban, a garbage collector (Szemétgyűjtő) végzi el a feladatokat. A Java szemétgyűjtőről bővebben ezen, ezen és ezen az oldalon olvashatunk.
Ha egy objektumot nem használunk tovább, beállíthatjuk null
-ra, ez a kulcsszó jelöli, hogy az adott referencia nem hivatkozik egyetlen objektumra sem (pl. jozsi = null;
)
Garbage collector hívása manuálisan (nem biztos hogy lefut):
Lehetőségünk van az osztályainknak egy finalize()
-nak nevezett metódust készíteni. Ez a metódus akkor fog lefutni, amikor a szemétgyűjtő felszabadítja a feleslegesnek ítélt memóriát, és egy adott objektum, amit már nem használunk, törlődni fog. Azt természetesen nem tudni, hogy mikor fog lefutni, vagy hogy le fog-e egyáltalán. A metódus célja az objektum által használt valamilyen erőforrás felszabadítása (erre később látunk példát).
Videók¶
- Osztályok létrehozása, tulajdonságok, példányosítás (EAIJ): https://youtu.be/72QdoUgEb8w
- Konstruktor fogalma, létrehozása (EAIJ): https://youtu.be/6CiBprNFWos
- toString metódus (EAIJ): https://youtu.be/SqwIQv7hZik
- Getterek és setterek (EAIJ): https://youtu.be/X3yhn2BNuvI
- Statikus adattagok és konstansok, statikus metódusok (EAIJ): https://youtu.be/zxkdWdle_XE
- CamelCase (EAIJ): https://youtu.be/9rZ9YS3E2ls
- Feladat 1. - Háromszög osztály létrehozása: https://youtu.be/UWTwRBgeyVE
- Feladat 2. - Küldetés osztály: https://youtu.be/94jvIxtUFWQ
- Feladat 3. - Email osztály létrehozása: https://youtu.be/NWXr2YypLi0
Feladatok¶
Téglalap¶
Írj egy osztályt, amely téglalapot reprezentál, annak oldalhosszait tárolja.
Készíts neki konstruktort, amely az oldalakat inicializálja. Írj az osztálynak még egy konstruktort, amely csak egy paramétert vár és amellyel négyzetet lehet létrehozni.
Készíts metódusokat a kerület és terület kiszámítására!
Írj egy másik osztályt, amely futtatható (van benne main függvény), és a parancssori paramétereknek megfelelően létrehoz téglalap objektumokat a Téglalap osztályból, és kiszámolja a Téglalapok területének és kerületének átlagát.
Példa a main függvényre: számhármasok, az első szám jelöli, hogy 1 vagy 2 paraméterből inicializálódik a téglalap, azaz négyzetet vagy téglalapot szeretnénk létrehozni, majd az ezt követő 1 vagy 2 szám tartalmazza a téglalap oldalhosszait: java TeglalapMain 1 5 2 10 22 2 9 8 1 100
.
Ennek jelentése: Először létrehozunk egy négyzetet, 5-ös oldalhosszal, majd téglalapot 10, 22 oldalhosszakkal, majd megint téglalapot 9 és 8 oldalhosszakkal, majd egy négyzet, melynek 100 az oldalhossza.
Kocsmaszimulátor part 1:¶
Bővítsük ki a már létező Ember osztályt egy privát pénz, és részegség int, és egy kocsmában boolean változókkal. Legyen egy új konstruktor, ez fogadjon a már meglévő paramétereken kívül egy pénz paramétert is, amit állítson be az Ember pénzének. A részegség 0, a kocsmában false legyen alapértelmezetten. Legyen az Embernek egy iszik(Kocsmáros kocsmaros)
metódusa, ami egy Kocsmárost vár majd. Ha ezt meghívják, akkor ha az illető a kocsmában van, fogyjon 1 a pénzéből, nőjön 1-gyel a részegsége, generáljon 1 koszos poharat, és adjon 1 pénzt a kocsmárosnak, akit paraméterül kapott. Majd látjuk, hogy a poharat hova kell eltárolni, és mi a Kocsmáros. Ha nincs a kocsmában, akkor írjon ki egy üzenetet erről. Legyen egy alszik()
metódusa is, ami nullázza a részegséget és kiírja, hogy elaludt, egy hazamegy()
metódusa, ami false-ra állítja a kocsmában változót, és egy jön()
metódusa, ami true-ra. Ezekről is történjen kiírás.
Legyen egy Kocsmáros
osztály is. Neki is legyen privát pénze, amit konstruktorban is meg lehet adni. Az összes kocsmáros ugyanazokon a koszos poharakon osztozzon (static), és legyen egy elmos()
metódusa, ami csökkenti eggyel a koszos poharak számát, és kiírja, hogy elmosott egy poharat. Ha nincs koszos pohár, akkor azt írja ki.
Legyen egy Ital
osztály is, aminek a következő privát tulajdonságai lesznek: ár, alkoholtartalom.
Az Embernek legyen egy olyan iszik metódusa is, aminek fejléce iszik(Kocsmáros kocsmáros, Ital ital)
, azaz italt is tud fogadni. Ekkor az ital árát adja át az Ember a Kocsmárosnak 1 helyett. Az Ember részegsége az ital alkoholtartalmával nőjön.
Ha a részegség eléri a 40-et, akkor az Ember mindkét iszik()
függvényénél automatikusan aludjon el.
Az összes osztály privát változóihoz legyenek getter, setter metódusok, és az osztályokhoz értelmes toString metódus.
Legyen egy main függvény, mondjuk Main nevű osztályban, itt írjatok egy rövidke futtatást, amiben eljátszogattok egy kicsit az emberekkel, bemutattok pár esetet, igyanak, aludjanak, stb...
Kapcsolódó linkek¶
Tömbök, Class Arrays, Tömbök tutorial
Kódolási stílus¶
Oracle Code Conventions for the Java Programming Language
Java Programming Style Guidelines