"Anh bạn, bạn có thích cá voi không?"

"Cá voi? Không, chưa bao giờ nghe nói về chúng."

"Nó giống như một con bò, chỉ lớn hơn và nó bơi. Nhân tiện, cá voi có nguồn gốc từ bò. Ờ, hoặc ít nhất chúng có chung một tổ tiên. Điều đó không quan trọng."

Đa hình và ghi đè - 1

"Nghe này. Tôi muốn nói với bạn về một công cụ rất mạnh mẽ khác của OOP: tính đa hình . Nó có bốn tính năng."

1) Ghi đè phương thức.

Hãy tưởng tượng rằng bạn đã viết một lớp "Cow" cho một trò chơi. Nó có rất nhiều biến thành viên và phương thức. Các đối tượng của lớp này có thể làm nhiều việc khác nhau: đi bộ, ăn, ngủ. Bò cũng rung chuông khi chúng đi bộ. Giả sử bạn đã triển khai mọi thứ trong lớp đến từng chi tiết nhỏ nhất.

Đa hình và ghi đè - 2

Sau đó, đột nhiên khách hàng nói rằng anh ta muốn phát hành một cấp độ mới của trò chơi, nơi mọi hành động diễn ra trên biển và nhân vật chính là một con cá voi.

Bạn bắt đầu thiết kế lớp Whale và nhận ra rằng nó chỉ khác một chút so với lớp Cow. Cả hai lớp đều sử dụng logic rất giống nhau và bạn quyết định sử dụng tính kế thừa.

Lớp Cow phù hợp lý tưởng để trở thành lớp cha: nó đã có tất cả các biến và phương thức cần thiết. Tất cả những gì bạn cần làm là thêm khả năng bơi lội của cá voi. Nhưng có một vấn đề: con cá voi của bạn có chân, sừng và chuông. Xét cho cùng, lớp Cow thực hiện chức năng này. Bạn có thể làm gì?

Đa hình và ghi đè - 3

Ghi đè phương thức đến để giải cứu. Nếu chúng ta kế thừa một phương thức không thực hiện chính xác những gì chúng ta cần trong lớp mới, chúng ta có thể thay thế phương thức đó bằng một phương thức khác.

Đa hình và ghi đè - 4

Làm thế nào điều này được thực hiện? Trong lớp con của chúng ta, chúng ta khai báo phương thức mà chúng ta muốn thay đổi (có cùng chữ ký phương thức như trong lớp cha) . Sau đó, chúng tôi viết mã mới cho phương thức. Đó là nó. Như thể phương thức cũ của lớp cha không tồn tại.

Đây là cách nó hoạt động:

Mã số Sự miêu tả
class Cow
{
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}class Whale extends Cow
{
public void printName()
{
System.out.println("I'm a whale");
}
}
Ở đây chúng tôi định nghĩa hai lớp:  Cow và  WhaleWhalekế thừa  Cow.

Lớp  Whale ghi đè  printName();phương thức.

public static void main(String[] args)
{
Cow cow = new Cow();
cow.printName();
}
Mã này hiển thị « Tôi là một con bò » trên màn hình.
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
Mã này hiển thị « Tôi là cá voi » trên màn hình

Sau khi kế thừa Cowvà ghi đè printName, Whalelớp thực sự có dữ liệu và phương thức sau:

Mã số Sự miêu tả
class Whale
{
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a whale");
}
}
Chúng tôi không biết gì về bất kỳ phương pháp cũ nào.

"Thành thật mà nói, đó là những gì tôi đã mong đợi."

2) Nhưng đó không phải là tất cả.

"Giả sử  Cow lớp có một  printAllphương thức gọi hai phương thức khác. Sau đó, mã sẽ hoạt động như sau:"

Màn hình sẽ hiển thị:
Tôi là người da trắng
Tôi là một con cá voi

Mã số Sự miêu tả
class Cow
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}

class Whale extends Cow
{
public void printName()
{
System.out.println("I'm a whale");
}
}
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printAll();
}
Màn hình sẽ hiển thị:
Tôi là người da trắng
Tôi là một con cá voi

Lưu ý rằng khi phương thức printAll() của lớp Bò được gọi trên một đối tượng Cá voi, phương thức printName() của Cá voi sẽ được sử dụng, không phải của Bò.

Điều quan trọng không phải là lớp mà phương thức được viết vào, mà là kiểu (lớp) của đối tượng mà phương thức được gọi.

"Tôi hiểu rồi."

"Bạn chỉ có thể kế thừa và ghi đè các phương thức không tĩnh. Các phương thức tĩnh không được kế thừa và do đó không thể bị ghi đè."

Đây là giao diện của lớp Whale sau khi chúng ta áp dụng tính kế thừa và ghi đè lên các phương thức:

Mã số Sự miêu tả
class Whale
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a whale");
}
}
Đây là giao diện của lớp Whale sau khi chúng ta áp dụng tính kế thừa và ghi đè phương thức. Chúng tôi không biết gì về bất kỳ printNamephương pháp cũ nào.

3) Loại đúc.

Đây là một điểm thậm chí còn thú vị hơn. Bởi vì một lớp kế thừa tất cả các phương thức và dữ liệu của lớp cha của nó, nên một đối tượng của lớp này có thể được tham chiếu bởi các biến của lớp cha (và cha của lớp cha, v.v., cho đến lớp Đối tượng). Hãy xem xét ví dụ này:

Mã số Sự miêu tả
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printColor();
}
Màn hình sẽ hiển thị:
Tôi là người da trắng.
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printColor();
}
Màn hình sẽ hiển thị:
Tôi là người da trắng.
public static void main(String[] args)
{
Object o = new Whale();
System.out.println(o.toString());
}
Màn hình sẽ hiển thị:
Whale@da435a.
Phương thức toString() được kế thừa từ lớp Object.

"Đồ tốt. Nhưng tại sao bạn lại cần thứ này?"

"Đó là một tính năng có giá trị. Sau này bạn sẽ hiểu rằng nó rất, rất có giá trị."

4) Ràng buộc muộn (công văn động).

Đây là những gì nó trông giống như:

Mã số Sự miêu tả
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
Màn hình sẽ hiển thị:
Tôi là một con cá voi.
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printName();
}
Màn hình sẽ hiển thị:
Tôi là một con cá voi.

Lưu ý rằng không phải kiểu của biến xác định phương thức printName cụ thể mà chúng ta gọi (phương thức của lớp Cow hay Whale), mà là kiểu đối tượng được tham chiếu bởi biến.

Biến Cow lưu trữ một tham chiếu đến một đối tượng Whale và phương thức printName được xác định trong lớp Whale sẽ được gọi.

"Chà, họ đã không thêm điều đó vì mục đích rõ ràng."

"Ừ, nó không rõ ràng lắm. Hãy nhớ quy tắc quan trọng này:"

Tập hợp các phương thức bạn có thể gọi trên một biến được xác định bởi loại biến. Nhưng phương thức/cách triển khai cụ thể nào được gọi được xác định bởi loại/lớp của đối tượng được tham chiếu bởi biến.

"Tôi sẽ thử."

"Bạn sẽ gặp phải điều này liên tục, vì vậy bạn sẽ nhanh chóng hiểu nó và không bao giờ quên."

5) Loại đúc.

Truyền hoạt động khác nhau đối với các loại tham chiếu, tức là các lớp, so với các loại nguyên thủy. Tuy nhiên, chuyển đổi mở rộng và thu hẹp cũng áp dụng cho các loại tham chiếu. Hãy xem xét ví dụ này:

Mở rộng chuyển đổi Sự miêu tả
Cow cow = new Whale();

Một chuyển đổi mở rộng cổ điển. Bây giờ bạn chỉ có thể gọi các phương thức được định nghĩa trong lớp Cow trên đối tượng Whale.

Trình biên dịch sẽ chỉ cho phép bạn sử dụng biến cow để gọi các phương thức được xác định bởi kiểu Cow.

Thu hẹp chuyển đổi Sự miêu tả
Cow cow = new Whale();
if (cow instanceof Whale)
{
Whale whale = (Whale) cow;
}
Chuyển đổi thu hẹp cổ điển với kiểm tra loại. Biến cow kiểu Cow lưu trữ tham chiếu đến đối tượng Whale.
Chúng tôi kiểm tra xem đây có phải là trường hợp không và sau đó thực hiện chuyển đổi loại (mở rộng). Điều này còn được gọi là ép kiểu .
Cow cow = new Cow();
Whale whale = (Whale) cow; //exception
Bạn cũng có thể thực hiện chuyển đổi thu hẹp loại tham chiếu mà không cần kiểm tra loại đối tượng.
Trong trường hợp này, nếu biến cow đang trỏ vào một thứ khác không phải là đối tượng Whale, thì một ngoại lệ (InvalidClassCastException) sẽ bị ném ra.

6) Và bây giờ là món ngon. Gọi phương thức ban đầu.

Đôi khi, khi ghi đè một phương thức kế thừa, bạn không muốn thay thế hoàn toàn phương thức đó. Đôi khi bạn chỉ muốn thêm một chút vào nó.

Trong trường hợp này, bạn thực sự muốn mã của phương thức mới gọi cùng một phương thức, nhưng trên lớp cơ sở. Và Java hãy để bạn làm điều này. Đây là cách nó được thực hiện:  super.method().

Dưới đây là một số ví dụ:

Mã số Sự miêu tả
class Cow
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}

class Whale extends Cow
{
public void printName()
{
System.out.print("This is false: ");
super.printName();

System.out.println("I'm a whale");
}
}
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printAll();
}
Màn hình sẽ hiển thị:
Tôi màu trắng
Điều này là sai: Tôi là một con bò
Tôi là một con cá voi

"Hmm. Chà, đó là một số bài học. Đôi tai robot của tôi gần như tan chảy."

"Vâng, đây không phải là thứ đơn giản. Đây là một trong những tài liệu khó nhất mà bạn sẽ gặp phải. Giáo sư hứa sẽ cung cấp liên kết đến tài liệu của các tác giả khác, vì vậy nếu bạn vẫn chưa hiểu điều gì đó, bạn có thể điền vào khoảng trống."