Chuyển tới nội dung chính

Polymorphism (Đa hình)

Bài trước: Inheritance (Kế thừa) — Đã học cách class con kế thừa và override methods từ class cha. Bài này sẽ tìm hiểu cách một đối tượng có thể có nhiều hình thái khác nhau thông qua polymorphism.

Mục tiêu bài học

Sau bài này, bạn sẽ:

  • Phân biệt compile-time polymorphism (overloading) và runtime polymorphism (overriding)
  • Hiểu cơ chế Dynamic Method Dispatch và cách Java chọn method tại runtime
  • Sử dụng upcasting và downcasting an toàn với instanceof operator
  • Áp dụng polymorphism để viết code linh hoạt và dễ mở rộng
  • Nắm vững sự khác biệt giữa overloading và overriding

Đa hình là gì?

Polymorphism (từ tiếng Hy Lạp: poly = nhiều, morph = hình dạng) là khả năng một đối tượng có thể có nhiều hình thái khác nhau.

Ý nghĩa "Nhiều hình thái"

Animal animal = new Dog(); // Dog IS-A Animal
animal = new Cat(); // Cat IS-A Animal
animal = new Bird(); // Bird IS-A Animal

Cùng một biến animal nhưng có thể tham chiếu đến các đối tượng khác nhau (Dog, Cat, Bird).

Lợi ích của Polymorphism
  • Linh hoạt: Xử lý nhiều loại đối tượng thông qua một interface chung
  • Mở rộng dễ dàng: Thêm class mới không cần sửa code cũ
  • Loose coupling: Giảm sự phụ thuộc giữa các module
  • Code reusability: Tái sử dụng code hiệu quả hơn

Compile-time Polymorphism (Static Polymorphism)

Đa hình tại thời điểm biên dịch, được thực hiện thông qua Method Overloading.

Method Overloading (Review)

class Calculator {
// Overload: cùng tên, khác tham số
public int add(int a, int b) {
return a + b;
}

public double add(double a, double b) {
return a + b;
}

public int add(int a, int b, int c) {
return a + b + c;
}

public String add(String a, String b) {
return a + b;
}
}

public class Test {
public static void main(String[] args) {
Calculator calc = new Calculator();

System.out.println(calc.add(5, 10)); // 15 (int)
System.out.println(calc.add(5.5, 10.3)); // 15.8 (double)
System.out.println(calc.add(1, 2, 3)); // 6 (int)
System.out.println(calc.add("Hello", "World")); // HelloWorld (String)
}
}
Lưu ý

Overloading được quyết định tại compile-time dựa trên:

  • Số lượng tham số
  • Kiểu dữ liệu tham số
  • Thứ tự tham số

Không phụ thuộc vào return type!

Runtime Polymorphism (Dynamic Polymorphism)

Đa hình tại thời điểm chạy, được thực hiện thông qua Method OverridingDynamic Method Dispatch.

Method Overriding

class Animal {
public void makeSound() {
System.out.println("Some generic animal sound");
}

public void eat() {
System.out.println("Animal is eating");
}
}

class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Woof! Woof!");
}

@Override
public void eat() {
System.out.println("Dog is eating bones");
}
}

class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Meow! Meow!");
}

@Override
public void eat() {
System.out.println("Cat is eating fish");
}
}

class Bird extends Animal {
@Override
public void makeSound() {
System.out.println("Tweet! Tweet!");
}
}

Dynamic Method Dispatch

Java tự động chọn method phù hợp tại runtime dựa trên kiểu thực tế của đối tượng:

public class TestPolymorphism {
public static void main(String[] args) {
// Tham chiếu kiểu Animal, đối tượng thực tế khác nhau
Animal animal1 = new Dog();
Animal animal2 = new Cat();
Animal animal3 = new Bird();

// Runtime sẽ gọi method của class thực tế
animal1.makeSound(); // Woof! Woof! (Dog's method)
animal2.makeSound(); // Meow! Meow! (Cat's method)
animal3.makeSound(); // Tweet! Tweet! (Bird's method)

animal1.eat(); // Dog is eating bones
animal2.eat(); // Cat is eating fish
animal3.eat(); // Animal is eating (Bird không override)
}
}

Ứng dụng thực tế: Array/List của đối tượng

public class AnimalShelter {
public static void main(String[] args) {
// Mảng chứa nhiều loại Animal
Animal[] animals = {
new Dog(),
new Cat(),
new Bird(),
new Dog(),
new Cat()
};

// Xử lý tất cả animals một cách thống nhất
System.out.println("=== Feeding time ===");
for (Animal animal : animals) {
animal.eat(); // Mỗi con gọi method eat() của riêng nó
}

System.out.println("\n=== Concert time ===");
for (Animal animal : animals) {
animal.makeSound(); // Mỗi con phát ra âm thanh riêng
}
}
}

Upcasting và Downcasting

Upcasting (Ngầm định - Implicit)

Chuyển từ subclass → superclass (tự động, an toàn):

Dog myDog = new Dog();
Animal animal = myDog; // Upcasting tự động

// Hoặc trực tiếp
Animal animal2 = new Dog(); // Upcasting
Upcasting
  • Tự động, không cần ép kiểu
  • An toàn (Dog luôn luôn là Animal)
  • Chỉ truy cập được members của Animal (mất quyền truy cập members riêng của Dog)

Downcasting (Tường minh - Explicit)

Chuyển từ superclass → subclass (cần ép kiểu, có thể lỗi):

Animal animal = new Dog(); // Upcasting

// Downcasting - cần ép kiểu tường minh
Dog dog = (Dog) animal; // OK - vì đối tượng thực tế là Dog
dog.makeSound();

// ⚠️ Nguy hiểm nếu không kiểm tra
Animal animal2 = new Cat();
Dog dog2 = (Dog) animal2; // ❌ ClassCastException tại runtime!
Downcasting
  • Cần ép kiểu tường minh
  • Không an toàn - có thể gây ClassCastException
  • Luôn kiểm tra trước khi downcast bằng instanceof

instanceof Operator

Kiểm tra xem một object có phải là instance của một class/interface hay không:

public class TestInstanceOf {
public static void main(String[] args) {
Animal animal1 = new Dog();
Animal animal2 = new Cat();
Animal animal3 = new Bird();

checkAndCast(animal1); // Dog
checkAndCast(animal2); // Cat
checkAndCast(animal3); // Bird
}

public static void checkAndCast(Animal animal) {
// Kiểm tra trước khi downcast
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
System.out.println("This is a Dog!");
dog.makeSound();
} else if (animal instanceof Cat) {
Cat cat = (Cat) animal;
System.out.println("This is a Cat!");
cat.makeSound();
} else if (animal instanceof Bird) {
Bird bird = (Bird) animal;
System.out.println("This is a Bird!");
bird.makeSound();
}
}
}

Pattern Matching for instanceof (Java 16+)

Java 16+ hỗ trợ pattern matching, giảm boilerplate code:

public static void checkAndCastModern(Animal animal) {
// Tự động cast nếu instanceof true
if (animal instanceof Dog dog) {
System.out.println("This is a Dog!");
dog.makeSound(); // Không cần cast thủ công
} else if (animal instanceof Cat cat) {
System.out.println("This is a Cat!");
cat.makeSound();
} else if (animal instanceof Bird bird) {
System.out.println("This is a Bird!");
bird.makeSound();
}
}

Advanced instanceof: Scoping và Negation

// Scope của pattern variable
if (animal instanceof Dog dog) {
dog.makeSound(); // ✅ dog visible here
} // dog out of scope

// Negation với pattern matching
if (!(animal instanceof Dog dog)) {
System.out.println("Not a dog");
// dog.makeSound(); // ❌ Compile error - dog không visible trong negated block
} else {
dog.makeSound(); // ✅ dog visible trong else
}

// Pattern variable với && (AND logic)
if (animal instanceof Dog dog && dog.getAge() > 5) {
System.out.println("Old dog: " + dog.getName());
// dog visible vì instanceof đã check trước &&
}

// ❌ Sai thứ tự
if (dog.getAge() > 5 && animal instanceof Dog dog) {
// Compile error! dog chưa được declare khi dùng dog.getAge()
}

Ví dụ thực tế

Ví dụ 1: Shape Hierarchy

abstract class Shape {
protected String color;

public Shape(String color) {
this.color = color;
}

public abstract double getArea();

public void display() {
System.out.println("Shape color: " + color);
System.out.println("Area: " + getArea());
}
}

class Circle extends Shape {
private double radius;

public Circle(String color, double radius) {
super(color);
this.radius = radius;
}

@Override
public double getArea() {
return Math.PI * radius * radius;
}
}

class Rectangle extends Shape {
private double width;
private double height;

public Rectangle(String color, double width, double height) {
super(color);
this.width = width;
this.height = height;
}

@Override
public double getArea() {
return width * height;
}
}

class Triangle extends Shape {
private double base;
private double height;

public Triangle(String color, double base, double height) {
super(color);
this.base = base;
this.height = height;
}

@Override
public double getArea() {
return 0.5 * base * height;
}
}

// Sử dụng
public class ShapeDemo {
public static void main(String[] args) {
// Polymorphic array
Shape[] shapes = {
new Circle("Red", 5),
new Rectangle("Blue", 4, 6),
new Triangle("Green", 3, 4),
new Circle("Yellow", 3)
};

// Tính tổng diện tích tất cả hình
double totalArea = 0;
for (Shape shape : shapes) {
shape.display();
totalArea += shape.getArea();
System.out.println("---");
}

System.out.println("Total area: " + totalArea);
}
}

Ví dụ 2: Payment System

abstract class Payment {
protected double amount;

public Payment(double amount) {
this.amount = amount;
}

public abstract void processPayment();
public abstract String getPaymentMethod();
}

class CreditCardPayment extends Payment {
private String cardNumber;

public CreditCardPayment(double amount, String cardNumber) {
super(amount);
this.cardNumber = cardNumber;
}

@Override
public void processPayment() {
System.out.println("Processing credit card payment...");
System.out.println("Card: **** **** **** " + cardNumber.substring(12));
System.out.println("Amount: $" + amount);
}

@Override
public String getPaymentMethod() {
return "Credit Card";
}
}

class PayPalPayment extends Payment {
private String email;

public PayPalPayment(double amount, String email) {
super(amount);
this.email = email;
}

@Override
public void processPayment() {
System.out.println("Processing PayPal payment...");
System.out.println("PayPal account: " + email);
System.out.println("Amount: $" + amount);
}

@Override
public String getPaymentMethod() {
return "PayPal";
}
}

class BankTransferPayment extends Payment {
private String accountNumber;

public BankTransferPayment(double amount, String accountNumber) {
super(amount);
this.accountNumber = accountNumber;
}

@Override
public void processPayment() {
System.out.println("Processing bank transfer...");
System.out.println("Account: " + accountNumber);
System.out.println("Amount: $" + amount);
}

@Override
public String getPaymentMethod() {
return "Bank Transfer";
}
}

// Payment Processor
class PaymentProcessor {
public void process(Payment payment) {
System.out.println("=== Payment Processing ===");
System.out.println("Method: " + payment.getPaymentMethod());
payment.processPayment();
System.out.println("Payment completed!\n");
}

public void processMultiple(Payment[] payments) {
double total = 0;
for (Payment payment : payments) {
process(payment);
total += payment.amount;
}
System.out.println("Total processed: $" + total);
}
}

// Demo
public class PaymentDemo {
public static void main(String[] args) {
PaymentProcessor processor = new PaymentProcessor();

Payment[] payments = {
new CreditCardPayment(100.50, "1234567812345678"),
new PayPalPayment(75.00, "[email protected]"),
new BankTransferPayment(200.00, "ACC123456789"),
new CreditCardPayment(50.25, "9876543298765432")
};

processor.processMultiple(payments);
}
}

Benefits of Polymorphism

1. Loose Coupling

// Bad: Tight coupling
public class OrderService {
public void processOrder(CreditCardPayment payment) {
// Chỉ xử lý được CreditCard
payment.processPayment();
}
}

// Good: Loose coupling
public class OrderService {
public void processOrder(Payment payment) {
// Xử lý được mọi loại payment
payment.processPayment();
}
}

2. Extensibility

// Thêm payment method mới - không cần sửa code cũ
class CryptoPayment extends Payment {
private String walletAddress;

public CryptoPayment(double amount, String walletAddress) {
super(amount);
this.walletAddress = walletAddress;
}

@Override
public void processPayment() {
System.out.println("Processing cryptocurrency payment...");
System.out.println("Wallet: " + walletAddress);
System.out.println("Amount: $" + amount);
}

@Override
public String getPaymentMethod() {
return "Cryptocurrency";
}
}

// PaymentProcessor vẫn hoạt động mà không cần sửa!
Payment crypto = new CryptoPayment(300, "1A2B3C4D5E6F");
processor.process(crypto); // ✅ Works!

Overloading vs Overriding

Đặc điểmOverloadingOverriding
Định nghĩaNhiều method cùng tên, khác tham sốMethod con override method cha
Thời điểmCompile-time (Static)Runtime (Dynamic)
ClassTrong cùng một classGiữa class cha và class con
Tên methodGiống nhauGiống nhau
Tham sốKhác nhau (số lượng/kiểu)Giống nhau hoàn toàn
Return typeCó thể khácCùng hoặc covariant
Access modifierCó thể khácKhông được hẹp hơn
static methodCó thể overloadKhông thể override
private methodCó thể overloadKhông thể override
final methodCó thể overloadKhông thể override
AnnotationKhông có@Override

So sánh code

class Demo {
// OVERLOADING - Compile-time polymorphism
public void print(int a) {
System.out.println("Integer: " + a);
}

public void print(String s) {
System.out.println("String: " + s);
}

public void print(int a, int b) {
System.out.println("Two integers: " + a + ", " + b);
}
}

class Parent {
public void display() {
System.out.println("Parent display");
}
}

class Child extends Parent {
// OVERRIDING - Runtime polymorphism
@Override
public void display() {
System.out.println("Child display");
}
}

public class Test {
public static void main(String[] args) {
// Overloading
Demo demo = new Demo();
demo.print(10); // Integer: 10
demo.print("Hello"); // String: Hello
demo.print(5, 15); // Two integers: 5, 15

// Overriding
Parent obj = new Child();
obj.display(); // Child display (runtime decision)
}
}

Bài tập thực hành

Bài 1: Employee Salary System

Tạo hệ thống tính lương polymorphic:

  • Abstract class Employee: calculateSalary(), getDetails()
  • FullTimeEmployee: lương cố định + bonus
  • PartTimeEmployee: hourly rate × hours worked
  • ContractEmployee: project rate × số project
  • Tạo array chứa các loại employee và tính tổng lương

Bài 2: File System

Tạo hệ thống file polymorphic:

  • Abstract class FileSystemItem: getName(), getSize(), display()
  • File: có size cố định
  • Directory: size = tổng size của items bên trong
  • SymbolicLink: tham chiếu đến FileSystemItem khác
  • Implement method đệ quy để tính tổng size

Bài 3: Notification System

Tạo hệ thống thông báo:

  • Interface Notification: send(String message)
  • EmailNotification, SMSNotification, PushNotification
  • Class NotificationService nhận array các Notification
  • Test gửi cùng message qua nhiều kênh

Bài 4: Drawing Application

Tạo ứng dụng vẽ:

  • Abstract class Shape: draw(), resize(double factor)
  • Implement Circle, Rectangle, Triangle, Line
  • Class Canvas chứa array of Shape và method drawAll()
  • Test tạo nhiều shapes, resize tất cả, rồi vẽ

Virtual Method Table (vtable) — Cách JVM thực hiện Dynamic Dispatch

Virtual Method Table (vtable) là cơ chế JVM sử dụng để quyết định method nào sẽ được gọi tại runtime.

Cách hoạt động của vtable

Mỗi class có một vtable chứa con trỏ đến implementation của các virtual methods (instance methods có thể override).

class Animal {
public void eat() {
System.out.println("Animal eats");
}

public void sleep() {
System.out.println("Animal sleeps");
}
}

class Dog extends Animal {
@Override
public void eat() {
System.out.println("Dog eats bones");
}
// sleep() không override - dùng của Animal
}

vtable của Animal:

Animal vtable:
eat() → Animal.eat()
sleep() → Animal.sleep()

vtable của Dog:

Dog vtable:
eat() → Dog.eat() // Override - trỏ đến Dog's implementation
sleep() → Animal.sleep() // Không override - trỏ đến Animal's implementation

Dynamic Method Dispatch Process

Animal animal = new Dog(); // Reference type: Animal, Object type: Dog
animal.eat(); // Output: Dog eats bones

Các bước JVM thực hiện:

  1. Compile-time: Compiler kiểm tra Animal có method eat() không → ✅ Có
  2. Runtime: JVM xác định object type thực tế là Dog
  3. vtable lookup: Tra cứu eat() trong vtable của Dog → tìm thấy Dog.eat()
  4. Execute: Gọi Dog.eat()
📖 Theo JLS §15.12.4.4

Method invocation expression sử dụng dynamic method lookup — method được chọn dựa trên runtime type của object, không phải compile-time type của reference.

💡 Cách nhớ: vtable như "Danh bạ điện thoại"

Mỗi class có một danh bạ (vtable) liệt kê tất cả methods mà class đó có. Mỗi dòng trong danh bạ ghi: "tên method → gọi implementation nào".

Ví dụ: Danh bạ của Dog:

  • eat() → gọi Dog.eat() (Dog tự viết)
  • sleep() → gọi Animal.sleep() (dùng của cha)

Khi JVM cần gọi method, nó tra danh bạ của object thật (không phải biến reference) để tìm đúng "số điện thoại" cần gọi.

Method Resolution Algorithm

Chi tiết thuật toán JVM tìm method phù hợp:

1. Compile-time: Method Signature Resolution

Compiler kiểm tra reference type có method với signature phù hợp:

Animal animal = new Dog();
animal.eat(); // ✅ Animal có eat()
animal.bark(); // ❌ Compile error! Animal không có bark()

Dog dog = new Dog();
dog.bark(); // ✅ Dog có bark()

2. Runtime: Dynamic Dispatch

JVM tìm implementation thực tế:

public class MethodResolutionDemo {
public static void main(String[] args) {
Animal a1 = new Animal();
Animal a2 = new Dog();
Animal a3 = new Cat();

// Compile-time: tất cả đều gọi Animal.eat() (signature check)
// Runtime: JVM dispatch đến implementation tương ứng
a1.eat(); // Animal.eat()
a2.eat(); // Dog.eat()
a3.eat(); // Cat.eat()
}
}

3. Overloading Resolution với null

class OverloadTest {
public void method(Object o) {
System.out.println("Object version");
}

public void method(String s) {
System.out.println("String version");
}

public void method(Integer i) {
System.out.println("Integer version");
}
}

public class OverloadingNullDemo {
public static void main(String[] args) {
OverloadTest test = new OverloadTest();

test.method("hello"); // Output: String version
test.method(123); // Output: Integer version

// ❌ Ambiguous method call với null!
// test.method(null); // Compile error: ambiguous
// null có thể match cả String, Integer, Object

// ✅ Phải cast tường minh
test.method((String) null); // Output: String version
test.method((Object) null); // Output: Object version
}
}
🔥 Bẫy OCP: Overloading với null argument

Câu hỏi: Đoạn code sau output gì?

class Test {
void method(String s) { System.out.println("String"); }
void method(Object o) { System.out.println("Object"); }
}

Test t = new Test();
t.method(null);

Đáp án: String — vì String more specific hơn Object. Compiler chọn overload method most specific khi có nhiều lựa chọn phù hợp với null.

Covariant Return Types

Từ Java 5, override method có thể return subtype của return type gốc:

class Animal {
public Animal reproduce() {
System.out.println("Animal reproducing");
return new Animal();
}
}

class Dog extends Animal {
@Override
public Dog reproduce() { // Covariant return: Dog is subtype of Animal
System.out.println("Dog reproducing");
return new Dog();
}
}

class Cat extends Animal {
@Override
public Cat reproduce() { // Covariant return: Cat is subtype of Animal
System.out.println("Cat reproducing");
return new Cat();
}
}

public class CovariantReturnDemo {
public static void main(String[] args) {
Animal animal = new Dog();
Animal offspring = animal.reproduce(); // Runtime: trả về Dog instance

Dog dog = new Dog();
Dog puppy = dog.reproduce(); // ✅ Không cần cast!

// So sánh với non-covariant (trước Java 5):
// Animal puppy = dog.reproduce(); // Phải như thế này
// Dog actualPuppy = (Dog) puppy; // Rồi cast
}
}
📖 Theo JLS §8.4.5

Override method có thể có return type là subtype của return type trong overridden method — gọi là covariant return type.

Bridge Methods — Compiler Magic cho Generics

Compiler tự động tạo bridge methods để đảm bảo type erasure không phá vỡ polymorphism:

class Node<T> {
private T data;

public void setData(T data) {
this.data = data;
}

public T getData() {
return data;
}
}

class StringNode extends Node<String> {
@Override
public void setData(String data) { // Covariant parameter specialization
System.out.println("Setting string: " + data);
super.setData(data);
}
}

Sau type erasure:

// Node.class (sau erasure)
class Node {
public void setData(Object data) { /* ... */ }
public Object getData() { /* ... */ }
}

// StringNode.class (sau erasure + bridge methods)
class StringNode extends Node {
// Method người dùng viết
public void setData(String data) { /* ... */ }

// Bridge method do compiler tạo
public void setData(Object data) {
setData((String) data); // Delegate đến String version
}
}
💡 Cách nhớ Bridge Methods

Giống cầu nối giữa generic world (trước compile) và raw type world (sau compile) — đảm bảo polymorphism vẫn hoạt động.

🔥 Bẫy OCP: Reference Type vs Object Type

Câu hỏi: Output của đoạn code sau?

class Parent {
public static void staticMethod() {
System.out.println("Parent static");
}

public void instanceMethod() {
System.out.println("Parent instance");
}
}

class Child extends Parent {
public static void staticMethod() {
System.out.println("Child static");
}

@Override
public void instanceMethod() {
System.out.println("Child instance");
}
}

Parent p = new Child();
p.staticMethod();
p.instanceMethod();

Đáp án:

Parent static      // Static method: resolved by reference type (compile-time)
Child instance // Instance method: resolved by object type (runtime)

Giải thích:

  • Static methods: Method hiding — quyết định theo reference type (Parent)
  • Instance methods: Method overriding — quyết định theo object type (Child)

Varargs và Method Overloading

Varargs (...) cho phép method nhận số lượng tham số không cố định. Nhưng khi kết hợp với overloading, có nhiều edge case dễ nhầm.

Compiler chọn method cụ thể nhất

class VarargsDemo {
// Method 1: exact match
void print(int a, int b) {
System.out.println("Two ints: " + a + ", " + b);
}

// Method 2: varargs
void print(int... nums) {
System.out.println("Varargs: " + java.util.Arrays.toString(nums));
}

public static void main(String[] args) {
VarargsDemo d = new VarargsDemo();
d.print(1, 2); // "Two ints: 1, 2" ← Method 1 thắng (cụ thể hơn)
d.print(1, 2, 3); // "Varargs: [1, 2, 3]" ← Chỉ Method 2 match
d.print(1); // "Varargs: [1]" ← Chỉ Method 2 match
}
}
OCP Trap

Quy tắc: Compiler luôn ưu tiên non-varargs method. Varargs chỉ được chọn khi không có exact match nào khác.

Primitive Widening vs Boxing khi Overload

Khi overload, compiler ưu tiên theo thứ tự:

  1. Exact match (int → int)
  2. Widening (int → long)
  3. Boxing (int → Integer)
  4. Varargs (int → int...)
class WideningVsBoxing {
void process(long x) {
System.out.println("Widening: long");
}

void process(Integer x) {
System.out.println("Boxing: Integer");
}

public static void main(String[] args) {
WideningVsBoxing obj = new WideningVsBoxing();
obj.process(42); // "Widening: long" ← int→long thắng int→Integer
}
}
Ưu tiênChuyển đổiVí dụ
1Exact matchintint
2Wideningintlong, floatdouble
3BoxingintInteger
4Widening sau boxingintIntegerNumber
5Varargsintint...
Lưu ý

Widening rồi boxing hợp lệ (intlongLong ❌ KHÔNG — đây là widening rồi boxing, không hỗ trợ). Nhưng boxing rồi widening hợp lệ (intIntegerNumber ✅).

Tổng kết

Key Takeaways
  1. Polymorphism cho phép xử lý nhiều loại đối tượng qua interface chung
  2. Compile-time polymorphism = Method Overloading (resolved by compiler)
  3. Runtime polymorphism = Method Overriding + Dynamic Dispatch (resolved by JVM)
  4. vtable là cơ chế JVM tra cứu method implementation tại runtime
  5. Upcasting tự động và an toàn
  6. Downcasting cần ép kiểu và kiểm tra với instanceof
  7. Covariant return types cho phép override method return subtype
  8. Bridge methods đảm bảo polymorphism hoạt động với generics
  9. Polymorphism giúp code linh hoạt, dễ mở rộng, loose coupling
Method TypeBinding TimeDepends OnMechanism
Static methodCompile-timeReference typeMethod hiding
Instance methodRuntimeObject typeDynamic dispatch + vtable
OverloadingCompile-timeArgument typesSignature matching
Varargs overloadCompile-timeNon-varargs firstMost specific wins
Widening vs BoxingCompile-timeWidening winsint→long beats int→Integer

Polymorphism là nền tảng cho nhiều design patterns và là một trong những tính năng mạnh mẽ nhất của OOP!

Đọc thêm