CodeGym /Blog Java /Random-PL /Przykłady dziedziczenia klas zagnieżdżonych
Autor
Oleksandr Miadelets
Head of Developers Team at CodeGym

Przykłady dziedziczenia klas zagnieżdżonych

Opublikowano w grupie Random-PL
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.
  1. Czy sprawiamy, że zagnieżdżona klasa dziedziczy jakąś klasę? A może sprawiamy, że jakaś klasa dziedziczy klasę zagnieżdżoną?
  2. Czy klasa dziecko/rodzic jest zwykłą klasą publiczną, czy też klasą zagnieżdżoną?
  3. Wreszcie, jakiego rodzaju klas zagnieżdżonych używamy we wszystkich tych sytuacjach?
Możliwych odpowiedzi na te wszystkie pytania jest tak wiele, że zakręci Ci się w głowie :) Jak wiesz, złożony problem można rozwiązać dzieląc go na prostsze części. Zróbmy to. Przyjrzyjmy się kolejno każdej grupie klas zagnieżdżonych z dwóch perspektyw: kto może dziedziczyć każdy typ klasy zagnieżdżonej i kto może dziedziczyć. Zacznijmy od statycznych klas zagnieżdżonych.

Statyczne klasy zagnieżdżone

Przykłady dziedziczenia klas zagnieżdżonych - 2Ich zasady dziedziczenia są najprostsze. Tutaj możesz zrobić prawie wszystko, czego dusza zapragnie. Statyczna klasa zagnieżdżona może dziedziczyć:
  • zwykła klasa
  • statyczna klasa zagnieżdżona zadeklarowana w klasie zewnętrznej lub jej przodkach
Przypomnij sobie przykład z naszej lekcji na temat statycznych klas 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 stworzyć Drawingstatycznie 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ąć Drawingklasę 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 Boeing737Drawingklasa wewnętrzna dziedziczy Drawingstatyczną 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 Boeing737Drawingnastę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 Boeing737Drawingklasa dziedziczy klasę statyczną, sama w sobie nie jest statyczna! W rezultacie zawsze będzie potrzebować instancji klasy zewnętrznej. Możemy usunąć Boeing737Drawingklasę z Boeing737klasy i uczynić ją prostą klasą publiczną. Nic się nie zmienia. Nadal może dziedziczyć Drawingstatyczną 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ć maxPassengersCountzmienną 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. Przykłady dziedziczenia klas zagnieżdżonych - 3Ponownie przejdźmy od prostych do złożonych :)

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 PhoneNumberklasę 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ć:
  1. Zwykła klasa.
  2. Klasa wewnętrzna zadeklarowana w tej samej klasie co klasa lokalna lub w jednym z jej przodków.
  3. Inna klasa lokalna zadeklarowana w tej samej metodzie (blok kodu).
Pierwszy i trzeci punkt wydają się oczywiste, ale drugi jest nieco mylący :/ Spójrzmy na dwa przykłady. Przykład 1 — „Sprawienie, aby klasa lokalna dziedziczyła klasę wewnętrzną zadeklarowaną 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);
           }
       }

       // ...number validation code
   }
}
Tutaj usunęliśmy PhoneNumberklasę 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ść PhoneNumberjeszcze wyżej w łańcuchu spadkowym. Zadeklarujmy AbstractPhoneNumberValidatorklasę abstrakcyjną, która stanie się przodkiem naszej PhoneNumberValidatorklasy:

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 PhoneNumberdo niej również klasę wewnętrzną. Jednak w swoim potomku PhoneNumberValidatorklasy lokalne zadeklarowane w metodach mogą dziedziczyć PhoneNumberbez ż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 Seatklasę wewnętrzną wewnątrz Bicycleklasy. SportSeatDziedziczy 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 SportSeatklasa była klasą wewnętrzną. Ale co, jeśli zdecydujemy się stworzyć SportSeatzwykłą klasę publiczną, która jednocześnie dziedziczy Seatklasę 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.Seatklasie 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ć Seatobiektu bez utworzenia Bicycleobiektu. 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 Bicycleprzedmiotu nie możemy stworzyć SportSeatprzedmiotu, tak jak w przypadku Seat. Dlatego pozostaje nam tylko jedno — jawnie przekazać konstruktorowi SportSeatreferencję do Bicycleobiektu. 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ć SportSeatobiekt, 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ń! :)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION