Ghi đè phương thức (thời gian chạy)
Thuộc phần Lập trình hướng đối tượng trong hành trình Java của Coddy. Bài học 29 trên 87.
Trong khi việc nạp chồng phương thức (method overloading) được xác định tại thời điểm biên dịch, ghi đè phương thức (method overriding) thể hiện tính đa hình lúc chạy (run-time polymorphism). Ở đây, Java quyết định phương thức nào sẽ chạy khi chương trình thực sự đang chạy, dựa trên kiểu thực tế của đối tượng thay vì kiểu được khai báo của biến.
Bạn đã học về việc ghi đè phương thức trong chương kế thừa. Điều khiến cách này trở nên đa hình là cách Java xử lý khi một tham chiếu đến lớp cha trỏ đến một đối tượng thuộc lớp con:
public class Animal {
public void speak() {
System.out.println("Some sound");
}
}
public class Dog extends Animal {
@Override
public void speak() {
System.out.println("Bark!");
}
}
public class Cat extends Animal {
@Override
public void speak() {
System.out.println("Meow!");
}
}Bây giờ, hãy quan sát điều gì xảy ra khi ta dùng các tham chiếu kiểu lớp cha:
Animal myPet = new Dog();
myPet.speak(); // Đầu ra: Bark!
myPet = new Cat();
myPet.speak(); // Đầu ra: Meow!Mặc dù myPet được khai báo là Animal, Java gọi phương thức đã ghi đè dựa trên kiểu thực tế của đối tượng khi chương trình chạy. Đây gọi là cơ chế điều phối phương thức động (dynamic method dispatch). JVM xem đối tượng thực sự thuộc kiểu nào, chứ không dựa vào kiểu mà biến khai báo.
Điều này cho phép bạn dùng các mẫu mạnh mẽ như xử lý đồng nhất nhiều đối tượng khác nhau:
Animal[] pets = {new Dog(), new Cat(), new Dog()};
for (Animal pet : pets) {
pet.speak(); // Mỗi đối tượng gọi phiên bản riêng của mình
}Đa hình lúc chạy (run-time polymorphism) giúp bạn viết code linh hoạt, hoạt động với các kiểu cha và tự động dùng hành vi phù hợp của lớp con.
Thử thách
DễHãy xây dựng một hệ thống thông báo để minh họa tính đa hình lúc chạy. Bạn sẽ tạo một hệ thống phân cấp các loại thông báo, trong đó cùng một lời gọi phương thức tạo ra đầu ra khác nhau tùy thuộc vào kiểu đối tượng thực tế lúc chạy.
Bạn sẽ sắp xếp code trong bốn tệp:
Notification.java: Tạo lớp cơ sở dùng chung cho tất cả thông báo. Mỗi thông báo có trườngrecipient(String). Thêm hàm khởi tạo để khởi tạo trường này, một phương thức getter và một phương thứcsend()để in ra:Sending notification to [recipient]EmailNotification.java: Tạo một lớp kế thừa từ Notification. Thông báo email có thêm trườngsubject. Dùngsuperđể khởi tạo lớp cha. Ghi đè phương thứcsend()để in ra:Emailing [recipient]: [subject]SMSNotification.java: Tạo một lớp khác kế thừa từ Notification. Thông báo SMS có thêm trườngphoneNumber. Ghi đèsend()để in ra:Texting [phoneNumber] for [recipient]Main.java: Đây là lúc tính đa hình lúc chạy phát huy tác dụng! Bạn sẽ nhận ba đầu vào: tên người nhận, tiêu đề email và số điện thoại. Tạo một mảng các tham chiếuNotificationchứa ba đối tượng theo thứ tự sau: một Notification cơ sở, một EmailNotification và một SMSNotification (tất cả đều dùng cùng một người nhận). Sau đó, lặp qua mảng và gọisend()trên từng phần tử. Hãy xem cách Java tự động gọi phiên bản được ghi đè phù hợp dựa trên kiểu thực tế của từng đối tượng!
Bạn sẽ nhận ba đầu vào: tên người nhận (String), tiêu đề email (String) và số điện thoại (String).
Đầu ra của bạn cần có ba dòng, mỗi lần gọi send() tạo ra đầu ra khác nhau dù bạn gọi cùng một phương thức trên các tham chiếu Notification. Đó chính là cơ chế điều phối phương thức động!
Tự làm thử
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Đọc các giá trị đầu vào
String recipient = scanner.nextLine();
String subject = scanner.nextLine();
String phoneNumber = scanner.nextLine();
// TODO: Tạo một mảng (array) gồm 3 phần tử là các tham chiếu đến Notification
// Mảng cần chứa các phần tử theo thứ tự sau:
// 1. Một đối tượng (object) Notification cơ sở
// 2. Một đối tượng EmailNotification
// 3. Một đối tượng SMSNotification
// Tất cả đều dùng cùng một người nhận
// TODO: Duyệt mảng bằng vòng lặp (loop) và gọi send() trên từng phần tử
// Đây là ví dụ về tính đa hình khi chạy chương trình!
}
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Kiến thức cơ bản về lập trình hướng đối tượng
Tệp bên ngoàiGiới thiệu về lập trình hướng đối tượngLớp và đối tượngTừ khóa thisPhương thứcTrường (thuộc tính)Phương thức khởi tạoNạp chồng phương thức khởi tạoÔn tập: Máy tính đơn giản4Kế thừa
Kế thừa cơ bản (extends)Từ khóa superGhi đè phương thức (@Override)Chuỗi gọi hàm khởi tạoLớp ObjectKế thừa đơn và đa cấpVì sao không hỗ trợ kế thừa nhiều lớpÔn tập: Phân cấp lớp Employee7Phương thức đặc biệt và lớp Object
Phương thức toString()equals() và hashCode()Phương thức clone()compareTo() và ComparableGiao diện ComparatorÔn tập: sắp xếp tùy chỉnh2Bộ điều chỉnh quyền truy cập và đóng gói
Tổng quan về các mức truy cậpPhương thức getter và setterẨn thông tinTừ khóa finalÔn tập: Trình quản lý tài khoản ngân hàng5Tính đa hình
Kiến thức cơ bản về nạp chồng phương thứcGhi đè phương thức (thời gian chạy)Chuyển kiểu lên và chuyển kiểu xuốngToán tử instanceofLớp và phương thức trừu tượngÔn tập: máy tính hình học8Khái niệm OOP nâng cao
Kết hợp và kế thừaKết tập và hợp thànhLớp bên trong, lớp lồng nhau và lớp ẩn danhKiểu enum và phương thức enumBản ghi (Java 16+)Lớp sealed (Java 17+)3Thuộc tính lớp và thành viên tĩnh
Biến của instance và biến tĩnhPhương thức tĩnhKhối tĩnhHằng số (static final)Ôn tập: bộ đếm và tiện ích6Giao diện và lớp trừu tượng
Giới thiệu về giao diệnTriển khai giao diệnTriển khai nhiều giao diệnPhương thức mặc định và tĩnh trong giao diệnLớp trừu tượng và giao diệnGiao diện hàmÔn tập: hệ thống thanh toán9Kiểu tổng quát
Giới thiệu về kiểu tổng quátLớp tổng quátPhương thức tổng quátTham số kiểu có giới hạnKý tự đại diện (?, extends, super)Ôn tập: vùng chứa tổng quát12Mẫu thiết kế, phần 2
Mẫu CommandMẫu AdapterMẫu DecoratorMẫu Template MethodMẫu StateMẫu CompositeMẫu IteratorTự luyện tập: Trình biên dịch Java online