12. hét Osztály és csomag
Tartalom:
12.1. Osztály létrehozása
12.2. Adattagok
12.3. Inicializáló blokkok
12.4. Konstruktorok, példányosítás
12.5. Metódusok
12.6.
Csomag
12.7.
Tesztkérdések
12.8. Feladatok
A Java nyelven való programozás az adattagokkal és metódusokkal rendelkező objektumok programozását jelenti. Nem beszélhetünk klasszikus értelemben vett eljárásokról és függvényekről, csak objektumok együttműködéséről, amely a közöttük lévő kommunikációval valósul meg.
Objektumot egy osztály példányosításával hozhatunk létre. Ha nem áll rendelkezésünkre megfelelő osztály, akkor nekünk kell definiálni egyet. Azaz először a "tervrajzot" kell elkészíteni, majd az alapján lehet objektumokat létrehozni.
Az osztálydefiníció általános szintaxisa a következő:
|
Osztály fej:
Osztály törzs: |
Az osztály fejrészének módosítói nemcsak hozzáférési
szinteket takarhatnak, hanem opcionálisan egyéb attribútumokat is:
|
Osztály-fejrész módosítók: [hozzáférési szint]: public | üres (csomagszintű)
[abstract]: nem példányosítható osztály, amely öröklődés
kiindulópontjaként használható; [final]: végleges osztály, metódusai nem módosíthatók, az öröklődés okafogyott |
Az
extends
(leszármazott osztály létrehozása) és az
implements
(interfészek osztályhoz fűzése) kulcsszavak a
későbbi fejezetek témáját képezik.
Egy objektum tulajdonságait (aktuális állapotát) az adattagjainak értékei reprezentálják. Egy adattagnak mindig van azonosítója (neve), valamint típusa, amely lehet primitív típus (pl. egész szám, string, logikai), vagy akár osztálytípus is. Az adattagok a típusuknak megfelelő értékek tárolására szolgálnak, de értéküket az objektum életciklusa során megváltoztathatják (kivéve a konstansokat).
Az adattagok lehetnek példányszintűek, azaz minden objektumpéldányban egyedileg létezők, valamint osztályszintűek, amelyek egy osztályon belül csak egyszer léteznek, és magához az osztályhoz kapcsolódnak. Utóbbi az adattag-definíciót bevezető static kulcsszóval deklarálható.
Egy példányszintű adattag mindig egy objektumhoz tartozik. Ha a deklarációs részben nem adunk kezdőértéket egy adattagnak, a Java automatikusan feltölti a típusának megfelelő "nullaszerű" kezdőértékkel: pl. boolean - false, számok - 0, char - nullás kódú karakter, osztály típusú adattagok - null referencia.
Az adattag neve lehet minden megengedett azonosító, amelyet szokás szerint kisbetűvel kezdünk. Két adattagnak nem lehet azonos neve egy osztályon belül.
|
Adattagok deklarációja: [módosítók] <típus> <adattag_neve> [=<kezdőérték>][, ...]; |
Az adattagok deklarációja az osztálytörzsben, bármilyen
konstruktor- ill. metódusdeklaráción kívül történik. A módosítók itt sem
kizárólag hozzáférési szinteket takarhatnak, hanem más eszközöket is:
|
Adattag-deklarációs módosítók: [hozzáférési szint]: public | protected | üres (csomagszintű) | private [static]: osztályváltozó deklarálása [final]: az adattag értéke végleges, konstans |
Az alábbi példában definiálunk egy kezdőérték nélküli
adattagokkal rendelkező Bankbetét
nevű osztályt:

Lássuk ugyanezt az osztályt két kezdőértékadással rendelkező
adattaggal definiálva:

A tömörebb kódok elérése miatt megengedett, hogy egy
adattag-deklaráción belül több azonos típusú adattagot is megadjunk, megjelölve
a típust, majd az egymástól vesszővel elválasztott neveket felsorolva:

A példaprogram forráskódja: Bankbetet.rar.
Az osztály adattagjainak kezdőértékét osztály- ill. példány-inicializáló blokkban is beállíthatjuk. Az inicializáló blokkok speciális, fej nélküli metódusok, melyeket csak a blokk-képző kapcsos zárójelpárok határolnak, és return utasítást sem tartalmaznak. Az osztály-inicializáló blokkot a blokk előtti static kulcsszóval kell jelölni. Fontos tudni, hogy az inicializáló blokk csak egyszer fut le: típusától függően az osztály létrehozásakor, ill. a példányosítások alkalmával egyszer-egyszer példányonként.
12.4. Konstruktorok, példányosítás
Egy osztály példányosítása mindig valamelyik konstruktorának meghívásával történik. A konstruktor egy konkrét objektum életciklusa alatt pontosan egyszer fut le, létrehozva ezzel az objektumot, s beállítva az adattagjainak kezdeti értékét. A konstruktor lefutása után az objektum kész az üzenetek fogadására, feladatának elvégzésére.
A konstruktor neve kötött (mindig megegyezik az őt hordozó osztály nevével), így egy osztálynak csak akkor lehet több konstruktora (konstruktor túlterhelés), ha azok eltérő formális paraméterlistával rendelkeznek. A konstruktor egy speciális, típus nélküli metódusként fogható fel, amelynek nincs visszatérési értéke. Más megfogalmazásban a konstruktor egy - az osztály típusával visszatérő - névtelen metódus.
Egy osztályhoz mindig tartoznia kell legalább egy konstruktornak, hiszen nélküle az osztály nem példányosítható, vagyis nem hozhatók létre az osztály típusának megfelelő objektumpéldányok. Ha nem adunk meg konstruktort, akkor a Java fordító mindig létrehoz egy alapértelmezett, paraméter nélküli konstruktort. Ennek meghívása olyan példányt hoz létre, amelynek adattagjai "nullaszerű" értékekkel rendelkeznek: a numerikus típusok nulla, a karakteres típusok "null", a logikai típusok false értéket vesznek fel. Ha azonban készítünk valamilyen konstruktort, akkor a paraméter nélküli konstruktor "elvész". Így ha mégis szükségünk van rá, definiálnunk kell!
Csak olyan paraméterlistát adhatunk át egy konstruktornak, amelynek megfelelő konstruktort készítettünk, egyébként a fordító hibát jelez.
|
Konstruktor deklarációja: [módosítók]
<osztály_neve>([<típus> <adattag_neve>[,
...]]) { |
A metódusokhoz hasonlóan ha egy konstruktorban olyan változó-
vagy paraméternevet alkalmazunk, amely egyben az osztály egy adattagjának is az
azonosítója, akkor a this
kulcsszóval minősítve hivatkozhatunk az adattagra, míg minősítés nélkül a
változóra ill. a paraméterre. Figyelem! A
this
minősítő ezen alkalmazása nem tévesztendő össze a
this()
alakú konstruktorhívással!
Nézzük az alábbi konstruktorfajtákat!

Az első konstruktor a paraméterek alapján hozza létre az
objektumpéldányt, a második esetben nincs paraméterátadás, mégis minden adattag
kezdőértéket kap. Az ilyen üres paraméterlistájú konstruktorban a
this
minősítő felesleges az adattagok elé, mivel ilyenkor nincsenek őket eltakaró
lokális nevek vagy paraméterek. A harmadik esetben csak egy paraméterrel hívunk meg egy újabb
konstruktorfajtát, a többi paramétert maga a konstruktor állítja be a
this([<aktuális paraméterlista>])
- osztályon belüli konstruktorhívást kezdeményező - kulcsszó használatával.
Egy konstruktor meghívása a new kulcsszóval történhet:
|
Példányosítás: vagy <osztály_neve> <objektumpéldány_neve> = new <osztály_neve>([aktuális paraméterlista]); |
A háromféle konstruktor
meghívásának módja:

Lássuk a konstruktorhívások eredményeképpen létrejövő
objektumok adattagjainak tartalmát is (az osztály
toString()
metódusának megfelelő módosítása után):

Egy konstruktorból az osztály egy másik konstruktorát, ill. a
közvetlen ősosztály egy konstruktorát a
this()
ill. a super()
kulcsszó alkalmazásával érhetjük el.
Az osztályokhoz tartozó objektumok viselkedését (vagyis az objektumokhoz küldhető üzeneteket) az osztály definíciójában felsorolt metódusok határozzák meg. A metódus egy olyan programrutin, amely egy objektum egy konkrét feladatának algoritmusát valósítja meg.
A Java nyelvben a metódusoknak két nagy csoportját különböztethetjük meg a visszatérési értéküknek megfelelően. Az egyik a visszatérési értékkel nem rendelkező eljárások, míg a másik az ezzel rendelkező függvények csoportja. Függvények esetén meg kell adni a visszatérési érték típusát, míg a másik csoport esetén a void kulcsszó helyettesíti azt.
A metódusdefiníció általános szintaxisa a következő:
|
Metódus fej:
Metódus törzs: |
A metódus fejrészének módosítói nemcsak hozzáférési szinteket
takarhatnak, hanem opcionálisan egyéb attribútumokat is:
|
Metódus-fejrész módosítók: [hozzáférési szint]: public | protected | üres (csomagszintű) | private
[abstract]: öröklődés céljából készült, üres metódus: nincs
metódusblokk (kapcsos zárójelpár), [final]: végleges metódus, a leszármazott osztályokban a metódus nem módosítható [static]: osztálymetódus, csak statikus osztályszintű adattagra és metódusra hivatkozhat |
Ha a static
kulcsszó nem szerepel a fejrész módosítói között, akkor példánymetódusról van
szó, amely példánymetódus példányszintű adattagokra és más példánymetódusokra
(összefoglaló néven példánytagokra), valamint nyilvános (public)
osztálytagokra (azaz osztályszintű adattagokra és metódusokra) hivatkozhat.
A <típus> a metódus visszatérési értékének típusát határozza meg. Az eljárások visszatérési típusa void, a függvényeké pedig bármilyen primitív típus, vagy hivatkozási típus lehet.
A formális paraméterlistát egy típusokból és szintaktikailag lokális változónak minősülő adattagokból álló párosok alkotják. Egy metódushíváskor az aktuális- és a formális paraméterlisták elemeinek páronként meg kell egyezniük, azaz értékadás szerint kompatibilisnek és páronként megfelelő sorrendűeknek kell lenniük. A Java-ban csak érték szerinti paraméterátadás létezik.
A throws (kivételek továbbküldése) kulcsszó egy későbbi fejezet témája lesz.
Egy metódust a neve és a paraméterlistája azonosít egyértelműen, tehát egy osztályon belül megadható két azonos nevű metódus, ha azok paraméterlistája (szignatúrája) vagy a listában szereplő paraméterek típusaiban, vagy azok sorrendjében, esetleg mindkettőben eltérnek. Hivatkozáskor a futtató környezet a megadott paraméterek típusából és sorrendjéből el tudja dönteni, hogy a két azonos nevű metódus körül melyiket kell végrehajtania. Az így létrehozott szerkezetet a polimorfizmus egyik formájának tekinthetjük (metódusnevek túlterhelése - overload).
Ha egy metódusban olyan változó-, vagy paraméternevet alkalmazunk, amely egyben az osztály egy adattagjának is az azonosítója, akkor a this kulcsszóval minősítve hivatkozhatunk az adattagra, míg minősítés nélkül a változóra ill. a paraméterre.
A metódusok visszatérési értékének típusa a metódus deklarációjakor adható meg. A metóduson belül a return - feltétel nélküli vezérlésátadó - utasítással lehet a visszaadott értéket előállítani. A void-ként deklarált metódusok nem adnak vissza értéket, ezért return utasítást sem szükséges tartalmazniuk. Viszont minden olyan metódusnak, amely nem void-ként lett deklarálva, kötelezően tartalmaznia kell a return utasítást. Sőt, a Java fordító azt is kikényszeríti, hogy return utasítással végződjön minden lehetséges végrehajtási ág. A visszaadott érték adattípusának meg kell egyeznie a metódus deklarált visszatérési értékével. Ezért nem lehetséges pl. egész számot visszaadni olyan metódusból, amelynek visszatérési értéke logikai típusú.
Az alábbi két metódus egyike eljárás, amely eldönti, hogy pozitív összeg-e a bankbetét, a másik pedig egy függvény (a paraméterként kapott egész számmal megnöveli az adó mértékét):

A két metódus meghívása és futásuk eredménye:


Speciális metódus a main
metódus, amelyet célszerű az osztályon belül utolsóként deklarálni. Egy
alkalmazás végrehajtása során a Java futtatórendszere először mindig meghívja az
alkalmazás valamelyik osztályának main
metódusát, és ez a metódus fogja az összes többit meghívni.
|
public static void main(String[]
args) { |
A public
módosító biztosítja, hogy a metódus a programon belül bárhonnan elérhető legyen,
a static
jelzi, hogy osztálymetódusról van szó, és a
void
kulcsszóból láthatjuk, hogy a metódusnak nincs visszatérési értéke. A
main
metódusnak egy string-tömb típusú paramétere van, amely a parancssori
paramétereket tartalmazza.
A csomag logikailag összefüggő osztály- és interfészdefiníciók gyűjteménye. Hierarchikus felépítésével rendszerezhetjük típusainknak, külön névtereket létrehozva számukra. A csomag elnevezése a package <csomagnév> utasítás segítségével történhet, amelyet a fordítási egység legelső sora kell, hogy legyen.
Ha olyan osztályokat is szeretnénk sokszor használni, amelyek más csomagokban vannak, akkor azokat (az állandó csomagnév hivatkozást elkerülendő) importálni érdemes. Formái:
|
teljes csomag: import <csomagnév>.*; csomagon belüli csomag: import <csomagnév>.<belső_csomagnév>.*; csomag egy osztálya: import <csomagnév>.<Osztálynév>; |
Az importáló utasítások
kötelezően a csomagdeklarációs sor után következnek. Egy utasításban csak egy
osztály (vagy a .*-gal egy teljes csomag) importálható. A Java fordító minden
csomaghoz automatikusan hozzáfordítja az alapvető osztályokat (pl.
System,
String) tartalmazó
java.lang csomagot, így ezt nem szükséges külön
importálni. Az importálás művelete nem rekurzív, tehát egy teljes csomag
importálásakor nem lesznek elérhetők az alcsomagok osztályainak definíciói.
12.7. Tesztkérdések
12.8.1. Bővítsük tovább az előző fejezet Dolgozó osztályát Dolgozó2 néven! Hozzunk létre egy - csak ebből az osztályból látható - osztályváltozót, amely a dolgozók számát tárolja! Gondoskodjunk arról is, hogy példány létrejöttekor automatikusan növekedjen az értéke! Ellenőrizzük, hogy eddig hány dolgozó van!
12.8.2. Készítsünk - más osztályból nem látható - metódusokat a fizetés emeléséhez! Az alábbi típusú fizetésemelések lehetségesek:
1. típus: rendkívüli - fix 10.000 Ft-os emelés
2. típus: százalékos - emelés a meglévő fizetés százalékában
3. típus: jogszabály szerinti - emelés a minimálbér 10%-ával
4. típus: általános - adott összegű fizetésemelés
A minimálbér összegét definiáljuk végleges osztályváltozóként, és ennek megfelelően módosítsuk a "minimálbéres" konstruktort! A dolgozókat (d1, ... d4) rendre rendkívüli, 20%-os, jogszabály szerinti, ill. 5.000 Ft-os általános fizetésemelésben részesítsük, majd jelenítsük meg képernyőn a nevüket és az új fizetésüket! Mennyi lett az új átlagfizetés?
12.8.3. Hozzuk létre az osztály paraméter nélküli konstruktorát, amely segítségével példányosítsunk egy újabb dolgozót! Adattagjai milyen értékeket vettek fel? Írjuk felül őket a d4-es dolgozó adataival, kivéve a nevet és a születési évet, amely Molnár Attila (szül. 1985) legyen! Írjunk egy példánymetódust a nettó fizetés kiszámítására! A levonások összesen 45%-ot tesznek ki. Mennyi a nettó bére az új dolgozónak?
12.8.4. Írjuk meg a példányváltozók lekérdező és beállító metódusait! Hozzunk létre osztály- és példány-inicializáló blokkokat, amelyekben jelezzük, hogy a program futása közben milyen inicializálás történik! Készítsünk egy osztálymetódust, amellyel a minimálárat adott százalékkal megnövelhetjük!