1. Sử dụng super để gọi phương thức của lớp cơ sở
Khi bạn tạo một lớp con, đôi khi sẽ có tình huống bạn cần truy cập vào các trường hoặc phương thức của lớp cơ sở, đặc biệt nếu bạn đã ghi đè (override) hoặc “che khuất” (ẩn) chúng trong lớp con. Để làm điều đó trong Java có một từ khóa đặc biệt — super.
Nếu ví von, thì super giống như “mẹ ơi, giúp với!”, khi ở lớp con bạn muốn rõ ràng truy cập vào những gì được định nghĩa trong lớp cha.
Hãy tưởng tượng bạn có lớp Animal với phương thức eat(), nó chỉ in ra "Động vật đang ăn". Còn trong lớp Cat bạn muốn con mèo trước tiên làm điều gì đó của riêng nó (ví dụ kêu meo meo), rồi vẫn thực hiện hành vi chuẩn “động vật đang ăn”. Lúc này super.eat() sẽ hữu ích.
Khi trong lớp con bạn ghi đè một phương thức nhưng vẫn muốn bên trong gọi phần triển khai phương thức đó từ lớp cơ sở, hãy dùng super.imyaMetoda().
Ví dụ: mở rộng hành vi
class Animal {
void eat() {
System.out.println("Động vật đang ăn");
}
}
class Cat extends Animal {
@Override
void eat() {
System.out.println("Mèo ngửi thức ăn...");
super.eat(); // gọi phương thức eat() từ Animal
System.out.println("Mèo gừ gừ đầy thỏa mãn");
}
}
Nó hoạt động như thế nào?
- Khi gọi eat() trên đối tượng kiểu Cat, trước tiên sẽ thực thi mã trong Cat.eat(), tức là phương thức riêng của lớp con.
- Bên trong phương thức này, ta gọi rõ ràng super.eat(), tức là phần triển khai từ lớp cha Animal.
- Điều này cho phép thêm hành vi bổ sung mà không quên phần “của cha”.
Thực hành: dùng trong ứng dụng
Giả sử trong ứng dụng học tập của chúng ta có lớp cơ sở Animal và các lớp con Dog và Cat. Chúng ta muốn khi cho động vật ăn sẽ thực hiện cả hành vi chung (ví dụ tăng độ no) lẫn hành vi đặc thù cho từng loại.
class Animal {
int satiety = 0;
void eat() {
satiety += 10;
System.out.println("Động vật đang ăn. Độ no: " + satiety);
}
}
class Dog extends Animal {
@Override
void eat() {
System.out.println("Chó vẫy đuôi trước khi ăn");
super.eat();
}
}
Bây giờ, nếu gọi dog.eat(), bạn sẽ thấy cả hai thông báo và satiety sẽ tăng chính xác.
2. Sử dụng super để truy cập các trường của lớp cơ sở
Nếu trong lớp con bạn khai báo một trường cùng tên với trường trong lớp cha, nó sẽ “che khuất” trường của cha. Đôi khi cần truy cập trường gốc từ lớp cơ sở — khi đó dùng super.imyaPolya.
Ví dụ: che khuất (shadowing) trường
class Animal {
String name = "Động vật";
}
class Cat extends Animal {
String name = "Mèo";
void printNames() {
System.out.println("Tên từ Cat: " + name);
System.out.println("Tên từ Animal: " + super.name);
}
}
Lệnh new Cat().printNames(); sẽ in ra:
Tên từ Cat: Mèo
Tên từ Animal: Động vật
Trong thực tế, không khuyến khích “che khuất” trường trừ khi thật sự cần thiết, nhưng bạn nên biết về khả năng này.
3. Gọi hàm tạo của lớp cơ sở qua super(...)
Đối tượng được tạo trong hệ phân cấp như thế nào?
Khi bạn tạo một đối tượng của lớp con, trước tiên hàm tạo của lớp cơ sở sẽ được gọi, sau đó mới đến hàm tạo của lớp con. Điều này là cần thiết để mọi trường được khởi tạo đúng cách, vì lớp con “kế thừa” một phần trạng thái từ lớp cha.
Gọi rõ ràng hàm tạo của lớp cơ sở
Nếu lớp cơ sở có hàm tạo không tham số — mọi thứ thật đơn giản: Java sẽ tự gọi nó trước khi thực thi hàm tạo của lớp con. Nhưng nếu lớp cha không có hàm tạo không tham số, bạn buộc phải gọi rõ ràng hàm tạo cần thiết thông qua super(...).
Ví dụ:
class Animal {
String name;
Animal(String name) {
this.name = name;
System.out.println("Đã tạo động vật: " + name);
}
}
class Cat extends Animal {
Cat(String name) {
super(name); // bắt buộc! Không có hàm tạo Animal() không tham số
System.out.println("Đã tạo mèo: " + name);
}
}
Lệnh new Cat("Murka") sẽ in ra:
Đã tạo động vật: Murka
Đã tạo mèo: Murka
Quan trọng: Lời gọi hàm tạo của lớp cha qua super(...) phải là dòng đầu tiên trong hàm tạo của lớp con. Nếu bạn cố viết thứ gì đó trước lời gọi này, trình biên dịch sẽ không đồng ý và nhắc bạn về điều đó.
Nếu không gọi rõ ràng thì sao?
Nếu lớp cha chỉ có hàm tạo có tham số còn bạn không gọi nó rõ ràng qua super(...), trình biên dịch sẽ báo lỗi: "constructor Animal in class Animal cannot be applied to given types".
4. Những điểm hữu ích
Khi nào dùng super?
Để mở rộng chứ không thay thế hoàn toàn hành vi.
Đôi khi bạn không muốn thay thế toàn bộ hành vi của phương thức mà chỉ “mở rộng” nó. Ví dụ, thêm điều gì đó trước hoặc sau logic của lớp cha. Trong các trường hợp như vậy, hãy dùng super.imyaMetoda() trong thân phương thức đã ghi đè.
Để khởi tạo các trường kế thừa.
Nếu lớp cha có các trường bắt buộc phải khởi tạo (ví dụ, tên của động vật), hãy luôn gọi hàm tạo của lớp cha với các tham số cần thiết qua super(...).
Để truy cập các trường/phương thức bị che khuất.
Nếu vì lý do nào đó bạn đã “che khuất” một trường hoặc phương thức của lớp cha và vẫn cần truy cập nó, hãy dùng super.imyaPolya hoặc super.imyaMetoda().
Hạn chế và đặc điểm khi dùng super
- Gọi hàm tạo của lớp cha qua super(...) chỉ có thể thực hiện trong hàm tạo và phải là dòng đầu tiên.
- Không thể gọi hàm tạo của lớp cha ở ngoài hàm tạo của lớp con.
- Nếu không gọi super(...) một cách rõ ràng, Java sẽ cố gọi hàm tạo không tham số của lớp cha (nếu có).
- Từ khóa super không thể dùng trong các phương thức static — chỉ dùng trong phương thức không static (theo đối tượng) và trong các hàm tạo.
- Nếu phương thức hoặc trường của lớp cha là private, thì super cũng không giúp gì: không có quyền truy cập vào các thành viên private.
5. Ví dụ để củng cố
Ví dụ 1. Mở rộng phương thức bằng super
class Animal {
void makeSound() {
System.out.println("Động vật phát ra âm thanh");
}
}
class Dog extends Animal {
@Override
void makeSound() {
super.makeSound(); // trước hết thực hiện hành vi chuẩn
System.out.println("Chó sủa: Gâu gâu!");
}
}
Ví dụ 2. Gọi hàm tạo của lớp cơ sở
class Vehicle {
String brand;
Vehicle(String brand) {
this.brand = brand;
System.out.println("Phương tiện: " + brand);
}
}
class Car extends Vehicle {
int year;
Car(String brand, int year) {
super(brand); // gọi hàm tạo của lớp cha
this.year = year;
System.out.println("Xe hơi " + brand + ", năm: " + year);
}
}
Car car = new Car("Toyota", 2023);
// Kết quả:
// Phương tiện: Toyota
// Xe hơi Toyota, năm: 2023
Ví dụ 3. Lỗi kinh điển: quên gọi super(...)
class Animal {
String name;
Animal(String name) {
this.name = name;
}
}
class Cat extends Animal {
Cat() {
// super(); // Lỗi! Không có hàm tạo Animal() không tham số
// Cần gọi rõ ràng super(name)
super("Mèo không tên");
}
}
6. Các lỗi thường gặp khi làm việc với super
Lỗi số 1: Gọi super(...) không ở dòng đầu tiên của hàm tạo.
Java yêu cầu nghiêm ngặt rằng lời gọi hàm tạo của lớp cha qua super(...) phải là dòng đầu tiên trong hàm tạo của lớp con. Nếu bạn cố làm điều gì đó trước lời gọi này (ví dụ in ra thông báo), trình biên dịch sẽ báo lỗi.
Lỗi số 2: Không có hàm tạo phù hợp ở lớp cha.
Nếu lớp cơ sở không có hàm tạo không tham số còn bạn không gọi một hàm tạo khác qua super(...), trình biên dịch sẽ không thể sinh lời gọi mặc định và sẽ thông báo lỗi.
Lỗi số 3: Cố gắng truy cập các thành viên private của lớp cha thông qua super.
Từ khóa super không mang lại quyền truy cập “ma thuật” tới các trường hay phương thức private của lớp cha. Nếu thứ gì đó được khai báo là private, nó vẫn không thể truy cập từ lớp con.
Lỗi số 4: Che khuất trường và phương thức mà không hiểu rõ.
Nếu bạn khai báo trong lớp con một trường hoặc phương thức trùng tên với lớp cha và quên điều đó, có thể dẫn tới kết quả bất ngờ. Luôn nhớ rằng trong trường hợp như vậy, truy cập thành viên của lớp cha — chỉ qua super.
Lỗi số 5: Dùng super trong phương thức static.
Trong các phương thức static không thể dùng super, vì chúng không thuộc về một đối tượng cụ thể.
GO TO FULL VERSION