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)

