Skip to main content

Tính Đa Hình trong Java 🎯

🎯 Mục tiêu: Học cách sử dụng tính đa hình để xử lý các đối tượng một cách linh hoạt và tái sử dụng code trong Java.

Giới thiệu 📝

Tính đa hình (Polymorphism) là một trong những nguyên tắc cơ bản của lập trình hướng đối tượng. Nó cho phép một đối tượng có thể có nhiều dạng khác nhau, giúp code linh hoạt và dễ bảo trì hơn.

💡 Fun Fact: Trong Java, tính đa hình được hỗ trợ thông qua hai cơ chế chính: ghi đè phương thức (method overriding) và nạp chồng phương thức (method overloading).

1. Cú Pháp Cơ Bản ⚡

Đa Hình Thông Qua Ghi Đè

public class Animal {
protected String name;

public Animal(String name) {
this.name = name;
}

public void makeSound() {
System.out.println("Tiếng kêu của động vật");
}
}

public class Dog extends Animal {
public Dog(String name) {
super(name);
}

@Override
public void makeSound() {
System.out.println("Gâu gâu!");
}
}

public class Cat extends Animal {
public Cat(String name) {
super(name);
}

@Override
public void makeSound() {
System.out.println("Meo meo!");
}
}

Đa Hình Thông Qua Nạp Chồng

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

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

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

2. Các Loại Đa Hình 🔄

Đa Hình Thời Gian Chạy (Runtime Polymorphism)

public class Shape {
protected String color;

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

public double getArea() {
return 0.0;
}
}

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

public class Rectangle extends Shape {
private double length;
private double width;

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

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

Đa Hình Thời Gian Biên Dịch (Compile-time Polymorphism)

public class MathUtils {
public static int max(int a, int b) {
return (a > b) ? a : b;
}

public static double max(double a, double b) {
return (a > b) ? a : b;
}

public static String max(String a, String b) {
return (a.compareTo(b) > 0) ? a : b;
}
}

3. Ví Dụ Thực Tế 🚀

Quản Lý Phương Tiện

public abstract class Vehicle {
protected String brand;
protected String model;
protected int year;

public Vehicle(String brand, String model, int year) {
this.brand = brand;
this.model = model;
this.year = year;
}

public abstract void start();
public abstract void stop();

public void displayInfo() {
System.out.printf("Phương tiện: %s %s %d%n", brand, model, year);
}
}

public class Car extends Vehicle {
private int numberOfDoors;

public Car(String brand, String model, int year, int numberOfDoors) {
super(brand, model, year);
this.numberOfDoors = numberOfDoors;
}

@Override
public void start() {
System.out.println("Vặn chìa khóa xe");
System.out.println("Đạp phanh");
System.out.println("Khởi động động cơ");
}

@Override
public void stop() {
System.out.println("Đạp phanh");
System.out.println("Tắt động cơ");
}

@Override
public void displayInfo() {
super.displayInfo();
System.out.printf("Số cửa: %d%n", numberOfDoors);
}
}

public class Motorcycle extends Vehicle {
private boolean hasSidecar;

public Motorcycle(String brand, String model, int year, boolean hasSidecar) {
super(brand, model, year);
this.hasSidecar = hasSidecar;
}

@Override
public void start() {
System.out.println("Đạp cần khởi động");
System.out.println("Bật công tắc điện");
System.out.println("Khởi động động cơ");
}

@Override
public void stop() {
System.out.println("Đạp phanh");
System.out.println("Tắt động cơ");
}

@Override
public void displayInfo() {
super.displayInfo();
System.out.printf("Có bánh phụ: %b%n", hasSidecar);
}
}

Quản Lý Nhân Viên

public abstract class Employee {
protected String id;
protected String name;
protected double baseSalary;

public Employee(String id, String name, double baseSalary) {
this.id = id;
this.name = name;
this.baseSalary = baseSalary;
}

public abstract double calculateSalary();

public void displayInfo() {
System.out.printf("Nhân viên: %s, ID: %s, Lương cơ bản: %.2f%n",
name, id, baseSalary);
}
}

public class RegularEmployee extends Employee {
private double overtimeHours;
private double overtimeRate;

public RegularEmployee(String id, String name, double baseSalary,
double overtimeHours, double overtimeRate) {
super(id, name, baseSalary);
this.overtimeHours = overtimeHours;
this.overtimeRate = overtimeRate;
}

@Override
public double calculateSalary() {
return baseSalary + (overtimeHours * overtimeRate);
}

@Override
public void displayInfo() {
super.displayInfo();
System.out.printf("Giờ làm thêm: %.1f, Tỷ lệ làm thêm: %.2f%n",
overtimeHours, overtimeRate);
}
}

public class Manager extends Employee {
private double bonus;
private List<Employee> subordinates;

public Manager(String id, String name, double baseSalary, double bonus) {
super(id, name, baseSalary);
this.bonus = bonus;
this.subordinates = new ArrayList<>();
}

@Override
public double calculateSalary() {
return baseSalary + bonus;
}

public void addSubordinate(Employee employee) {
subordinates.add(employee);
}

@Override
public void displayInfo() {
super.displayInfo();
System.out.printf("Thưởng: %.2f%n", bonus);
System.out.println("Nhân viên dưới quyền:");
for (Employee emp : subordinates) {
System.out.printf("- %s (Lương: %.2f)%n", emp.name, emp.calculateSalary());
}
}
}

Quản Lý Hình Học

public abstract class Shape {
protected String color;
protected boolean filled;

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

public abstract double getArea();
public abstract double getPerimeter();

public void displayInfo() {
System.out.printf("Hình: %s, Màu: %s, Đã tô: %b%n",
getClass().getSimpleName(), color, filled);
System.out.printf("Diện tích: %.2f, Chu vi: %.2f%n",
getArea(), getPerimeter());
}
}

public class Circle extends Shape {
private double radius;

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

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

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

@Override
public void displayInfo() {
super.displayInfo();
System.out.printf("Bán kính: %.2f%n", radius);
}
}

public class Rectangle extends Shape {
private double length;
private double width;

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

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

@Override
public double getPerimeter() {
return 2 * (length + width);
}

@Override
public void displayInfo() {
super.displayInfo();
System.out.printf("Chiều dài: %.2f, Chiều rộng: %.2f%n", length, width);
}
}

4. Best Practices ✅

  1. Sử Dụng @Override Annotation

    // Không nên
    public class Child extends Parent {
    public void display() { // Không có @Override
    System.out.println("Child class");
    }
    }

    // Nên
    public class Child extends Parent {
    @Override
    public void display() {
    System.out.println("Child class");
    }
    }
  2. Gọi Phương Thức Lớp Cha Khi Cần

    // Không nên
    public class Child extends Parent {
    @Override
    public void display() {
    System.out.println("Child class");
    // Quên gọi super.display()
    }
    }

    // Nên
    public class Child extends Parent {
    @Override
    public void display() {
    super.display();
    System.out.println("Child class");
    }
    }
  3. Sử Dụng Đa Hình Cho Mảng Đối Tượng

    // Không nên
    List<Circle> circles = new ArrayList<>();
    List<Rectangle> rectangles = new ArrayList<>();

    // Nên
    List<Shape> shapes = new ArrayList<>();
    shapes.add(new Circle("red", true, 5.0));
    shapes.add(new Rectangle("blue", true, 4.0, 6.0));

5. Lỗi Thường Gặp ⚠️

  1. Quên Gọi super() Trong Constructor

    // Lỗi
    public class Child extends Parent {
    public Child() {
    // Quên gọi super()
    }
    }

    // Đúng
    public class Child extends Parent {
    public Child() {
    super();
    }
    }
  2. Gọi Phương Thức Không Tồn Tại

    // Lỗi
    Shape shape = new Circle("red", true, 5.0);
    shape.getRadius(); // Lỗi: phương thức không tồn tại trong lớp Shape

    // Đúng
    if (shape instanceof Circle) {
    Circle circle = (Circle) shape;
    circle.getRadius();
    }
  3. Quên Ghi Đè Phương Thức Abstract

    // Lỗi
    public class Circle extends Shape {
    // Quên ghi đè getArea() và getPerimeter()
    }

    // Đúng
    public class Circle extends Shape {
    @Override
    public double getArea() {
    return Math.PI * radius * radius;
    }

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

💡 Lời khuyên: Hãy thực hành với các ví dụ thực tế để hiểu rõ hơn về cách sử dụng tính đa hình trong các tình huống khác nhau.

Tiếp Theo 🎯

Trong các bài học tiếp theo, chúng ta sẽ:

  • Tìm hiểu về interface và abstract class
  • Học cách sử dụng package và import
  • Thực hành với xử lý ngoại lệ (Exception Handling)
  • Tìm hiểu về generic types