Gyakorlati alapok III.

Öröklődés (inheritance)

 

Az objektumnév (referencia) statikus és dinamikus működése


Bevezetés

A dinamikus összekapcsolás szabályai

Típuskényszerítés

 

Bevezetés

 

Az objektum létrehozásának pillanatában eldől annak típusa és ezen tulajdonsága élete végéig változatlan marad. Ez a típus nyilvánvalóan az az ősosztály, amelyből az objektum keletkezett. Ezt nevezzük az objektumreferencia statikus típusának. Például: Negyzet negyzet = new Negyzet(), ahol a típus Negyzet, a keletkezett objektum pedig Negyzet típusú negyzet.

 

Maga az objektum felé mutató referencia azonban -amint ezt az Ezt sajnos most kell tisztáznunk: mutató és referencia című fejezetben tisztáztuk-, csak egy szigorú működési feltételek közé szorított mutató (legalábbis a Java nyelvben), amellyel voltaképpen rugalmasabban is bánhatunk. Összefoglaló néven ezt a lehetőséget nevezzük az objektumreferencia dinamikus típusának.

 

Ez eddig nem teljesen érthető, főként gyakorlati szempontból. Az ügy tisztázásához először nézzük meg az előző fejezetekben ismertetett származtatásos hierarchiánkat:

 

Object

Sikidom

Kor

Negyzet

Teglalap

Haromszog

 

Mindegyik osztálynak saját típusa van. Ezen szintén sejtelmes kijelentés mögött az húzódik meg, hogy mindegyik típus rendelkezik egy csakis rá jellemző tulajdonsághalmazzal. Ennek felfedezése viszonylag könnyű, ha tudjuk, hogy mi a különbség egy kör és egy négyzet között. Már most az a kérdés, hogy a objektumreferencia dinamikus felhasználásakor főként értékadás formájában összekapcsolhatjuk-e például a kor és a negyzet objektumot?

 

A kérdés lényegében érinti az öröklődés alapelveit. Az öröklődés alkalmazásakor egymáshoz alapelvben hasonló, de mégis kissé különböző, sőt tulajdonságaikban egyre kibővültebb osztályok jönnek létre. Másként egyébként nem sok értelme volna az öröklődés alkalmazásának.

 

A dinamikus összekapcsolás szabályai

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

1. szabály - Adott típusú referenciával mutathatunk különböző típusú objektumok felé. A szabály ekkor, hogy a referenciának vagy azonos típusúnak, vagy valamelyik ősosztály típusúnak kell lennie. Például az Örököltetés: síkidom - kör - négyzet - téglalap a super() segítségével című fejezetben foglaltak szerint egy Negyzet típusú objektummal mutathatunk Object, Sikidom, Kor, Negyzet objektumreferenciák felé, de Teglalap és Haromszog típusúak felé nem.

 

A futtatásos ellenőrzéshez először létre kéne hoznunk a kérdéses objektumokat, de emlékezzünk egy további fontos szabályra: abstract osztály nem példányosítható (Abstract osztály - abstract metódus című fejezet), már pedig a Sikidom osztály példánkban éppen ilyen, ezért az alábbi példányosítás csak elméleti:

 

Object object = new Object();

Sikidom sikidom = new Sikidom();

Kor kor = new Kor();

Negyzet negyzet = new Negyzet();

Teglalap teglalap = new Teglalap();

Haromszog haromszog = new Haromszog();

 

Az 1. szabály szerint (elméletileg) a következő értékadások működőképesek:

 

object = negyzet;

sikidom = negyzet;

kor = negyzet;

negyzet = negyzet;

 

Az 1. szabály szerint a következő értékadások NEM működőképesek:

 

teglalap = negyzet;

haromszog = negyzet;

 

A hiba már fordításkor kiderülhet (Type mismatch: cannot convert from...) vagy ClassCastException nevű kivétellel szembesülünk, ami azt jelenti, hogy ütközés lép fel az osztálytípusok között.

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

2. szabály - Egy objektumra több, különböző típusú referenciával is hivatkozhatunk. A szabály ekkor, hogy a referencia mutathat azonos típus, vagy származtatott osztályú objektum felé. Például az Örököltetés: síkidom - kör - négyzet - téglalap a super() segítségével című fejezetben foglaltak szerint egy Negyzet típusú referenciára mutathatunk Teglalap és Haromszog referenciával, de nem Object, Sikidom, Kor, Negyzet referenciával.

 

A futtatásos ellenőrzéshez először létre kéne hoznunk a kérdéses objektumokat, de emlékezzünk egy további fontos szabályra: abstract osztály nem példányosítható (Abstract osztály - abstract metódus című fejezet), már pedig a Sikidom osztály példánkban éppen ilyen, ezért az alábbi példányosítás csak elméleti:

 

Object object = new Object();

Sikidom sikidom = new Sikidom();

Kor kor = new Kor();

Negyzet negyzet = new Negyzet();

Teglalap teglalap = new Teglalap();

Haromszog haromszog = Haromszog();

 

A 2. szabály szerint a következő értékadások működőképesek:

 

negyzet = teglalap;
negyzet = haromszog;

 

A 2. szabály szerint a következő értékadások NEM működőképesek:

 

negyzet = object;

negyzet = sikidom;

negyzet = kor;

 

A hiba már fordításkor kiderülhet (Type mismatch: cannot convert from...) vagy ClassCastException nevű kivétellel szembesülünk, ami azt jelenti, hogy ütközés lép fel az osztálytípusok között.

 

Ellenőrizzük le a fenti állításokat 1 futtatható Java-kódon:

 

www.informatika-programozas.hu - Szabályellenőrzés

 

A kódból természetesen ki lett hagyva az Object és Sikidom osztályok példányosítása, a többi azonban az ismertetett szabályok szerint működik.

 

Típuskényszerítés

 

A hibák ellenére megpróbálhatjuk a típusokat a típuskényszerítés eszközével (casting, classcasting) egymásra kényszeríteni. Ha ezt az ősosztály(ok) irányába tesszük, a típuskényszerítés felfelé történik (upcasting), ha a származtatott osztály(ok) irányába, a típuskényszerítés lefelé történik (downcasting).

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

Jobban belegondolva mit is teszünk például a negyzet objektummal, ha bármerre is típuskényszerítjük? Megpróbáljuk megváltoztatni tulajdonságait, (szándék szerint) vagy szűkítve vagy bővítve azt.

 

Upcasting

Ebben az esetben a negyzet objektumot visszaléptetjük egy ős szintre, ezáltal tehát szűkítjük, butítjuk tulajdonságait.

 

Downcasting

Ebben az esetben kísérletet teszünk a negyzet objektum tulajdonságainak bővítésére, hiszen a belőle származtatott osztály garantáltan több, de kissé másféle tulajdonságokkal van felruházva. Ez azonban nem fog sikerülni, hiszen ez a mozzanat éppen az örököltetés mechanizmusa és azt nem tudjuk megkerülni. A downcasting-műveletnek tehát önmagában véve nincs értelme, mert bár lehetséges, hogy programozás-technikailag kivitelezhető, ám úgysem leszünk képesek a származtatott osztály új tulajdonságait elérni, csakis a régieket.

 

A típuskényszerítés anomáliái egy jellemző példán keresztül könnyen megmutathatók. Az alábbi példában a szabályok szerint gond nélkül sikerül az upcasting, hiszen kor objektumban a negyzet objektum tulajdonságait használtuk fel, amely azonban garantáltan rossz végeredményeket fog szolgáltatni:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
public static void main(String[] args) {
    Negyzet negyzet = new Negyzet(10);
    System.out.println(negyzet.getKerulet());
    System.out.println(negyzet.getTerulet());

    Kor kor = new Negyzet(10);
    System.out.println(kor.getKerulet());
    System.out.println(kor.getTerulet());
    }
}

 

Végeredmény:
40.0
100.0
40.0
100.0

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

Következésképpen nagyon óvatosan bánjunk az osztályok közti típuskényszerítéssel!