8. fejezet

Újabb megvilágosodás: statikus változók és metódusok. Egyedváltozók és osztályváltozók kavalkádja. Öröklõdés és konverziók.

Mint említettem, egy normális, mezei osztálynak egyedváltozói vannak, amelyek minden egyedben külön létrejönnek és külön életet élnek az egyes egyedekben. Miután létrejöttek, semmi közük egymáshoz. Ugyanúgy, a mezei osztály metódusai az egyedváltozókon manipulálnak, így amikor meghívunk egy metódust pl.

j1.eredmenyKiiras();

akkor implicit módon odaadtuk a j1 által hivatkozott objektumegyedet is az eredménykiíras metódusnak, ami aztán buzgón felhasználja ezen objektumegyed változókészletét. Ez eddig rendben is van. Lehetnek azonban esetek, amikor nem akarjuk, hogy a változó minden egyedben létrejöjjön, hanem csak egyet akarnánk, közöset minden objektumegyednek, ami az adott osztályból példányosítódott. Ha netán ilyenben törnénk a fejünket, a statikus változók pont nekünk valók. A statikus változó pont olyan, mint bármely változó, csak a típusdeklarációja elé még oda van írva az hogy static. Így:

static int szamlalo;

Az ilyen változó nem jön létre újra meg újra, valahányszor az osztályt példányosítjuk, csak egyszer, amikor a Jáva virtuális gép elõször tölti be az osztálydefiníciót. A konstruktorral összeházasítva például felhasználhatjuk ezt a fajta változót arra, hogy megszámolja, hányszor példányosították az adott osztályt. A következõ példában ezt tesszük.

class Szamlalo {
  static int peldanyok = 0;

  Szamlalo() {
    ++peldanyok;
  }
}

Ezek után valahányszor azt mondjuk: new Szamlalo(), a peldanyok valtozo megnõ eggyel.

Statikus változóra kétféleképpen hivatkozhatunk: vagy egy referencián keresztül, mint más becsületes változóra vagy egyszerûen az osztály nevével pl. így:

int k = Szamlalo.peldanyok;

Ez utóbbi azért lehetséges, mert a peldanyok változó nem az objektumegyedeknek, hanem az osztálynak lefoglalt memóriaterületen tárolódik, eléréséhez nincs szükség objektumreferenciára. A statikus változóknak ezt a tulajdonságát elõszeretettel használják fel arra, hogy konstansokat deklaráljanak vele. Ha pl. a Szamlalo osztályban azt mondom:

static int Milennium = 2000;

azt késõbb bárhonnan kényelmesen elérhetem Szamlalo.Milennium formában anélkül, hogy mindenféle referenciákra kellene vadászni. A konvenció szerint (amit a Jáva nem tesz kötelezõvé, csak követni ajánlják) a konstansok nevét csupa nagybetûvel kell írni, hogy megkülönböztessük a változó változóktól. Így e:

static int MILENNIUM = 2000;

Feladat

Vedd elõ nagy sikerû kõ-papír-olló programunkat és valósíts meg benne két szolgáltatást a statikus változók segítségével! Elõször is számolja meg és a futás végén írja ki, hány döntetlen eredmény született összesen. A Jatekos osztályban hozzál létre egy statikus változót és növelgesd, valahányszor döntetlen fordult elõ. Másodszor is a kõ, papír, olló tárgyakhoz rendelt számokat helyettesítsd konstansokkal. Íme az én változatom!

Metódusok is lehetnek statikusok. Ha egy metódusnév elõtt áll a static kulcsszó, ennek három következménye lesz.

  1. A metódus csak statikus változókat érhet el. Ne feledd: a statikus metódus hívásakor nem ismeri az objektumegyed kontextusát, így aztán az egyedváltozókat nem tudja elérni.
  2. A metódus hívható osztálynév.metódusnév formában. Példa: Math.random() (ugye ismerõs?)
  3. A metódus nem hívhat nem statikus metódusokat. Ez ugyanazon ok miatt van, amiért egyedváltozókat sem érhet el.

Statikus metódusoknak a legfõbb haszna az, hogy nem kell érte példányosítani az osztályt, meghívható egyszerûen osztálynév alapján. Ezzel kitörhetünk a Jáva erõszakos objektumszemléletébõl és mindenhonnan elérhetõ függvényeket írhatunk.

Feladat

Fogd az Elso.java programot és érd el, hogy ne kelljen az Elso-t példányosítani! Töröld ki tehát a következõ sort

Elso e = new Elso();

és tedd meg a szükséges változtatásokat, utána ellenõrizd a megoldást!

Most már talán megértheted, miért hagytuk ilyen sokáig burjánzani a misztikumot a main-ban található két sor körül. Minthogy a main statikus (így szól a Jáva specifikáció), mert a Jáva virtuális gép nem példányosítja a java parancs által hivatkozott osztályt, így nem érhet el nem statikus változót vagy metódust. A sok static megzavart volna és nem is tudtuk volna ilyen könnyedén bevezetni az egyedváltozókat. Mostantól azonban tudjuk, mit csinálunk, így a programosztályt csak akkor példányosítjuk, ha kell.

És most valami teljesen más! Megmutatom, hogyan lehet minimális munkával okos objektumokat készíteni. Eddig azt tanultuk, hogy az osztályok változóit és metódusait nekünk kell deklarálni. Ha azonban olyan lusta vagy, mint amilyen én, inkább veszünk valami készet és átalakítjuk csupán azt módosítva benne, amiben a kész nem tesz eleget igényeinknek. A módszert, ami objektumorientált rendszerekben erre a célra rendelkezésre áll, öröklõdésnek hívjuk.

Öröklõdéssel azt jelenthetjük ki, hogy egy osztályt nem a nulláról hozunk létre, hanem átveszünk egy létezõ osztályból mindent, majd bõvítjük illetve fölüldefiniáljuk az új funkciókkal. Vegyük a következõ példát:

class Eredeti {
  int i;

  void novel() {
    ++i;
  }
}

class Leszarmazott extends Eredeti {
  void novel() {
    i = i + 2;
  }

  void csokkent() {
    i = i -2;
  }
}

A kicsit buta példánkban (miért kell ahhoz leszármazás, hogy megspóroljunk egy int i;-t?) a leszármazott osztályban felülírtuk a novel() metódust, hogy ezentúl kettõvel növelgesse i-t és hozzáadtunk egy csokkent metódust, ami csökkentgetni is tud. A Leszarmazott osztályt pont úgy kell használni, mint az Eredeti-t.

Még egy megjegyzés: a konstruktorok nem öröklõdnek. Ha a leszármazott osztálynak is szükségük van spéci (tehát paramétert fogadó) konstruktorokra, azt újra kell definiálni a leszármazott osztályban is. Ha nincs szükség változtatásra, használhatod a super() hívást, amivel a szülõosztály konstruktora felé passzolhatod a paramétereket. Vegyünk egy példát, ahol egy egész paramétert passzolunk felfelé:

class Leszarmazott extends Eredeti {
  Leszarmazott( int i ) {
    super( i );
  }

  ...
}

Az öröklõdés kiválóan alkalmas arra, hogy bevezessek egy régen esedékes fogalmat, a típuskonverziót. Típuskonverzióval hasonló típusú értékeket alakíthatunk egymásba. Kézenfekvõ példa a számok: miért kell fallal elválasztani egymástól az int-et és double-t? Nem kell és íme egy kis példa, hogyan lehet típuskonverzióval egymásba alakítani õket.

double n = 2.7;
int ni = (int)n;
n = (double)ni;

Az ni változóba átkonvertáltuk n-t. Csoda azonban most se történt, ni-be 2 került, a törtrész levágódott. A következõ lépésben az n-be visszakonvertáltuk az ni értéket, minek eredményeképpen n-ben most 2.0 van.

Ez eddig nagyon egyszerû, de mik lehetnek hasonló típusok még? A Jáva nagyon szigorú típuskonverzió terén és a számok mellett csak a leszármazott osztályok referenciáit engedi egymásba átkonvertálni. Például az elõbbi példánknál maradva ha netán azt mondanánk

Leszarmazott l = new Leszarmazott();
Eredeti e = (Eredeti)e;

Ez sikerülne, mert az Eredeti és Leszarmazott "hasonló" típusok. Azért megengedett a mûvelet, mert az Eredeti legalább azt tudja, amit a Leszarmazott, mert belõle jött létre módosítással. Ha ugyanezt visszafelé csinálnánk, kicsit bonyolultabb a helyzet.

Leszarmazott l2 = (Leszarmazott)e;

Ezt a fordító elfogadja, de azért még nem biztos, hogy helyes. Ha a három utasítás úgy követte egymást, ahogy az elõbb leírtuk, akkor helyes, hiszen e-ben valójában egy Leszarmazott referenciája van. Ha azonban netán ez lenne a sorrend:

Eredeti e = new Eredeti();
Leszarmazott l2 = (Leszarmazott)e;

ezt a fordító ugyan nem tekinti hibának (rokon osztályok között a konverzió megengedett), de a Jáva virtuális gép a futás során észreveszi a turpisságot és hibaüzenettel leáll.

Most már csak az a kérdés, ha vesszük az elõzõ példát

Leszarmazott l = new Leszarmazott();
Eredeti e = (Eredeti)e;

és meghívjuk a novel metódust

e.novel();

vajon eggyel vagy kettõvel nõ-e az i változó? A Jáva dinamikus típusfeloldással rendelkezik, tehát mindig annak az osztálynak a metódusai hívódnak meg, ami az osztály valójában, nem pedig a referencia típusa szerint, i tehát kettõvel nõ. Referenciáinkat tehát nyugodtan konvertálhatjuk a szülõosztály felé, ha elhasználjuk a referenciát, az a megfelelõ eredménnyel fog járni.

Feladat

Ragadd meg barátunkat, a kõ-papír-olló programot és csináld meg azt, hogy egy játékos azzal a "szisztémával" játszik, hogy mindig pl. kõre fogad. Csinálj egy leszármazottat a Jatekos osztályból és írd felül a fogadas() metódust megfelelõképpen! Ezek után az egyik játékost a leszármazott osztály szerint hozdd létre és használj típuskonverziót a típushibák elkerülésére! Itt a megoldás ellenõrzésképpen. Ki a buta játékos?

Ellenõrzõ kérdések

  1. Mi a statikus változó?
  2. Elérhetõ-e a statikus változó nem statikus metódusból?
  3. Milyen referenciával lehet elérni a statikus változót?
  4. Mi a statikus metódus?
  5. Elérhet-e normál egyedváltozót statikus metódus?
  6. Meghívhat-e statikus metódust normál, nem statikus metódus?
  7. Meghívhat-e normál metódust statikus metódus?
  8. Mit jelent az, hogy egyik osztály leszármazottja a másiknak?
  9. Lehet-e egy osztályreferenciát a szülõosztály felé konvertálni?
  10. Lehet-e egy osztályreferenciát a leszármazott osztály felé konvertálni?
  11. Lehet-e Jávában különbözõ típusú értékek között értékadás?
  12. Ha létrehozunk egy egyedet és egy szülõosztály típusa szerinti referenciával hivatkozunk rá, a szülõosztály vagy a leszármazott osztály szerinti metódus hívódik-e meg?

Kovetkezo lecke Tartalomjegyzek