16 kwietnia 2008

SCJP – Dziedziczenie i wielodziedziczenie

Streszczenie drugiego rozdziału Książki do SCJP kontynuujemy! Jakie streszczenie? Patrz artykuł "SCJP - Związki typu IS-A oraz HAS-A". Dziś będzie o dziedziczeniu i wielodziedziczeniu, a także jego braku.

Zacznijmy od paru prostych konstatacji. Obiekty to są instancje klas. Do obiektów tych odwołujemy się za pomocą zmiennych, które są de facto referencjami. Każda referencja, tj. zmienna ma swój typ. Raz zadeklarowana zmienna nie może później zmieniać swego typu, ale może wskazywać różne obiekty, także obiekty różnych typów – tych typów, które są zgodne pod względem przypisania (ang. assignment compatible) z typem zmiennej. Typ zmiennej może być określony poprzez klasę, interfejs lub typ wyliczeniowy. Jeśli A jest pewną klasą, to zgodne pod względem przypisania ze zmienną typu A są obiekty klasy A oraz jej klas pochodnych a jeśli A jest interfejsem to obiekty klas implementujących ten interfejs. Dla przykładu ‘A a = new B()’ jest ok, jeśli np. ‘B extends A’ albo ‘B implements A’, albo jeśli B rozszerza czy też implementuje A nie bezpośrednio. Typy wyliczeniowe są tutaj jak klasy, z dokładnością do tego, że nie mogą one rozszerzać innych typów wyliczeniowych – patrz artykuł "SCJP - Typy wyliczeniowe". I jeszcze jedno – to typ zmiennej, a nie typ faktycznego obiektu określa API. Zerknijmy na poniższy przykład.

class Parent {
void testFromParent() {}
}

class Child extends Parent {
void testFromChild() {}
}

class Test {
void test() {
Parent p = new Child();

p.testFromParent();
p.testFromChild(); // Błąd!
}
}

Zmienna p w powyższym przykładzie jest typu Parent a więc nie możemy na niej wywołać metody testFromChild(), mimo że rzeczywisty obiekt wskazywany przez zmienną p jest typu Child i posiada implementację tej metody. Gdybyśmy natomiast powiedzieli kompilatorowi, że zmienna p wskazuje na obiekt klasy Child stosując rzutowanie, to byłoby ok. Instrukcje ‘p.testFromChild()’ trzeba więc w powyższym przykładzie zastąpić przez ‘((Child)p).testFromChild()’. Zwróćmy jeszcze uwagę na nawiasy przy rzutowaniu – wszystkie są konieczne, a może ich zabraknąć w godzinie próby, tj. w czasie egzaminu na SCJP.

Przejdźmy do wielodziedziczenia. Jak wiadomo w języku Java (inaczej niż np. w C++) nie dysponujemy wielodziedziczeniem klas, tj. klasa może rozszerzać co najwyżej jedną klasę. Powód jest prosty – chęć uniknięcia problemów. Wyobraźmy sobie, że zarówno klasa A jak i B implementują metodę anyOp(). Jeśliby teraz dopuścić wielodziedziczenie, to moglibyśmy zadeklarować klasę C, która rozszerza zarówno A i B co prowadzi do pytania – którą implementację metody anyOp() mamy w klasie C? Tą z klasy A czy z B? Jak widać, problem wynika z konieczności wyboru implementacji, a więc nie dotyczy interfejsów. Tak, język Java dopuszcza wielodziedziczenie interfejsów, interfejs może rozszerzać dowolną liczbę innych interfejsów. Klasa może rozszerzać co najwyżej jedną klasę ale jeśli chodzi o implementację interfejsów nie ma ograniczeń. Klasy mogą implementować dowolną liczbę interfejsów.

1 komentarz:

Anonimowy pisze...

Dziękuję bardzo serdecznie za ten blog. Piszesz w tak przystempny sposób, że przydało mi się to do napisania projektu i chyba zapaliło mnie do nauki javy:)
Pozdrawiam
kasia kate