Bună! Astăzi ne vom uita la un mecanism important: moștenirea în clase imbricate. Te-ai gândit vreodată ce ai face dacă ai avea nevoie ca o clasă imbricată să moștenească o altă clasă. Dacă nu, credeți-mă: această situație poate fi confuză, pentru că există o mulțime de nuanțe.
  1. Facem ca o clasă imbricată să moștenească o clasă? Sau facem ca o clasă să moștenească o clasă imbricată?
  2. Este clasa copil/părinte o clasă publică obișnuită sau este și o clasă imbricată?
  3. În sfârșit, ce tip de clase imbricate folosim în toate aceste situații?
Există atât de multe răspunsuri posibile la toate aceste întrebări, capul tău se va învârti :) După cum știi, putem rezolva o problemă complexă împărțind-o în părți mai simple. Hai să facem asta. Să luăm în considerare fiecare grup de clase imbricate pe rând din două perspective: cine poate moșteni fiecare tip de clasă imbricată și cine poate moșteni. Să începem cu clase imbricate statice.

Clase imbricate statice

Exemple de moștenire de clase imbricate - 2Regulile lor de moștenire sunt cele mai simple. Aici poți face aproape orice îți dorește inima. O clasă imbricată statică poate moșteni:
  • o clasă obișnuită
  • o clasă imbricată statică care este declarată într-o clasă exterioară sau strămoșii acesteia
Amintiți-vă un exemplu din lecția noastră despre clasele imbricate statice.

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;
       }
   }
}
Să încercăm să schimbăm codul și să creăm o Drawingclasă imbricată statică și descendentul acesteia — 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;
       }
   }
}
După cum puteți vedea, nicio problemă. Putem chiar să scoatem Drawingclasa și să o transformăm într-o clasă publică obișnuită în loc de o clasă imbricată statică - nimic nu se va schimba.

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;
       }
   }
}
Înțelegem asta. Dar ce clase pot moșteni o clasă imbricată statică? Practic orice! Imbricat/neimbricat, static/non-static — nu contează. Aici facem ca Boeing737Drawingclasa interioară să moștenească Drawingclasa imbricată statică:

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;
       }
   }
}
Puteți crea o instanță Boeing737Drawingca aceasta:

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());

   }

}
Deși Boeing737Drawingclasa noastră moștenește o clasă statică, nu este static în sine! Ca rezultat, va avea întotdeauna nevoie de o instanță a clasei exterioare. Putem elimina Boeing737Drawingclasa din Boeing737clasă și o putem face o clasă publică simplă. Nimic nu se schimba. Încă poate moșteni Drawingclasa imbricată statică.

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;
   
}
Singurul punct important este că în acest caz trebuie să facem maxPassengersCountpublică variabila statică. Dacă rămâne privată, atunci o clasă publică obișnuită nu va avea acces la ea. Ne-am dat seama de clase statice! :) Acum să trecem la clasele interioare. Ele vin în 3 tipuri: clase interioare simple, clase locale și clase interioare anonime. Exemple de moștenire de clase imbricate - 3Din nou, să trecem de la simplu la complex :)

Clasele interioare anonime

O clasă interioară anonimă nu poate moșteni o altă clasă. Nicio altă clasă nu poate moșteni o clasă anonimă. Mai simplu nu poate fi! :)

Clasele locale

Clasele locale (în cazul în care ați uitat) sunt declarate în interiorul unui bloc de cod al altei clase. Cel mai adesea, acest lucru se întâmplă în interiorul unei metode din clasa exterioară. În mod logic, doar alte clase locale din cadrul aceleiași metode (sau bloc de cod) pot moșteni o clasă locală. Iată un exemplu:

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
   }
}
Acesta este codul din lecția noastră despre orele locale. Clasa noastră de validare a numărului are o PhoneNumberclasă locală. Dacă avem nevoie de el pentru a reprezenta două entități distincte, de exemplu, un număr de telefon mobil și un număr de telefon fix, putem face acest lucru doar în cadrul aceleiași metode. Motivul este simplu: domeniul de aplicare al unei clase locale este limitat la metoda (blocul de cod) în care este declarată. Ca urmare, nu vom putea să-l folosim extern (inclusiv pentru moștenirea clasei). Cu toate acestea, posibilitățile de moștenire în cadrul clasei locale sunt mult mai largi! O clasă locală poate moșteni:
  1. O clasă obișnuită.
  2. O clasă interioară care este declarată în aceeași clasă cu clasa locală sau într-unul dintre strămoșii săi.
  3. O altă clasă locală declarată în aceeași metodă (bloc de cod).
Primul și al treilea punct par evidente, dar al doilea este puțin confuz :/ Să ne uităm la două exemple. Exemplul 1 — „Facerea unei clase locale să moștenească o clasă interioară declarată în aceeași clasă cu clasa locală”:

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
   }
}
Aici am eliminat PhoneNumberclasa din validatePhoneNumber()metodă și am făcut-o o clasă interioară în loc de o clasă locală. Acest lucru nu ne împiedică să facem ca cele 2 clase locale să o moștenească. Exemplul 2 — „... sau în strămoșii acestei clase”. Acum acest lucru este deja mai interesant. Ne putem deplasa PhoneNumberși mai sus în lanțul moștenirii. Să declarăm o AbstractPhoneNumberValidatorclasă abstractă, care va deveni strămoșul PhoneNumberValidatorclasei noastre:

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;
       }
   }

}
După cum puteți vedea, nu doar am declarat-o, ci am mutat și PhoneNumberclasa interioară în ea. Totuși, în descendentul său PhoneNumberValidator, clasele locale declarate în metode pot moșteni PhoneNumberfără nicio problemă!

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
   }
}
Datorită relației de moștenire, clasele locale din interiorul unei clase descendente „văd” clasele interioare din interiorul unui strămoș. Și, în sfârșit, să trecem la ultimul grup :)

Clasele interioare

O clasă interioară declarată în aceeași clasă exterioară (sau în descendentul acesteia) poate moșteni o altă clasă interioară. Să explorăm acest lucru folosind exemplul nostru cu bicicletele din lecția despre orele interioare.

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
   }
}
Aici am declarat Seatclasa interioară în interiorul Bicycleclasei. Un tip special de scaun de curse, SportSeat, îl moștenește. Dar, am putea crea un tip separat de „bicicletă de curse” și să-l punem într-o clasă separată:

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!");
       }
   }
}
Aceasta este, de asemenea, o opțiune. Clasa interioară a descendentului ( SportBicycle.SportSeat) „vede” clasele interioare ale strămoșului și le poate moșteni. Moștenirea claselor interioare are o caracteristică foarte importantă! În cele două exemple anterioare, SportSeatclasa noastră a fost o clasă interioară. Dar dacă decidem să facem SportSeato clasă publică obișnuită care moștenește simultan Seatclasa interioară?

// 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!");
   }
}
Avem o eroare! Poți ghici de ce? :) Totul este simplu. Când am vorbit despre Bicycle.Seatclasa interioară, am menționat că o referință la o instanță a clasei exterioare este trecută implicit către constructorul clasei interioare. Aceasta înseamnă că nu puteți crea un Seatobiect fără a crea un Bicycleobiect. Dar cum rămâne cu crearea unui SportSeat? Spre deosebire de Seat, nu are acest mecanism încorporat pentru a trece implicit constructorului o referință la o instanță a clasei exterioare. Totuși, fără un Bicycleobiect, nu putem crea un SportSeatobiect, la fel ca în cazul lui Seat. Prin urmare, ne rămâne un singur lucru de făcut - transmiteți în mod explicit constructorului SportSeato referință la un Bicycleobiect. Iată cum să o faci:

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!");
   }
}
Numim constructorul de superclasă folosind super(); Now, dacă vrem să creăm un SportSeatobiect, nimic nu ne va împiedica să facem asta:

public class Main {

   public static void main(String[] args) {

       Bicycle bicycle = new Bicycle("Peugeot", 120);
       SportSeat peugeotSportSeat = new SportSeat(bicycle);

   }
}
Pf! Această lecție a fost destul de lungă :) Dar ai învățat multe! Acum este timpul să rezolvi niște sarcini! :)