JavaRush /Blog Java /Random-VI /Kế thừa các lớp lồng nhau

Kế thừa các lớp lồng nhau

Xuất bản trong nhóm
Xin chào! Hôm nay chúng ta sẽ xem xét hoạt động của một cơ chế quan trọng - kế thừa trong các lớp lồng nhau. Tôi không biết liệu bạn đã bao giờ nghĩ về việc mình sẽ làm khi cần kế thừa một lớp lồng nhau từ một lớp khác hay chưa. Nếu không, hãy tin tôi: tình huống này có thể gây nhầm lẫn vì có rất nhiều sắc thái ở đây:
  1. Chúng ta kế thừa một lớp lồng nhau từ một lớp nào đó hay chúng ta kế thừa một lớp khác từ một lớp lồng nhau?
  2. Lớp kế thừa/kế thừa có phải là lớp công khai thông thường hay cũng là lớp lồng nhau?
  3. Cuối cùng, chính xác thì chúng ta đang sử dụng loại lớp lồng nhau nào trong tất cả các tình huống này?
Nếu bạn trả lời tất cả những câu hỏi này, sẽ có rất nhiều câu trả lời khả dĩ khiến đầu bạn quay cuồng :) Như bạn đã biết, để giải một bài toán phức tạp, bạn cần chia nó thành những phần đơn giản hơn. Đó là những gì chúng tôi sẽ làm. Chúng ta hãy lần lượt xem xét từng nhóm lớp lồng nhau từ hai góc độ: ai có thể kế thừa từ loại lớp lồng nhau này và nó có thể kế thừa từ ai. Hãy bắt đầu với các lớp lồng nhau tĩnh.

Các lớp lồng nhau tĩnh

Ví dụ về kế thừa của các lớp bên trong - 2Quy tắc kế thừa của họ là đơn giản nhất. Ở đây bạn có thể làm hầu hết mọi thứ mà trái tim bạn mong muốn. Một lớp lồng tĩnh có thể được kế thừa từ:
  • lớp học bình thường
  • một lớp lồng tĩnh được khai báo ở lớp bên ngoài hoặc lớp tổ tiên của nó
Hãy nhớ lại ví dụ trong bài giảng về các lớp lồng nhau tĩnh.
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;
       }
   }
}
Hãy thử thay đổi mã và tạo một lớp lồng tĩnh Drawingvà lớp con của nó - 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;
       }
   }
}
Như bạn có thể thấy, không có vấn đề gì. Chúng ta có thể loại bỏ hoàn toàn lớp đó Drawingvà biến nó thành lớp công khai thông thường thay vì lớp lồng tĩnh - sẽ không có gì thay đổi.
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;
       }
   }
}
Thế là xong. Và những lớp nào có thể kế thừa từ một lớp lồng nhau tĩnh? Gần như! Lồng nhau/thông thường, tĩnh/không tĩnh - không thành vấn đề. Ở đây chúng ta kế thừa lớp bên trong Boeing737Drawingtừ lớp lồng tĩnh 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;
       }
   }
}
Bạn có thể tạo một thể hiện Boeing737Drawingnhư thế này:
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());

   }

}
Mặc dù lớp của chúng ta Boeing737Drawingkế thừa từ một lớp tĩnh nhưng bản thân nó không phải là lớp tĩnh! Vì vậy nó sẽ luôn cần một thể hiện của lớp bên ngoài. Chúng ta có thể đưa lớp Boeing737Drawingra khỏi lớp Boeing737và biến nó thành một lớp công khai. Sẽ không có gì thay đổi - nó cũng có thể kế thừa từ một tệp 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;

}
Điểm quan trọng duy nhất: trong trường hợp này chúng ta cần đặt biến tĩnh ở chế độ maxPassengersCountcông khai. Nếu nó vẫn ở chế độ riêng tư, lớp công khai thông thường sẽ không có quyền truy cập vào nó. Chúng tôi đã sắp xếp các lớp tĩnh! :) Bây giờ hãy chuyển sang các lớp bên trong. Như bạn nhớ, có 3 loại trong số chúng: lớp bên trong đơn giản, lớp cục bộ và lớp bên trong ẩn danh. Ví dụ về kế thừa của các lớp bên trong - 3Một lần nữa, hãy chuyển từ đơn giản đến phức tạp :)

Các lớp bên trong ẩn danh

Một lớp bên trong ẩn danh không thể kế thừa từ một lớp khác. Không có lớp nào khác có thể kế thừa từ một lớp ẩn danh. Nó không thể đơn giản hơn! :)

Các lớp học địa phương

Các lớp cục bộ (trong trường hợp bạn quên) được khai báo bên trong khối mã của lớp khác. Thông thường nhất - bên trong một số phương thức của lớp bên ngoài này. Điều hợp lý là chỉ các lớp cục bộ khác trong cùng một phương thức (hoặc khối) mới có thể kế thừa từ một lớp cục bộ. Đây là một ví dụ:
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 {


       }

       //...code валидации номера
   }
}
Đây là mã từ bài giảng của chúng tôi về các lớp học địa phương. Bên trong lớp trình xác thực số, chúng ta có một lớp địa phương PhoneNumber- số điện thoại. Nếu vì mục đích của chúng tôi, chúng tôi cần tách hai thực thể riêng biệt khỏi nó, ví dụ: số điện thoại di động và số điện thoại cố định, chúng tôi chỉ có thể thực hiện việc này trong cùng một phương pháp. Lý do rất đơn giản: phạm vi của một lớp cục bộ nằm trong phương thức (khối) nơi nó được khai báo. Do đó, chúng tôi sẽ không thể sử dụng nó ở bên ngoài bằng cách nào đó (bao gồm cả để kế thừa). Tuy nhiên, bản thân lớp địa phương có khả năng kế thừa rộng hơn! Một lớp cục bộ có thể kế thừa từ:
  1. Lớp học bình thường.
  2. Một lớp bên trong được khai báo cùng lớp với lớp cục bộ hoặc lớp tổ tiên của nó.
  3. Từ một lớp cục bộ khác được khai báo trong cùng một phương thức (khối).
Điểm thứ nhất và thứ ba có vẻ rõ ràng, nhưng điểm thứ hai hơi khó hiểu:/Hãy xem hai ví dụ. Ví dụ 1 - “kế thừa một lớp cục bộ từ một lớp bên trong được khai báo cùng lớp với lớp cục bộ”:
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);
           }
       }

       //...code валидации номера
   }
}
Ở đây chúng ta đã loại bỏ lớp này PhoneNumberkhỏi phương thức validatePhoneNumber()và biến nó thành nội bộ thay vì cục bộ. Điều này không ngăn cản chúng ta kế thừa 2 lớp cục bộ từ nó. Ví dụ 2 – “…hoặc ở tổ tiên của lớp này.” Đây là nơi nó trở nên thú vị hơn. Chúng ta có thể đưa nó PhoneNumberlên cao hơn nữa trong chuỗi kế thừa. Hãy khai báo một lớp trừu tượng AbstractPhoneNumberValidatorsẽ trở thành tổ tiên của chúng ta 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;
       }
   }

}
Như bạn có thể thấy, chúng tôi không chỉ khai báo nó mà còn chuyển lớp bên trong vào đó PhoneNumber. Tuy nhiên, trong lớp con của nó - PhoneNumberValidator- các lớp cục bộ trong các phương thức có thể kế thừa từ 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);
           }
       }

       //...code валидации номера
   }
}
Nhờ sự kết nối thông qua kế thừa, các lớp cục bộ bên trong lớp con cháu “nhìn thấy” các lớp bên trong bên trong lớp tổ tiên. Và cuối cùng, hãy chuyển sang nhóm cuối cùng :)

Các lớp bên trong

Một lớp bên trong có thể được kế thừa bởi một lớp bên trong khác được khai báo trong cùng lớp bên ngoài (hoặc lớp con của nó). Hãy xem xét điều này bằng ví dụ về chiếc xe đạp trong bài giảng về lớp học bên trong.
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("Go!");
   }

   class Seat {

       public void up() {

           System.out.println("Сидение поднято выше!");
       }

       public void down() {

           System.out.println("Сидение опущено ниже!");
       }
   }

   class SportSeat extends Seat {

       //...methods
   }
}
BicycleỞ đây chúng ta đã khai báo một lớp nội bộ bên trong lớp Seat- ghế. Một loại ghế đua đặc biệt đã được kế thừa từ nó - SportSeat. Tuy nhiên, chúng ta có thể tạo một loại "xe đạp đua" riêng và đặt nó vào một lớp riêng:
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("Сидение опущено ниже!");
       }
   }
}
Điều này cũng có thể. Lớp bên trong của đứa trẻ ( SportBicycle.SportSeat) “thấy” các lớp bên trong của tổ tiên và có thể kế thừa từ chúng. Kế thừa từ các lớp bên trong có một tính năng rất quan trọng! Trong hai ví dụ trước chúng ta SportSeatcó nội bộ. Nhưng điều gì sẽ xảy ra nếu chúng ta quyết định biến nó thành SportSeatmột lớp public thông thường, cũng kế thừa từ lớp bên trong 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("Сидение опущено ниже!");
   }
}
Chúng tôi đã gặp lỗi! Bạn có đoán được nó được kết nối với cái gì không? :) Thật đơn giản. Khi nói về lớp bên trong Bicycle.Seat, chúng ta đã đề cập rằng hàm tạo của lớp bên trong ngầm chuyển một tham chiếu đến một đối tượng của lớp bên ngoài. Vì vậy, không tạo đối tượng thì Bicyclebạn không thể tạo đối tượng Seat. Còn sự sáng tạo thì sao SportSeat? Nó không có cùng cơ chế tích hợp để truyền ngầm một tham chiếu đến một đối tượng lớp bên ngoài trong hàm tạo như trong Seat. Tuy nhiên, nếu không có object Bicycle, giống như trường hợp Seat, chúng ta không thể tạo object SportSeat. Do đó, chúng ta chỉ còn một việc phải làm - chuyển một cách rõ ràng SportSeatmột tham chiếu đến đối tượng tới hàm tạo. BicycleĐây là cách nó được thực hiện:
class SportSeat extends Bicycle.Seat {

   public SportSeat(Bicycle bicycle) {

       bicycle.super();
   }

   public void up() {

       System.out.println("Сидение поднято выше!");
   }

   public void down() {

       System.out.println("Сидение опущено ниже!");
   }
}
Để làm điều này, chúng tôi sử dụng một từ đặc biệt super(); Bây giờ, nếu chúng tôi muốn tạo một đối tượng SportSeat, sẽ không có gì ngăn cản chúng tôi làm điều này:
public class Main {

   public static void main(String[] args) {

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

   }
}
Phù, bài giảng hóa ra khá dài :) Nhưng bạn đã học được rất nhiều điều mới! Bây giờ là lúc để giải quyết một số vấn đề! :)
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION