Java. Programowanie obiektowe
Java. Programowanie obiektowe
Java. Dziedziczenie
Java programowanie obiektowe - strona g��wna Programowanie sterowane zdarzeniami Hermetyzacja Dziedziczenie Polimorfizm Inne publikacje Analiza portfelowa Projektowanie magazynów Stylystycznie blog Moda męska

Dziedziczenie

Dziedziczenie to operacja, która powoduje przeniesienie danych i metod z klasy bazowej do potomnej. Mechanizm ten uwalnia programistę od ponownego tworzenia i implementowania struktur danych oraz funkcji działających na tych strukturach. Udostępnia on możliwość korzystania z własnej bądź cudzej pracy jedynie poprzez rozszerzanie już zaimplementowanych elementów. Nie ma więc konieczności ponownego definiowania tego, co raz już zostało zrobione. Zyski z dziedziczenia przedstawiłem na przykładzie, który zawiera też moja książka (fragment książki poniżej). Warto pamiętać, że dziedziczenie może dotyczyć również interfejsów, gdzie trzaba zachować większą ostrzność w stosowaniu tego mechanizmu.







Książka: Java. Programowanie obiektowe

Marek Wierzbicki

Rozdział 2

Klasy i obiekty w Javie

...

2.1.9. Dziedziczenie

Zanim przejdziemy dalej, należy wprowadzić pojęcie dziedziczenia. Jak zwracałem na to uwagę w poprzednim rozdziale, dziedziczenie jest jedną z podstawowych cech programowania obiektowego. Mechanizm ten umożliwia rozszerzanie możliwości wcześniej utworzonych klas bez konieczności ich ponownego tworzenia. Zasada dziedziczenia w Javie ma za podstawę założenie, że wszystkie klasy dostępne w tym języku bazują w sposób pośredni lub bezpośredni na klasie głównej o nazwie Object. Wszystkie klasy pochodzące od tej oraz każdej innej są nazywane, w stosunku do tej, po której dziedziczą, podklasami. Klasa, po której dziedziczy własności dana klasa, jest w stosunku do niej nazywana nadklasą. Jeśli nie deklarujemy w żaden sposób nadklasy, tak jak jest to pokazane w przykładowej deklaracji klasy Point, oznacza to, że stosujemy domyślne dziedziczenie po klasie Object. Formalnie deklaracja klasy Point mogłaby mieć postać zaprezentowaną na listingu 2.18.

Listing 2.18. Dziedziczenie po klasie głównej

class Point extends Object {
  // ...
  // ciało klasy Point
  // ...

}

Wytłuszczony fragment listingu 2.18 deklaruje dziedziczenie po klasie Object. Jak wcześniej pisałem, jest ono opcjonalne, to znaczy, że jeśli go nie zastosujemy, Point również będzie domyślnie dziedziczył po Object.

Przedstawiony sposób jest używany w przypadku dziedziczenia po innych klasach, tak jak na listingu 2.19.

Listing 2.19. Praktyczne użycie dziedziczenia

class Figura extends Point {
  ...
}

class Wielokat extends Figura {
  ...
}

W przykładzie tym klasa Wielokat dziedziczy po klasie Figura, która z kolei dziedziczy po Point, a ta po Object. W Javie nie ma żadnych ograniczeń co do zagnieżdżania poziomów dziedziczenia. Poprawne więc będzie dziedziczenie na stu i więcej poziomach. Jakkolwiek takie głębokie dziedziczenie jest bardzo atrakcyjne w teorii, w praktyce wiąże się z niepotrzebnym obciążaniem zarówno pamięci, jak i procesora. To samo zadanie zrealizowane za pomocą płytszej struktury dziedziczenia będzie działało szybciej aż z trzech powodów:

Ponadto w przypadku apletów możemy liczyć na szybsze ładowanie się strony do przeglądarki, a więc będzie to kolejna pozytywna strona.

...

W praktyce znaczenie dziedziczenia możemy pokazać poprzez przykład. Jeśli zdefiniujemy 2 klasy dziedziczące w postaci:

class A {
  int a;
  public void setA(int newA) {
    a = newX;
  }
}
class B extends A {
  int b;
  public void setB(int newB) {
    b = newB;
  }
}

to operacja definiowania klasy B jest równoważna (w sensie definicji klasy) deklaracji:

class B {
  int a;
  int b;
  public void setA(int newA) {
    a = newA;
  }
  public void setB(int newB) {
    b = newB;
  }
}

Jeśli nie używamy nigdzie klasy A, to raczej powinniśmy deklarować od razu klasę B (chyba, że zależy nam na wprawkach p rogramistycznych).

...

Poza dziedziczeniem w dowolnie długim łańcuchu od klasy głównej do najniższej klasy potomnej w niektórych językach programowania (na przykład C++) istnieje wielokrotne dziedziczenie jednocześnie i równorzędnie po kilku klasach. W Javie jest to niemożliwe, to znaczy w definicji każdej klasy może wystąpić co najwyżej jedno słowo extends. Zamiast wielokrotnego dziedziczenia w Javie dostępny jest mechanizm interfejsów opisany w podrozdziale 2.4. "Interfejsy".

...

2.4.6. Dziedziczenie interfejsów

Podobnie jak klasy (patrz moja książka, paragraf 2.1.9. "Dziedziczenie"), interfejsy mogą podlegać dziedziczeniu. Jednak dziedziczenie interfejsów jest bardziej rozbudowane, a mianowicie w procesie tym nie występuje ograniczenie co do jednokrotnego dziedziczenia. Tak jak na listingu 2.68 interfejs może więc dziedziczyć po większej liczbie interfejsów.

Listing 2.68. Dziedziczenie interfejsu po wielu interfejsach

interface Interfejs {
  void metoda(int i);
}
interface Interfejs1 {
  void metoda1(int i);
}
interface Interfejs2 
  extends Interfejs, Interfejs1 {
  void metoda2(int i);
}

Tak jak w przypadku klasycznego dziedziczenia ostatni w łańcuchu interfejs, czyli w naszym przykładzie Interfejs2, posiada wszystkie cechy interfejsów, po których dziedziczy, to znaczy jego implementacja wymaga uwzględnienia wszystkich metod w równorzędny sposób, tak jak to pokazałem na listingu 2.69.

Listing 2.69. Implementacja interfejsu o wielu przodkach

import java.applet.*;

public class Applet2 extends Applet
  implements Interfejs2 {
  public void metoda(int i) {
    // ciało metody
  }
  public void metoda1(int i) {
    // ciało metody
  }
  public void metoda2(int i) {
    // ciało metody
  }
}
Gdyby okazało się, że w łańcuchu wielokrotnego dziedziczenia któraś z metod ma dokładnie taką samą nazwę i dokładnie taki sam zestaw parametrów, w obiekcie implementującym umieszcza się ją tylko jeden raz. Problem pojawia się w przypadku, gdy interfejsy implementowane w danej klasie dostarczają metody o tej samej nazwie, tym samym zestawie parametrów i różnym typie wartości zwracanej. Kompilator nie jest w stanie stworzyć takiego przeciążenia metody, więc generuje błąd semantyki języka Java. Warto jednak zauważyć, że błąd jest generowany dopiero na etapie kompilacji klasy. Tak więc konstrukcja pokazana na listingu 2.70 będzie poprawna z punktu widzenia kompilatora, choć całkowicie bezsensowna z punktu widzenia języka Java.

Listing 2.70. Błędne wielokrotne dziedziczenie interfejsów

interface Interfejs {
  void metoda(int i);
}
interface Interfejs1 {
  int metoda(int i);
}
interface Interfejs2
  extends Interfejs, Interfejs1 { }

Dzieje się tak dlatego, że formalnie rzecz biorąc, powyższy zapis jest równoważny deklaracji z listingu 2.71.

Listing 2.71. Pozorna postać interfejsu z listingu 2.70

interface Interfejs2 {
  void metoda(int i);
  int metoda(int i);
}

Kompilator na szczęście tego już nie zaakceptuje, gdyż przeciążenie z punktu widzenia typu wyniku nie jest dopuszczalne.