6. Pojęcie dziedziczenia
Zajmiemy się teraz krótko pojęciem dziedziczenia. Pełna dyskusja tej tematyki
przewidziana jest na drugi semestr, Tutaj zwrócimy uwagę na te elementy, które
będą nam potrzebne w najbliższych wykladach.
Dziedziczenie polega na przejęciu właściwości i funkcjonalności
obiektów innej klasy i ewentualnej ich modyfikacji i/lub uzupelnieniu w taki sposób, by były
one bardziej wyspecjalizowane.
Omawiana wyżej klasa Publication opisuje właściwości publikacji, które
kupuje i sprzedaje księgarnia. Zauważmy, że za pomocą tej klasy nie możemy
w pełni opisać książek. Książki są szczegołną, "wyspecjalizowaną" wersją
publikacji, oprócz tytułu, wydawcy, ceny itd - mają jeszcze jedną właściwość
- autora (lub autorów). Słowo kluczowe extends służy do wyrażenia relacji dziedziczenia jednej klasy przez drugą. Piszemy: class B extends A { ... } co oznacza, że klasa B dziedziczy (rozszerza) klasę A. Mówimy:
Zapiszmy zatem: public class Book extends Publication { // definicja klasy Book }
Co należy podać w definicji nowej klasy? class Book extends Publication { private String author; public String getAuthor() { return author; } }
Czy to wystarczy?
Czyli konstruktor powinien mieć postać: public Book(String aut, String tit, String pub, int y, String id, double price, int quant) { .... } Zwróćmy jednak uwagę: pola tytułu, wydawcy, roku, identyfikatora, ceny i
ilości - są prywatnymi polami klasy Publication. Z klasy Book nie mamy do
nich dostępu. Jak je zainicjowac? Pola nadklasy (klasy bazowej) inicjujemy za pomocą wywołania z konstruktora
klasy pochodnej konstruktora klasy bazowej (nadklasy)
Użycie w konstruktorze następującej konstrukcji składniowej: super(lista_argumentów); oznacza wywołanie konstruktora klasy bazowej z argumentami lista_argumentów . Jeśli występuje - MUSI być pierwszą instrukcją konstruktora klasy pochodnej. Jeśli nie występuje - przed utworzeniem obiektu klasy pochodnej zostanie wywołany konstruktor bezparametrowy klasy bazowej.
Konstruktor klasy Book musi więc wywołać konstruktor nadklasy, po to by zainicjować
jej pola, a następnie zainicjować pole author. // Konstruktor klasy Book // argumenty: aut - autor, tit - tytuł, pub - wydawca, y - rok wydania // id - ISBN, price - cena, quant - ilość public Book(String aut, String tit, String pub, int y, String id, double price, int quant) { super(tit, pub, y, id, price, quant); author = aut; }
Teraz można podać już pełną definicję klasy Book. public class Book extends Publication { private String author; public Book(String aut, String tit, String pub, int y, String id, double price, int quant) { super(tit, pub, y, id, price, quant); author = aut; } public String getAuthor() { return author; } }
Zwróćmy uwagę: wykorzystanie klasy Publication (poprzez jej odziedziczenie)
oszczędziło nam wiele pracy. Nie musieliśmy ponownie definiować pól i metod
z klasy Publication w klasie Book. Book b = new Book("James Gossling", "Moja Java", "WNT", 2002, "ISBN6893", 51.0, 0); Ten obiekt zawiera:
![]() Podkreślmy: jest to jeden obiekt klasy Book. Ale ponieważ klasa Book dziedziczy klasę Publication to obiekty klasy Book
mają również wszelkie właściwości obiektów klasy Publication , a zatem możemy
na ich rzecz używać również metod zdefiniowanych w klasie Publication.
Nic zatem nie stoi na przeszkodzie, by napisać taki program: class TestBook { public static void main(String[] args) { Book b = new Book("James Gossling", "Moja Java", "WNT", 2002, "ISBN6893", 51.0, 0); int n = 100; b.buy(n); double koszt = n * b.getPrice(); System.out.println("Na zakup " + n + " książek:"); System.out.println(b.getAuthor()); System.out.println(b.getTitle()); System.out.println(b.getPublisher()); System.out.println(b.getYear()); System.out.println(b.getIdent()); System.out.println("---------------\nwydano: " + koszt); b.sell(90); System.out.println("---------------"); System.out.println("Po sprzedaży zostało " + b.getQuantity() + " pozycji"); } } Na zakup 100 książek:
James Gossling Moja Java WNT 2002 ISBN6893 --------------- wydano: 5100.0 --------------- Po sprzedaży zostało 10 pozycji który skompiluje się i wykona poprawnie dając w wyniku pokazany listing. Możemy powiedzieć, że obiekty klasy Book są również obiektami klasy Publication
(w tym sensie, że mają wszelkie właściwości obiektów klasy Publication)
Dzięki temu referencje do obiektów klasy Book możemy przypisywać zmiennym,
oznaczającym obiekty klasy Publication (zawierającym referencje do obiektów
klasy Publication). Np. Nieco mniej precyzyjnie, ale za to podkreślając, że chodzi o operowanie
na obiektach, będziemy mówić o takich konwersjach jako o obiektowych konwersjach rozszerzających (ang. "upcasting" - up - bo w górę hierarchii dziedziczenia).
Obiektowe konwersje rozszerzające dokonywane są automatycznie przy:
Ta zdolność obiektów Javy do "stawania się" obiektem swojej nadklasy jest niesłychanie użyteczna. public double incomeDiff(Publication p1, Publication p2) { double income1 = p1.getQuantity() * p1.getPrice(); double income2 = p2.getQuantity() * p2.getPrice(); return income1 - income2; }
i wywoływać ją dla dowolnych (różnych rodzajów) par publikacji Book b1 = new Book(...); Book b2 = new Book(...); Journal j = new Journal(...); CDisk cd1 = new CDisk(...); CDisk cd2 = new CDisk(...); double diff = 0; diff = incomeDiff(b1, b2); diff = incomeDifg(b1, j); diff = inocmeDiff(cd1, b1);
Gdyby nie było obiektowych konwersji rozszerzających, to dla każdej mozliwej
kombinacji "rodzajowej" par - musielibyśmy napisać inną metodę incomeDiff
np. W Javie każda klasa może bezpośrednio odziedziczyć tylko jedną klasę.
Ale pośrednio może mieć dowolnie wiele nadklas, co wynika z hierarchii dziedziczenia. Ta hierarchia zawsze zaczyna się na klasie Object (której definicja znajduje się w zestawie stanardowych klas Javy). Zatem w Javie wszystkie klasy pochodzą pośrednio od klasy Object. Jeśli definiując klasę nie użyjemy słowa extends (nie zażądamy jawnie dziedziczenia), to i tak nasza klasa domyślnie będzie dziedziczyć klasę Object (tak jakbyśmy napisali class A extends Object).
Wobec tego hierarchia dziedziczenia omawianych tu klas wygląda następująco: ![]() Z tego wynika, że:
referencję do obiektu dowolnej klasy można przypisać zmiennej typu Object (zawierającej referencję do obiektu klasy Object). Z właściwości tej korzysta wiele "narzędziowych" metod zawartych w klasach standardu Javy.
|