Java: Đa Hình (Polymorphism) | V1Study
Có thể bạn quan tâm
Giới thiệu
Trong thực tế, những động vật như tắc kè hoa chẳng hạn có khả năng thay đổi màu sắc dựa trên môi trường. Một người nào đó có thể đóng các vai trò khác nhau trong cuộc sống hàng ngày của anh ta, như là cha, con, chồng. Như vậy thì trong những tình huống khác nhau thì anh ta cũng hành xử khác nhau. Tương tự như vậy, Java cung cấp đặc điểm gọi là đa hình (polymorphism) trong đó một đối tượng có thể có những hành xử khác nhau dựa trên bối cảnh mà nó được sử dụng.
Từ 'polymorph' là sự kết hợp của hai từ, 'poly' nghĩa là 'many' và 'morph' nghĩa là 'forms'. Vì thế, 'polymorphism' là 'đa hình', nó tham chiếu tới một đối tượng mà có thể có nhiều dạng. Nguyên tắc này cũng có thể áp dụng cho các lớp con của một lớp mà có thể định nghĩa các hành vi đặc trưng của chính chúng cũng như dẫn xuất một số chức năng tương tự của lớp cha. Khái niệm ghi đè phương thức là một ví dụ về đa hình trong lập trình hướng đối tượng trong đó cùng một phương thức nhưng lại cư xử khác nhau giữa lớp cha và lớp con.
Hiểu rõ về liên kết tĩnh và liên kết động
Khi trình biên dịch giải quyết liên kết của các phương thức và các lời gọi phương thức lúc biên dịch, nó được gọi là liên kết tĩnh hay liên kết sớm. Nếu trình biên dịch giải quyết các lời gọi phương thức và các liên kết trong thời gian chạy (runtime), nó được gọi là liên kết động hoặc liên kết muộn. Tất cả các lời gọi phương thức tĩnh đều được giải quyết lúc biên dịch và do đó, liên kết tĩnh được thực hiện cho tất cả các lời gọi phương thức tĩnh. Các lời gọi phương thức thể hiện luôn luôn được giải quyết lúc thực thi chương trình.
Các phương thức tĩnh là các phương thức lớp và được truy cập bằng cách sử dụng tên của chính lớp đó. Việc sử dụng phương thức tĩnh là được khuyến khích, bởi vì các tham chiếu đối tượng không được yêu cầu để truy cập chúng và do đó, các lời gọi phương thức tĩnh được giải quyết trong quá trình biên dịch chính chúng. Đó cũng là lý do vì sao mà các phương thức tĩnh không được ghi đè.
Tương tự như vậy, Java không cho phép có hành vi đa hình trong các biến của một lớp bất kỳ. Do đó, việc truy cập đến tất cả các biến cũng là theo liên kết tĩnh.
Một số điểm khác biệt quan trọng giữa liên kết tĩnh và liên kết động được thể hiện như bảng dưới đây.
Liên kết tĩnh | Liên kết động |
---|---|
Liên kết tĩnh xảy ra khi biên dịch. | Liên kết động xảy ra khi thực thi. |
Các phương thức và biến private, static, và final sử dụng liên kết tĩnh và được quy định bởi trình biên dịch. | Các phương thức trừu tượng được quy định lúc thực thi và dựa trên đối tượng thực thi. |
Liên kết tĩnh sử dụng thông tin kiểu đối tượng để liên kết, đó là kiểu của lớp. | Liên kết động sử dụng kiểu tham chiếu để giải quyết liên kết. |
Các phương thức được tải chồng được quy định sử dụng liên kết tĩnh. | Các phương thức được ghi đè được quy định sử dụng liên kết động. |
Đoạn mã 1 thể hiện một ví dụ về liên kết tĩnh.
Đoạn mã 1:
class NhanVien { String idNV; // Biến để lưu mã nhân viên String tenNV; // Lưu tên nhân viên int luongNV; // Lưu lương float hoaHong; // Lưu hoa hồng /* Hàm tạo có tham số để khởi tạo các biến */ public NhanVien(String id, String ten, int luong) { idNV = id; tenNV = ten; luongNV = luong; } /* Tính hoa hồng dựa trên doanh số bán hàng */ public void tinhHoaHong(float doanhSo) { if(doanhSo > 10000) hoaHong = luongNV * 20 / 100; else hoaHong = 0; } /* Tải chồng chương thức. Tính hoa hồng dựa trên thời gian vượt giờ */ public void tinhHoaHong(int vuotGio) { if(vuotGio > 8) hoaHong = luongNV/30; else hoaHong = 0; } /* Hiển thị thông tin chi tiết nhân viên */ public void chiTietNhanVien() { System.out.println("Mã nhân viên: " + idNV); System.out.println("Tên nhân viên: " + tenNV); System.out.println("Lương: " + luongNV); System.out.println("Hoa hồng: " + hoaHong); } } /* Định nghĩa lớp TestNhanVien */ class TestNhanVien { public static void main(String[] args) { // Tạo đối tượng của lớp NhanVien NhanVien objNV = new NhanVien("NV001", "Phan Ba Sang", 5000000); // Gọi phương thức tinhHoaHong() với đối số kiểu float objNV.tinhHoaHong(3000000F); // In ra thông tin chi tiết nhân viên objNV.chiTietNhanVien(); } }Đoạn mã 1 cho thấy lớp NhanVien có bốn biến thành phần. Hàm tạo được sử dụng để khởi tạo các biến thành phần với các giá trị nhận được. Lớp bao gồm hai phương thức tinhHoaHong(). Phương thức thứ nhất tính hoa hồng dựa trên doanh số bán hàng được thực hiện bởi nhân viên và những tính toán khác dựa trên số giờ làm việc ngoài giờ của nhân viên. Phương thức chiTietNhanVien() được dùng để in ra chi tiết thông tin nhân viên.
Một lớp khác là lớp TestNhanVien được tạo chứa phương thức main(). Bên trong phương thức main(), đối tượng objNV của lớp NhanVien được tạo và hàm tạo có tham số được gọi với các đối số khác nhau. Tiếp theo, phương thức tinhHoaHong() được gọi với đối số là 3000000F. Khi phương thức tinhHoaHong() được thực thi thì phương thức tương ứng với đối số có kiểu float được gọi đến bởi vì nó được quy định trong quá trình biên dịch dựa trên kiểu của biến. Sau cùng, phương thức chiTietNhanVien() được gọi để in ra chi tiết thông tin của nhân viên.
Đoạn mã 2 thể hiện một ví dụ về liên kết động.
Đoạn mã 2:
class NhanVienPastTime extends NhanVien { // Biến của lớp con String chuyenCa; // Biến này dùng để lưu trữ thông tin chuyển đổi ca làm /* Hàm tạo có tham số để khởi tạo các giá trị dựa trên giá trị nhập vào từ người dùng */ public NhanVienPastTime(String id, String ten, int luong, String chuyenCa) { // Gọi hàm tạo lớp cha super(id, ten, luong); this.chuyenCa = chuyenCa; } /* Ghi đè phương thức để hiển thị thông tin chi tiết của nhân viên */ @Override public void chiTietNhanVien() { tinhHoaHong(12); // Gọi phương thức đã thừa kế super.chiTietNhanVien(); // Gọi phương thức hiển thị của lớp super System.out.println("Chuyển ca làm: "+shift); } } /* Thay đổi định nghĩa lớp TestNhanVien */ class TestNhanVien{ public static void main(String[] args) { // Tạo đối tượng lớp NhanVien NhanVien objNV = new NhanVien("NV001", "Dinh Thi Kim Ngan", 4000000); objNV.tinhHoaHong(3000000F); // Tính hoa hồng objNV.chiTietNhanVien(); // In thông tin chi tiết của objNV System.out.println("-------------------------"); /* Tạo biến tham chiếu của lớp NhanVien nhưng lại tham chiếu đến đối tượng của lớp NhanVienPartTime */ NhanVien objNV1 = new NhanVienPartTime("NV002", "Bui Quynh Hoa", 3000000, "Ca sang"); objNV1.chiTietNhanVien(); // In thông tin chi tiết của objNV1 } }Đoạn mã 2 hiển thị lớp NhanVienPartTime thừa kế từ lớp NhanVien đã tạo ở đoạn mã 1. Lớp này có biến riêng của nó là chuyenCa để chỉ ra rằng nhân viên làm ca ngày hay ca đêm. Hàm tạo của lớp NhanVienPartTime gọi hàm tạo của lớp cha sử dụng từ khóa super để khởi tạo các thuộc tính chung của nhân viên. Ngoài ra, nó cũng khởi tạo cho biến chuyenCa.
Lớp con ghi đè phương thức chiTietNhanVien(). Bên trong phương thức được ghi đè, phương thức tinhHoaHong() được gọi đến với một đối số kiểu nguyên. Nó sẽ tính hoa hồng dựa trên giờ làm thêm. Tiếp theo, phương thức chiTietNhanVien() của lớp cha được gọi để hiển thị nhưng thông tin cơ bản của nhân viên cũng như chi tiết về chuyenCa.
Lớp TestNhanVien được sửa đổi trong đó tạo một đối tượng khác là objNV1 của lớp NhanVien. Tuy nhiên, đối tượng được gán tham chiếu của lớp NhanVienPartTime và hàm tạo được gọi là hàm tao bốn đối số. Sau đó, phương thức chiTietNhanVien() được gọi để in ra chi tiết nhân viên.
Chú ý rằng đầu ra của nhân viên mã "NV002" cũng thể hiện chi tiết của biến chuyenCa. Điều này chỉ ra rằng phương thức displayDetails() của lớp con NhanVienPartTime được gọi mặc dù kiểu của đối tượng objNV1 là NhanVien. Đó là bởi vì, trong quá trình tạo nó lưu trữ tham chiếu của lớp NhanVienPartTime.
Đây là liên kết động, đó là lời gọi phương thức được quy định cho đối tượng lúc thực thi dựa trên tham chiếu được gán cho đối tượng.
Sự khác nhau giữa kiểu tham chiếu và kiểu đối tượng
Trong đoạn mã 2, kiểu của của đối tượng objNv1 là NhanVien. Điều này có nghĩa là đối tượng sẽ có tất cả các đặc điểm của lớp NhanVien. Tuy nhiên, tham chiếu được gán cho đối tượng của lớp NhanVienPartTime. Điều này có nghĩa là đối tượng sẽ liên kết với các thành phần của lớp NhanVienPartTime trong quá trình chạy. Trong trường hợp này, kiểu đối tượng là NhanVien và kiểu tham chiếu là NhanVienPartTime. Điều này chỉ có thể xảy ra khi các lớp có mối liên quan theo quan hệ cha-con.
Java cho phép gán một thể hiện của lớp con cho lớp cha của nó. Điều này gọi là upcasting.
Ví dụ,
NhanVienPartTime objNVPT = new NhanVienPartTime(); NhanVien objNV = objNVPT; // upcastingTrong khi upcasting một đối tượng, thì đối tượng con objNVPT được gán trực tiếp cho đối tượng objNV của lớp cha. Tuy nhiên, đối tượng cha không thể truy cập các thành phần riêng của đối tượng con và không có sẵn trong lớp cha.
Java cũng cho phép ép kiểu tham chiếu cha trở về kiểu con. Điều này là bởi cha tham chiếu một đối tượng có kiểu con. Ép kiểu một đối tượng cha sang kiểu con được gọi là downcasting bởi vì một đối tượng có quyền được ép kiểu sang một lớp nhỏ hơn trong cấu trúc phân cấp. Tuy nhiên, downcasting yêu cầu ép kiểu tường minh bằng cách xác định tên lớp con trong cặp ngoặc tròn. Ví dụ,
NhanVienPartTime objNVPT = (NhanVienPartTime) objNV; // downcastingLời gọi phương thức ảo
Trong đoạn mã 2, trong quá trình thực thi câu lệnh NhanVien objNV1= new NhanVienPartTime(…);, kiểu runtime (thời gian thực thi) của đối tượng NhanVien được xác định. Trình biên dịch không phát sinh lỗi bởi vì lớp NhanVien cũng có phương thức chiTietNhanVien(). Tại thời điểm chạy, phương thức đã thực thi được tham chiếu từ đối tượng lớp NhanVienPartTime. Khía cạnh này của đa hình gọi là lời gọi phương thức ảo.
Sự khác nhau ở đây là giữa trình biên dịch và thời gian thực thi. Trình biên dịch kiểm tra khả năng truy cập của mỗi phương thức và biến thể hiện dựa trên định nghĩa lớp, trong khi đó hành vi liên quan đến một đối tượng được xác định tại thời gian thực thi.
Đây là một khía cạnh quan trọng của đa hình trong đó hành vi của đối tượng được xác định tại thời điểm thực thi dựa trên tham chiếu được truyền tới nó.
Ở đây, vì đối tượng được tạo là thuộc về lớp NhanVienPartTime, nên phương thức chiTietNhanVien() của NhanVienPartTime được gọi mặc dù đối tượng có kiểu NhanVien. Điều này được tham chiếu như là lời gọi phương thức ảo và phương thức được tham chiếu tới như là phương thức ảo.
Trong Java, tất cả các phương thức đều hành xử theo cách này, theo đó một phương thức được ghi đè trong lớp con được gọi tại thời điểm thực thi không phân biệt kiểu tham chiếu được sử dụng trong mã nguồn lúc biên dịch. Trong các ngôn ngữ khác như C++, ta cũng có thể đạt được điều tương tự bằng cách sử dụng từ khóa virtual.
Từ khóa » Cách Tính đa Hình Trong Java
-
Tính đa Hình Trong Java - Học Java Miễn Phí Hay Nhất - VietTuts
-
Tính đa Hình (polymorphism) Trong Java - Góc Học IT
-
Tính đa Hình (Polymorphism) Trong Java - GP Coder (Lập Trình Java)
-
Tính đa Hình (Polymorphism) Trong Java - Freetuts
-
Java Bài 30: Đa Hình (Polymorphism) - Yellow Code Books
-
[Tự Học Java] Tính đa Hình Trong Java »
-
Tính đa Hình Trong Java - Quá Trình Overriding Một Phương Thức
-
Tính đa Hình Trong OOP - KungFu Tech
-
Đa Hình Trong Java - Hoclaptrinh
-
Tính đa Hình Trong Java | Codelearn
-
Thừa Kế Và đa Hình Trong Java - Openplanning
-
Tính đa Hình Trong Java - Lập Trình Từ Đầu
-
Đa Hình Trong Java - Nắm Vững Khái Niệm đa Hình Java
-
Đa Hình Tại Runtime Trong Java - Viblo