Java là ngôn ngữ lập trình hướng đối tượng, vì vậy, nó sở hữu đầy đủ tính chất OOP, trong đó có tính đa hình. Trong bài viết này, hãy cùng Rikkei Academy tìm hiểu chi tiết về tính đa hình trong Java trên nhiều phương diện khác nhau.
Tính đa hình (polymorphism) trong Java là gì?
Tính đa hình là gì?
Tính đa hình (polymorphism) xuất phát từ tiếng Hy Lạp, có nghĩa là “nhiều hình dạng” ý chỉ khả năng mà một đối tượng có thể nhận nhiều hình thức khác nhau. Ví dụ thực tế, cùng là một chiếc điện thoại, nhưng có thể sử dụng để gọi điện, nhắn tin, chụp ảnh, nghe nhạc, v.v. Đây chính là biểu hiện của tính đa hình
Trong lập trình, đa hình giúp một đối tượng có khả năng nhận nhiều dạng khác nhau.
Tính đa hình trong Java là gì?
Trong Java, tính đa hình được thể hiện qua việc cho phép một biến tham chiếu có thể trỏ đến các đối tượng thuộc các lớp khác nhau, miễn là chúng kế thừa từ một lớp chung. Điều này giúp chúng ta có thể xây dựng các hệ thống phần mềm linh hoạt và dễ mở rộng hơn. Biến tham chiếu ở đây được sử dụng để lưu trữ địa chỉ bộ nhớ của đối tượng đó (tương tự như số nhà của bạn vậy) và mỗi biến tham chiếu sẽ tương ứng cho mỗi đối tượng
Giả sử chúng ta có lớp NhanVien kế thừa từ lớp ConNguoi. Một biến tham chiếu có thể trỏ đến đối tượng thuộc lớp ConNguoi hoặc đối tượng thuộc lớp NhanVien, biến tham chiếu nv1, nv2,nv3,… Trong ngữ cảnh này, chúng ta có thể gọi phương thức thongTin của lớp ConNguoi hoặc lớp NhanVien thông qua biến tham chiếu đó.
Phương thức để đạt tính đa hình trong Java
Vậy làm sao để đạt tính đa hình trong Java? Chúng ta sẽ sử dụng 2 phương thức chính sau:
Overriding (Ghi đè)
Overriding là kỹ thuật cho phép lớp con định nghĩa lại phương thức của lớp cha với cùng tên, cùng tham số và cùng kiểu trả về. Khi gọi phương thức thông qua đối tượng của lớp con, phương thức của lớp con sẽ được thực thi thay vì phương thức của lớp cha.
Nó giống như việc bạn được chỉ định để thực hiện nhiệm vụ đó bởi một người quản lý. Bạn nhận thấy có một cách để thực hiện công việc tốt hơn so với cách ban đầu. Bạn quyết định áp dụng cách của mình và thông báo cho người quản lý.
Ví dụ:
class ConNguoi {
void hienThi() { System.out.println(“Tôi là con người.”); } } class NhanVien extends ConNguoi { void hienThi() { System.out.println(“Tôi là nhân viên.”); } } public class Main { public static void main(String[] args) { ConNguoi conNguoi = new NhanVien(); conNguoi.hienThi(); // In ra: “Tôi là nhân viên.” } } |
Overloading (Ghi chồng)
Overloading là kỹ thuật tạo ra nhiều phương thức cùng tên nhưng khác nhau về số lượng hoặc kiểu dữ liệu của tham số. Các phương thức được gọi tương ứng với các tham số truyền vào. Môt ví dụ thực tế để bạn dễ hiểu hơn là máy giặt có nhiều chế độ giặt khác nhau. Máy giặt sử dụng kỹ thuật nạp chồng (overloading) để cung cấp cho bạn nhiều tùy chọn khác nhau như giặt nhanh, giặt êm, giặt bằng nước nóng để giặt các loại quần áo khác nhau.
Ví dụ:
class HinhChuNhat {
double tinhDienTich(double chieuDai, double chieuRong) { return chieuDai * chieuRong; } double tinhDienTich(double canh) { return canh * canh; } } |
Phân loại đa hình trong Java
Đa hình trong Java được chia làm 2 loại chính là đa hình Compile time (thời điểm biên dịch) và đa hình runtime (thời điểm chạy). Với mỗi loại đa hình này sẽ có phương thức thực hiện khác nhau.
Đa hình Compile -Time trong Java
Đa hình compile time (thời điểm biên dịch) trong Java còn được gọi là đa hình tĩnh (static) vì nó quyết định về phương thức nào sẽ được gọi tại thời điểm biên dịch, dựa trên kiểu của biến tham chiếu được sử dụng để gọi phương thức. Điều này xảy ra khi chúng ta sử dụng các phương thức được định nghĩa ở lớp cha hoặc interface và triển khai lại chúng trong các lớp con. Đa hình biên dịch có thể được đạt được thông qua quá trình nạp chồng phương thức (Method Overloading).
Ví dụ:
public class Example {
public void display(int num) { System.out.println(“Number: ” + num); } public void display(String text) { System.out.println(“Text: ” + text); } public void display(int num1, int num2) { System.out.println(“Numbers: ” + num1 + “, ” + num2); } } |
Ở ví dụ trên, chúng ta có ba phương thức display() có cùng tên nhưng có tham số khác nhau. Tại thời điểm biên dịch, Java sẽ xác định phương thức nào sẽ được gọi dựa trên thông tin về tham số truyền vào.
Lưu ý: Ngoài method overloading, tính đa hình nói chung còn có thể đạt được bằng operator overloading. Tuy nhiên, Java không hỗ trợ operator overloading nên chúng ta sẽ không đi sâu về vấn đề này.
Đa hình Runtime trong Java
Đa hình Runtime (thời điểm chạy) hay đa hình động (dynamic) trong Java là việc gọi phương thức được được ghi đè trong thời gian chạy chương trình. Điều này có nghĩa là JVM quyết định xem sử dụng cài đặt của phương thức nào dựa trên kiểu thực tế của đối tượng tại thời điểm chạy.xảy ra khi quyết định. Đa hình runtime đạt được thông qua việc ghi đè phương thức (overriding).
Ở đây, ghi đè được xác định bằng cách sử dụng một biến tham chiếu của lớp cha. Phương thức sẽ được gọi dựa trên đối tượng xác định bởi biến tham chiếu. Điều này còn được gọi là upcasting ( quá trình chuyển đổi kiểu dữ liệu đối tượng từ lớp con sang lớp cha).
Lưu ý: Đa hình Runtime chỉ có thể được đạt được thông qua phương thức ghi đè, không phải thông qua biến do biến tham chiếu đã được xác định tại Compile time và không thẻ thay đổi tại run-time.
public class Animal {
public void makeSound() { System.out.println(“Animal is making a sound”); } } public class Cat extends Animal { @Override public void makeSound() { System.out.println(“Meow!”); } } public class Dog extends Animal { @Override public void makeSound() { System.out.println(“Woof!”); } } public class Main { public static void main(String[] args) { Animal animal1 = new Cat(); Animal animal2 = new Dog(); animal1.makeSound(); // Output: “Meow!” animal2.makeSound(); // Output: “Woof!” } } |
Ở ví dụ trên, chúng ta tạo ra hai đối tượng animal1 và animal2 với kiểu dữ liệu là lớp cha Animal, nhưng thực sự được khởi tạo là đối tượng của lớp con Cat và Dog. Khi gọi phương thức makeSound() trên hai đối tượng này, phương thức được ghi đè trong lớp con sẽ được thực thi.
Đa hình Runtime trong Java với kế thừa đa tầng
Kế thừa đa tầng là một tính năng trong Java cho phép một lớp con kế thừa từ một lớp cha, và lớp cha này lại kế thừa từ một lớp khác. Ví dụ, lớp con Dog kế thừa từ lớp cha Animal, và lớp cha Animal kế thừa từ lớp cha LivingBeing.
Khi kết hợp đa hình runtime với kế thừa đa tầng, chúng ta có thể sử dụng tính đa hình để gọi phương thức của đối tượng đang được xác định bởi biến tham chiếu của lớp cha, đối tượng này có thể thuộc lớp con của lớp cha đó hoặc thuộc một lớp cha khác nằm trong chuỗi kế thừa đa tầng. Ví dụ:
class LivingBeing {
public void eat() { System.out.println(“This living being is eating”); } } class Animal extends LivingBeing { @Override public void eat() { System.out.println(“This animal is eating”); } } class Dog extends Animal { @Override public void eat() { System.out.println(“This dog is eating”); } } public class Main { public static void main(String[] args) { LivingBeing lb1 = new LivingBeing(); LivingBeing lb2 = new Animal(); LivingBeing lb3 = new Dog(); lb1.eat(); // This living being is eating lb2.eat(); // This animal is eating lb3.eat(); // This dog is eating } } |
Khi gọi phương thức eat() thông qua biến tham chiếu lb1 của lớp cha LivingBeing, phương thức eat() của LivingBeing sẽ được gọi. Khi gọi phương thức eat() thông qua biến tham chiếu lb2 của lớp con Animal, phương thức eat() của Animal sẽ được gọi. Và khi gọi phương thức eat() thông qua biến tham chiếu lb3 của lớp con Dog, phương thức eat() của Dog sẽ được gọi.
Tại sao sử dụng tính đa hình trong Java?
Đa hình (Polymorphism) trong Java cho phép viết một phương thức có thể xử lý chính xác nhiều loại chức năng khác nhau có cùng tên. Chúng ta cũng có thể đạt được tính nhất quán trong mã của mình bằng cách sử dụng đa hình.
Ưu điểm của đa hình trong Java
- Nó cung cấp khả năng tái sử dụng cho mã.Chúng ta có thể tận dụng các lớp và phương thức đã được định nghĩa trước để triển khai các chức năng mới mà không cần viết lại mã nguồn từ đầu. Ngoài ra, người lập trình có thể thay đổi mã mà không ảnh hưởng đến mã gốc.
- Cho phép tạo ra các lớp con có tính chất riêng của chúng và sử dụng chúng trong một cấu trúc kế thừa phức tạp hơn.
- Với ít dòng mã hơn, người lập trình dễ dàng hơn trong việc sửa lỗi mã.
Nhược điểm của tính đa hình trong Java
- Tính đa hình có thể làm tăng độ phức tạp của mã nguồn và khó hiểu hơn, đặc biệt là khi sử dụng nhiều lớp con kế thừa từ cùng một lớp cha.
- Tính đa hình có thể làm giảm hiệu suất của chương trình trong một số trường hợp. Khi chúng ta sử dụng đa hình, Java phải tìm kiếm phương thức thích hợp để thực thi, điều này có thể làm giảm hiệu suất.
Tuy nhiên chúng ta vẫn có cải thiện nhược điểm của tính đa hình trong Java bằng việc sử dụng các các thiết kế mẫu (design patterns), áp dụng các kỹ thuật và các công cụ hỗ trợ khác như Kotlin, Scala hoặc IDE như Eclipse,…
Kết luận
Tính đa hình là một kỹ thuật quan trọng trong lập trình hướng đối tượng, giúp chúng ta xây dựng các hệ thống phần mềm linh hoạt và dễ mở rộng. Vì vậy, việc hiểu rõ về tính đa hình trong Java sẽ giúp bạn rất nhiều trong quá trình học và phát triển các ứng dụng, hệ thống. Hy vọng bài viết này đã cung cấp các thông tin cần thiết để bạn hiểu hơn về tính đa hình trong Java.
Nếu bạn tìm kiếm địa chỉ uy tín để học lập trình Java, tham khảo ngay khóa học lập trình tại Rikkei Academy! Rikkei Academy cung cấp các khóa đào tạo lập trình ngắn hạn, tập trung vào kiến thức, kỹ năng chính cho công việc, giúp bạn tự tin ứng tuyển việc làm lập trình Java chỉ sau 6 tháng! Đăng ký tư vấn miễn phí ngay!