Gyakorlati alapok III.
Öröklődés (inheritance)
Örököltetés: síkidom - kör - négyzet - téglalap a super() segítségével
A fejezet hatékony tanulmányozásához először mindenképpen olvassuk el a
gyakorlati fejezeteket, amelyekben konkrét származtatásos példakódok vannak:
Kiindulópontjaink az implementált Sikidom és Kor osztályok:
public
abstract class Sikidom {
private double kerulet;
private double terulet;
public double getKerulet() {
return this.kerulet;
}
public double getTerulet() {
return this.terulet;
}
public void setKerulet(double kerulet) {
if (kerulet <= 0) {
System.out.println("A kerületérték nem lehet 0 vagy negatív!");
}
else
this.kerulet
= kerulet;
}
public void setTerulet(double terulet) {
if (terulet <= 0) {
System.out.println("A területérték
nem lehet negatív!");
} else {
this.terulet = terulet;
}
}
}
public class
Kor extends Sikidom {
private double sugar;
public double getSugar() {
return sugar;
}
public void setSugar(double sugar) {
if(sugar < 0) {
System.out.println("A sugár értéke nem lehet negatív!");
} else
this.sugar = sugar;
}
public Kor(double sugar) {
this.sugar = sugar;
keruletSzamitas();
teruletSzamitas();
}
private void keruletSzamitas() {
setKerulet(this.sugar * 2 * Math.PI);
}
private void teruletSzamitas() {
setTerulet(Math.pow(this.sugar, 2) *
Math.PI);
}
public static void main(String[] args) {
Kor kor = new Kor(1);
System.out.println("Körkerület: " +
kor.getKerulet());
System.out.println("Körterület: " +
kor.getTerulet());
kor.setTerulet(-100);
kor.setSugar(-2);
}
}
Végeredmény:
Körkerület: 6.283185307179586
Körterület: 3.141592653589793
Az érték nem lehet negatív!
A sugár értéke nem lehet negatív!
Mivel később a Kor osztályból is származtatni akarunk, az érthetőség kedvéért írjuk át a körszámításhoz szükséges egyetlen hosszadatot sugar névről hosszA névre! (A változó refaktorálása nem lesz egyértelműen egyszerű, tanulmányozzuk át a Gyorsan tanuljuk meg: a refaktorálás című fejezetben leírtakat, illetve vegyük észre, hogy ezzel együtt a getter-setter függvényeket is át kell neveznünk!)
public class
Kor extends Sikidom {
private double hosszA;
public double getHosszA() {
return hosszA;
}
public void setHosszA(double hosszA) {
if(hosszA <= 0) {
System.out.println("Az érték nem lehet 0 vagy negatív!");
} else
this.hosszA =
hosszA;
}
public Kor(double hosszA) {
this.hosszA = hosszA;
keruletSzamitas();
teruletSzamitas();
}
private void keruletSzamitas() {
setKerulet(this.hosszA * 2 * Math.PI);
}
private void teruletSzamitas() {
setTerulet(Math.pow(this.hosszA, 2) *
Math.PI);
}
}
Még egyszer emlékeztetőül:
-
a Sikidom osztály még nem rendelkezett oldalhosszal, ez először a Kor osztályban jelent meg sugar néven, de utóbb átneveztük az érthetőbb hosszA névre, mert ezt tovább fogjuk örökíteni, illetve további oldalhosszakat is fogunk hozzácsatolni (hosszB és hosszC),
-
a keruletSzamitas és teruletSzamitas függvényeket minden származtatás után kötelezően felül kell írnunk (overriding), hiszen annak kiszámítása minden geometriai test esetében különböző,
mivel a cél egy örököltetési lánc kialakítása (most csakis demonstrációs célból), praktikus művelet a main() főmetódus elkülönítése az utódosztályoktól: egyszerűen betesszük egy különálló, Main nevű osztályba.
Először nézzük meg a futtatható Java-kódot, amely a Negyzet osztály Kor osztályból származtatását tartalmazza, majd alatta szétboncoljuk a történteket, főként a super funkcióját:
import
java.math.*;
public class Negyzet extends Kor {
public Negyzet(double hosszA) {
super(hosszA);
keruletSzamitas();
teruletSzamitas();
}
private void keruletSzamitas() {
setKerulet(this.getHosszA() * 4);
}
private void teruletSzamitas() {
setTerulet(Math.pow(this.getHosszA(),
2));
}
}
A konstruktor függvényben észrevehetjük a super módosítószót. Működése hasonló a this funkcionalitásához; az utóbbiról kellő részletességgel olvashattunk a This one, please! - a this referencia című fejezetben.
Mindketten virtuális mutatóujjak: az éppen aktuális, az éppen futó objektum felé mutatnak, ám az egyetlen, fontos különbség közöttük, hogy a super funkcionálisan lebutított, mert mindig az ősosztály(ok) felé mutat, így segítségével -voltaképpen az osztályok felett-, hivatkozni tudunk az ősosztály adataira, metódusaira. A super tehát egy virtuális híd, de csakis az ősosztály(ok) felé.
Pontosan erre van szükségünk a
fenti kódban. A Negyzet osztály konstruktor
függvényében (Negyzet(double hosszA)) láthatóan
deklarálunk egy double hosszA változót. Ám ez a
változó már szerepelt a Kor ősosztálynál is, sőt:
szándékunk szerint a 2 ugyanaz. Nincs értelme a változót újból deklarálni,
mert a super referenciával tudunk rá hivatkozni:
public Negyzet(double hosszA) {
super(hosszA);
keruletSzamitas();
teruletSzamitas();
}
A super mindig az ősosztály konstruktorát hívja meg, használata pedig azért szükségszerű, mert néha onnan van szükségünk adatokra-metódusokra, hiszen jócskán lehetnek ott olyan programelemek, amelyek az utódosztályban nem lettek felülírva, megváltoztatva.
További fontos megjegyzés: ha a
Negyzet utódosztályban szintén deklarálnánk egy
double hosszA változót, az elfedné, elrejtené,
leárnyékolná az ősosztály double hosszA
változóját. Funkcionálisan tehát szükségünk van a super
lehetőségére.
A fentiekből következően most már könnyedén származtathatunk egy Teglalap osztályt a Negyzet osztályból. A téglalap csupán 1 tulajdonságában különbözik a négyzettől: nem 1 azonos, hanem 2 különböző oldalhosszal rendelkezik:
public class
Teglalap extends Negyzet{
private double hosszB;
public double getHosszB() {
return hosszB;
}
public void setHosszB(double hosszB) {
if(hosszB <= 0) {
System.out.println("Az érték nem lehet 0 vagy negatív!");
} else
this.hosszB = hosszB;
}
public Teglalap(double hosszA, double hosszB) {
super(hosszA);
this.hosszB = hosszB;
keruletSzamitas();
teruletSzamitas();
}
private void keruletSzamitas() {
setKerulet((this.getHosszA() + this.getHosszA()) * 2);
}
private void teruletSzamitas() {
setTerulet(this.getHosszA() * this.getHosszB());
}
}
Jól lehet látni, hogy a Teglalap osztály konstruktorában miképpen történik az oldalhosszak menedzselése. HosszA beállításához az ősosztályhoz nyúlunk vissza, hosszB beállítása pedig lokálisan történik:
public
Teglalap(double hosszA, double hosszB) {
super(hosszA);
this.hosszB = hosszB;
keruletSzamitas();
teruletSzamitas();
}
A kerület-, és területszámító metódusok természetesen felülírásra kerültek.
Ezután már szinte gyerekjáték
utolsó származtatott osztályunk megalkotása, ez a
Haromszog osztály lesz. Egyetlen különbsége a
Teglalap osztályhoz képest, hogy nem 2, hanem 3 oldalhosszal
rendelkezik:
public class Haromszog extends Teglalap{
private double hosszC;
public double getHosszC() {
return hosszC;
}
public void setHosszC(double hosszC) {
if(hosszC <= 0) {
System.out.println("Az érték nem lehet 0 vagy negatív!");
} else
this.hosszC = hosszC;
}
public Haromszog(double hosszA, double hosszB, double hosszC) {
super(hosszA, hosszB);
this.hosszC = hosszC;
keruletSzamitas();
teruletSzamitas();
}
private void keruletSzamitas() {
setKerulet(this.getHosszA() + this.getHosszB() +
this.getHosszC());
}
private void teruletSzamitas() {
double s = (this.getHosszA() +
this.getHosszB() + this.getHosszC()) / 2;
setTerulet(Math.sqrt(s * (s - this.getHosszA()) * (s -
this.getHosszB()) * (s - this.getHosszC())));
}
}
Konstruktorában a super nyilvánvalóan a
Teglalap osztály 2 oldalhosszú őskonstruktorát
hívja meg (super(hosszA, hosszB)):
public Haromszog(double hosszA, double hosszB, double
hosszC) {
super(hosszA, hosszB);
this.hosszC = hosszC;
keruletSzamitas();
teruletSzamitas();
}
További megjegyzés, hogy a háromszög területszámítása a híres Héron-képlettel történik (Különutas háromszögezés Héron-képlettel, ellenőrzéseskedéssel című fejezet).
A Sikidom ősosztályt is beleszámolva példánkban 5 származtatásos osztályunk keletkezett (+1 Main osztály). A Sikidom ősosztályt és az utolsó, 5. szintű Haromszog osztályt összehasonlítva túl sok és nagy különbségeket nem vehetünk észre, bár ennek oka pusztán oktatási jellegű: az egyedüli cél a super működésének nyomon követése volt és ennek eme komplex példakód tökéletesen megfelelt. Ám amint az már említésre került Az öröklődés lehetőségei a Javán belül, valamint tartalmi elemei című fejezetben, konkrét Java-osztályok esetében nem ajánlott 5 szintnél mélyebben tovább származtatni, mert követhetetlenné fog válni a kód.
Végezetül nézzük meg a main() főprogram kódblokkját is:
public class
Main {
public static void main(String[] args) {
System.out.println("Kör-szekció");
Kor kor = new Kor(10);
System.out.println("Körkerület: " + kor.getKerulet());
System.out.println("Körterület: " + kor.getTerulet());
kor.setTerulet(-100);
kor.setHosszA(-2);
System.out.println();
System.out.println("Négyzet-szekció");
Negyzet negyzet = new Negyzet(10);
System.out.println("Négyzetkerület: " + negyzet.getKerulet());
System.out.println("Négyzetterület: " + negyzet.getTerulet());
negyzet.setTerulet(-100);
negyzet.setHosszA(-2);
System.out.println();
System.out.println("Téglalap-szekció");
Teglalap teglalap = new Teglalap(2, 4);
System.out.println("Téglalapkerület: " + teglalap.getKerulet());
System.out.println("Tégalalapterület: " + teglalap.getTerulet());
teglalap.setTerulet(-100);
teglalap.setHosszA(-2);
System.out.println();
System.out.println("Háromszög-szekció");
Haromszog haromszog = new Haromszog(2, 2, 2);
System.out.println("Háromszögkerület: " +
haromszog.getKerulet());
System.out.println("Háromszögterület: " +
haromszog.getTerulet());
haromszog.setTerulet(-100);
haromszog.setHosszC(-2);
}
}
Végeredmény:
Kör-szekció
Körkerület: 62.83185307179586
Körterület: 314.1592653589793
A területérték nem lehet negatív!
Az érték nem lehet 0 vagy negatív!
Négyzet-szekció
Négyzetkerület: 40.0
Négyzetterület: 100.0
A területérték nem lehet negatív!
Az érték nem lehet 0 vagy negatív!
Téglalap-szekció
Téglalapkerület: 8.0
Tégalalapterület: 8.0
A területérték nem lehet negatív!
Az érték nem lehet 0 vagy negatív!
Háromszög-szekció
Háromszögkerület: 6.0
Háromszögterület: 1.7320508075688772
A területérték nem lehet negatív!
Az érték nem lehet 0 vagy negatív!
Láthatóan mindegyik származtatott osztályt sikeresen példányosítottuk és a kerület-, valamint a területszámító metódusokat sikeresen felülírva, illetve a super lehetőségét kihasználva a különböző objektumok kimeneti adatai korrektek.
Ugyanakkor mindegyik szekció végén már kissé szájbarágós gyakorisággal meghívunk 2 setter-függvényt (setTerulet és setHossz) annak bizonyítására, hogy mindkét függvény már az ős Sikidom osztályban definiálva volt és öröklődés után az utódosztályban is megőrzik teljes funkcionalitásukat. Ezt egy szándékosan rossz bemeneti adat megadásával ellenőrizzük le, amely már az ősosztályban deklarált hibaüzeneteket fog generálni.