Dziedziczenie

Co to jest dziedziczenie?
Otóż krótko mówiąc jest to technika wykorzystywania klas już istniejących w innych. Używamy do tego słowa kluczowego „extends„.
Klasę już istniejącą z której dziedziczymy nazywamy nadklasą, klasą bazową lub też klasą macierzystą. Natomiast klasa dziedzicząca z nadklasy nazywana jest podklasą, klasą pochodną lub też klasą potomną.
Wyobraźmy sobie klasę Employee i Manager. W tym przypadku Manager dziedziczyłby po Employee gdyż też jest pracownikiem, ale mógłby w swojej nadklasie na przykład mieć pole bonus i związane z nim mutatory, których zwykły pracownik by nie posiadał. Musi mieć też wszystkie pola składowe i metody zwykłego pracownika.

public class Employee{
private String name;
private double salary;
...
}
public class Manager extends Employee{
private double bonus;
public void setBonus(double bonus){
this.bonus = bonus;
}
}

Od razu nasuwa się spostrzeżenie, że jeżeli Manager korzysta z metod klasy Employee, to metoda getSalary jest niewłaściwa, gdyż nie uwzględnia dodatku dla Managera – pola bonus.
Konieczne jest więc przesłonięcie w podklasie Manager tej metody, tzw. po angielsku overriding.

public class Manager extends Employee{
public double getSalary(){
double baseSalary = super.getSalary();
return baseSalary + bonus;
}
}

Czemu jednak tak komplikujemy sprawę i po protu nie napiszemy salary + bonus?
Otóż salary byłaby polem prywatnym z prawidłowej konwencji programistycznej/hermetyzacji(zamykania właściwości obiektów) i dlatego Manager nie ma dostępu do pola prywatnego nadklasy, stąd musimy się odwołać do metody getSalary() ze słowem kluczowym super – odwołuje to do metody Manager-a. Samo getSalary() wywoływałoby samą siebie i prowadziłoby to do załamania programu.

Stwórzmy teraz konstruktor.

public Manager(String name, double salary){
super(name,salary);
bonus = 0;
}

Tutaj super znaczy wywołaj konstruktor klasy Employee z parametrami name i salary.

Rozważmy teraz następującą kwestię:
Tworzymy obiekt Manager:

Manager boss = new Manager("Piotr Kowalski", 7000);
boss.setBonus(2000);

I stwórzmy tablicę dwóch pracowników:

Employee[] staff = new Employee[2];
staff[0] = boss;
staff[1] = new Employee("Jan Piotrowicz", 4000);
for (Employee e : staff){
System.out.println(e.getName() + " " + e.getSalary());

Od razu wyświetlamy całą zawartość tablicy.
Powinno wyświetlić się na ekranie:
Piotr Kowalski 9000.0
Jan Piotrowicz 4000.0
Na pewno naszą uwagę przykuwa fakt, jak niby e.getSalary wywołuje tą metodę z klasy Manager?
Otóż maszyna wirtualna rozpoznaje, do jakiego typu odwołuje się zmienna e, dzięki czemu może wywołać odpowiednią metodę.

Możliwość odwoływania się przez obiekty do wielu różnych typów nosi nazwę polimorfizmu.
Natomiast dobór odpowiednich metod w trakcie działania programu nazywa się wiązaniem dynamicznym.

Możemy jednak dla danej klasy wyłączyć dziedziczenie. Bo na przykład nie chcemy aby tworzono podklasy jednej z klas.
Są to wtedy tak zwane klasy finalne:

public final class Executive extends Manager{
}

Finalna może też być metoda i nie można jej przesłonić.

Przejdźmy teraz do rzutowania – jest to proces wymuszania konwersji pomiędzy dwoma typami.

double salary = 3000.90;
int intSalary = (int) salary;

Utraciliśmy tutaj część ułamkową.
Podobnie z obiektami.

Manager boss = (Manager) staff[1];   //<-tutaj wyskoczy błąd

Kompilator wykryje niedorzeczne wymaganie i wygeneruje wyjątek ClassCastException.
Aby tego uniknąć należy sprawdzić czy rzutowanie się powiedzie:

if (staff[1] instanceof Manager){
boss  = (Manager) staff[1];
}

I wtedy rzutowanie się nie odbędzie.

Ograniczanie dostępu.
Deklarowaliśmy pewne pola jako private, itp.
Mamy cztery modyfikatory dostępu:
1. private – widoczny tylko w obrębie klasy
2. public – widoczny wszędzie
3. protected – widoczny w pakiecie i wszystkich podklasasch
4. domyślny czyli żaden – widoczny w obrębie pakietu

Mamy domyślną klasę wszystkich klas – klasę Object.
Istnieje dostępna w klasie Object metoda equals do porównywania dwóch obiektów.

Przejdźmy teraz do tablic. Wszystkie tablice muszą mieć ustalony rozmiar.
A co jeżeli nie wiemy np. ilu pracowników będzie zatrudniała firma?
Możemy wtedy skorzystać z generycznych list tablicowych.

ArrayList<Employee> staff = new ArrayList<Employee>();

Metoda size() sprawdzi Nam liczbę elementów w liście tablicowej.
Metoda 'E set(int index, O obj)’ – wstawi wartość obj do listy w miejscu o określonym indeksie, zwróci poprzednią wartość.
E get(int index) – pobierze wartość zapisaną w określonym indeksie
void add(int index, O obj) – przesunie elementy, aby dodać obj w określonym miejscu
E remove(int index) – usuwa element o określonym indeksie i przesuwa w dół wszystkie elementy, które znajdowały się nad nim, zwraca usunięty element.

Możemy wywoływać metody z różną liczbą parametrów, stosując trzykropek ’’:

public static void main(String... args);

Możemy tworzyć metody i klasy abstrakcyjne (za pomocą słowa kluczowego 'abstract’).
Gdy tworzymy np. metody abstrakcyjne nie mamy potrzeby jej implementacji.
Klasa zawierająca metodę abstrakcyjną również musi być abstrakcyjna.
Nie można tworzyć obiektów klas abstrakcyjnych.

Klasy wyliczeniowe, czyli tzw. enum-y.
Przykład:

public enum Size {SMALL, MEDIUM, LARGE}

Do porównywania używamy '==’.
Możemy do typu wyliczeniowego dodawać konstruktory, metody i pola.
Posiadają one statyczne metody takie jak:
valueOf(), values(), ordinal()
Metoda ordinal() zwraca położenie stałej wyliczeniowej w deklaracji enum licząc od zera.

Klasy zapieczętowane (’sealed’).

public abstract sealed class Value permits Number{...}

Próba zdefiniowania klasy spoza dozwolonej listy jest błędem:

public class Array extends Value{...}         //<-błąd

Podklasa zapieczętowanej klasy musi określać, czy jest zapieczętowana, finalna czy otwarta na dalsze tworzenie podklas (non-sealed).

Komentarze

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *