CHÀO! Hôm nay chúng ta sẽ xem xét một cơ chế quan trọng: kế thừa trong các lớp lồng nhau. Bạn đã bao giờ nghĩ về những gì bạn sẽ làm nếu bạn cần tạo một lớp lồng nhau kế thừa một số lớp khác 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, bởi vì có rất nhiều sắc thái.
Quy 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ể kế thừa:
Một lần nữa, hãy chuyển từ đơn giản sang phức tạp :)
- Chúng ta có đang tạo một lớp lồng nhau kế thừa một số lớp không? Hay chúng ta đang làm cho một số lớp kế thừa một lớp lồng nhau?
- Lớp con/lớp cha là lớp công khai bình thường hay nó cũng là lớp lồng nhau?
- Cuối cùng, loại lớp lồng nhau nào chúng ta sử dụng trong tất cả các tình huống này?
Các lớp lồng nhau tĩnh

- một lớp học bình thường
- một lớp lồng tĩnh được khai báo trong một lớp bên ngoài hoặc tổ tiên của nó
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 Drawing
lớp lồng nhau tĩnh và hậu duệ 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 thậm chí có thể kéo Drawing
lớp đó ra và biến nó thành một lớp công khai bình thường thay vì một lớp lồng nhau 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;
}
}
}
Chúng tôi hiểu điều này. Nhưng những lớp nào có thể kế thừa một lớp lồng tĩnh? Thực tế là bất kỳ! Lồng/không lồng, tĩnh/không tĩnh — không thành vấn đề. Ở đây chúng ta làm cho Boeing737Drawing
lớp bên trong kế thừa Drawing
lớp lồng 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 class Boeing737Drawing extends Drawing {
public int getMaxPassengersCount() {
return maxPassengersCount;
}
}
}
Bạn có thể tạo một ví dụ Boeing737Drawing
như 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ù Boeing737Drawing
lớp của chúng ta kế thừa một lớp tĩnh nhưng bản thân nó không tĩnh! Kết quả là, nó sẽ luôn cần một thể hiện của lớp bên ngoài. Chúng ta có thể xóa Boeing737Drawing
lớp khỏi Boeing737
lớp và biến nó thành một lớp công khai đơn giản. Không có gì thay đổi. Nó vẫn có thể kế thừa Drawing
lớp lồng nhau tĩnh.
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 là trong trường hợp này, chúng ta cần đặt maxPassengersCount
biến tĩnh ở chế độ công khai. Nếu nó vẫn ở chế độ riêng tư, thì một lớp công khai bình thường sẽ không có quyền truy cập vào nó. Chúng tôi đã tìm ra các lớp tĩnh! :) Bây giờ hãy chuyển sang các lớp bên trong. Chúng có 3 loại: lớp bên trong đơn giản, lớp cục bộ và lớp bên trong ẩn danh. 
Các lớp bên trong ẩn danh
Một lớp bên trong ẩn danh không thể kế thừa một lớp khác. Không có lớp nào khác có thể kế thừa một lớp ẩn danh. Nó không thể đơn giản hơn! :)lớp đị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 một khối mã của lớp khác. Thông thường, điều này xảy ra bên trong một số phương thức của lớp bên ngoài. Về mặt logic, chỉ các lớp cục bộ khác bên trong cùng một phương thức (hoặc khối mã) mới có thể kế thừa 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 {
}
// ...number validation code
}
}
Đây là đoạn mã từ bài học của chúng ta về các lớp cục bộ. Lớp trình xác nhận số của chúng tôi có một PhoneNumber
lớp cục bộ. Nếu chúng ta cần nó để đại diện cho hai thực thể riêng biệt, ví dụ: số điện thoại di động và số điện thoại cố định, thì chúng ta chỉ có thể thực hiện việc này trong cùng một phương thức. Lý do rất đơn giản: phạm vi của một lớp cục bộ được giới hạn ở phương thức (khối mã) nơi nó được khai báo. Do đó, chúng tôi sẽ không thể sử dụng nó bên ngoài (bao gồm cả kế thừa lớp). Tuy nhiên, khả năng kế thừa trong chính lớp cục bộ rộng hơn nhiều! Một lớp cục bộ có thể kế thừa:
- Một lớp học bình thường.
- Một lớp bên trong được khai báo trong cùng một lớp với lớp cục bộ hoặc trong một trong các tổ tiên của nó.
- Một lớp cục bộ khác được khai báo trong cùng một phương thức (khối mã).
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
}
}
Ở đây chúng tôi đã loại bỏ PhoneNumber
lớp khỏi validatePhoneNumber()
phương thức và biến nó thành lớp bên trong thay vì lớp cục bộ. Điều này không ngăn chúng ta làm cho 2 lớp cục bộ của chúng ta kế thừa nó. Ví dụ 2 - "... hoặc trong tổ tiên của lớp này." Bây giờ điều này đã thú vị hơn. Chúng ta có thể tiến PhoneNumber
cao hơn nữa trong chuỗi thừa kế. Hãy khai báo một AbstractPhoneNumberValidator
lớp trừu tượng, lớp này sẽ trở thành tổ tiên của PhoneNumberValidator
lớp chúng ta:
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ó — chúng tôi còn chuyển PhoneNumber
lớp bên trong vào đó. Tuy nhiên, trong lớp kế thừa của nó PhoneNumberValidator
, các lớp cục bộ được khai báo trong các phương thức có thể kế thừa PhoneNumber
mà không gặp vấn đề gì!
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
}
}
Do mối quan hệ kế thừa, các lớp cục bộ bên trong lớp hậu duệ "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 tiến tới nhóm cuối cùng :)
lớp học bên trong
Một lớp bên trong được khai báo trong cùng một lớp bên ngoài (hoặc trong hậu duệ của nó) có thể kế thừa một lớp bên trong khác. Hãy khám phá điều này bằng cách sử dụng ví dụ về xe đạp trong bài học về các lớp học bên trong.
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
}
}
Ở đây chúng tôi đã khai báo Seat
lớp bên trong bên trong Bicycle
lớp. Một loại ghế đua đặc biệt, SportSeat
kế thừa nó. Tuy nhiên, chúng ta có thể tạo một loại "xe đạp đua" riêng biệt và đặt nó trong một lớp riêng biệt:
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!");
}
}
}
Đây cũng là một lựa chọn. Lớp bên trong của hậu duệ ( SportBicycle.SportSeat
) "nhìn thấy" các lớp bên trong của tổ tiên và có thể kế thừa chúng. Kế thừa 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, SportSeat
lớp của chúng tôi là một lớp bên trong. Nhưng điều gì sẽ xảy ra nếu chúng ta quyết định tạo SportSeat
một lớp công khai thông thường đồng thời kế thừa Seat
lớp bên trong?
// 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!");
}
}
Chúng tôi gặp lỗi! Bạn có đoán được tại sao không? :) Tất cả đều đơn giản. Khi chúng ta nói về Bicycle.Seat
lớp bên trong, chúng ta đã đề cập rằng một tham chiếu đến một thể hiện của lớp bên ngoài được chuyển hoàn toàn cho hàm tạo của lớp bên trong. Điều này có nghĩa là bạn không thể tạo Seat
đối tượng mà không tạo Bicycle
đối tượng. Nhưng những gì về việc tạo ra một SportSeat
? Không giống như Seat
, nó không có cơ chế tích hợp sẵn này để chuyển hoàn toàn hàm tạo tham chiếu đến một thể hiện của lớp bên ngoài. Cho đến khi không có Bicycle
đối tượng, chúng ta không thể tạo SportSeat
đối tượng, giống như trường hợp của Seat
. Do đó, chúng ta chỉ còn một việc phải làm — chuyển rõ ràng cho SportSeat
hàm tạo một tham chiếu đến một Bicycle
đối tượng. Đây là cách thực hiện:
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!");
}
}
Chúng ta gọi hàm tạo của lớp bậc trên bằng cách sử dụng super();
Bây giờ, nếu chúng ta muốn tạo một SportSeat
đối tượng, không gì có thể ngăn chúng ta 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 học này khá dài :) Nhưng bạn đã học được rất nhiều! Bây giờ là lúc để giải quyết một số nhiệm vụ! :)
GO TO FULL VERSION