1. Tính đa hình trong Java là gì?

Tính đa hình trong Java – Polymorphism được hiểu là trong từng trường hợp, hoàn cảnh khác nhau thì đối tượng có hình thái khác nhau tùy thuộc vào từng ngữ cảnh. 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ể.

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à đủ.

Ví dụ thực tế như này: khi bạn ở lớp là học sinh đi học và học bài. Ở nhà các bạn là con cháu phải làm việc nhà, nấu cơm, … Khi đi làm thì các bạn làm nhân viên phải làm công việc được giao…

Có 2 kiểu đa hình chính trong Java là:

  • Đa hình lúc thực thi runtime hay còn gọi là overriding
  • Đa hình lúc biên dịch compile hay còn gọi là overloading

2. Sử dụng đ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?

Đ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 gọi Đa hình này là Đa hình tại runtime .

3. Tính đa hình lúc thực thi trong Java

Đa hình lúc thực thi – 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. Việc quyết định phương thức được gọi là dựa trên đối tượng nào đang được tham chiếu bởi biến tham chiếu.

Trong tính đa hình này có quá trình là Upcasting. Đây là quá trình 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 Point{
}
class Cricle extends Point {
}
Point A = new Cricle() ;
A là một biến tham chiếu kiểu Point (lớp cha).

Đây chính là quá trình Upcasting.

Sau đó ta có ví dụ về 2 lớp CarOto. 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.

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();
    }
}

Kết quả

Oto running

Quá trình quá trình ghi đè lúc runtime 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 đè.

Với ví dụ sau sẽ báo lỗi

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();
    }
}

Hoặc ta cũng không thể overriding cho các thành viên dữ liệu

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);
    }
}

Kết quả

10

Như vậy thuộc tính a của lớp cha không thể bị ghi đè.

4. Ví dụ đ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

BabyDog1 không ghi đè phương thức eat() , nên phương thức eat() của lớp Dog được gọi.

5. Biến đa hình trong Java

Trong Java, các biến đối tượng (biến instance) biểu thị hoạt động của các biến đa hình. Đó là bởi vì các biến đối tượng của một class có thể dùng để chỉ các đối tượng của class cũng như các đối tượng của các class con của nó.

Ví dụ:

class Animal {
   public void displayInfo() {
      System.out.println("I am an animal.");
   }
}

class Dog extends Animal {
   @Override
   public void displayInfo() {
      System.out.println("I am a dog.");
   }
}

class Main {
   public static void main(String[] args) {
    
     // declaration of object variable a1 of the Animal class
      Animal a1;
    
    // object creation of the Animal class
      a1 = new Animal();
      a1.displayInfo();
    // object creation of the dog class
      a1 = new Dog();
      a1.displayInfo();
   }
}

Kết quả

I am an animal.
I am a dog.

Trong ví dụ trên, chúng ta đã tạo một biến đối tượng là a1 của class Animal. Ở đây, a1 là một biến đa hình. Bởi vì:

  • Trong câu lệnh a1 = new Animal(), a1 dùng để chỉ đối tượng của class Animal.
  • Trong câu lệnh a1 = new Dog(), a1 dùng để chỉ đối tượng của class Dog.