Sử dụng từ khóa “super”

Kế thừa

Từ khóa super đại diện cho lớp cha và có thể sử dụng để gọi tới phương thức cũng như khởi tạo tử của lớp cha.

Hãy bắt đầu bằng những câu hỏi: Một lớp con kế thừa những trường dữ liệu truy xuất được cũng như các phương thức của lớp cha của nó. Vậy liệu lớp con có kế thừa khởi tạo tử của lớp cha của nó hay không? Liệu khởi tạo tử của lớp cha có thể được gọi từ trong lớp con của nó hay không?

Bài viết này trả lời những thắc mắc đó cũng như các vấn đề kéo theo.

Ta hãy nhớ lại rằng, từ khóa this tham chiếu tới bản thân đối tượng, và khi được dùng trong một khởi tạo tử, nó gọi tới khởi tạo tử của cùng một lớp. Tương tự như thế, từ khóa super tham chiếu tới lớp cha của class mà từ khóa super xuất hiện. Và nó được sử dụng theo hai cách:

  • Để gọi tới khởi tọa tử ở lớp cha.
  • Để gọi tới phương thức ở lớp cha.

Gọi tới những khởi tạo tử ở lớp cha

Một khởi tạo tử được sử dụng để xây dựng nên một thực thể của một lớp (một đối tượng của lớp đối tượng đó). Không giống như thuộc tính và phương thức, khởi tạo tử không được thừa kế. Chúng chỉ có thể được truy cập từ trong khởi tạo tử của lớp con bằng từ khóa super.

Cú pháp là super() hoặc super(parameters).

Câu lệnh super() gọi tới khởi tạo tử không có tham số của lớp cha, và câu lệnh super(arguments) gọi tới khởi tạo tử của lớp cha mà có bộ các parameter tương xứng với bộ các argument. Cả hai lệnh trên đều cần phải là dòng lệnh đầu tiên trong khởi tạo tử, và đó là cách duy nhất để gọi tường minh khởi tạo tử của lớp cha. Ví dụ, khởi tạo tử của lớp Circle trong bài đọc trước có thể được viết lại như sau:

public Circle(double radius, String color, String filled) {
    super(color, filled);
    this.radius = radius;
}

Chuỗi khởi tạo tử

Lỗi thực thi có thể xảy ra nếu thực thể của lớp được tạo ra mà không có một khởi tạo tử nào của lớp cha được thực thi (một số thuộc tính có thể sẽ không được cài đặt đúng đắn chẳng hạn). Bởi vậy mà Java có cơ chế gọi ngầm định khởi tạo tử của lớp cha như là câu lệnh đầu tiên trong khởi tạo tử. Nói cách khác, những khối mã sau hoàn toàn tương đương nhau:

và:

Trong bất kỳ trường hợp nào, quá trình dựng nên một thực thể của bất kỳ lớp đối tượng nào sẽ gọi tới khởi tạo tử của tất cả các lớp cha theo dây chuyền thừa kế. Khi dựng đối tượng của lớp con, khởi tạo tử của lớp con đầu tiên sẽ gọi thực thi khởi tạo tử của lớp cha của nó trước khi thực thi những lệnh của riêng nó. Nếu lớp cha được kế thừa từ một lớp khác nữa, hành động trên sẽ được gọi đệ quy cho tới khi khởi tạo tử cuối cùng trong chuỗi thừa kế đã được gọi thực thi. Quy tắc thực thi này được gọi là “chuỗi khởi tạo”.

Trong hàm main của class Faculty dưới đây, chúng ta cố dựng một thực thể của class đó, hãy xem chuỗi khởi tạo tử đã được gọi thực thi như thế nào:

class Faculty extends Employee {
    public Faculty() {
        System.out.println("Faculty performing its tasks");
    }

    public static void main(String[] args) {
        new Faculty();
    }
}

class Employee extends Person {
    public Employee() {
        this("Employee overloading its tasks");
        System.out.println("Employee performing its tasks!");
    }

    public Employee(String s) {
        System.out.println(s);
    }
}

class Person {
    public Person() {
        System.out.println("Person performing its tasks!");
    }
}

Kết quả:

Person performing its tasks!
Employee overloading its tasks
Employee performing its tasks!
Faculty performing its tasks

Đầu ra cho thấy đối tượng được dựng từ những lớp ở rất xa trong chuỗi kế thừa. Tại sao lại vậy? Hãy theo dấu mũi tên trong lưu đồ sau đây:

Hệ quả

Nếu một lớp được thiết kế để kế thừa cho lớp khác, tốt hơn hết nên viết ra một khởi tạo tử không có tham số để tránh lỗi thực thi xảy ra khi chuỗi khởi tạo tử được gọi lên. Hãy xem xét mã sau đây:

public class Apple extends Fruit {
}

class Fruit {
    public Fruit(String name) {
        System.out.println("Fruit's constructor invoked");
    }
}

Ở class Apple, bởi vì không có khởi tạo tử nào được viết tường minh, lớp Apple sẽ ngầm định có một khởi tạo tử không có tham số. Khởi tạo tử đó ngầm định gọi tới khởi tạo tử không có tham số của lớp cha là lớp Fruit, nhưng bởi vì Fruit có tường minh một khởi tạo tử có tham số, vậy nên Fruit không có khởi tạo tử không có tham số nào, điều này sẽ gây ra lỗi khi compile. Chương trình này thậm chí còn không thể build được.

Gọi tới những phương thức của lớp cha

Giả sử lớp Ai được kế thừa từ lớp A, và lớp A có một phương thức kế thừa được tên là aM, vậy trong lớp Ai ta có thể gọi tới phương thức đó chỉ bằng tên, hoặc gọi tường minh “phương thức aM của lớp cha” bằng cú pháp super.aM().

Tuy nhiên, nếu lớp Ai đã tự có phương thức aM của chính nó, vậy thì trong lớp Ai, lời gọi this.aM() và super.aM() là gọi tới hai phương thức khác nhau, và ta phải chọn đúng super hay this.

Bạn hãy tự viết ra mã để kiểm nghiệm phỏng đoán trên.


Hãy tham gia nhóm Học lập trình để thảo luận thêm về các vấn đề cùng quan tâm.

Leave a Reply

Your email address will not be published. Required fields are marked *