Cześć! Dzisiaj przyjrzymy się ważnemu mechanizmowi: dziedziczeniu w klasach zagnieżdżonych. Czy kiedykolwiek zastanawiałeś się, co byś zrobił, gdybyś musiał sprawić, by zagnieżdżona klasa odziedziczyła inną klasę. Jeśli nie, uwierz mi: ta sytuacja może być myląca, ponieważ istnieje wiele niuansów.
Ich zasady dziedziczenia są najprostsze. Tutaj możesz zrobić prawie wszystko, czego dusza zapragnie. Statyczna klasa zagnieżdżona może dziedziczyć:
Ponownie przejdźmy od prostych do złożonych :)
- Czy sprawiamy, że zagnieżdżona klasa dziedziczy jakąś klasę? A może sprawiamy, że jakaś klasa dziedziczy klasę zagnieżdżoną?
- Czy klasa dziecko/rodzic jest zwykłą klasą publiczną, czy też klasą zagnieżdżoną?
- Wreszcie, jakiego rodzaju klas zagnieżdżonych używamy we wszystkich tych sytuacjach?
Statyczne klasy zagnieżdżone

- zwykła klasa
- 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 stworzyć Drawing
statycznie zagnieżdżoną klasę i 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 nawet wyciągnąć Drawing
klasę i uczynić ją zwykłą klasą publiczną zamiast statycznej klasy 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;
}
}
}
Rozumiemy to. Ale jakie klasy mogą dziedziczyć statyczną klasę zagnieżdżoną? Praktycznie każdy! Zagnieżdżone/niezagnieżdżone, statyczne/niestatyczne — to nie ma znaczenia. Tutaj sprawiamy, że Boeing737Drawing
klasa wewnętrzna dziedziczy Drawing
statyczną klasę zagnieżdżoną:
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;
}
}
}
Możesz utworzyć instancję w Boeing737Drawing
następujący sposób:
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 Boeing737Drawing
klasa dziedziczy klasę statyczną, sama w sobie nie jest statyczna! W rezultacie zawsze będzie potrzebować instancji klasy zewnętrznej. Możemy usunąć Boeing737Drawing
klasę z Boeing737
klasy i uczynić ją prostą klasą publiczną. Nic się nie zmienia. Nadal może dziedziczyć Drawing
statyczną klasę zagnieżdżoną.
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;
}
Jedyną ważną kwestią jest to, że w tym przypadku musimy upublicznić maxPassengersCount
zmienną statyczną. Jeśli pozostanie prywatny, zwykła klasa publiczna nie będzie miała do niego dostępu. Wymyśliliśmy klasy statyczne! :) Przejdźmy teraz do zajęć wewnętrznych. Występują w 3 typach: proste klasy wewnętrzne, klasy lokalne i anonimowe klasy wewnętrzne. 
Anonimowe klasy wewnętrzne
Anonimowa klasa wewnętrzna nie może dziedziczyć innej klasy. Żadna inna klasa nie może dziedziczyć klasy anonimowej. To nie może być prostsze! :)Klasy lokalne
Klasy lokalne (jeśli zapomniałeś) są zadeklarowane wewnątrz bloku kodu innej klasy. Najczęściej dzieje się to wewnątrz jakiejś metody klasy zewnętrznej. Logicznie rzecz biorąc, tylko inne klasy lokalne wewnątrz tej samej metody (lub bloku kodu) mogą dziedziczyć klasę lokalną. 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 {
}
// ...number validation code
}
}
To jest kod z naszej lekcji na klasach lokalnych. Nasza klasa walidatora liczb ma PhoneNumber
klasę lokalną. Jeśli potrzebujemy, aby reprezentował dwie różne jednostki, na przykład numer telefonu komórkowego i numer telefonu stacjonarnego, możemy to zrobić tylko w ramach tej samej metody. Powód jest prosty: zasięg klasy lokalnej jest ograniczony do metody (bloku kodu), w której została zadeklarowana. W rezultacie nie będziemy mogli używać go zewnętrznie (w tym do dziedziczenia klas). Jednak możliwości dziedziczenia w ramach samej klasy lokalnej są znacznie szersze! Klasa lokalna może dziedziczyć:
- Zwykła klasa.
- Klasa wewnętrzna zadeklarowana w tej samej klasie co klasa lokalna lub w jednym z jej przodków.
- Inna klasa lokalna zadeklarowana w tej samej metodzie (blok kodu).
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);
}
}
// ...number validation code
}
}
Tutaj usunęliśmy PhoneNumber
klasę z validatePhoneNumber()
metody i uczyniliśmy ją klasą wewnętrzną zamiast klasy lokalnej. Nie powstrzymuje nas to przed dziedziczeniem go przez nasze 2 lokalne klasy. Przykład 2 — „… lub w przodkach tej klasy”. Teraz jest to już bardziej interesujące. Możemy przejść PhoneNumber
jeszcze wyżej w łańcuchu spadkowym. Zadeklarujmy AbstractPhoneNumberValidator
klasę abstrakcyjną, która stanie się przodkiem naszej PhoneNumberValidator
klasy:
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 — przenieśliśmy PhoneNumber
do niej również klasę wewnętrzną. Jednak w swoim potomku PhoneNumberValidator
klasy lokalne zadeklarowane w metodach mogą dziedziczyć PhoneNumber
bez żadnego problemu!
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);
}
}
// ...number validation code
}
}
Ze względu na relację dziedziczenia, lokalne klasy 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 zadeklarowana w tej samej klasie zewnętrznej (lub w jej potomku) może dziedziczyć inną klasę wewnętrzną. Zbadajmy to na naszym przykładzie z rowerami z lekcji o klasach wewnętrznych.
public class Bicycle {
private String model;
private int maxWeight;
public Bicycle(String model, int maxWeight) {
this.model = model;
this.maxWeight = maxWeight;
}
public void start() {
System.out.println("Let's go!");
}
class Seat {
public void up() {
System.out.println("Seat up!");
}
public void down() {
System.out.println("Seat down!");
}
}
class SportSeat extends Seat {
// ...methods
}
}
Tutaj zadeklarowaliśmy Seat
klasę wewnętrzną wewnątrz Bicycle
klasy. SportSeat
Dziedziczy go specjalny rodzaj fotela wyścigowego . Ale moglibyśmy stworzyć osobny typ „rower wyścigowy” i umieścić go w osobnej klasie:
public class SportBicycle extends Bicycle {
public SportBicycle(String model, int maxWeight) {
super(model, maxWeight);
}
class SportSeat extends Seat {
public void up() {
System.out.println("Seat up!");
}
public void down() {
System.out.println("Seat down!");
}
}
}
Jest to również opcja. Wewnętrzna klasa potomka ( SportBicycle.SportSeat
) „widzi” wewnętrzne klasy przodka i może je dziedziczyć. Dziedziczenie klas wewnętrznych ma jedną bardzo ważną cechę! W poprzednich dwóch przykładach nasza SportSeat
klasa była klasą wewnętrzną. Ale co, jeśli zdecydujemy się stworzyć SportSeat
zwykłą klasę publiczną, która jednocześnie dziedziczy Seat
klasę wewnętrzną?
// Error! No enclosing instance of type 'Bicycle' is in scope
class SportSeat extends Bicycle.Seat {
public SportSeat() {
}
public void up() {
System.out.println("Seat up!");
}
public void down() {
System.out.println("Seat down!");
}
}
Mamy błąd! Czy możesz zgadnąć dlaczego? :) Wszystko jest proste. Kiedy mówiliśmy o Bicycle.Seat
klasie wewnętrznej, wspomnieliśmy, że odwołanie do instancji klasy zewnętrznej jest niejawnie przekazywane do konstruktora klasy wewnętrznej. Oznacza to, że nie można utworzyć Seat
obiektu bez utworzenia Bicycle
obiektu. Ale co z tworzeniem SportSeat
? W przeciwieństwie do Seat
, nie ma tego wbudowanego mechanizmu do niejawnego przekazywania konstruktorowi odniesienia do instancji klasy zewnętrznej. Do tej pory bez Bicycle
przedmiotu nie możemy stworzyć SportSeat
przedmiotu, tak jak w przypadku Seat
. Dlatego pozostaje nam tylko jedno — jawnie przekazać konstruktorowi SportSeat
referencję do Bicycle
obiektu. Oto jak to zrobić:
class SportSeat extends Bicycle.Seat {
public SportSeat(Bicycle bicycle) {
bicycle.super();
}
public void up() {
System.out.println("Seat up!");
}
public void down() {
System.out.println("Seat down!");
}
}
Konstruktor nadklasy wywołujemy za pomocą super();
Now, jeśli chcemy stworzyć SportSeat
obiekt, nic nas przed tym nie powstrzyma:
public class Main {
public static void main(String[] args) {
Bicycle bicycle = new Bicycle("Peugeot", 120);
SportSeat peugeotSportSeat = new SportSeat(bicycle);
}
}
Uff! Ta lekcja była dość długa :) Ale dużo się nauczyłeś! Teraz nadszedł czas, aby rozwiązać kilka zadań! :)
GO TO FULL VERSION