Đa hình lúc runtime trong Java với thành viên dữ liệu
Phương thức bị ghi đè không là thành viên dữ liệu, vì thế đa hình tại runtime không thể có được bởi thành viên dữ liệu. Trong ví dụ sau đây, cả hai lớp có một thành viên dữ liệu là speedlimit, chúng ta truy cập thành viên dữ liệu bởi biến tham chiếu của lớp cha mà tham chiếu tới đối tượng lớp con. Khi chúng ta truy cập thành viên dữ liệu mà không bị ghi đè, thì nó sẽ luôn luôn truy cập thành viên dữ liệu của lớp cha.
class Bike{ int speedlimit=90; } class Honda3 extends Bike{ int speedlimit=150; public static void main(String args[]){ Bike obj=new Honda3(); System.out.println(obj.speedlimit);//90 }
Kết quả:
90
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; |
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,…
KHÁI NIỆM VỀ ĐA HÌNH TẠI RUNTIME
Đa hình tại runtime trong Java là một tiến trình mà trong đó một lời gọi tới một phương thức được ghi đè sẽ được xử lý tại runtime thay vì tại compile time. Trong tiến trình này, một phương thức được ghi đè sẽ được gọi thông quan biến tham chiếu của một lớp cha. Việc quyết định phương thức được gọi dựa trên đối tượng được tham chiếu bởi biến tham chiếu. Trước khi tìm hiểu về đa hình tại runtime, chúng ta cùng tìm hiểu về Upcasting. Upcasting là khi biến tham chiếu của lớp cha tham chiếu tới đối tượng của lớp con, ví dụ:
class A{} class B extends A{} A a=new B();//day la upcasting
VÍ DỤ
Trong ví dụ ta tạo hai lớp Bike và Splendar, lớp Splendar kế thừa lớp Bike và ghi đè phương thức run() của nó. Chúng ta gọi phương thức run bằng biến tham chiếu của lớp cha. Khi nó tham chiếu tới đối tượng của lớp con và phương thức lớp con ghi đè phương thức của lớp cha, phương thức của lớp con được triệu hồi tại runtime. Việc gọi phương thức được quyết định bởi JVM chứ không phải Compiler.
class Bike{ void run(){System.out.println("dang chay");} } class Splender extends Bike{ void run(){System.out.println("chay an toan voi 60km");} public static void main(String args[]){ Bike b = new Splender(); b.run(); // chay an toan voi 60km } }
GIỚI THIỆU
Đa hình là khả năng của một đối tượng có thể nhận nhiều hình thái khác nhau. Cách dùng phổ biến của đa hình trong OOP là khi một tham chiếu của lớp cha được dùng để tham chiếu đến đối tượng của lớp con. Bất kỳ đối tượng nào trong Java đều mang tính chất đa hình do tất cả đối tượng đều được kế thừa từ lớp Object. Cách duy nhất để truy cập đến một đối tượng là thông qua biến tham chiếu. Một biến tham chiếu chỉ có thể có một kiểu, khi đã được khai báo, kiểu của một biến tham chiếu không thể thay đổi. Biến tham chiếu có thể được gán lại cho một đối tượng khác khi biến tham chiếu đó không phải là final. Kiểu của biến tham chiếu sẽ quyết định phương thức có thể gọi được từ đối tượng. Một biến tham chiếu có thể được xem như bất kỳ đối tượng nào thuộc cùng kiểu hoặc bất kỳ kiểu con của kiểu mà biến đó được khai báo.
Các câu hỏi liên quan đến OOP là một phần không thể thiếu trong cuộc phỏng vấn kỹ thuật cho vị trí nhà phát triển Java trong một công ty CNTT. Trong bài viết này, chúng ta sẽ nói về một nguyên tắc của OOP – đa hình. Chúng tôi sẽ tập trung vào các khía cạnh thường được hỏi trong các cuộc phỏng vấn, đồng thời đưa ra một vài ví dụ để làm rõ.Bạn có thể bắt đầu với thực tế là phương pháp OOP liên quan đến việc xây dựng một chương trình Java dựa trên sự tương tác giữa các đối tượng, dựa trên các lớp. Các lớp là các bản thiết kế (mẫu) được viết trước đây được sử dụng để tạo các đối tượng trong chương trình. Hơn nữa, một lớp luôn có một loại cụ thể, với phong cách lập trình tốt, có một cái tên gợi ý mục đích của nó. Hơn nữa, có thể lưu ý rằng vì Java được gõ mạnh, mã chương trình phải luôn chỉ định một loại đối tượng khi các biến được khai báo. Thêm vào đó là thực tế là việc gõ nghiêm ngặt cải thiện tính bảo mật và độ tin cậy của mã, đồng thời giúp ngăn ngừa lỗi do các loại không tương thích (ví dụ: cố gắng chia một chuỗi cho một số) ngay cả khi biên dịch. Đương nhiên, trình biên dịch phải “biết” loại được khai báo – nó có thể là một lớp từ JDK hoặc một lớp do chúng tôi tự tạo. Chỉ ra cho người phỏng vấn biết rằng mã của chúng ta không chỉ có thể sử dụng các đối tượng thuộc loại được chỉ định trong khai báo mà còn cả các đối tượng con của nó.Đây là một điểm quan trọng: chúng ta có thể làm việc với nhiều kiểu khác nhau dưới dạng một kiểu duy nhất (với điều kiện là các kiểu này được dẫn xuất từ một kiểu cơ sở). Điều này cũng có nghĩa là nếu chúng ta khai báo một biến có kiểu là siêu lớp, thì chúng ta có thể gán một thể hiện của một trong các biến con của nó cho biến đó. Người phỏng vấn sẽ thích nếu bạn đưa ra ví dụ. Chọn một số lớp có thể được chia sẻ bởi (một lớp cơ sở cho) một số lớp và làm cho một vài trong số chúng kế thừa nó. Lớp cơ sở:
Tính đa hình mang lại cho chúng ta điều gì?
Rất có thể bạn cũng sẽ được hỏi về việc tính đa hình có lợi cho chúng ta như thế nào. Bạn có thể trả lời câu hỏi này một cách ngắn gọn mà không bị sa lầy vào các chi tiết lông lá:
- Nó làm cho nó có thể thay thế việc triển khai lớp. Thử nghiệm được xây dựng trên đó.
- Nó tạo điều kiện cho khả năng mở rộng, giúp dễ dàng tạo ra một nền tảng có thể được xây dựng trong tương lai. Thêm các loại mới dựa trên các loại hiện có là cách phổ biến nhất để mở rộng chức năng của các chương trình OOP.
- Nó cho phép bạn kết hợp các đối tượng có chung kiểu hoặc hành vi vào một tập hợp hoặc mảng và xử lý chúng một cách thống nhất (như trong ví dụ của chúng tôi, nơi chúng tôi buộc mọi người phải nhảy() hoặc bơi() 🙂
- Tính linh hoạt trong việc tạo các loại mới: bạn có thể chọn cách triển khai phương thức của lớp cha hoặc ghi đè lên lớp con.
Đa hình lúc runtime trong Java với kế thừa nhiều tầng
Ví dụ 1:
class Animal { void eat() { System.out.println(“eating”); } } class Dog extends Animal { void eat() { System.out.println(“eating fruits”); } } class BabyDog extends Dog { void eat() { System.out.println(“drinking milk”); } public static void main(String args[]) { Animal a1, a2, a3; a1 = new Animal(); a2 = new Dog(); a3 = new BabyDog(); a1.eat(); a2.eat(); a3.eat(); } }
Kết quả:
eating eating fruits drinking Milk
Ví dụ 2:
class Animal { void eat() { System.out.println(“animal is eating…”); } } class Dog extends Animal { void eat() { System.out.println(“dog is eating…”); } } class BabyDog1 extends Dog { public static void main(String args[]) { Animal a = new BabyDog1(); a.eat(); } }
Kết quả:
Dog is eating
Vì BabyDog1 không ghi đè phương thức eat(), nên phương thức eat() của lớp Dog được gọi.
CHÀO! Hôm nay chúng ta kết thúc một loạt bài học về các nguyên tắc của OOP. Trong bài học này, chúng ta sẽ nói về tính đa hình của Java. Đa hình là khả năng làm việc với một số loại như thể chúng là cùng một loại. Hơn nữa, hành vi của các đối tượng sẽ khác nhau tùy thuộc vào loại của chúng. Chúng ta hãy xem xét kỹ hơn tuyên bố này. Hãy bắt đầu với phần đầu tiên: ‘khả năng làm việc với một số loại như thể chúng là cùng một loại’. Làm thế nào các loại khác nhau có thể giống nhau? Nghe có vẻ hơi lạ :/Thật ra tất cả đều rất đơn giản. Ví dụ, tình huống này phát sinh trong quá trình sử dụng tài sản thừa kế thông thường. Hãy xem cách nó hoạt động. Giả sử chúng ta có một lớp cha Cat đơn giản với một phương thức run() duy nhất :
Tất nhiên, bản thân chương trình sẽ hoạt động, nhưng chúng ta phải liên tục thêm các phương thức mới vào lớp Dolittle để chữa bệnh cho từng loại mèo. Kết quả là, nó sẽ phát triển đến kích thước chưa từng thấy. Đây là lúc tính đa hình — “khả năng làm việc với một số loại như thể chúng là cùng một loại” — phát huy tác dụng. Chúng ta không cần phải tạo ra vô số phương pháp để làm điều tương tự — chữa lành vết thương cho một con mèo. Một phương pháp là đủ cho tất cả chúng:
Bây giờ chúng ta sẽ tạo ba lớp kế thừa Cat : Lion , Tiger và Cheetah .
public class Cat { public void run() { System.out.println("Run!"); } }
Vậy là chúng ta có 3 lớp. Hãy lập mô hình tình huống mà chúng ta có thể làm việc với họ như thể họ là cùng một lớp. Hãy tưởng tượng rằng một trong những con mèo của chúng ta bị ốm và cần sự giúp đỡ của Tiến sĩ Dolittle. Hãy thử tạo một lớp Dolittle có thể chữa lành vết thương cho sư tử, hổ và báo.
public class Lion extends Cat { @Override public void run() { System.out.println("Lion runs at 80 km/h"); } } public class Tiger extends Cat { @Override public void run() { System.out.println("Tiger runs at 60 km/h"); } } public class Cheetah extends Cat { @Override public void run() { System.out.println("Cheetah runs at up to 120 km/h"); } }
Có vẻ như vấn đề đã được giải quyết: lớp đã được viết và sẵn sàng hoạt động. Nhưng chúng ta sẽ làm gì nếu muốn kéo dài chương trình của mình? Chúng tôi hiện chỉ có 3 loại: sư tử, hổ và báo đốm. Nhưng có hơn 40 loại mèo trên thế giới. Hãy tưởng tượng điều gì sẽ xảy ra nếu chúng tôi thêm các lớp riêng biệt cho manul, báo đốm Mỹ, Maine Coons, mèo nhà và tất cả những loài còn lại.
public class Dolittle { public void healLion(Lion lion) { System.out.println("Lion is healthy!"); } public void healTiger(Tiger tiger) { System.out.println("Tiger is healthy!"); } public void healCheetah(Cheetah cheetah) { System.out.println("Cheetah is healthy!"); } }
Phương thứcealCat () có thể chấp nhận các đối tượng Lion , Tiger và Cheetah — tất cả chúng đều là phiên bản của Cat :
public class Dolittle { public void healCat(Cat cat) { System.out.println("The patient is healthy!"); } }
Đầu ra bảng điều khiển: Bệnh nhân khỏe mạnh! Bệnh nhân khỏe mạnh! Bệnh nhân khỏe mạnh! Vì vậy, Dolittle của chúng tôilớp hoạt động với các loại khác nhau như thể chúng là cùng một loại. Bây giờ hãy giải quyết phần thứ hai: “hơn nữa, hành vi của các đối tượng sẽ khác nhau tùy thuộc vào loại của chúng”. Tất cả đều rất đơn giản. Trong tự nhiên, mỗi con mèo chạy theo một cách khác nhau. Ở mức tối thiểu, chúng chạy ở các tốc độ khác nhau. Trong số ba con mèo của chúng tôi, con báo là nhanh nhất, trong khi hổ và sư tử chạy chậm hơn. Nói cách khác, hành vi của họ là khác nhau. Tính đa hình không chỉ cho phép chúng ta sử dụng các loại khác nhau làm một. Nó cũng cho phép chúng tôi nhớ sự khác biệt của chúng, duy trì hành vi cụ thể của từng người trong số chúng. Ví dụ sau đây minh họa điều này. Giả sử rằng những con mèo của chúng ta, sau khi phục hồi thành công, quyết định chạy một chút. Chúng tôi sẽ thêm phần này vào lớp Dolittle của chúng tôi:
public class Main { public static void main(String[] args) { Dolittle dolittle = new Dolittle(); Lion simba = new Lion(); Tiger shereKhan = new Tiger(); Cheetah chester = new Cheetah(); dolittle.healCat(simba); dolittle.healCat(shereKhan); dolittle.healCat(chester); } }
Hãy thử chạy cùng một mã để xử lý ba con vật:
public class Dolittle { public void healCat(Cat cat) { System.out.println("The patient is healthy!"); cat.run(); } }
Và đây là kết quả: Bệnh nhân khỏe mạnh! Sư tử chạy với tốc độ 80 km/h. Bệnh nhân khỏe mạnh! Tiger chạy với tốc độ 60 km/h. Bệnh nhân khỏe mạnh! Cheetah chạy với tốc độ lên tới 120 km/h Ở đây chúng ta thấy rõ rằng hành vi cụ thể của các đối tượng được giữ nguyên, mặc dù chúng ta đã chuyển cả ba con vật cho phương thức sau khi ‘khái quát hóa’ chúng thành Cat . Do tính đa hình, Java nhớ rõ rằng đây không chỉ đơn giản là ba con mèo bất kỳ. Chúng là một con sư tử, một con hổ và một con báo, mỗi con chạy khác nhau. Điều này minh họa ưu điểm chính của tính đa hình: tính linh hoạt. Khi chúng ta cần triển khai một số chức năng được chia sẻ bởi nhiều loại, thì sư tử, hổ và báo đốm chỉ đơn giản trở thành ‘mèo’. Tất cả các loài động vật đều khác nhau, nhưng trong một số trường hợp, mèo là mèo, bất kể loài của nó là gì 🙂 Đây là một số video xác nhận dành cho bạn.
public static void main(String[] args) { Dolittle dolittle = new Dolittle(); Lion simba = new Lion(); Tiger shereKhan = new Tiger(); Cheetah chester = new Cheetah(); dolittle.healCat(simba); dolittle.healCat(shereKhan); dolittle.healCat(chester); }
Khi ‘sự khái quát hóa’ này là không mong muốn và thay vào đó chúng ta cần mỗi loài cư xử khác nhau, thì mỗi loại sẽ làm việc riêng của mình. Nhờ tính đa hình, bạn có thể tạo một giao diện (tập hợp các phương thức) duy nhất cho nhiều loại lớp. Điều này làm cho các chương trình ít phức tạp hơn. Ngay cả khi chúng tôi mở rộng chương trình để hỗ trợ 40 loại mèo, chúng tôi vẫn sẽ có giao diện đơn giản nhất: một phương thức run() duy nhất cho tất cả 40 con mèo.
Đọc thêm: |
Bài viết hôm nay mình sẽ giới thiệu đến các bạn một tính chất quan trọng trong lập trình hướng đối tượng đó là tính đa hình trong java.
Đ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 (polymorphism)
Tính đa hình (polymorphism) trong Java cho phép chúng ta tạo ra những mã code nhất quán. Lấy ví dụ ở phần 1, chúng ta có thể tạo ra các hàm render với các tên khác nhau như renderSquare() và renderCircle() cho từng lớp Square và Circle.
Chương trình của bạn cũng sẽ hoạt động tốt thôi nhưng việc tạo ra các tên khác nhau cho một hàm có chức năng giống nhau thì sẽ làm code không nhất quán, rườm rà, cồng kềnh.
Tính đa hình cho phép chúng ta tạo ra một hàm tên là
render()
duy nhất nhưng sẽ thực thi khác nhau với từng lớp khác nhau.
Trong Java, chúng ta có thể đạt được tính đa hình (polymorphism) với nạp chồng phương thức (method overloading), ghi đè phương thức (method overriding).
Lưu ý: Trong Java không hỗ trợ lập trình viên tự định nghĩa nạp chồng toán tử (operator overloading) như trong C++.
Vài lời chia tay
Đa hình là một chủ đề rất quan trọng và rộng lớn. Nó là chủ đề của gần một nửa bài viết này về OOP trong Java và là một phần tốt trong nền tảng của ngôn ngữ này. Bạn sẽ không thể tránh được việc xác định nguyên tắc này trong một cuộc phỏng vấn. Nếu bạn không biết hoặc không hiểu nó, cuộc phỏng vấn có thể sẽ đi đến hồi kết. Vì vậy, đừng là kẻ lười biếng — hãy đánh giá kiến thức của bạn trước cuộc phỏng vấn và làm mới nó nếu cần.
Đọc thêm: |
Tính đa hình (polymorphism) là một trong bốn tính chất cơ bản của lập trình hướng đối tượng trong Java.
Tính đa hình là khả năng một đối tượng có thể thực hiện một tác vụ theo nhiều cách khác nhau.
Đối với tính chất này, nó được thể hiện rõ nhất qua việc gọi phương thức của đối tượng. Các phương thức hoàn toàn có thể giống nhau, nhưng việc xử lý luồng có thể khác nhau. Nói cách khác: Tính đa hình cung cấp khả năng cho phép người lập trình gọi trước một phương thức của đối tượng, tuy chưa xác định đối tượng có phương thức muốn gọi hay không. Đến khi thực hiện (run-time), chương trình mới xác định được đối tượng và gọi phương thức tương ứng của đối tượng đó. Kết nối trễ giúp chương trình được uyển chuyển hơn, chỉ yêu cầu đối tượng cung cấp đúng phương thức cần thiết là đủ.
Trong Java, chúng ta sử dụng nạp chồng phương thức (method overloading) và ghi đè phương thức (method overriding) để có tính đa hình.
- Nạp chồng (Overloading): Đây là khả năng cho phép một lớp có nhiều thuộc tính, phương thức cùng tên nhưng với các tham số khác nhau về loại cũng như về số lượng. Khi được gọi, dựa vào tham số truyền vào, phương thức tương ứng sẽ được thực hiện.
- Ghi đè (Overriding): là hai phương thức cùng tên, cùng tham số, cùng kiểu trả về nhưng thằng con viết lại và dùng theo cách của nó, và xuất hiện ở lớp cha và tiếp tục xuất hiện ở lớp con. Khi dùng override, lúc thực thi, nếu lớp Con không có phương thức riêng, phương thức của lớp Cha sẽ được gọi, ngược lại nếu có, phương thức của lớp Con được gọi.
Nội dung
Toán tử instanceof
Toán tử
instanceof
trong Java được sử dụng để kiểm tra một đối tượng có phải là thể hiện của một kiểu dữ liệu (lớp) nào đó hay không.
instanceof
trong Java được gọi là toán tử so sánh kiểu. Nó trả về giá trị boolean là true hoặc false. Nếu bạn dùng toán tử
instanceof
với bất kỳ biến nào mà có giá trị null thì kết quả trả về sẽ là false. Ví dụ:
public class Simple { public static void main(String args[]) { Simple s = new Simple(); System.out.println(s instanceof Simple);// true } }
Một đối tượng có kiểu của lớp con thì cũng có kiểu của lớp cha.
Ví dụ: Nếu Dog kế thừa từ Animal thì đối tượng của Dog có thể tham chiếu đến cả hai lớp Dog và Animal.
class Animal {} public class Dog extends Animal {// Dog inherits Animal public static void main(String args[]) { Dog dog = new Dog(); System.out.println(dog instanceof Animal);// true } }
Nếu sử dụng toán tử
instanceof
với biến có kiểu bất kỳ có giá trị null thì giá trị trả về luôn là null. Ví dụ:
public class Dog { public static void main(String args[]) { Dog d = null; System.out.println(d instanceof Dog);// false } }
Chào mừng các bạn đã đến với bài học Java số 30. Bài học về tính đa hình (polymorphism). Đây là bài học trong chuỗi bài viết về lập trình ngôn ngữ Java của Yellow Code Books.
Bài hôm nay chúng ta sẽ nói sâu về tính Đa hình trong Java. Nghe qua đặc tính này thì có vẻ khó. Một phần vì ứng dụng của chúng không nhiều. Với cái tên nghe chẳng có cố định gì cả, như là biến hình gì gì đó. Cộng với khá ít tài liệu viết rõ về công năng này của OOP.
Vậy thì chúng ta cùng đi sâu vào bài học để xem Đa hình là gì và nó có thực sự khó không nhé.
Tính Đa Hình (Polymorphism) Là Gì?
Lần này thì nghĩa tiếng Anh và tiếng Việt trong lập trình Java lại khớp với nhau. Không nhiều các từ lan man, chỉ có Đa hình, hoặc Polymorphism mà thôi.
Vậy tại sao lại Đa hình? Như bạn biết, vốn dĩ OOP là một cách thức tư duy lập trình hướng thực tế, nên hiển nhiên các khái niệm của nó cũng phải sát với các đặc điểm trong thực tế. Trong đó có Đa hình. Trong thực tế, sự Đa hình được xem như một đối tượng đặc biệt, có lúc đối tượng này mang một hình dạng (trở thành một đối tượng) nào đó, và cũng có lúc đối tượng này lại mang một hình dạng khác nữa, tùy vào từng hoàn cảnh. Sự “nhập vai” vào các hình dạng (đối tượng) khác nhau này giúp cho đối tượng Đa hình ban đầu có thể thực hiện những hành động khác nhau của từng đối tượng cụ thể. Chẳng hạn nếu ở công ty bạn, có nhân viên nhận hai trách nhiệm khác nhau, họ vừa là nhân viên toàn thời gian ở các ngày trong tuần, nhưng làm bán thời gian ở các ngày cuối tuần. Vậy thì, để tính lương cho nhân viên này, tùy vào từng thời điểm mà hệ thống sẽ xem nhân viên đó là toàn thời gian hay bán thời gian, và phương thức tính lương của mỗi loại nhân viên sẽ thực hiện tính toán một cách hiệu quả nhất dựa vào từng vai trò khác nhau này. Bạn cũng hiểu sơ sơ về Đa hình rồi đúng không nào.
Có một điều chắc chắn rằng. Nếu như không xem hành động tính lương của nhân viên như ví dụ trên kia là Đa hình, thì chúng ta vẫn cứ xây dựng được một hệ thống tính lương hoàn chỉnh, nhưng sẽ phức tạp hơn là nếu bạn biết kiến thức về Đa hình là gì.
Và còn một ý nữa. Rằng tính Đa hình của bài hôm nay cũng là một trong các đặc tính nổi trội mà OOP mang lại đấy nhé. Bạn cố gắng nắm bắt và tận dụng. Ôn lại một tí các đặc tính cốt lõi của OOP bao gồm:
– Tính Gói ghém dữ liệu (Encapsulation). Tính chất này được thể hiện qua các kiến thức về khả năng truy cập, getter/setter.– Tính Kế thừa (Inheritance). Tính chất này được thể hiện qua các kiến thức về kế thừa, overriding, overloading.– Tính Đa hình (Polymorphism). Bài hôm nay chúng ta sẽ học.– Tính Trừu tượng (Abstraction). Bài sau chúng ta sẽ học.
Sử Dụng Tính Đa Hình Như Thế Nào?
Đến đây chắc chắn bạn đã hiểu sơ bộ khái niệm Đa hình. Vậy thì trong OOP chúng ta tổ chức và sử dụng đặc tính Đa hình này như thế nào?
Thứ nhất, Đa hình sẽ gắn liền với kế thừa. Và, Đa hình cũng sẽ gắn liền với ghi đè phương thức (overriding) nữa. Bởi vì như trên đây có nói đó, Đa hình là nói đến một đối tượng nào đó có khả năng nhập vai thành các đối tượng khác. Vậy thì để mà một đối tượng có thể là một đối tượng nào đó, ắt hẳn nó phải là đối tượng cha. Và để đối tượng cha có thể là một trong các đối tượng con ở từng hoàn cảnh, thì nó phải định nghĩa ra các phương thức để con của nó có thể ghi đè. Điều này giúp hệ thống xác định được đối tượng nào và phương thức nào thực sự đang hoạt động khi ứng dụng đang chạy. Nên nhiều tài liệu gọi Đa hình này là Đa hình tại runtime là vậy.
Chúng ta sẽ đến ví dụ sau cho dễ hiểu hơn. Ví dụ khá đơn giản. Lớp HinhHoc là lớp cha, hai lớp con HinhTron và HinhChuNhat đều override phương thức tinhDienTich() từ cha.
Code của chúng cũng khá đơn giản, chúng ta loại bỏ hết tất cả các râu ria khác, chỉ tập trung vào các phương thức override mà thôi.
– HinhHoc
public class HinhHoc { public void tinhDienTich() { System.out.println(“Chưa biết hình nào”); } }
– HinhTron
public class HinhTron extends HinhHoc { @Override public void tinhDienTich() { System.out.println(“Đây là Diện tích hình Tròn”); } }
– HinhChuNhat
public class HinhChuNhat extends HinhHoc { @Override public void tinhDienTich() { System.out.println(“Đây là Diện tích hình Chữ nhật”); } }
Nào, sự diệu kỳ của tính Đa hình là đây, bạn hãy chú ý vào đoạn code khai báo và sử dụng các phương thức được overriding trên kia như sau.
– MainClass
public class MainClass { public static void main(String[] args) { HinhHoc hinhHoc = new HinhHoc(); hinhHoc.tinhDienTich(); // Đoạn code này bình thường, sẽ in ra “Chưa biết hình nào” // Có lúc hinhHoc đóng vai trò là HinhTron trong một ngữ cảnh nào đó hinhHoc = new HinhTron(); hinhHoc.tinhDienTich(); // Đoạn code này sẽ in ra “Đây là Diện tích Hình tròn” // Có lúc hinhHoc đóng vai trò là HinhChuNhat trong một ngữ cảnh nào đó hinhHoc = new HinhChuNhat(); hinhHoc.tinhDienTich(); // Đoạn code này sẽ in ra “Đây là Diện tích Chữ nhật” } }
Bạn đã thấy đó, đối tượng HinhHoc bản thân nó có một phương thức tinhDienTich(). Nhưng khác với cách sử dụng các đối tượng từ các bài học từ trước đến giờ, rằng mỗi khi cần đến các lớp con thực hiện việc tính diện tích, chúng ta sẽ khai báo lớp con và gọi phương thức được override ở lớp con. Thì bài hôm nay chúng ta cho phép lớp HinhHoc có khả năng đóng vai trò là lớp con, bằng cách khởi tạo lại đối tượng là lớp con của nó, HinhHoc hinhHoc = new HinhTron(), rồi chính nó sẽ đóng vai là lớp con đó. Tính Đa hình là đây.
Đa hình lúc runtime trong java
Đa hình lúc runtime là quá trình gọi phương thức đã được ghi đè trong thời gian thực thi chương trình. Trong quá trình này, một phương thức được ghi đè được gọi thông qua biến tham chiếu của một lớp cha.
Trước khi tìm hiểu về đa hình tại runtime, chúng ta cùng tìm hiểu về Upcasting.
Upcasting là gì?
Khi biến tham chiếu của lớp cha tham chiếu tới đối tượng của lớp con, thì đó là Upcasting. Ví dụ:
class A { } class B extends A { }
A a = new B(); // upcasting
Các bạn xem thêm ở bài viết Cơ chế Upcasting và Downcasting trong java để hiểu rõ hơn.
Ví dụ về đa hình tại runtime trong Java
Ví dụ 1: chúng ta tạo hai lớp Bike và Splendar. Lớp Splendar kế thừa lớp Bike và ghi đè phương thức run() của nó. Chúng ta gọi phương thức run bởi biến tham chiếu của lớp cha. Khi nó tham chiếu tới đối tượng của lớp con và phương thức lớp con ghi đè phương thức của lớp cha, phương thức lớp con được triệu hồi tại runtime.
Khi việc gọi phương thức được quyết định bởi JVM chứ không phải Compiler, vì thế đó là đa hình tại runtime.
public class Bike { public void run() { System.out.println(“running”); } } public class Splender extends Bike { public void run() { System.out.println(“running safely with 60km”); } public static void main(String args[]) { Bike b = new Splender(); // upcasting b.run(); } }
Kết quả:
running safely with 60km
Ví dụ 2: Giả sử Bank là một lớp cung cấp phương thức để lấy lãi suất. Nhưng lãi suất lại khác nhau giữa từng ngân hàng. Ví dụ, các ngân hàng VCB, AGR và CTG có thể cung cấp các lãi suất lần lượt là 8%, 7% và 9%. (Ví dụ này cũng có trong chương ghi đè phương thức nhưng không có Upcasting).
class Bank { int getRateOfInterest() { return 0; } } class VCB extends Bank { int getRateOfInterest() { return 8; } } class AGR extends Bank { int getRateOfInterest() { return 7; } } class CTG extends Bank { int getRateOfInterest() { return 9; } } class Test3 { public static void main(String args[]) { Bank b1 = new VCB(); // upcasting Bank b2 = new AGR(); // upcasting Bank b3 = new CTG(); // upcasting System.out.println(“VCB lai suat la: ” + b1.getRateOfInterest()); System.out.println(“AGR lai suat la: ” + b2.getRateOfInterest()); System.out.println(“CTG lai suat la: ” + b3.getRateOfInterest()); } }
Kết quả:
VCB lai suat la: 8 VCB lai suat la: 7 VCB lai suat la: 9
Ví dụ 3: Shape
class Shape { void draw() { System.out.println(“drawing…”); } } class Rectangle extends Shape { void draw() { System.out.println(“drawing rectangle…”); } } class Circle extends Shape { void draw() { System.out.println(“drawing circle…”); } } class Triangle extends Shape { void draw() { System.out.println(“drawing triangle…”); } } class TestPolymorphism2 { public static void main(String args[]) { Shape s; s = new Rectangle(); s.draw(); s = new Circle(); s.draw(); s = new Triangle(); s.draw(); } }
Kết quả:
drawing rectangle… drawing circle… drawing triangle…
Nạp chồng phương thức (method overloading)
Nếu một lớp có nhiều phương thức cùng tên nhưng khác nhau về kiểu dữ liệu hoặc số lượng các tham số, thì đó là nạp chồng phương thức (Method Overloading).
Sử dụng nạp chồng phương thức giúp tăng khả năng đọc hiểu chương trình.
Nạp chồng phương thức được sử dụng để thu được tính đa hình lúc biên dịch (compile).
Có 2 cách nạp chồng phương thức trong java
- Thay đổi số lượng các tham số
- Thay đổi kiểu dữ liệu của các tham số
Nạp chồng phương thức: thay đổi số lượng các tham số
Ví dụ: tạo 2 phương thức có cùng kiểu dữ liệu: phương thức add() đầu tiên thực hiện việc tính tổng của 2 số, phương thức thứ hai thực hiện việc tính tổng của 3 số.
class Adder { static int add(int a, int b) { return a + b; } static int add(int a, int b, int c) { return a + b + c; } } class TestOverloading1 { public static void main(String[] args) { System.out.println(Adder.add(5, 5)); System.out.println(Adder.add(5, 5, 5)); } }
Kết quả chạy chương trình trên:
10 15
Nạp chồng phương thức: thay đổi kiểu dữ liệu của các tham số
Ví dụ: tạo 2 phương thức có kiểu dữ liệu khác nhau: phương thức add() đầu tiên nhận 2 đối số có kiểu giá trị là integer, phương thức thứ hai nhận 2 đối số có kiểu giá trị là double.
class Adder {static int add(int a, int b) {return a + b;}
static double add(double a, double b) {return a + b;}}
class TestOverloading2 {public static void main(String[] args) {System.out.println(Adder.add(5, 5));System.out.println(Adder.add(4.3, 5.6));}}
Kết quả chạy chương trình trên:
10 9.9
Một số câu hỏi về nạp chồng phương thức trong java
Tại sao không thể nạp chồng phương thức bằng cách chỉ thay đổi kiểu trả về của phương thức?
Trong java, không thể nạp chồng phương thức bằng cách chỉ thay đổi kiểu trả về của phương thức bởi vì không biết phương thức nào sẽ được gọi.
Ví dụ:
class Adder { static int add(int a, int b) { return a + b; } static double add(int a, int b) { return a + b; } } class TestOverloading3 { public static void main(String[] args) { System.out.println(Adder.add(11, 11)); // Không biết gọi phương thức nào } }
Có thể nạp chồng phương thức main() không?
Có, bạn có thể nạp chồng n phương thức main. Nhưng JVM chỉ gọi phương thức main() có tham số truyền vào là một mảng String.
Ví dụ:
public class TestOverloading4 { public static void main(String[] args) { System.out.println(“main with String[]”); } public static void main(String args) { System.out.println(“main with String”); } public static void main() { System.out.println(“main without args”); } }
Kết quả khi chạy chương trình trên:
main with String[]
Nạp chồng phương thức và tự động ép kiểu
Kiểu dữ liệu của đối số truyền vào được thay đổi sang kiểu dữ liệu khác (tự động ép kiểu) nếu giá trị của đối số đó không phù hợp với kiểu dữ liệu của tham số đã được đinh nghĩa.
Để hiểu khái niệm này hãy xem ảnh sau:
Kiểu byte có thể được ép sang các kiểu short, int, long, float hoặc double. Kiểu dữ liệu short có thể được ép sang các kiểu int, long, float hoặc double. Kiểu dữ liệu char có thể được ép sang các kiểu int, long, float or double…
Ví dụ:
class OverloadingCalculation1 { void sum(int a, long b) { System.out.println(a + b); } void sum(int a, int b, int c) { System.out.println(a + b + c); } public static void main(String args[]) { OverloadingCalculation1 obj = new OverloadingCalculation1(); obj.sum(20, 20);// now second int literal will be promoted to long obj.sum(20, 20, 20); } }
Kết quả khi chạy chương trình trên:
40 60
Ví dụ: nếu không có kiểu đối số nào phù hợp, chuyển đổi kiểu sẽ không được thực hiện.
class OverloadingCalculation2 { void sum(int a, int b) { System.out.println(“int arg method invoked”); } void sum(long a, long b) { System.out.println(“long arg method invoked”); } public static void main(String args[]) { OverloadingCalculation2 obj = new OverloadingCalculation2(); obj.sum(20, 20);// now int arg sum() method gets invoked } }
Kết quả khi chạy chương trình trên:
int arg method invoked
Ví dụ: không có kiểu đối số nào phụ hợp trong phương thức và mỗi phương thức thay đổi số đối số tương tự nhau. Trường hợp này sẽ không xác định được phương thức nào được gọi.
public class OverloadingCalculation3 { void sum(int a, long b) { System.out.println(“a method invoked”); } void sum(long a, int b) { System.out.println(“b method invoked”); } public static void main(String args[]) { OverloadingCalculation3 obj = new OverloadingCalculation3(); obj.sum(20, 20); // không xác định được phương thức nào được gọi } }
Tính đa hình trong Java là gì?
Tính đa hình là khả năng của chương trình xử lý các đối tượng có cùng giao diện theo cùng một cách mà không cần thông tin về loại cụ thể của đối tượng. Nếu bạn trả lời câu hỏi về tính đa hình là gì, rất có thể bạn sẽ được yêu cầu giải thích ý của mình. Không khơi mào cho hàng loạt câu hỏi bổ sung, hãy trình bày lại tất cả cho người phỏng vấn.
Trong các lớp con, ghi đè phương thức của lớp cơ sở:
public class Dancer { private String name; private int age; public Dancer(String name, int age) { this.name = name; this.age = age; } public void dance() { System.out.println(toString() + " I dance like everyone else."); } @Override public String toString() { Return "I'm " + name + ". I'm " + age + " years old."; } }
Một ví dụ về tính đa hình và cách các đối tượng này có thể được sử dụng trong một chương trình:
public class ElectricBoogieDancer extends Dancer { public ElectricBoogieDancer(String name, int age) { super(name, age); } // Override the method of the base class @Override public void dance() { System.out.println(toString () + " I dance the electric boogie!"); } } public class Breakdancer extends Dancer { public Breakdancer(String name, int age) { super(name, age); } // Override the method of the base class @Override public void dance() { System.out.println(toString() + " I breakdance!"); } }
Trong phương pháp chính , chỉ ra rằng các dòng
public class Main { public static void main(String[] args) { Dancer dancer = new Dancer("Fred", 18); Dancer breakdancer = new Breakdancer("Jay", 19); // Widening conversion to the base type Dancer electricBoogieDancer = new ElectricBoogieDancer("Marcia", 20); // Widening conversion to the base type List
disco = Arrays.asList(dancer, breakdancer, electricBoogieDancer); for (Dancer d : disco) { d.dance(); // Call the polymorphic method } } }
khai báo một biến của một siêu lớp và gán cho nó một đối tượng là một thể hiện của một trong những hậu duệ của nó. Rất có thể bạn sẽ được hỏi tại sao trình biên dịch không hiển thị khi các kiểu được khai báo ở bên trái và bên phải của toán tử gán không nhất quán — xét cho cùng, Java được gõ mạnh. Giải thích rằng một chuyển đổi kiểu mở rộng đang hoạt động ở đây — một tham chiếu đến một đối tượng được coi như một tham chiếu đến lớp cơ sở của nó. Hơn nữa, khi gặp một cấu trúc như vậy trong mã, trình biên dịch sẽ thực hiện chuyển đổi một cách tự động và hoàn toàn. Mã mẫu cho thấy loại được khai báo ở phía bên trái của toán tử gán ( Dancer ) có nhiều dạng (loại) được khai báo ở phía bên phải ( Breakdancer , ElectricBoogieDancer). Mỗi biểu mẫu có thể có hành vi độc đáo của riêng nó đối với chức năng chung được xác định trong lớp cha ( phương thức nhảy ). Nghĩa là, một phương thức được khai báo trong lớp cha có thể được triển khai khác trong lớp con của nó. Trong trường hợp này, chúng ta đang xử lý việc ghi đè phương thức, đây chính xác là thứ tạo ra nhiều biểu mẫu (hành vi). Điều này có thể được nhìn thấy bằng cách chạy mã trong phương thức chính: Đầu ra chương trình: Tôi là Fred. Tôi 18 tuổi. Tôi nhảy như mọi người khác. Tôi là Jay. Toi 19 tuoi. Tôi nhảy! Tôi là Marcia. Tôi 20 tuổi. Tôi nhảy boogie điện! Nếu chúng ta không ghi đè phương thức trong các lớp con, thì chúng ta sẽ không có hành vi khác. Ví dụ,ElectricBoogieDancer , thì đầu ra của chương trình sẽ là: Tôi là Fred. Tôi 18 tuổi. Tôi nhảy như mọi người khác. Tôi là Jay. Toi 19 tuoi. Tôi nhảy như mọi người khác. Tôi là Marcia. Tôi 20 tuổi. Tôi nhảy như mọi người khác. Và điều này có nghĩa là việc tạo các lớp Breakdancer và ElectricBoogieDancer đơn giản là không hợp lý . Nguyên tắc đa hình thể hiện cụ thể ở đâu? Trường hợp một đối tượng được sử dụng trong chương trình mà không biết loại cụ thể của nó? Trong ví dụ của chúng ta, nó xảy ra khi phương thức dance() được gọi trên đối tượng Dancer d . Trong Java, tính đa hình có nghĩa là chương trình không cần biết liệu đối tượng có phải là mộtBreakdancer hoặc ElectricBoogie Dancer . Điều quan trọng là nó là hậu duệ của class Dancer . Và nếu bạn đề cập đến hậu duệ, bạn nên lưu ý rằng tính kế thừa trong Java không chỉ là các phần mở rộng mà còn là các phần thực hiện.. Bây giờ là lúc để đề cập rằng Java không hỗ trợ đa kế thừa — mỗi loại có thể có một cha (lớp cha) và số lượng con cháu (lớp con) không giới hạn. Theo đó, các giao diện được sử dụng để thêm nhiều bộ chức năng vào các lớp. So với các lớp con (kế thừa), các giao diện ít kết hợp với lớp cha hơn. Chúng được sử dụng rất rộng rãi. Trong Java, một giao diện là một kiểu tham chiếu, vì vậy chương trình có thể khai báo một biến kiểu giao diện. Bây giờ là lúc để đưa ra một ví dụ. Tạo một giao diện:
Dancer breakdancer = new Breakdancer("Jay", 19); Dancer electricBoogieDancer = new ElectricBoogieDancer("Marcia", 20);
Để rõ ràng, chúng tôi sẽ lấy các lớp không liên quan khác nhau và làm cho chúng triển khai giao diện:
public interface CanSwim { void swim(); }
phương pháp chính :
public class Human implements CanSwim { private String name; private int age; public Human(String name, int age) { this.name = name; this.age = age; } @Override public void swim() { System.out.println(toString()+" I swim with an inflated tube."); } @Override public String toString() { return "I'm " + name + ". I'm " + age + " years old."; } } public class Fish implements CanSwim { private String name; public Fish(String name) { this.name = name; } @Override public void swim() { System.out.println("I'm a fish. My name is " + name + ". I swim by moving my fins."); } public class UBoat implements CanSwim { private int speed; public UBoat(int speed) { this.speed = speed; } @Override public void swim() { System.out.println("I'm a submarine that swims through the water by rotating screw propellers. My speed is " + speed + " knots."); } }
Các kết quả gọi một phương thức đa hình được định nghĩa trong một giao diện cho chúng ta thấy sự khác biệt trong hành vi của các kiểu triển khai giao diện này. Trong trường hợp của chúng tôi, đây là các chuỗi khác nhau được hiển thị bằng phương thức bơi . Sau khi nghiên cứu ví dụ của chúng tôi, người phỏng vấn có thể hỏi tại sao chạy mã này trong phương thức chính
public class Main { public static void main(String[] args) { CanSwim human = new Human("John", 6); CanSwim fish = new Fish("Whale"); CanSwim boat = new UBoat(25); List
swimmers = Arrays.asList(human, fish, boat); for (Swim s : swimmers) { s.swim(); } } }
làm cho các phương thức ghi đè được định nghĩa trong các lớp con của chúng ta được gọi? Cách triển khai mong muốn của phương thức được chọn trong khi chương trình đang chạy? Để trả lời những câu hỏi này, bạn cần giải thích ràng buộc muộn (động). Liên kết có nghĩa là thiết lập ánh xạ giữa lời gọi phương thức và cách triển khai lớp cụ thể của nó. Về bản chất, mã xác định phương thức nào trong ba phương thức được định nghĩa trong các lớp sẽ được thực thi. Java sử dụng liên kết muộn theo mặc định, nghĩa là liên kết xảy ra trong thời gian chạy chứ không phải tại thời điểm biên dịch như trường hợp liên kết sớm. Điều này có nghĩa là khi trình biên dịch biên dịch mã này
for (Swim s : swimmers) { s.swim(); }
nó không biết lớp nào ( Human , Fish , hoặc Uboat ) có mã sẽ được thực thi khi bơiphương thức được gọi. Điều này chỉ được xác định khi chương trình được thực thi, nhờ cơ chế liên kết động (kiểm tra loại đối tượng trong thời gian chạy và chọn cách triển khai chính xác cho loại này). Nếu bạn được hỏi làm thế nào điều này được thực hiện, bạn có thể trả lời rằng khi tải và khởi tạo các đối tượng, JVM sẽ xây dựng các bảng trong bộ nhớ và liên kết các biến với các giá trị của chúng và các đối tượng với các phương thức của chúng. Khi làm như vậy, nếu một lớp được kế thừa hoặc triển khai một giao diện, thứ tự đầu tiên của công việc là kiểm tra sự hiện diện của các phương thức bị ghi đè. Nếu có, chúng bị ràng buộc vào loại này. Nếu không, việc tìm kiếm một phương thức phù hợp sẽ chuyển sang lớp cao hơn một bước (cha) và cứ thế cho đến gốc trong hệ thống phân cấp nhiều cấp. Khi nói đến tính đa hình trong OOP và việc triển khai nó trong mã, chúng tôi lưu ý rằng nên sử dụng các lớp trừu tượng và giao diện để cung cấp các định nghĩa trừu tượng của các lớp cơ sở. Thực hành này tuân theo nguyên tắc trừu tượng – xác định hành vi và thuộc tính chung và đặt chúng vào một lớp trừu tượng hoặc chỉ xác định hành vi chung và đặt nó vào một giao diện. Cần phải thiết kế và tạo một hệ thống phân cấp đối tượng dựa trên các giao diện và kế thừa lớp để thực hiện tính đa hình. Về tính đa hình và những đổi mới trong Java, chúng tôi lưu ý rằng bắt đầu từ Java 8, khi tạo các lớp và giao diện trừu tượng, có thể sử dụng hoặc chỉ xác định hành vi phổ biến và đưa nó vào một giao diện. Cần phải thiết kế và tạo một hệ thống phân cấp đối tượng dựa trên các giao diện và kế thừa lớp để thực hiện tính đa hình. Về tính đa hình và những đổi mới trong Java, chúng tôi lưu ý rằng bắt đầu từ Java 8, khi tạo các lớp và giao diện trừu tượng, có thể sử dụng hoặc chỉ xác định hành vi phổ biến và đưa nó vào một giao diện. Cần phải thiết kế và tạo một hệ thống phân cấp đối tượng dựa trên các giao diện và kế thừa lớp để thực hiện tính đa hình. Về tính đa hình và những đổi mới trong Java, chúng tôi lưu ý rằng bắt đầu từ Java 8, khi tạo các lớp và giao diện trừu tượng, có thể sử dụngtừ khóa mặc định để viết triển khai mặc định cho các phương thức trừu tượng trong các lớp cơ sở. Ví dụ:
for (Swim s : swimmers) { s.swim(); }
Đôi khi những người phỏng vấn hỏi về cách các phương thức trong các lớp cơ sở phải được khai báo để nguyên tắc đa hình không bị vi phạm. Câu trả lời rất đơn giản: các phương thức này không được là tĩnh , riêng tư hay cuối cùng . Riêng tư làm cho một phương thức chỉ khả dụng trong một lớp, vì vậy bạn sẽ không thể ghi đè lên phương thức đó trong một lớp con. Tĩnh liên kết một phương thức với lớp chứ không phải bất kỳ đối tượng nào, vì vậy phương thức của lớp cha sẽ luôn được gọi. Và final làm cho một phương thức trở nên bất biến và ẩn khỏi các lớp con.
public interface CanSwim { default void swim() { System.out.println("I just swim"); } }
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 đó.
Ví dụ về đa hình lúc runtime trong java
Ví dụ 1:
Chúng ta tạo hai lớp Bike và Splendar. Lớp Splendar kế thừa lớp Bike và ghi đè phương thức run() của nó. Chúng ta gọi phương thức run bởi biến tham chiếu của lớp cha. Khi nó tham chiếu tới đối tượng của lớp con và phương thức lớp con ghi đè phương thức của lớp cha, phương thức lớp con được gọi lúc runtime.
Khi việc gọi phương thức được quyết định bởi JVM chứ không phải Compiler, vì thế đó là đa hình lúc runtime.
class Bike { void run() { System.out.println(“running”); } } public class Splender extends Bike { void run() { System.out.println(“running safely with 60km”); } public static void main(String args[]) { Bike b = new Splender();// upcasting b.run(); } }
Kết quả:
running safely with 60km
Ví dụ 2: Bank:
Giả sử Bank là một lớp cung cấp chức năng xem thông tin tỷ lệ lãi suất. Nhưng mỗi ngân hàng có một lãi xuất khác nhau, ví dụ các ngân hàng SBI, ICICI và AXIS có tỷ lệ lãi suất lần lượt là 8%, 7% và 9%.
class Bank { int getRateOfInterest() { return 0; } } class SBI extends Bank { int getRateOfInterest() { return 8; } } class ICICI extends Bank { int getRateOfInterest() { return 7; } } class AXIS extends Bank { int getRateOfInterest() { return 9; } } public class Test2 { public static void main(String args[]) { Bank b; b = new SBI(); System.out.println(“SBI Rate of Interest: ” + b.getRateOfInterest()); b = new ICICI(); System.out.println(“ICICI Rate of Interest: ” + b.getRateOfInterest()); b = new AXIS(); System.out.println(“AXIS Rate of Interest: ” + b.getRateOfInterest()); } }
Kết quả:
SBI Rate of Interest: 8 ICICI Rate of Interest: 7 AXIS Rate of Interest: 9
Ví dụ 3: Shape:
class Shape { void draw() { System.out.println(“drawing…”); } } class Rectangle extends Shape { void draw() { System.out.println(“drawing rectangle…”); } } class Circle extends Shape { void draw() { System.out.println(“drawing circle…”); } } class Triangle extends Shape { void draw() { System.out.println(“drawing triangle…”); } } class TestPolymorphism2 { public static void main(String args[]) { Shape s; s = new Rectangle(); s.draw(); s = new Circle(); s.draw(); s = new Triangle(); s.draw(); } }
Kết quả:
drawing rectangle… drawing circle… drawing triangle…
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!
Đa hình trong java (Polymorphism) là một khái niệm mà chúng ta có thể thực hiện một hành động bằng nhiều cách khác nhau. Polymorphism được cấu tạo từ 2 từ Hy Lạp: poly và morphs. Trong đó “poly” có nghĩa là nhiều và “morphs” có nghĩa là hình thể. Vậy polymorphism có nghĩa là nhiều hình thể.
Có hai kiểu của đa hình trong java, đó là đa hình lúc biên dịch (compile) và đa hình lúc thực thi (runtime). Chúng ta có thể thực hiện đa hình trong java bằng cách nạp chồng phương thức và ghi đè phương thức.
Nếu bạn nạp chồng phương thức static trong java, đó là một ví dụ về đa hình lúc biên dịch. Trong bài này, chúng ta tập trung vào đa hình lúc runtime trong java.
Nội dung chính
KẾT LUẬN
- Sử dụng Override bản chất là đa hình tại runtime
- Việc gọi phương thức khi override được quyết định bở JVM chứ không phải Compiler
- Không thể áp dụng đa hình tại runtime với các thuộc tính chứa dữ liệu
- Việc gọi phương thức của một lớp kế thừa lớp khác nếu không ghi đè phương thức của lớp cha thì phương thức đó sẽ được gọi từ lớp cha được kế thừa trực tiếp
Tài liệu tham khảo:
All rights reserved
Đa hình tại runtime trong Java với thành viên dữ liệu
Phương thức bị ghi đè không là thành viên dữ liệu, vì thế đa hình tại runtime không thể có được bởi thành viên dữ liệu. Trong ví dụ sau đây, cả hai lớp có một thành viên dữ liệu là speedlimit, chúng ta truy cập thành viên dữ liệu bởi biến tham chiếu của lớp cha mà tham chiếu tới đối tượng lớp con. Khi chúng ta truy cập thành viên dữ liệu mà không bị ghi đè, thì nó sẽ luôn luôn truy cập thành viên dữ liệu của lớp cha.
Qui tắc: Đa hình tại runtime không thể có được bởi thành viên dữ liệu.
Ví dụ:
class Bike { int speedlimit = 90; } class Honda3 extends Bike { int speedlimit = 150; public static void main(String args[]){ Bike obj=new Honda3(); System.out.println(obj.speedlimit); // 90 } }
Kết quả:
90
Tính đa hình (polymorphism) trong Java là gì?
Tính đa hình (polymorphism) hiểu đơn giản là các đối tượng, các phương thức giống nhau có thể có các hành vi khác nhau tùy vào từng tình huống khác nhau.
class Polygon { //phương thức render của lớp Polygon public void render() { System.out.println("Rendering Polygon..."); } } class Square extends Polygon { //ghi đè phương thức render @Override public void render() { System.out.println("Rendering Square..."); } } class Circle extends Polygon { //ghi đè phương thức render @Override public void render() { System.out.println("Rendering Circle..."); } } class Main { public static void main(String[] args) { // create an object of Square Square s1 = new Square(); s1.render(); // create an object of Circle Circle c1 = new Circle(); c1.render(); } }
Kết quả
Rendering Square... Rendering Circle...
Ở ví dụ trên, lớp Polygon có 2 lớp con là Square and Circle. Chúng đều có hàm
render()
nhưng hàm này thực thi khác nhau trong từng lớp. Đó là biểu hiện của tính đa hình (polymorphism).
Ghi đè phương thức (method overriding)
Ghi đè phương thức trong java xảy ra nếu lớp con có phương thức giống lớp cha.
Nói cách khác, nếu lớp con cung cấp sự cài đặt cụ thể cho phương thức đã được cung cấp bởi một lớp cha của nó được gọi là ghi đè phương thức (method overriding) trong java.
Ghi đè phương thức được sử dụng để thu được tính đa hình tại runtime.
Nguyên tắc ghi đè phương thức:
- Phương thức phải có tên giống với lớp cha.
- Phương thức phải có tham số giống với lớp cha.
- Lớp con và lớp cha có mối quan hệ kế thừa.
Ví dụ về ghi đè phương thức (method overriding)
Ví dụ 1: chúng ta định nghĩa phương thức run() trong lớp con giống như đã được định nghĩa trong lớp cha, nhưng được cài đặt rõ ràng trong lớp con. Tên và tham số của phương thức là giống nhau, 2 lớp cha và con có quan hệ kế thừa.
class Vehicle { void run() { System.out.println(“Vehicle is running”); } } class Bike extends Vehicle { void run() { System.out.println(“Bike is running safely”); } public static void main(String args[]) { Bike obj = new Bike(); obj.run(); } }
Kết quả khi chạy chương trình trên:
Bike is running safely
Ví dụ 2: Giả sử Bank là một đối tượng cung cấp lãi suất. Nhưng lãi suất lại khác nhau giữa từng ngân hàng. Ví dụ, các ngân hàng VCB, AGR và CTG có thể cung cấp các lãi suất lần lượt là 8%, 7% và 9%.
class Bank { int getRateOfInterest() { return 0; } } class VCB extends Bank { int getRateOfInterest() { return 8; } } class AGR extends Bank { int getRateOfInterest() { return 7; } } class CTG extends Bank { int getRateOfInterest() { return 9; } } class BankApp { public static void main(String args[]) { VCB s = new VCB(); AGR i = new AGR(); CTG a = new CTG(); System.out.println(“VCB Rate of Interest: ” + s.getRateOfInterest()); System.out.println(“AGR Rate of Interest: ” + i.getRateOfInterest()); System.out.println(“CTG Rate of Interest: ” + a.getRateOfInterest()); } }
Kết quả khi chạy chương trình trên:
VCB Rate of Interest: 8 AGR Rate of Interest: 7 CTG Rate of Interest: 9
Một số câu hỏi về ghi đè phương thức (method overriding) trong java
Có ghi đè được phương thức static không?
Không, phương thức static không thể ghi đè được, bởi vì ghi đè phương thức được thực thi lúc runtime (tính đa hình).
Tại sao không ghi đè được phương thức static?
Vì phương thức static được ràng buộc với class, còn phương thức instance được ràng buộc với đối tượng. Static thuộc về vùng nhớ class còn instance thuộc về vùng nhớ heap.
Có ghi đè phương thức main được không?
Không, vì main là phương thức static.
Đa hình lúc runtime trong Java với kế thừa nhiều tầng
Ví dụ 1:
class Animal { void eat() { System.out.println(“eating”); } } class Dog extends Animal { void eat() { System.out.println(“eating fruits”); } } class BabyDog extends Dog { void eat() { System.out.println(“drinking milk”); } public static void main(String args[]) { Animal a1, a2, a3; a1 = new Animal(); a2 = new Dog(); a3 = new BabyDog(); a1.eat(); a2.eat(); a3.eat(); } }
Kết quả:
eating eating fruits drinking Milk
Ví dụ 2:
class Animal { void eat() { System.out.println(“animal is eating…”); } } class Dog extends Animal { void eat() { System.out.println(“dog is eating…”); } } class BabyDog1 extends Dog { public static void main(String args[]) { Animal a = new BabyDog1(); a.eat(); } }
Kết quả:
Dog is eating
Vì BabyDog1 không ghi đè phương thức eat(), nên phương thức eat() của lớp Dog được gọi.
Sự khác nhau giữa overloading và overriding trong java
Nạp chồng phương thức | Ghi đè phương thức |
Nạp chồng phương thức được sử dụng để tăng tính có thể đọc của chương trình | Ghi đè phương thức được sử dụng để cung cấp trình triển khai cụ thể của phương thức mà đã được cung cấp bởi lớp cha của nó |
Nạp chồng phương thức được thực hiện bên trong lớp (class) | Ghi đè phương thức xuất hiện trong hai lớp mà có mối quan hệ IS-A (kế thừa) |
Trong Nạp chồng phương thức, tham số phải khác nhau | Trong Ghi đè phương thức, tham số phải là giống nhau |
Nạp chồng phương thức là ví dụ của đa hình tại biên dịch (compile) | Ghi đè phương thức là ví dụ của đa hình tại thực thi (runtime) |
Trong Java, Nạp chồng phương thức không thể được thực hiện bởi thay đổi kiểu trả về của phương thức. Kiểu trả về có thể là giống hoặc khác trong Nạp chồng phương thức. Nhưng bạn phải thay đổi tham số | Kiểu trả về phải là giống. |
Tài liệu tham khảo:
- http://vietjack.com/java/da_hinh_trong_java.jsp
- https://www.javatpoint.com/runtime-polymorphism-in-java
- http://vietjack.com/java/overloading_trong_java.jsp
- https://www.javatpoint.com/method-overloading-in-java
- http://vietjack.com/java/overriding_trong_java.jsp
- https://www.javatpoint.com/method-overriding-in-java
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.
ĐA HÌNH TẠI RUNTIME TRONG JAVA VỚI THUỘC TÍNH
Phương thức bị ghi đè không phải là thuộc tính, vì thế đa hình tại runtime không hoạt động với thuộc tính. Cùng xem ví dụ dưới đây:
class Bike{ int speedlimit=90; } class Honda3 extends Bike{ int speedlimit=150; public static void main(String args[]){ Bike obj=new Honda3(); System.out.println(obj.speedlimit);//90 }
Cả hai lớp đều có một thuộc tính là speedlimit, chúng ta có thể truy cập nó bằng cách sử dụng tham chiếu của lớp cha tham chiếu vào đối tượng của lớp con, nhưng khi truy cập thuộc tính đó nó sẽ không bị ghi đè mà kết quả in ra vẫn là giá trị thuộc tính của lớp cha.
ĐA HÌNH TẠI RUNTIME TRONG JAVA VỚI KẾ THỪA NHIỀU TẦNG
Cùng theo dõi ví dụ bên dưới về kế thừa nhiều tầng
class Animal{ void eat(){System.out.println("an");} } class Dog extends Animal{ void eat(){System.out.println("an hoa qua");} } class BabyDog extends Dog{ public static void main(String args[]){ Animal a1,a2,a3; a1=new Animal(); a2=new Dog(); a3=new BabyDog(); a1.eat(); // an a2.eat(); // an hoa qua a3.eat(); // an hoa qua } }
Trong ví dụ trên, lớp Dog kế thừa lớp Animal và ghi đè phương thức eat() của lớp Animal. Lớp BabyDog kế thừa lớp Dog nhưng không ghi đè phương thức eat(). Khi gọi phương thức eat() từ tham chiếu của lớp BabyDog, phương thức eat() của lớp Dog sẽ được gọi đến.
Thực Hành Xây Dựng Ứng Dụng Tính Lương Cho Nhân Viên
Nếu như ở bài học trước chúng ta đã xây dựng “hoàn chỉnh” một hệ thống tính lương “phức tạp” cho một công ty “bự”. Nhưng code khi đó lại không mang rõ tính ứng dụng thực tế, bởi vì chúng ta đã code “cứng” ở chỗ biết trước anh nhân viên nào là lính, anh nào là sếp, anh nào làm toàn thời gian, anh nào làm bán thời gian, để mà khai báo các đối tượng NhanVienFullTime hay NhanVienPartTime tương ứng.
Vậy sang bài hôm nay, chúng ta sẽ hoàn thiện ứng dụng tính lương nhân viên của bài trước. Làm cho hệ thống trở nên thực tế hơn. Cụ thể, bài này chúng ta sẽ cho người dùng nhập bằng tay thông tin nhân viên. Và vì vậy sẽ có một mảng các nhân viên trong ứng dụng. Lớp NhanVien sẽ là lớp có sử dụng đặc tính Đa hình để có thể đóng vai trò là NhanVienFullTime hoặc NhanVienPartTime ở từng hoàn cảnh cụ thể.
Mô Tả Lại Yêu Cầu Chương Trình
Yêu cầu của chương trình tính lương không hề thay đổi so với bài trước. Mình chỉ mô tả lại thôi.
– Công ty có hai loại nhân viên: nhân viên toàn thời gian và nhân viên thời vụ.– Nhân viên toàn thời gian là lính sẽ hưởng lương 10 củ một tháng. Nhân viên toàn thời gian là sếp sẽ hưởng lương 20 củ một tháng.– Nhân viên toàn thời gian nếu làm thêm ngày nào thì sẽ được cộng thêm 800k mỗi ngày, bất kể chức vụ.– Nhân viên thời vụ cứ làm mỗi giờ được 100k, không phân biệt chức vụ gì cả. Làm nhiều thì hưởng nhiều.
Ứng dụng sẽ cho phép người dùng nhập vào số lượng nhân viên. Sau đó với từng nhân viên, người dùng phải nhập vào tên nhân viên, loại nhân viên toàn thời gian hay bán thời gian, nhân viên toàn thời gian thì là nhân viên lính hay nhân viên sếp, có làm thêm ngày nào không, nhân viên thời vụ thì làm được mấy giờ. Cuối cùng dựa vào các thông tin đó, sẽ xuất ra màn hình lương tương ứng cho tất cả nhân viên.
Sơ Đồ Lớp
Chúng ta vẫn dựa vào sơ đồ lớp của bài trước. Nhưng chỉnh sửa một chút sao cho lớp NhanVien sẽ “vào vai” tốt các lớp con của nó. Bằng cách xây dựng thêm phương thức tinhLuong() ở lớp này, rồi ở các lớp con sẽ phải override lại.
Xây Dựng Các Lớp
Lớp Configs không hề thay đổi.
package util; public class Configs { // Loại nhân viên public static final int NHAN_VIEN_SEP = 1; public static final int NHAN_VIEN_LINH = 2; // Lương nhân viên public static final long LUONG_NHAN_VIEN_FULL_TIME_SEP = 20000000; // Lương tháng của sếp public static final long LUONG_NHAN_VIEN_FULL_TIME_LINH = 10000000; // Lương tháng của lính public static final long LUONG_LAM_THEM_MOI_NGAY = 800000; // Làm thêm mỗi ngày của nhân viên toàn thời gian được 800 k public static final long LUONG_NHAN_VIEN_PART_TIME_MOI_GIO = 100000; // Lương nhân viên thời vụ mỗi giờ 100 k }
Lớp NhanVien chỉ có thêm phương thức tinhLuong() để thực hiện Đa hình trên phương thức này.
package model; public class NhanVien { protected String ten; protected long luong; public NhanVien() { } public NhanVien(String ten) { this.ten = ten; } protected String loaiNhanVien() { // Lớp con phải override để lo vụ loại nhân viên này return “”; } public void tinhLuong() { // Lớp con phải override để lo vụ tính lương này } public void xuatThongTin() { System.out.println(“===== Nhân viên: ” + ten + ” =====”); System.out.println(“- Loại nhân viên: ” + loaiNhanVien()); System.out.println(“- Lương: ” + luong + ” VND”); } }
Lớp NhanVienFullTime và NhanVienPartTime cũng không thay đổi gì. Chỉ có giảm bớt overloading ở constructor để khâu nhập liệu được dễ dàng hơn thôi.
package model; import util.Configs; /** * NhanVienFullTime chính là nhân viên toàn thời gian */ public class NhanVienFullTime extends NhanVien { private int ngayLamThem; // Ngày làm thêm của nhân viên private int loaiChucVu; // Chức vụ là lính hay sếp public NhanVienFullTime(String ten, int ngayLamThem, int loaiChucVu) { super(ten); this.ngayLamThem = ngayLamThem; this.loaiChucVu = loaiChucVu; } @Override public String loaiNhanVien() { if (loaiChucVu == Configs.NHAN_VIEN_LINH) { return “Lính toàn thời gian” + (ngayLamThem > 0 ? ” (có làm thêm ngày)”:””); } else { return “Sếp toàn thời gian” + (ngayLamThem > 0 ? ” (có làm thêm ngày)”:””); } } @Override public void tinhLuong() { if (loaiChucVu == Configs.NHAN_VIEN_LINH) { luong = Configs.LUONG_NHAN_VIEN_FULL_TIME_LINH + ngayLamThem * Configs.LUONG_LAM_THEM_MOI_NGAY; } else if (loaiChucVu == Configs.NHAN_VIEN_SEP) { luong = Configs.LUONG_NHAN_VIEN_FULL_TIME_SEP + ngayLamThem * Configs.LUONG_LAM_THEM_MOI_NGAY; } } }
package model; import util.Configs; /** * NhanVienPartTime chính là nhân viên thời vụ */ public class NhanVienPartTime extends NhanVien { private int gioLamViec; // Tổng số giờ làm việc của nhân viên public NhanVienPartTime(String ten, int gioLamViec) { this.ten = ten; this.gioLamViec = gioLamViec; } @Override public String loaiNhanVien() { return “Nhân viên thời vụ”; } @Override public void tinhLuong() { luong = Configs.LUONG_NHAN_VIEN_PART_TIME_MOI_GIO * gioLamViec; } }
Và đây. Mọi thay đổi sẽ nằm ở phương thức main(). Nếu bạn đừng để ý đến các đoạn code râu ria nhập liệu từ console. Thì phương thức main() có các ý sau chúng ta nên lưu tâm.
– Lần đầu tiên, chúng ta sử dụng đến mảng các đối tượng. Và bạn thấy rằng, mảng các đối tượng cũng chẳng khác mảng của các kiểu nguyên thủy mà bạn đã học là mấy.– Với mỗi phần tử trong mảng các NhanVien. Chúng ta khởi tạo NhanVien này là NhanVienFullTime hay NhanVienPartTime là do điều kiện mà người dùng nhập vào. Tính Đa hình phát huy tác dụng ở đây.
package main; import java.util.Scanner; import model.NhanVien; import model.NhanVienFullTime; import model.NhanVienPartTime; public class MainClass { public static void main(String[] args) { // Kêu người dùng nhập vào số lượng nhân viên trong công ty Scanner scanner = new Scanner(System.in); System.out.print(“Hãy nhập số lượng nhân viên: “); int tongNhanVien = Integer.parseInt(scanner.nextLine()); // Khai báo mảng các nhân viên NhanVien[] mangNhanVien = new NhanVien[tongNhanVien]; for (int i = 0; i < tongNhanVien; i++) { // Khai báo từng loại nhân viên, và kêu người dùng nhập thông tin nhân viên System.out.print(“Tên nhân viên ” + (i + 1) + “: “); String ten = scanner.nextLine(); System.out.print(“Là nhân viên (1-Toàn thời gian; 2-Bán thời gian): “); int laNhanVien = Integer.parseInt(scanner.nextLine()); if (laNhanVien == 1) { // Nhân viên toàn thời gian System.out.print(“Chức vụ nhân viên (1-Sếp; 2-Lính): “); int chucVu = Integer.parseInt(scanner.nextLine()); System.out.print(“Ngày làm thêm (nếu có): “); int ngayLamThem = Integer.parseInt(scanner.nextLine()); mangNhanVien[i] = new NhanVienFullTime(ten, ngayLamThem, chucVu); } else { System.out.print(“Giờ làm: “); int gioLamViec = Integer.parseInt(scanner.nextLine()); mangNhanVien[i] = new NhanVienPartTime(ten, gioLamViec); } } System.out.println(“\nKết quả tính lương\n”); // Tính lương và xuất thông tin nhân viên for (NhanVien nhanVien : mangNhanVien) { nhanVien.tinhLuong(); nhanVien.xuatThongTin(); } } }
Cuối cùng là kết quả thực thi chương trình. Ngoài việc nhập liệu động ra thì kết quả in ra là như bài hôm trước. Bạn thử kết hợp việc nhập liệu động của bài hôm nay với việc không sử dụng tính Đa hình, mà dùng như bài học hôm trước xem. Với cách thử nghiệm này, bạn sẽ hiểu rõ hơn về thế mạnh của Đa hình đấy.
Đa hình là vậy, bạn có thấy khó không nào. Sẽ còn kiến thức có liên quan đến Đa hình nữa, như ép kiểu trong OOP chẳng hạn, mà chúng ta sẽ nói ở bài học sau.
Cảm ơn bạn đã đọc các bài viết của Yellow Code Books. Bạn hãy đánh giá 5 sao nếu thấy thích bài viết, hãy comment bên dưới nếu có thắc mắc, hãy để lại địa chỉ email của bạn để nhận được thông báo mới nhất khi có bài viết mới, và nhớ chia sẻ các bài viết của Yellow Code Books đến nhiều người khác nữa nhé.
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.
Tính đa hình trong java
Tính đa hình ( Polymorphism ) là một khái niệm mà chúng ta có thể thực hiện một công việc bằng nhiều cách khác nhau. Có hai kiểu đa hình trong java đó là đa hình lúc thực thi ( runtime ) và đa hình trong lúc biên dịch ( compile ).
- Đa hình lúc biên dịch ( compile ) gọi là overloading.
- Đa hình lúc thực thi ( runtime ) gọi là overriding.
Đa hình lúc thực thi ( overriding )
- Overriding là quá trình gọi phương thức đã được ghi đè trong thời gian thực thi chương trình. Trong quá trình này, một phương thức được ghi đè được gọi thông qua một biến tham chiếu của lớp cha.
class Point{ } class Cricle extends Point { }
Point A = new Cricle() ; A là một biến tham chiếu kiểu Point (lớp cha).
Quá trình này gọi là Upcasting.
Các ví dụ về overriding trong java
-
Ví dụ này ta tạo ra hai lớp là
Car
và
Oto
. Lớp Oto kế thừa lớp Car và ghi đè phương thức run() của nó. Chúng ta gọi phương thức run bởi biến tham chiếu của lớp Car. Khi nó tham chiếu tới đối tượng của lớp con ( lớp Oto) và phương thức lớp con ghi đè phương thức của lớp cha, phương thức lớp con được gọi lúc runtime. Quá trình ghi đè phương thức trên gọi là overriding.
class Car{ public void run(){ System.out.println(“Car running…”); } } class Oto extends Car { public void run(){ System.out.println(“Oto running”); } } class Test{ public static void main(String[] args){ Car A = new Oto(); // Quá trình Upcasting A.run(); } }
Sau khi chạy chương trình trên ta có kết quả sau
Oto running
- Lưu ý là quá trình quá trình ghi đè lúc runtime ( hay overriding ) chỉ xảy ra với các phương thức giống nhau về tham số và kiểu trả về. Các thuộc tính của đối tượng cha không thể bị ghi đè.
Xét ví dụ sau
class Car{ public int run(){ System.out.println(“Car running…”); return 0; } } class Oto extends Car { public void run(){ System.out.println(“Oto running”); } } class Test{ public static void main(String[] args){ Car A = new Oto(); A.run(); } }
- Biên dịch chương trình trên sẽ bị lỗi.
Xét một ví dụ khác để chứng minh không thể overriding cho các thành viên dữ liệu ta xét ví dụ sau
class Car{ int a = 10; } class Oto extends Car { int a = 20; } class Test{ public static void main(String[] args){ Car A = new Oto(); System.out.println(A.a); } }
10
Như vậy thuộc tính của lớp cha không thể bị ghi đè.
Bài viết của mình đến đây là kết thúc. Cám ơn các bạn đã theo dõi !
Đa hình tại Runtime trong Java
Bài đăng này đã không được cập nhật trong 5 năm
Chuyển đổi kiểu dữ liệu đối tượng
Kiểu dữ liệu tham chiếu (các lớp) có thể được chuyển đổi kiểu khi kiểu dữ liệu tham chiếu (lớp) tương thích, tức là nằm trên cùng một cây phân cấp kế thừa.
Có hai cách chuyển đổi là Up-casting và Down-casting.
Up-casting
– Đi lên trên cây phân cấp thừa kế. Up-casting là khả năng nhìn nhận đối tượng thuộc lớp dẫn xuất như là một đối tượng thuộc lớp cơ sở.
– Tự động chuyển đổi kiểu.
Ví dụ:
class Person{ String name; Date birthday; public void setName(String name){ this.name = name; } public void setBirthday(Date birthday){ this.birthday = birthday; } public String getDetails(){ String detail = ""; detail = "Name: " + name + ", birthday: " + new SimpleDateFormat("MM-dd-yyyy").format(birthday); return detail; } } class Employee extends Person{ double salary; public void setSalary(double salary){ this.salary = salary; } @Override public String getDetails(){ String detail=""; detail = "Name: " + name + ", birthday: " + new SimpleDateFormat("MM-dd-yyyy").format(birthday) + ", salary: " + salary; return detail; } } class Main { public static void main(String[] args) throws ParseException { Employee e = new Employee(); Person p; p = e; p.setName("Henry"); p.setBirthday(new SimpleDateFormat("MM-dd-yyyy").parse("12-31-1998")); System.out.println(p.getDetails()); } }
Kết quả
Name: Henry, birthday: 12-31-1998, salary: 0.0
Trong ví dụ trên, chúng ta tạo ra đối tượng p của lớp Person. Mà lớp Person là lớp cha của lớp Employee. Một Employee cũng là một Person. Nên p có thể tham chiếu đến đối tượng của lớp Employee với câu lệnh
p = e;
. Các hàm mà Person p gọi là những hàm mà một Employee kế thừa được từ Person.
Lưu ý, một Person chưa chắc là một Employee bởi có thể có những lớp khác cũng kế thừa lớp Person như lớp Student, Teacher. Thì lúc này, Person cũng có thể là Student hay Teacher. Nhưng một Student hay Teacher thì chắc chắn vẫn là một Person.
Lưu ý tiếp theo, nếu đối tượng p của lớp Person gọi hàm
setSalary()
của đối tượng e thuộc lớp Employee thì sẽ báo lỗi. Bởi vì một đối tượng người thì không có
setSalary()
. Tức là, Employee e bây giờ là 1 Person bình thường.
p.setSalary(7000.0);//error
Down-casting
– Đi xuống cây phân cấp thừa kế. Down-casting là khả năng nhìn nhận một đối tượng thuộc lớp cơ sở như một đối tượng thuộc lớp dẫn xuất.
– Không tự động chuyển đổi kiểu, phải ép kiểu.
class Person{ String name; Date birthday; public void setName(String name){ this.name = name; } public void setBirthday(Date birthday){ this.birthday = birthday; } public String getDetails(){ String detail = ""; detail = "Name: " + name + ", birthday: " + new SimpleDateFormat("MM-dd-yyyy").format(birthday); return detail; } } class Employee extends Person{ double salary; public void setSalary(double salary){ this.salary = salary; } @Override public String getDetails(){ String detail=""; detail = "Name: " + name + ", birthday: " + new SimpleDateFormat("MM-dd-yyyy").format(birthday) + ", salary: " + salary; return detail; } } class Main { public static void main(String[] args) throws ParseException { Person p1 = new Employee(); //down-casting Employee e1 = (Employee) p1; e1.setName("Henry"); e1.setBirthday(new SimpleDateFormat("MM-dd-yyyy").parse("12-31-1998")); e1.setSalary(7000.0); System.out.println(e1.getDetails()); } }
Kết quả
Name: Henry, birthday: 12-31-1998, salary: 7000.0
Tóm lại, Up-casting là gán object của lớp con cho biến tham chiếu của lớp cha. Down-casting là gán object của lớp cha cho biến tham chiếu của lớp con.
Keywords searched by users: tính đa hình trong java là gì
Categories: Cập nhật 35 Tính Đa Hình Trong Java Là Gì
See more here: kientrucannam.vn
See more: https://kientrucannam.vn/vn/