Nauka programowania Java #9: dziedziczenie

Jedną z zasad właściwego programowania jest niepowtarzanie raz zaimplementowanego kodu. Czasami jest to trudne, ponieważ, aby użyć tych samych pól i metod w innych klasach musielibyśmy:

  1. używać programowania strukturalnego (czyli słowa kluczowego static), co łamie zasady programowania obiektowego, więc odrzucam tą opcję,
  2. użycie kompozycji (np. pola, które będzie typem referencyjnym do obiektu, z którego chcemy skorzystać)

Jednak istnieje jeszcze jeden sposób, w jaki można przekazać Twojej klasie informacje z innej klasy. Nazywa się ono dziedziczeniem. Najprościej można je zrozumieć jako skorzystanie z pewnych funkcjonalności klasy bazowej w klasach potomnych. Warto podkreślić, że będą one tylko dostępne dla innej klasy poprzez dziedziczenie (dla innych klas te zmienne i metody będą zachowywały się jak przy użyciu modyfikatora dostępu private).

Spójrz na poniższy przykład:

public class Parent {
  public String publicName;
  Long packageNumber;
  protected Double protectedNumber;
  private Integer privateNumber;
  
  public Parent(String publicName, Long packageNumber, Double protectedNumber, Integer privateNumber) {
    this.publicName = publicName;
    this.packageNumber = packageNumber;
    this.protectedNumber = protectedNumber;
    this.privateNumber = privateNumber;
  }

  public String getPublicName() {
    return publicName;
  }

  public Long getPackageNumber() {
    return packageNumber;
  }

  public Double getProtectedNumber() {
    return protectedNumber;
  }

  public Integer getPrivateNumber() {
    return privateNumber;
  }
}
public class Child extends Parent{

  public Child(String publicName, Long packageNumber, Double protectedNumber, Integer privateNumber) {
    super(publicName, packageNumber, protectedNumber, privateNumber);
  }
  
  public void checkValues() {
    String name = super.publicName;
    Long packageNumber = super.packageNumber;
    Double protectedNumber = super.protectedNumber;
    
    System.out.println("Test 1) " + name + " " + packageNumber + " " + protectedNumber);
    
    String nameByGet = super.getPublicName();
    Long packageNumberByGet = super.getPackageNumber();
    Double protectedNumberByGet = super.getProtectedNumber();
    Integer privateNumberByGet = super.getPrivateNumber();
    
    System.out.println("Test 2) " + nameByGet + " " + packageNumberByGet + " " + protectedNumberByGet + " " + privateNumberByGet);
  }
  
}
public class MainApp {
  public static void main(String args []) {
    Child child = new Child("test child", 10l, 20.0, 30);
    child.checkValues();	
  }
}

Magia dziedziczenia zadziałała. Wystarczyło dodać do nazwy klasy słowo kluczowe extends i wypisać nazwą klasy rodzica. Jak widzisz, pomimo, że klasa Child nie posiada żadnych pól definicji swojej klasy, to mogła skorzystać z tych, które zadeklarowałem w klasie Parent. Były to pola publiczne, pakietowa oraz chronione (protected). Dodatkowo, chociaż pole prywatne privateNumber nie zostało odziedziczone (z reguły pola będziesz pisał/a z prywatnym modyfikatorem dostępu), to mogłem się do niego odwołać przez publiczny getter.

Sama zasada dziedziczenia wydaje się prosta, jednak ma kilka dodatkowych reguł, o których możesz nie wiedzieć.

  • W Javie nie istnieje możliwość dziedziczenia wielu klas za pomocą słowa kluczowego extends. Możesz rozszerzyć swoją klasę tylko o jednego rodzica
  • Jeśli chcesz uchronić swoją klasę przed dziedziczeniem możesz skorzystać ze słowa kluczowe final przed nazwą klasy rodzica.
  • Jeśli chcesz skorzystać pola lub metody, które posiada rodzic, używasz słowa kluczowego super (wiem, też uważam, że to kretyńska nazwa).
  • Jeśli chcesz wywołać konstruktor klasy rodzica to w pierwszej linii swojego nowego konstruktora wpisujesz słowo super() i w nawiasie wpisujesz parametry konstruktora, które wywołujesz. Np. w moim przypadku rodzic miał tylko jeden konstruktor, więc właśnie jego wywołałem (Java poznaje, który chcesz wywołać po parametrach jakie wpisujesz w super, kolejność ma tu znaczenie!).

Teraz zagadka. Co się stało, gdybym nie wywołał konstruktora bazowego (rodzica) w konstruktorze dziecka?

  public Child(String publicName, Long packageNumber, Double protectedNumber, Integer privateNumber) {
//		super(publicName, packageNumber, protectedNumber, privateNumber);
  }

Kompilator teraz zaświeci na czerwono pierwszą linię i wyświetli informację: „Implicit super constructor Parent() is undefined. Must explicitly invoke another constructor„. Jeśli nie wywołasz konstruktora bazowego, nie uda się stworzyć konstruktora w klasie potomnej. Dzieje się tak dlatego, bo dziecko ‚tworzy’ też obiekt rodzica, aby pomóc skorzystać z jego właściwości.

Jest jednak pewien wyjątek od tej reguły. Jeśli rodzic nie będzie posiadał konstruktora, to nie będziemy musieli go wywoływać z klasy potomnej (logiczne). W praktyce jednak, gdy klasa nie ma kontruktora, to Java sama tworzy bezparametrowy konstruktor domyślny (np. Parent() {} ). Czy w takim razie, aby na pewno, w klasie dziecka nie jest użyte słowo super() w pierwszej linii jego konstuktora? Otóż faktycznie, choć go nie widzimy, jest użyty przez kompilator, bowiem przy tworzeniu dziecka zawsze tworzony jest obiekt rodzica.

 

Dodaj komentarz