Java #9: dziedziczenie oraz modyfikator protected

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ć)

Wpływ hermetyzacji na dziedziczenie

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();	
  }
}

Modyfikator protected

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.

Dziedziczenie – reguły

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 konstruktora, 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 konstruktora? Otóż faktycznie, choć go nie widzimy, jest użyty przez kompilator, bowiem przy tworzeniu dziecka zawsze tworzony jest obiekt rodzica.

Link do lekcji: https://github.com/developeronthego/java-basics/tree/main/src/main/java/basics/lesson9

Więcej informacji odnośne modyfikatorów dostępów, znajdziesz w lekcji o enkapsulacji: Java #6: Hermetyzacja (enkapsulacja)

Stay in the Loop

Get the daily email from CryptoNews that makes reading the news actually enjoyable. Join our mailing list to stay in the loop to stay informed, for free.

Ostatnio dodane

- Advertisement - spot_img

Powiązane wpisy