Dziedziczenie

Dziedziczenie jest jednym z podstawowych mechanizmów programowania obiektowego. Mechanizm ten umożliwia definiowanie nowych klas na bazie istniejących.

Słowo dziedziczenie kojarzy się nam z życia codziennego z przejmowaniem przez jedną osobę majątku po drugiej osobie. Analogia w języku Java nie jest bardzo daleka. Dziedziczenie oznacza tu przejmowanie przez jedną klasę metod i zmiennych z innej klasy.

Dziedziczenie jest w języku Java mechanizmem wszechobecnym i niezwykle potężnym. Prawie każda klasa – a mówiąc precyzyjniej każda klasa z wyjątkiem klasy java.lang.Object – dziedziczy z jakiejś innej klasy, każda bowiem klasa dziedziczy w sposób niejawny ze wspomnianej klasy Object.

Co dziedziczymy z klasy Object? Kilka metod, a wśród nich metodę equals(…), która służy do sprawdzania równości obiektów. Cóż z tego? Ano chociażby to, że możemy każdy obiekt dowolnego typu porównać z każdym innym każdego dowolnego typu za pomocą dokładnie tej samej metody. Kolejną metodą którą dziedziczymy z klasy Object jest metoda toString(). Metoda ta zwraca tekstową reprezentację obiektu. Ta sama metoda zwraca tekstową reprezentację każdego obiektu, niezależnie od jego typu.

Zobaczmy teraz na przykładzie, w jaki sposób deklarujemy dziedziczenie i w jaki sposób metody zdefiniowane w nadklasie są dziedziczone przez podklasę. Zdefiniujmy wpierw klasę Product, która mogłaby opisywać aktykuły które sprzedajemy w implementowanym przez nas sklepie internetowym. Klasa ta ma jedną metodę, która zwraca cenę towaru i metodę do przypisania tej ceny tuż po utworzeniu obiektu. Kod poniżej:

class Product {
  private double price;

  public double getPrice() {
    return price;
  }

  public void setPrice(double price) {
    this.price = price;
  }
}

Oczywiście trudno jest sprzedać coś takiego jak produkt. Sprzedajemy raczej konkretne rzeczy, np. książkę, która ma konkretny tytuł, liczbę stron, typ oprawy, autora i numer ISBN. Książka jest produktem i tak jak każdy produkt ma swoją cenę. Skoro książka jest produktem, to ma też wszelkie cechy produktu, zatem klasa która opisuje obiekt książki powinna dziedziczyć z klasy która opisuje produkt, tj. z klasy Product. Przykładowa implementacja klasy modelującej produkty typu książka poniżej:

class Book extends Product {
  private int pagesNum;
  
  public Book(double price, int pagesNum) {
    this.setPrice(price);
    
    this.pagesNum = pagesNum;
  }

  public int getPagesNum() {
    return pagesNum;
  }
}

Zwróćmy uwagę na słówko kluczowe extends w deklaracji klasy. To właśnie w ten sposób oznaczamy, że klasa Book dziedziczy z klasy Product.

Zauważmy, że implementując konstruktor w klasie Book posłużyliśmy się odziedziczoną metodą setPrice(…) aby zapisać cenę książki. Co więcej, klasa Book dziedziczy nie tylko metody zdefiniowane w klasie Product, tj. metody setPrice(…) getPrice(), ale także metody odziedziczone przez nią z klasy Object. Klasa Product nie dziedziczy jawnie z żadnej klasy, a więc – zgodnie z tym co sobie już powiedzieliśmy – dziedziczy niejawnie z klasy Object. Dziedziczenie działa przechodnio, tak więc metody odziedziczone przez klasę Product przechodzą dalej, do dziedziczącej z niej klasy Book.