12.1. Metódusok felülírása és elrejtése

Ha egy leszármazott osztálybeli metódus, melynek ugyanaz a szignatúrája és visszatérési értéke, mint a szülőosztály metódusának, akkor a leszármazott osztály felülírja a szülőosztály metódusát. (Megjegyzendő, hogy egy metódus szignatúrája a nevéből, valamint paramétereinek számából és típusából áll.)

Egy leszármazott osztály felülíró képessége lehetővé teszi, hogy egy osztály örököljön egy olyan szülőosztálytól, melynek viselkedése elég közeli, majd szükség szerint változtasson a viselkedésen. Például az Object osztály tartalmaz egy toString nevű metódust, amelynek a visszaadja az objektumpéldány szöveges reprezentációját. Minden osztály megörökli ezt a metódust. Az Object metódusának végrehajtása általában nem túl hasznos a leszármazott osztályok számára, ezért a metódus felülírása célszerű, hogy jobb információt nyújthasson az objektum saját magáról. Ez különösen hasznos például nyomkövetés esetén. A következő kód egy példa a toString felülírására:

public class MyClass {
    private int anInt = 4;
    public String toString() {
        return "Instance of MyClass. anInt = " + anInt;
    }
}

A felülíró metódusának neve, valamint paramétereinek száma és típusa, valamint visszatérési értéke megegyezik azzal a metódussal, amelyet felülír. (Valójában a leszármazott osztálybeli metódus visszatérési típusa lehet a szülőosztály visszatérő típusának leszármazottja is a Java 5 óta.)

A felülíró metódusnak lehet az őstől eltérő throws záradéka, ha nem ad meg olyan típusokat, melyek nincsenek a felülírt metódus záradékában előírva. Másrészt, a felülíró metódus láthatósága lehet bővebb, mint a felülírt metódusé, de szűkebb nem. Például a szülő osztály protected metódusa a leszármazott osztályban publikussá (public) tehető, de priváttá (private) nem.

Megjegyzés: Érdemes átgondolni e szabályok hátterét. Egy leszármazott osztály objektuma bárhol használható, ahol egy ősosztálybeli objektum is. Éppen ezért a leszármazott semelyik tagjának láthatósága nem szűkülhet, hiszen akkor az ilyen használat lehetetlen lenne. Ugyanígy egy felülírt metódus által dobott újfajta kivétel kezelése nem lenne biztosított.

Egy leszármazott osztály nem tudja felülírni az olyan metódusokat, melyek az ősosztályban végleges (final) minősítésű (a definíció szerint a végleges metódusok nem felülírhatók). Ha mégis megpróbálunk felülírni egy végleges metódust, a fordító hibaüzenetet küld.

Egy leszármazott osztálynak felül kell írnia azon metódusokat, melyek a felsőbb osztályban absztraktnak (abstract) nyilvánítottak, vagy maga a leszármazott osztály is absztrakt kell, hogy legyen. Emlékezzünk vissza, hogy a Java programnyelv megengedi a metódusok túlterhelését, ha a metódus paramétereinek a számát vagy típusát megváltoztatjuk. Egy ősosztályban is megengedhető a metódusok túlterhelése. Alábbiakban nézzünk egy példát a toString metódus túlterhelésére:

public class MyClass {
    private int anInt = 4;
    public String toString() {
        return "Instance of MyClass. anInt = " + anInt;
    }
    public String toString(String prefix) {
        return prefix + ": " + toString();
    }
}

Amint azt a példa illusztrálja, túlterhelhetünk egy ősosztálybeli metódust, hogy további funkciókkal is szolgálhasson. Amikor egy olyan metódus írunk, mely azonos nevű a felsőbb osztálybeli metódussal, le kell ellenőrizni a paramétereket és a kivétellistát (throws záradék), hogy biztosak lehessünk afelől, hogy a felülírás olyan lett, amilyennek akartuk.

Ha egy leszármazott osztály egy osztálymetódust ugyanazzal az aláírással definiál, mint a felsőbb osztálybeli metódus, akkor a leszármazott osztály metódusa elrejti (másként fogalmazva elfedi) a szülőosztálybelit. Nagy jelentősége van az elrejtés és a felülírás megkülönböztetésének. Nézzük meg egy példán keresztül, hogy miért! E példa két osztályt tartalmaz. Az első az Animal, melyben van egy példánymetódus és egy osztálymetódus:

public class Animal {
    public static void hide() {
        System.out.println("The hide method in Animal.");
    }
    public void override() {
        System.out.println("The override method in Animal.");
    }
}

A második osztály neve Cat, ez az Animal-nak egy leszármazott osztálya:

public class Cat extends Animal {
    public static void hide() {
        System.out.println("The hide method in Cat.");
    }
    public void override() {
        System.out.println("The override method in Cat.");
    }

    public static void main(String[] args) {
        Cat myCat = new Cat();
        Animal myAnimal = (Animal)myCat;
        myAnimal.hide();
        myAnimal.override();
    }
}

A Cat osztály felülírja az override metódust az Animal-ban, és elrejti a hide osztálymetódust az Animal-ban. Ebben az osztályban a main metódus létrehoz egy Cat példányt, beteszi az Animal típusú hivatkozás alá is, majd előhívja mind az elrejtett, mind a felülírt metódust. A program eredménye a következő:

The hide method in Animal.
The override method in Cat.

A szülőosztályból hívjuk meg a rejtett metódust, a leszármazott osztályból pedig a felülírtat. Osztálymetódushoz a futtatórendszer azt a metódust hívja meg, mely a hivatkozás szerkesztési idejű típusában van definiálva, amellyel a metódust elnevezték. A példánkban az myAnimal szerkesztési idejű típusa az Animal. Ekképpen a futtatórendszer az Animal-ban definiált rejtett metódust hívja meg. A példánymetódusnál a futtatórendszer a hivatkozás futásidejű típusában meghatározott metódust hívja meg. A példában az myAnimal futásidejű típusa a Cat. Ekképpen a futtatórendszer a Cat-ban definiált felülíró metódust hívja meg.

Egy példánymetódus nem tud felülírni egy osztálymetódust, és egy osztálymetódus nem tud elrejteni egy példánymetódust. Mindkét esetben fordítási hibát kapunk.