Được chỉnh sửa ngày 8/2/2023.
Chào mừng các bạn đã đến với bài học Java thứ 23, bài học về Tính phủ quyết (Overriding). Đây là bài viết trong chuỗi bài về lập trình ứng dụng Java của Yellow Code Books.
Với bài học hôm nay, mình sẽ bổ sung kiến thức tiếp theo trong phần kiến thức về kế thừa. Nếu như ở bài 21, các bạn đã biết cách thức sử dụng từ khóa extends để thể hiện sự kế thừa từ một lớp tới một lớp khác. Và khi đó, bạn cũng làm quen được việc tận dụng lại tất cả các giá trị từ một lớp cha (hay lớp cơ sở) để lại cho lớp con (hay lớp dẫn xuất), lúc đó mình gọi đây là sự dùng lại trong kế thừa. Hôm nay chúng ta sẽ xem đến một khía cạnh tiếp theo trong kế thừa, nó không còn ý nghĩa dùng lại nữa, mà là sự phủ quyết (overriding).
Tính Phủ Quyết (Overriding) Là gì?
Mình quen dùng từ phủ quyết, có thể là do thói quen từ thời còn sử dụng C++ hay C#. Sang đến ngôn ngữ Java mình thấy người ta hay gọi là ghi đè. Bạn cũng có thể không cần quan tâm đến hai từ tiếng Việt này, mà chỉ cần nhớ từ tiếng Anh của nó là overriding là được.
Như mình có nói ở trên, khi bạn làm quen với kế thừa, bạn biết rằng khái niệm này ám chỉ đặc tính dùng lại mà chúng ta đã tìm hiểu sơ qua ở bài 21, đặc tính này cho phép các lớp con có thể mặc định có được các phương thức và thuộc tính mà lớp cha của nó đã khai báo (và cho phép).
Vậy còn đặc tính phủ quyết? Đặc tính này cho phép lớp con có thể khai báo các phương thức trùng với các phương thức của lớp cha, rồi sau đó lớp con đó sẽ định nghĩa lại nội dung của phương thức đó. Nó mang ý nghĩa “không chấp nhận” phương thức đó của lớp cha. Chính vì ý nghĩa này mà người ta hay gọi là phủ quyết (hay ghi đè, hay overriding) là vậy.
Phủ Quyết (Overriding) Như Thế Nào?
Như đã nói ở trên, để thực hiện cho nhu cầu overriding, bạn chỉ cần đặt tên phương thức ở lớp con trùng tên và tham số truyền vào với phương thức đã có ở lớp cha. Sau đó, tuy không bắt buộc, nhưng bạn nên khai báo một annotation (đừng dịch annotation ra tiếng Việt) là @Override ở mỗi phương thức overriding để có sự rõ ràng về code. Với phương thức đã override này ở lớp con, bạn có thể thiết kế lại logic bên trong nó, một khi ở nơi nào đó gọi đến phương thức đó của lớp con, thay vì sử dụng phương thức của lớp cha theo nguyên lý kế thừa, thì phương thức của lớp con được sử dụng.
Chúng ta cùng xem ví dụ sau. Ví dụ sẽ sử dụng hai lớp HinhTron và HinhTru, trong đó HinhTru kế thừa từ HinhTron. Chúng ta hãy thử khai báo phương thức xuatThongTin() ở cả hai lớp, theo sơ đồ lớp sau.

Code của chúng cũng đơn giản như sau.
public class HinhTron { public void xuatThongTin() { System.out.println("Đây là Hình tròn"); } }
public class HinhTru extends HinhTron { @Override public void xuatThongTin() { System.out.println("Đây là Hình trụ"); } }
Với việc khai báo hai lớp như trên, thì bạn xem cách gọi đến chúng ở main() như thế nào nhé.
public class MainClass { public static void main(String[] args) { HinhTron hinhTron = new HinhTron(); HinhTru hinhTru = new HinhTru(); hinhTron.xuatThongTin(); hinhTru.xuatThongTin(); } }
Nếu thực thi chương trình, bạn sẽ thấy hai lệnh xuatThongTin() ở hai lớp sẽ in ra console như sau.
Đây là Hình tròn Đây là Hình trụ
Đó là bởi vì xuatThongTin() ở HinhTron đã bị override (hay bị phủ quyết, ghi đè) bởi xuatThongTin() ở HinhTru. Nếu bạn thử nghiệm bằng cách xóa xuatThongTin() ở HinhTru đi, bạn sẽ nhận được hai dòng in ra console như nhau, đó là bởi vì nếu không có sự override này, thì đặc tính dùng lại của kế thừa sẽ được tận dụng. Bạn đã hiểu khái niệm overriding rồi đúng không nào.
Có một ý mở rộng cho bạn. Đó là nếu bạn muốn phương thức overriding có dùng đến phương thức trùng nhau đó của lớp cha, thì bạn cứ mạnh dạn sử dụng từ khóa super như ví dụ dưới đây nhé.
public class HinhTru extends HinhTron { @Override public void xuatThongTin() { super.xuatThongTin(); System.out.println("Đây là Hình trụ"); } }
Thực Hành Kế Thừa (Có Tính Phủ Quyết)
Lý thuyết của overriding chỉ có như trên đây thôi. Dễ nhớ đúng không nào.
Giờ thì chúng ta thử tạo ra một mối quan hệ hoàn chỉnh giữa hai lớp HinhTron và HinhTru. Tất nhiên vẫn là quan hệ kế thừa, nhưng bạn sẽ thấy đầy đủ hai tính năng dùng lại và phủ quyết.
Bạn có thể tạo project mới cho bài thực hành này.
Chúng ta cùng nhìn qua sơ đồ lớp sau.

Ở lớp HinhTron, chúng ta không cần phương thức kêu người dùng nhập bán kính từ console như các bài thực hành trước nữa, mà hãy truyền bán kính này vào constructor luôn cho nó lẹ. Các phương thức tinhChuVi() và tinhDienTich() đều trả kết quả tính toán dựa vào bán kính có được từ constructor. Và cuối cùng, phương thức xuatThongTin() sẽ như ví dụ trên kia, phương thức này sẽ bị override bởi lớp HinhTru kế thừa sau đó.
public class HinhTron { public final float PI = 3.14f; public float banKinh; // Constructor public HinhTron(float banKinh) { this.banKinh = banKinh; } public float tinhChuVi() { return 2 * PI * banKinh; } public float tinhDienTich() { return PI * banKinh * banKinh; } public void xuatThongTin() { System.out.println("Đây là Hình tròn"); System.out.println("Hình tròn có Chu vi: " + tinhChuVi() + " và Diện tích: " + tinhDienTich()); } }
Ở lớp Hinhtru, lớp này kế thừa các thuộc tính PI và banKinh, các phương thức tinhChuVi() và tinhDienTich(). HinhTru khai báo thêm thuộc tính chieuCao và phương thức tinhTheTich(). Và cuối cùng, phương thức xuatThongTin() sẽ override lại so với lớp cha.
public class HinhTru extends HinhTron { public float chieuCao; // Constructor public HinhTru(float banKinh, float chieuCao) { super(banKinh); this.chieuCao = chieuCao; } public float tinhTheTich() { return tinhDienTich() * chieuCao; } @Override public void xuatThongTin() { System.out.println("Đây là Hình trụ"); System.out.println("Hình trụ có Thể tích: " + tinhTheTich()); } }
Xong. Bây giờ thì chúng ta sẽ sử dụng chúng ở main().
public class MainClass { public static void main(String[] args) { HinhTron hinhTron = new HinhTron(10); HinhTru hinhTru = new HinhTru(10, 20); hinhTron.xuatThongTin(); hinhTru.xuatThongTin(); } }
Và đây là kết quả khởi chạy chương trình.
Đây là Hình tròn Hình tròn có Chu vi: 62.800003 và Diện tích: 314.0 Đây là Hình trụ Hình trụ có Thể tích: 6280.0
Kết Luận
Chúng ta vừa xem qua một đặc tính nữa trong chuỗi kiến thức về OOP. Với kiến thức về overriding này, mình xem nó như một phần của sự kế thừa, nó nói về tính phủ quyết, bên cạnh tính dùng lại mà chúng ta đã làm quen từ bài học trước.
Cảm ơn bạn đã đọc các bài viết của Yellow Code Books. Bạn hãy ủng hộ blog bằng cách:
– Đánh giá 5 sao ở mỗi bài viết nếu thấy thích.
– Comment bên dưới mỗi bài viết nếu có thắc mắc.
– Để lại địa chỉ email của bạn ở thanh bên phải để nhận được thông báo sớm nhất khi có bài viết mới.
– Chia sẻ các bài viết của Yellow Code Books đến nhiều người khác.
– Ủng hộ blog theo hướng dẫn ở thanh bên phải để blog ngày càng phát triển hơn.
Bài Kế Tiếp
Chúng ta sẽ tìm hiểu về lớp Object, để xem lớp này có vai trò gì trong chuỗi kiến thức về OOP của chúng ta nhé.
Giả sử em muốn tạo một thuộc tính banKinh riêng cho HinhTru mà không muốn kế thừa từ lớp HinhTron thì k cần dùng super(banKinh); đúng k ạ ?
Em có thử vô hiệu dòng đó thì IDE báo lỗi là : “Implicit super constructor HinhTron() is undefined. Must explicitly invoke another constructor”
Anh có thể giải thích em tại sao có lỗi này k ạ ? Em cảm ơn nhiều
public class HinhTru extends HinhTron {
public float chieuCao;
public float banKinh;
// Constructor
public HinhTru(float banKinh, float chieuCao) {
// super(banKinh);
this.banKinh = banKinh;
this.chieuCao = chieuCao;
}
public float tinhTheTich() {
return tinhDienTich() * chieuCao;
}
@Override
public void xuatThongTin() {
System.out.println(“Đây là Hình trụ”);
System.out.println(“Hình trụ có Thể tích: ” + tinhTheTich());
}
}
same ideal