JavaRush /Blog Java /Random-PL /Dziedziczenie klas zagnieżdżonych

Dziedziczenie klas zagnieżdżonych

Opublikowano w grupie Random-PL
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:
  1. Czy dziedziczymy klasę zagnieżdżoną z jakiejś klasy, czy też inną klasę z zagnieżdżonej?
  2. Czy następca/dziedziczona jest zwykłą klasą publiczną, czy jest to również klasa zagnieżdżona?
  3. Wreszcie, jakiego dokładnie typu klas zagnieżdżonych używamy we wszystkich tych sytuacjach?
Jeśli odpowiesz na wszystkie te pytania, możliwych odpowiedzi będzie tak wiele, że zakręci Ci się w głowie :) Jak wiadomo, aby rozwiązać skomplikowany problem, trzeba go podzielić na prostsze części. To właśnie zrobimy. Przyjrzyjmy się kolejno każdej grupie klas zagnieżdżonych z dwóch perspektyw: kto może dziedziczyć po tego typu klasie zagnieżdżonej i od kogo może ona dziedziczyć. Zacznijmy od statycznych klas zagnieżdżonych.

Statyczne klasy zagnieżdżone

Przykłady dziedziczenia klas wewnętrznych - 2Ich 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
Przypomnijmy sobie przykład z wykładu o statycznych klasach zagnieżdżonych.
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ą Drawingoraz 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ę Drawingi 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ą Boeing737Drawingze 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;
       }
   }
}
Boeing737DrawingMoż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 Boeing737Drawingdziedziczy po klasie statycznej, sama w sobie nie jest statyczna! Dlatego zawsze będzie potrzebować instancji klasy zewnętrznej. Możemy wyjąć tę klasę Boeing737Drawingz klasy Boeing737i 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. Przykłady dziedziczenia klas wewnętrznych - 3Znó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:
  1. Regularne zajęcia.
  2. Klasa wewnętrzna zadeklarowana w tej samej klasie co klasa lokalna lub w jej przodkach.
  3. Z innej klasy lokalnej zadeklarowanej w tej samej metodzie (bloku).
Punkt pierwszy i trzeci wydają się oczywiste, natomiast drugi jest nieco mylący :/ Przyjrzyjmy się dwóm przykładom. Przykład 1 – „dziedziczenie klasy lokalnej z klasy wewnętrznej, która została zadeklarowana w tej samej klasie co klasa lokalna”:
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ę PhoneNumberz 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ę PhoneNumberjeszcze 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
   }
}
BicycleTutaj 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 SportSeatfunkcję internal. Ale co, jeśli zdecydujemy się uczynić z niej SportSeatzwykłą 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 Bicyclenie 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 SportSeatreferencji do obiektu . BicycleOto 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! :)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION