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:
- używać programowania strukturalnego (czyli słowa kluczowego static), co łamie zasady programowania obiektowego, więc odrzucam tą opcję,
- 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)