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.
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).
- 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)
}
}
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 Overriding và Dynamic 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
- 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!
- 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ểm | Overloading | Overriding |
|---|---|---|
| Định nghĩa | Nhiều method cùng tên, khác tham số | Method con override method cha |
| Thời điểm | Compile-time (Static) | Runtime (Dynamic) |
| Class | Trong cùng một class | Giữa class cha và class con |
| Tên method | Giống nhau | Giống nhau |
| Tham số | Khác nhau (số lượng/kiểu) | Giống nhau hoàn toàn |
| Return type | Có thể khác | Cùng hoặc covariant |
| Access modifier | Có thể khác | Không được hẹp hơn |
| static method | Có thể overload | Không thể override |
| private method | Có thể overload | Không thể override |
| final method | Có thể overload | Không thể override |
| Annotation | Khô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 + bonusPartTimeEmployee: hourly rate × hours workedContractEmployee: 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ố địnhDirectory: size = tổng size của items bên trongSymbolicLink: 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
NotificationServicenhậ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
Canvaschứa array of Shape và methoddrawAll() - 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:
- Compile-time: Compiler kiểm tra
Animalcó methodeat()không → ✅ Có - Runtime: JVM xác định object type thực tế là
Dog - vtable lookup: Tra cứu
eat()trong vtable củaDog→ tìm thấyDog.eat() - Execute: Gọi
Dog.eat()
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.
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ọiDog.eat()(Dog tự viết)sleep()→ gọiAnimal.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
}
}
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
}
}
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
}
}
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.
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
}
}
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ự:
- Exact match (int → int)
- Widening (int → long)
- Boxing (int → Integer)
- 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ên | Chuyển đổi | Ví dụ |
|---|---|---|
| 1 | Exact match | int → int |
| 2 | Widening | int → long, float → double |
| 3 | Boxing | int → Integer |
| 4 | Widening sau boxing | int → Integer → Number |
| 5 | Varargs | int → int... |
Widening rồi boxing hợp lệ (int → long → Long ❌ KHÔNG — đây là widening rồi boxing, không hỗ trợ). Nhưng boxing rồi widening hợp lệ (int → Integer → Number ✅).
Tổng kết
- Polymorphism cho phép xử lý nhiều loại đối tượng qua interface chung
- Compile-time polymorphism = Method Overloading (resolved by compiler)
- Runtime polymorphism = Method Overriding + Dynamic Dispatch (resolved by JVM)
- vtable là cơ chế JVM tra cứu method implementation tại runtime
- Upcasting tự động và an toàn
- Downcasting cần ép kiểu và kiểm tra với
instanceof - Covariant return types cho phép override method return subtype
- Bridge methods đảm bảo polymorphism hoạt động với generics
- Polymorphism giúp code linh hoạt, dễ mở rộng, loose coupling
| Method Type | Binding Time | Depends On | Mechanism |
|---|---|---|---|
| Static method | Compile-time | Reference type | Method hiding |
| Instance method | Runtime | Object type | Dynamic dispatch + vtable |
| Overloading | Compile-time | Argument types | Signature matching |
| Varargs overload | Compile-time | Non-varargs first | Most specific wins |
| Widening vs Boxing | Compile-time | Widening wins | int→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!