Cześć! Dzisiaj przyjrzymy się działaniu ważnego mechanizmu - dziedziczeniu w klasach zagnieżdżonych. Nie wiem, czy kiedykolwiek zastanawiałeś się, co zrobisz, gdy będziesz musiał odziedziczyć klasę zagnieżdżoną od innej. Jeśli nie, uwierz mi: ta sytuacja może być myląca, ponieważ jest tu wiele niuansów:
- Czy dziedziczymy klasę zagnieżdżoną z jakiejś klasy, czy też inną klasę z zagnieżdżonej?
- Czy następca/dziedziczona jest zwykłą klasą publiczną, czy jest to również klasa zagnieżdżona?
- Wreszcie, jakiego dokładnie typu klas zagnieżdżonych używamy we wszystkich tych sytuacjach?
Statyczne klasy zagnieżdżone
Ich zasady dziedziczenia są najprostsze. Tutaj możesz zrobić prawie wszystko, czego dusza zapragnie. Statyczną klasę zagnieżdżoną można dziedziczyć z:- regularne zajęcia
- statyczna klasa zagnieżdżona zadeklarowana w klasie zewnętrznej lub jej przodkach
public class Boeing737 {
private int manufactureYear;
private static int maxPassengersCount = 300;
public Boeing737(int manufactureYear) {
this.manufactureYear = manufactureYear;
}
public int getManufactureYear() {
return manufactureYear;
}
public static class Drawing {
public static int getMaxPassengersCount() {
return maxPassengersCount;
}
}
}
Spróbujmy zmienić kod i utworzyć statyczną klasę zagnieżdżoną Drawing
oraz jej potomka - Boeing737Drawing
.
public class Boeing737 {
private int manufactureYear;
private static int maxPassengersCount = 300;
public Boeing737(int manufactureYear) {
this.manufactureYear = manufactureYear;
}
public int getManufactureYear() {
return manufactureYear;
}
public static class Drawing {
}
public static class Boeing737Drawing extends Drawing {
public static int getMaxPassengersCount() {
return maxPassengersCount;
}
}
}
Jak widać, nie ma problemu. Możemy całkowicie usunąć tę klasę Drawing
i uczynić ją zwykłą klasą publiczną zamiast statycznej zagnieżdżonej - nic się nie zmieni.
public class Drawing {
}
public class Boeing737 {
private int manufactureYear;
private static int maxPassengersCount = 300;
public Boeing737(int manufactureYear) {
this.manufactureYear = manufactureYear;
}
public int getManufactureYear() {
return manufactureYear;
}
public static class Boeing737Drawing extends Drawing {
public static int getMaxPassengersCount() {
return maxPassengersCount;
}
}
}
To załatwione. A jakie klasy mogą dziedziczyć ze statycznego zagnieżdżenia? Prawie każdy! Zagnieżdżone/regularne, statyczne/niestatyczne – to nie ma znaczenia. Tutaj dziedziczymy klasę wewnętrzną Boeing737Drawing
ze statycznej klasy zagnieżdżonej Drawing
:
public class Boeing737 {
private int manufactureYear;
private static int maxPassengersCount = 300;
public Boeing737(int manufactureYear) {
this.manufactureYear = manufactureYear;
}
public int getManufactureYear() {
return manufactureYear;
}
public static class Drawing {
}
public class Boeing737Drawing extends Drawing {
public int getMaxPassengersCount() {
return maxPassengersCount;
}
}
}
Boeing737Drawing
Możesz utworzyć taką instancję :
public class Main {
public static void main(String[] args) {
Boeing737 boeing737 = new Boeing737(1990);
Boeing737.Boeing737Drawing drawing = boeing737.new Boeing737Drawing();
System.out.println(drawing.getMaxPassengersCount());
}
}
Chociaż nasza klasa Boeing737Drawing
dziedziczy po klasie statycznej, sama w sobie nie jest statyczna! Dlatego zawsze będzie potrzebować instancji klasy zewnętrznej. Możemy wyjąć tę klasę Boeing737Drawing
z klasy Boeing737
i uczynić ją po prostu publiczną. Nic się nie zmieni - może też dziedziczyć po statycznym zagnieżdżeniu Drawing
.
public class Boeing737 {
private int manufactureYear;
public static int maxPassengersCount = 300;
public Boeing737(int manufactureYear) {
this.manufactureYear = manufactureYear;
}
public int getManufactureYear() {
return manufactureYear;
}
public static class Drawing {
}
}
public class Boeing737Drawing extends Boeing737.Drawing {
public int getMaxPassengersCount() {
return Boeing737.maxPassengersCount;
}
Jedyna ważna kwestia: w tym przypadku musimy upublicznić zmienną statyczną maxPassengersCount
. Jeśli pozostanie prywatny, normalna klasa publiczna nie będzie miała do niego dostępu. Uporządkowaliśmy zajęcia statyczne! :) Przejdźmy teraz do klas wewnętrznych. Jak pamiętasz, są ich 3 typy: po prostu klasy wewnętrzne, klasy lokalne i anonimowe klasy wewnętrzne. Znów przejdźmy od prostych do złożonych :)
Anonimowe klasy wewnętrzne
Anonimowa klasa wewnętrzna nie może dziedziczyć z innej klasy. Żadna inna klasa nie może dziedziczyć po klasie anonimowej. To nie może być prostsze! :)Zajęcia lokalne
Klasy lokalne (jeśli zapomniałeś) są zadeklarowane w bloku kodu innej klasy. Najczęściej - wewnątrz jakiejś metody tej klasy zewnętrznej. Logiczne jest, że tylko inne klasy lokalne w ramach tej samej metody (lub bloku) mogą dziedziczyć z klasy lokalnej. Oto przykład:public class PhoneNumberValidator {
public void validatePhoneNumber(final String number) {
class PhoneNumber {
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
class CellPhoneNumber extends PhoneNumber {
}
class LandlinePhoneNumber extends PhoneNumber {
}
//...kod валидации номера
}
}
To jest kod z naszego wykładu o klasach lokalnych. Wewnątrz klasy walidatora liczb mamy klasę lokalną PhoneNumber
- numer telefonu. Jeżeli dla naszych celów musimy oddzielić od niego dwa osobne podmioty, np. numer telefonu komórkowego i numer telefonu stacjonarnego, możemy to zrobić jedynie w ramach tej samej metody. Powód jest prosty: zasięg klasy lokalnej mieści się w metodzie (bloku), w której jest ona zadeklarowana. Nie będziemy więc mogli go w jakiś sposób wykorzystać zewnętrznie (m.in. do dziedziczenia). Jednak sama klasa lokalna ma szersze możliwości dziedziczenia! Klasa lokalna może dziedziczyć po:
- Regularne zajęcia.
- Klasa wewnętrzna zadeklarowana w tej samej klasie co klasa lokalna lub w jej przodkach.
- Z innej klasy lokalnej zadeklarowanej w tej samej metodzie (bloku).
public class PhoneNumberValidator {
class PhoneNumber {
private String phoneNumber;
public PhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
public void validatePhoneNumber(final String number) {
class CellPhoneNumber extends PhoneNumber {
public CellPhoneNumber(String phoneNumber) {
super(number);
}
}
class LandlinePhoneNumber extends PhoneNumber {
public LandlinePhoneNumber(String phoneNumber) {
super(number);
}
}
//...kod валидации номера
}
}
W tym przypadku usunęliśmy klasę PhoneNumber
z metody validatePhoneNumber()
i uczyniliśmy ją wewnętrzną, a nie lokalną. Nie przeszkadza nam to w dziedziczeniu po nim naszych 2 klas lokalnych. Przykład 2 – „...lub u przodków tej klasy.” Tutaj robi się ciekawiej. Możemy wznieść się PhoneNumber
jeszcze wyżej w łańcuchu dziedziczenia. Zadeklarujmy klasę abstrakcyjną AbstractPhoneNumberValidator
, która stanie się naszym przodkiem PhoneNumberValidator
:
public abstract class AbstractPhoneNumberValidator {
class PhoneNumber {
private String phoneNumber;
public PhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
}
Jak widać, nie tylko to zadeklarowaliśmy, ale także przenieśliśmy do niego klasę wewnętrzną PhoneNumber
. Jednakże w klasie potomnej - PhoneNumberValidator
- lokalne klasy metod mogą dziedziczyć po PhoneNumber
!
public class PhoneNumberValidator extends AbstractPhoneNumberValidator {
public void validatePhoneNumber(final String number) {
class CellPhoneNumber extends PhoneNumber {
public CellPhoneNumber(String phoneNumber) {
super(number);
}
}
class LandlinePhoneNumber extends PhoneNumber {
public LandlinePhoneNumber(String phoneNumber) {
super(number);
}
}
//...kod валидации номера
}
}
Dzięki połączeniu poprzez dziedziczenie klasy lokalne wewnątrz klasy potomnej „widzą” klasy wewnętrzne wewnątrz przodka. I na koniec przejdźmy do ostatniej grupy :)
Klasy wewnętrzne
Klasa wewnętrzna może być dziedziczona przez inną klasę wewnętrzną zadeklarowaną w tej samej klasie zewnętrznej (lub jej potomku). Spójrzmy na to na naszym przykładzie roweru z wykładu o klasach wewnętrznych.public class Bicycle {
private String model;
private int mawWeight;
public Bicycle(String model, int mawWeight) {
this.model = model;
this.mawWeight = mawWeight;
}
public void start() {
System.out.println("Iść!");
}
class Seat {
public void up() {
System.out.println("Сидение поднято выше!");
}
public void down() {
System.out.println("Сидение опущено ниже!");
}
}
class SportSeat extends Seat {
//...metody
}
}
Bicycle
Tutaj zadeklarowaliśmy klasę wewnętrzną wewnątrz klasy Seat
- seat. Odziedziczono po nim specjalny podtyp foteli wyścigowych - SportSeat
. Moglibyśmy jednak stworzyć odrębny typ „rowerów wyścigowych” i umieścić go w osobnej klasie:
public class SportBicycle extends Bicycle {
public SportBicycle(String model, int mawWeight) {
super(model, mawWeight);
}
class SportSeat extends Seat {
public void up() {
System.out.println("Сидение поднято выше!");
}
public void down() {
System.out.println("Сидение опущено ниже!");
}
}
}
Jest to również możliwe. Klasa wewnętrzna dziecka ( SportBicycle.SportSeat
) „widzi” klasy wewnętrzne przodka i może po nich dziedziczyć. Dziedziczenie z klas wewnętrznych ma jedną bardzo ważną cechę! W poprzednich dwóch przykładach mieliśmy SportSeat
funkcję internal. Ale co, jeśli zdecydujemy się uczynić z niej SportSeat
zwykłą klasę publiczną, która również dziedziczy po klasie wewnętrznej Seat
?
//ошибка! No inclosing instance of type 'Bicycle' is in scope
class SportSeat extends Bicycle.Seat {
public SportSeat() {
}
public void up() {
System.out.println("Сидение поднято выше!");
}
public void down() {
System.out.println("Сидение опущено ниже!");
}
}
Wystąpił błąd! Czy domyślasz się z czym to jest powiązane? :) To proste. Kiedy mówiliśmy o klasie wewnętrznej Bicycle.Seat
, wspomnieliśmy, że konstruktor klasy wewnętrznej domyślnie przekazuje referencję do obiektu klasy zewnętrznej. Dlatego bez utworzenia obiektu Bicycle
nie można utworzyć obiektu Seat
. A co z tworzeniem SportSeat
? Nie ma tego samego wbudowanego mechanizmu niejawnego przekazywania referencji do obiektu klasy zewnętrznej w konstruktorze, jak w Seat
. Jednak bez obiektu Bicycle
, podobnie jak w przypadku Seat
, nie możemy stworzyć obiektu SportSeat
. W związku z tym pozostaje nam tylko jedno do zrobienia - jawne przekazanie konstruktorowi SportSeat
referencji do obiektu . Bicycle
Oto jak to się robi:
class SportSeat extends Bicycle.Seat {
public SportSeat(Bicycle bicycle) {
bicycle.super();
}
public void up() {
System.out.println("Сидение поднято выше!");
}
public void down() {
System.out.println("Сидение опущено ниже!");
}
}
W tym celu używamy specjalnego słowa. super();
Teraz, jeśli chcemy stworzyć obiekt SportSeat
, nic nie stanie nam na przeszkodzie, aby to zrobić:
public class Main {
public static void main(String[] args) {
Bicycle bicycle = new Bicycle("Peugeot", 120);
SportSeat peugeotSportSeat = new SportSeat(bicycle);
}
}
Uff, wykład okazał się dość obszerny :) Ale dowiedziałeś się wielu nowych rzeczy! Nadszedł czas, aby rozwiązać kilka problemów! :)
GO TO FULL VERSION