Constructor Trong Java: Hướng Dẫn Chi Tiết & Thực Tế Nhất Năm 2026
Lê Đình Đài

Tổng Quan Về Constructor Trong Java: Từ Cơ Bản Đến Nâng Cao (Cập Nhật 2026)
Chào các lập trình viên Java!
Bạn đang đau đầu vì đối tượng khởi tạo lộn xộn, code lặp lại, hay lo lắng về performance trong dự án lớn? Đừng lo! Constructor trong Java chính là "người hùng thầm lặng" giúp bạn khởi tạo đối tượng sạch sẽ, an toàn và hiệu quả.
Năm 2026, với Java 25 LTS (ra mắt tháng 9/2025) đang là phiên bản chính thức mới nhất và Java 26 sắp tới (tháng 3/2026), constructor còn mạnh mẽ hơn nhờ tính năng Flexible Constructor Bodies – cho phép viết code validation trước khi gọi this() hoặc super().
Bài viết này sẽ dẫn bạn từ A-Z: lý thuyết dễ hiểu, ví dụ thực tế, hình ảnh minh họa sống động, và best practices cập nhật 2026. Hãy cùng "xây dựng" kiến thức vững chắc nào!
Đừng quyên truy cập dinhdai.tech để biết thêm những kiến thức mới, những hướng dẫn chi tiết về lập trình nhé!
1. Constructor Trong Java Là Gì?

new. Nó không có kiểu trả về (kể cả void), có tên trùng khớp chính xác với tên lớp, và được gọi tự động ngay khi đối tượng được tạo. Trong lập trình hướng đối tượng (OOP), constructor đóng vai trò thiết lập trạng thái ban đầu cho đối tượng, đảm bảo đối tượng luôn ở trạng thái hợp lệ và toàn vẹn dữ liệu ngay từ giây phút đầu tiên.
Constructor luôn được gọi đầu tiên khi tạo đối tượng, giúp khởi tạo các thuộc tính một cách an toàn trước khi đối tượng được sử dụng.
Ví dụ đơn giản:
Java
public class Person {
private String name;
private int age;
// Constructor
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
// Tạo đối tượng → constructor tự động chạy
Person p = new Person("Ngọc Linh", 20);
Trong Java hiện đại (bao gồm các phiên bản đến JDK 26 năm 2026), constructor vẫn giữ vai trò cốt lõi không thay đổi. Tuy nhiên, nó tích hợp tốt hơn với các tính năng mới như record classes (một dạng lớp đặc biệt tối ưu cho dữ liệu bất biến với constructor được tự động sinh ra), pattern matching (hỗ trợ tốt hơn trong xử lý dữ liệu), và virtual threads (cải thiện concurrency mà không ảnh hưởng trực tiếp đến cách khởi tạo đối tượng, nhưng giúp ứng dụng xử lý hàng triệu task khởi tạo nhanh chóng và hiệu quả hơn).
Ví dụ nhỏ về record class (constructor đặc biệt gọn gàng, từ Java 16 trở lên, ổn định ở các phiên bản mới):
Java
public record Person(String name, int age) {
// Compact constructor: tự động gán giá trị + có thể thêm validation
public Person {
if (age < 0) {
throw new IllegalArgumentException("Tuổi không thể âm!");
}
}
}
Record giúp giảm boilerplate code rất nhiều so với class thông thường.
So sánh constructor và method thông thường
Constructor khác biệt rõ rệt so với các method khác:
- Không có kiểu trả về (không thể dùng void).
- Chỉ được gọi một lần duy nhất khi tạo đối tượng (qua new).
- Không thể bị kế thừa trực tiếp (dù subclass có thể gọi super constructor).
- Mục đích: khởi tạo trạng thái ban đầu.
Còn method thông thường:
- Có thể có kiểu trả về (hoặc void).
- Có thể gọi nhiều lần trên đối tượng đã tồn tại.
- Có thể bị override trong subclass.
- Mục đích: thực hiện hành động, thay đổi trạng thái, hoặc trả về kết quả.
Ví dụ minh họa:
Constructor → thiết lập dữ liệu ban đầu.
Method → xử lý hành động trên dữ liệu đã có.
Vai trò của constructor trong khởi tạo đối tượng
Nhiệm vụ chính của nó là gán giá trị ban đầu cho các thuộc tính của đối tượng, giúp đối tượng sẵn sàng sử dụng ngay sau khi khởi tạo. Điều này tuân thủ nguyên tắc đóng gói trong thiết kế phần mềm, một khái niệm quan trọng được trình bày chi tiết trên Wikipedia về Java.Constructor chịu trách nhiệm gán giá trị ban đầu cho các thuộc tính (fields) của lớp, đảm bảo đối tượng không bao giờ ở trạng thái không hợp lệ (ví dụ: không để age âm, name null, v.v.).
Điều này cực kỳ quan trọng trong các ứng dụng lớn:
- Trong microservices: Mỗi service khởi tạo rất nhiều đối tượng (DTO, entity, config). Constructor đảm bảo trạng thái hợp lệ ngay từ đầu → giảm lỗi runtime, tránh lỗi lan truyền giữa các service, tăng độ tin cậy hệ thống phân tán.
- Trong ứng dụng AI/ML dựa trên Java: Đối tượng dữ liệu lớn (vector, model params) cần được khởi tạo chính xác → constructor kết hợp với
finalgiúp tránh thay đổi ngoài ý muốn.
Ngoài ra, constructor kết hợp với từ khóa final còn hỗ trợ tính bất biến (immutability) rất tốt: các field final chỉ gán giá trị một lần trong constructor → an toàn cho multi-threading, đặc biệt khi dùng virtual threads.
Lợi ích sử dụng constructor trong lập trình Java năm 2026
- Code dễ đọc, dễ bảo trì hơn nhờ khởi tạo rõ ràng tại một nơi duy nhất.
- Giảm lỗi nhờ đảm bảo trạng thái hợp lệ ngay từ đầu.
- Trong năm 2026 (JDK 26 đã ra mắt), constructor hỗ trợ cực tốt cho immutable objects qua record classes → giảm boilerplate code đáng kể, tăng tốc độ phát triển.
- Tích hợp mượt mà với các framework hiện đại như Spring Boot 3.x+, nơi constructor injection là cách ưu tiên để inject dependency (beans) → rõ ràng, dễ test, hỗ trợ immutable design.
- Kết hợp với virtual threads và concurrency cải tiến → ứng dụng có thể khởi tạo và xử lý hàng triệu đối tượng/task mà vẫn hiệu suất cao, ít tốn tài nguyên.
2. Các Loại Constructor Trong Java

- Constructor mặc định (default constructor)
- Constructor có tham số (parameterized constructor)
- Constructor overloading (nhiều constructor cùng tên nhưng khác tham số)
- Constructor không tham số (no-arg constructor do lập trình viên tự viết)
- Constructor private (thường dùng trong singleton, builder pattern, hoặc ngăn tạo đối tượng trực tiếp)
Việc hiểu rõ từng loại constructor giúp bạn chọn cách khởi tạo phù hợp, viết code an toàn, dễ bảo trì và tận dụng tối đa các tính năng hiện đại của Java (đặc biệt trong năm 2026 với JDK 26 đã ổn định).
Constructor mặc định (Default Constructor) trong Java và cách hoạt động
Nếu bạn không định nghĩa bất kỳ constructor nào trong lớp, Java compiler sẽ tự động tạo một constructor mặc định không tham số. Constructor này chỉ đơn giản khởi tạo các thuộc tính với giá trị mặc định (0, null, false, v.v.) và không chứa logic nào khác.
Quan trọng: Nếu bạn định nghĩa bất kỳ constructor nào (dù là có tham số), compiler sẽ KHÔNG tạo constructor mặc định nữa. Đây là lỗi rất phổ biến khi mới học Java!
Ví dụ minh họa:
Java
public class Car {
String brand; // null
int year; // 0
boolean isElectric; // false
// Không định nghĩa constructor nào → Java tự tạo default constructor
}
// Sử dụng
Car myCar = new Car(); // constructor mặc định chạy ngầm
System.out.println(myCar.brand); // in ra: null
System.out.println(myCar.year); // in ra: 0
Nếu bạn thêm constructor có tham số vào lớp Car, thì đoạn code new Car() sẽ báo lỗi biên dịch vì constructor mặc định không còn tồn tại nữa.
Constructor có tham số (Parameterized Constructor) với ví dụ cụ thể
Đây là loại constructor phổ biến nhất, cho phép truyền giá trị ngay khi tạo đối tượng, giúp đối tượng có trạng thái hợp lệ ngay từ đầu.
Ví dụ thực tế:
Java
public class Person {
private final String name;
private final int age;
// Constructor có tham số
public Person(String name, int age) {
if (age < 0) {
throw new IllegalArgumentException("Tuổi không thể âm!");
}
this.name = name;
this.age = age;
}
// Getter
public String getName() { return name; }
public int getAge() { return age; }
}
// Sử dụng
Person p = new Person("Ngọc Linh", 25);
System.out.println(p.getName() + " - " + p.getAge()); // Ngọc Linh20
Constructor có tham số kết hợp với final giúp tạo immutable object rất dễ dàng – cực kỳ hữu ích trong microservices, ứng dụng concurrent (virtual threads), và các framework như Spring.
Overloading Constructor trong Java – Tăng tính linh hoạt
Constructor overloading là khi một lớp có nhiều constructor cùng tên (tên lớp) nhưng khác nhau về số lượng, kiểu hoặc thứ tự tham số. Điều này cho phép người dùng lớp có nhiều cách khởi tạo khác nhau.
Ví dụ điển hình:
Java
public class Product {
private String name;
private double price;
private String category;
// Constructor 1: đầy đủ thông tin
public Product(String name, double price, String category) {
this.name = name;
this.price = price;
this.category = category;
}
// Constructor 2: chỉ cần tên và giá, category mặc định
public Product(String name, double price) {
this(name, price, "General"); // gọi constructor đầy đủ
}
// Constructor 3: chỉ cần tên
public Product(String name) {
this(name, 0.0); // gọi constructor 2
}
}
// Sử dụng linh hoạt
Product p1 = new Product("Laptop", 25000, "Electronics");
Product p2 = new Product("Mouse", 500);
Product p3 = new Product("Keyboard");
Overloading giúp code gọn gàng, dễ dùng, và thường thấy trong các thư viện lớn của Java (như StringBuilder, ArrayList, v.v.).
Constructor không tham số (No-arg Constructor) so với Constructor mặc định
Hai khái niệm này dễ nhầm lẫn:
- Constructor mặc định: Do compiler tự tạo, không tham số, không có logic bên trong, chỉ khởi tạo giá trị mặc định.
- Constructor không tham số (no-arg constructor): Do lập trình viên tự viết, có thể thêm logic tùy chỉnh (validation, gán giá trị mặc định có ý nghĩa, log, v.v.).
Ví dụ phân biệt:
Java
public class Student {
private String name = "Unknown"; // giá trị mặc định có ý nghĩa
// Constructor không tham số do bạn tự viết
public Student() {
System.out.println("Tạo student mới với tên mặc định");
}
}
→ Khác với constructor mặc định (chỉ gán null cho name).
Tóm tắt ngắn gọn các loại constructor chính trong Java 2026:
- Default constructor → tự động, không logic, biến mất nếu có constructor khác
- Parameterized → truyền giá trị, hỗ trợ immutable & validation
- Overloading → nhiều cách khởi tạo linh hoạt
- No-arg → tự viết, có thể tùy chỉnh logic
- Private constructor → dùng cho pattern Singleton, Utility class, Builder (sẽ nói chi tiết ở bài sau)
3. Cách Sử Dụng Constructor Trong Java

Hướng dẫn cách sử dụng constructor Java cơ bản
- Constructor tự động được gọi khi bạn dùng từ khóa
new→ không cần gọi thủ công. - Định nghĩa constructor trùng tên lớp, không có kiểu trả về.
- Nên ưu tiên constructor có tham số + validation để tránh đối tượng ở trạng thái không hợp lệ.
- Trong Java hiện đại (JDK 26 năm 2026), bạn có thể kết hợp constructor với:
- Record classes (constructor compact + validation tự động)
- @ConstructorProperties annotation (hỗ trợ serialization tốt hơn, đặc biệt với JSON frameworks như Jackson)
- Sealed classes/interfaces (yêu cầu xử lý constructor cẩn thận để tuân thủ ràng buộc sealed)
Nguyên tắc vàng khi dùng constructor:
- Luôn khởi tạo tất cả các field quan trọng.
- Sử dụng
this.để phân biệt field và tham số. - Gọi
super()(nếu cần) ở đầu constructor của lớp con.
Ví dụ constructor Java đơn giản cho người mới
Java
public class Car {
private String model;
private int year;
private boolean isElectric;
// Constructor không tham số (no-arg)
public Car() {
this.model = "Unknown";
this.year = 2025;
this.isElectric = false;
System.out.println("Tạo xe mặc định");
}
// Constructor có tham số đầy đủ
public Car(String model, int year, boolean isElectric) {
if (year < 1886) { // Năm xe hơi đầu tiên ra đời
throw new IllegalArgumentException("Năm sản xuất không hợp lệ!");
}
this.model = model;
this.year = year;
this.isElectric = isElectric;
}
// Constructor overloading: chỉ cần model
public Car(String model) {
this(model, 2026, false); // Gọi constructor đầy đủ
}
// Getter (cho immutable thì có thể bỏ setter)
public String getModel() { return model; }
public int getYear() { return year; }
}
// Cách sử dụng
Car car1 = new Car(); // Xe mặc định
Car car2 = new Car("Tesla Model Y", 2025, true); // Xe cụ thể
Car car3 = new Car("Honda Civic"); // Chỉ truyền model
→ Dễ thấy: constructor giúp khởi tạo linh hoạt, an toàn và giảm boilerplate.
Lỗi thường gặp khi sử dụng constructor trong Java & cách tránh (năm 2026)
- Quên gọi super() trong constructor của lớp con → Lỗi compile nếu lớp cha không có constructor mặc định. Khắc phục: Luôn gọi
super(...)ở dòng đầu tiên nếu lớp cha có constructor có tham số. - Overloading sai kiểu tham số → Ví dụ: hai constructor cùng số lượng tham số nhưng kiểu khác nhau không rõ ràng → compiler nhầm lẫn. Khắc phục: Đặt tên tham số rõ nghĩa, ưu tiên dùng builder pattern nếu quá nhiều tham số.
- Không xử lý sealed classes đúng cách (tính năng ổn định từ Java 17, phổ biến 2026) → Nếu lớp cha là sealed, constructor của lớp con phải tuân thủ quyền truy cập. Khắc phục: Kiểm tra modifier của lớp cha (sealed, non-sealed, final).
- Dùng constructor để khởi tạo static fields → Sai! Static fields chỉ khởi tạo một lần qua static initializer. Khắc phục: Dùng static block hoặc field initializer cho static.
- Tạo đối tượng với constructor mặc định khi đã có constructor có tham số → Lỗi compile. Khắc phục: Luôn thêm constructor không tham số nếu cần.
Tích hợp constructor với biến instance và static
- Biến instance (non-static): Chỉ constructor mới khởi tạo/gán giá trị cho chúng. → Đây là nơi lý tưởng để gán giá trị dựa trên tham số hoặc logic.
- Biến static: Được khởi tạo trước cả constructor, khi lớp được load. → Không nên gán trong constructor (vô nghĩa vì static chỉ có 1 bản duy nhất). → Dùng static initializer block hoặc gán trực tiếp khi khai báo.
Ví dụ tích hợp thực tế (config + instance):
Java
public class DatabaseConnection {
private static final String DEFAULT_URL; // static
static {
DEFAULT_URL = System.getenv("DB_URL") != null
? System.getenv("DB_URL")
: "jdbc:postgresql://localhost:5432/mydb";
}
private final String url;
private final String username;
public DatabaseConnection(String username) {
this.url = DEFAULT_URL; // dùng static config
this.username = username;
}
public DatabaseConnection(String url, String username) {
this.url = url;
this.username = username;
}
}
→ Constructor dùng static config để giảm lặp code, tăng tính linh hoạt.
Tóm tắt cách sử dụng constructor hiệu quả năm 2026:
- Ưu tiên constructor có tham số + validation + final cho immutable.
- Dùng overloading hợp lý để tăng tính tiện dụng.
- Kết hợp record nếu chỉ cần data class.
- Tránh lỗi bằng cách kiểm tra super(), sealed classes, và không nhầm lẫn static/instance.
4. Constructor Chaining Trong Java

Có hai cách chính để thực hiện constructor chaining:
this(…)– gọi constructor khác trong cùng một lớpsuper(…)– gọi constructor của lớp cha
Nguyên tắc quan trọng:
- Lời gọi
this()hoặcsuper()phải là dòng đầu tiên trong constructor (không được có bất kỳ câu lệnh nào trước nó). - Giúp đảm bảo tất cả các trường cần thiết được khởi tạo một cách nhất quán.
Constructor chaining trong Java là gì và ứng dụng thực tế
Constructor chaining tạo ra một chuỗi gọi các constructor, đảm bảo logic khởi tạo được thực hiện theo thứ tự hợp lý.
Ứng dụng phổ biến:
- Giảm lặp code khi một lớp có nhiều constructor (overloading).
- Đảm bảo các trường bắt buộc luôn được khởi tạo, bất kể constructor nào được gọi.
- Trong kế thừa: đảm bảo lớp cha được khởi tạo đầy đủ trước khi lớp con tiếp tục.
- Hỗ trợ thiết kế immutable objects (kết hợp với
final). - Dễ dàng thêm validation hoặc giá trị mặc định ở một nơi duy nhất.
Sử dụng từ khóa this() để chaining trong cùng lớp
this() dùng để gọi constructor khác trong cùng một lớp. Thường dùng để:
- Cung cấp giá trị mặc định cho các tham số.
- Tập trung logic khởi tạo phức tạp vào một constructor chính.
Ví dụ minh họa:
Java
public class Employee {
private final String name;
private final int age;
private final String department;
// Constructor chính (chứa toàn bộ logic khởi tạo)
public Employee(String name, int age, String department) {
if (age < 18) {
throw new IllegalArgumentException("Nhân viên phải từ 18 tuổi trở lên");
}
this.name = name;
this.age = age;
this.department = department != null ? department : "Chưa xác định";
System.out.println("Đã khởi tạo nhân viên: " + name);
}
// Constructor chaining: cung cấp giá trị mặc định
public Employee(String name, int age) {
this(name, age, null); // gọi constructor đầy đủ
}
// Constructor chaining: chỉ cần tên
public Employee(String name) {
this(name, 20); // gọi constructor có 2 tham số
}
// Constructor không tham số
public Employee() {
this("Unknown", 20, "IT"); // giá trị mặc định
}
// Getter
public String getName() { return name; }
}
Cách sử dụng:
Java
Employee e1 = new Employee(); // Unknown - 20 - IT
Employee e2 = new Employee("Ngọc Linh"); // Ngọc Linh - 25 - Chưa xác định
Employee e3 = new Employee("Minh", 30); // Minh - 30 - Chưa xác định
Employee e4 = new Employee("Hùng", 28, "HR"); // Hùng - 28 - HR
Constructor chaining với lớp con và lớp cha (sử dụng super())
Khi lớp con được khởi tạo, constructor của lớp cha phải được gọi (trực tiếp hoặc gián tiếp).
Lời gọi super(…) dùng để gọi constructor của lớp cha.
Ví dụ thực tế: Vehicle và Car
Java
public class Vehicle {
protected String brand;
protected int year;
public Vehicle(String brand, int year) {
if (year < 1886) {
throw new IllegalArgumentException("Năm sản xuất không hợp lệ");
}
this.brand = brand;
this.year = year;
System.out.println("Khởi tạo Vehicle: " + brand + " (" + year + ")");
}
// Constructor không tham số cho lớp cha
public Vehicle() {
this("Unknown", 2025);
}
}
public class Car extends Vehicle {
private int numberOfDoors;
private boolean isElectric;
// Constructor đầy đủ
public Car(String brand, int year, int numberOfDoors, boolean isElectric) {
super(brand, year); // gọi constructor của lớp cha
this.numberOfDoors = numberOfDoors;
this.isElectric = isElectric;
System.out.println("Khởi tạo Car: " + numberOfDoors + " cửa, điện: " + isElectric);
}
// Constructor chaining trong lớp con
public Car(String brand, int year) {
this(brand, year, 4, false); // gọi constructor đầy đủ trong cùng lớp
}
// Constructor không tham số
public Car() {
super(); // gọi constructor không tham số của lớp cha
this.numberOfDoors = 4;
this.isElectric = false;
}
}
Cách sử dụng:
Java
Car c1 = new Car(); // Unknown (2025) + 4 cửa, không điện
Car c2 = new Car("Tesla", 2025); // Tesla (2025) + 4 cửa, không điện
Car c3 = new Car("VinFast", 2024, 5, true);
Tóm tắt các quy tắc quan trọng khi làm constructor chaining
| Quy tắc | Giải thích |
|---|---|
this() / super() phải ở dòng đầu | Không được có câu lệnh nào trước nó |
Một constructor chỉ gọi một this() hoặc super() | Không thể gọi cả hai cùng lúc |
Nếu không gọi super() rõ ràng | Java sẽ tự động gọi super() không tham số (nếu lớp cha có) |
| Lớp cha không có constructor mặc định | Lớp con bắt buộc phải gọi super(…) với tham số phù hợp |
| Tránh vòng lặp vô hạn | Không để this() gọi lẫn nhau thành vòng tròn |
Constructor chaining là một kỹ thuật mạnh mẽ giúp code Java sạch hơn, ít lặp lại hơn và đặc biệt hữu ích trong các dự án lớn, khi làm việc với kế thừa hoặc khi thiết kế các lớp immutable trong Java hiện đại (2026).
5. Từ Khóa This Và Super Trong Constructor Java

this và super là hai công cụ cực kỳ quan trọng khi làm việc với constructor trong Java. Chúng giúp thực hiện constructor chaining một cách an toàn, tránh lặp code và đảm bảo thứ tự khởi tạo đúng trong kế thừa.
Từ khóa this() trong constructor – Gọi constructor khác trong cùng lớp
this() dùng để gọi một constructor khác (overloaded) trong cùng một lớp.
Điều này giúp tuân thủ nguyên tắc DRY (Don't Repeat Yourself) – không lặp lại logic khởi tạo ở nhiều nơi.
Quy tắc quan trọng:
- Lời gọi
this(…)phải là dòng đầu tiên trong constructor (không có câu lệnh nào trước nó). - Chỉ dùng được trong constructor, không dùng trong method thông thường.
Ví dụ minh họa constructor chaining với this():
Java
public class Product {
private String name;
private double price;
private String category;
// Constructor chính (chứa toàn bộ logic khởi tạo + validation)
public Product(String name, double price, String category) {
if (price < 0) {
throw new IllegalArgumentException("Giá không thể âm!");
}
this.name = name;
this.price = price;
this.category = (category != null && !category.isBlank()) ? category : "General";
System.out.println("Khởi tạo sản phẩm: " + name + " - " + price);
}
// Constructor chaining: cung cấp giá trị mặc định
public Product(String name, double price) {
this(name, price, null); // gọi constructor đầy đủ
}
// Constructor không tham số: dùng giá trị mặc định
public Product() {
this("Unknown Product", 0.0, "Default"); // gọi constructor có 3 tham số
}
}
Lợi ích rõ ràng:
- Logic validation, gán giá trị mặc định chỉ viết một lần → dễ bảo trì.
- Khi cần thêm validation mới (ví dụ: kiểm tra tên không rỗng), chỉ sửa ở constructor chính.
- Code ngắn gọn, dễ đọc hơn rất nhiều.
Từ khóa super() trong constructor – Gọi constructor của lớp cha
super() dùng để gọi constructor của lớp cha (superclass).
Đây là bước bắt buộc trong kế thừa để đảm bảo phần "cha" của đối tượng được khởi tạo hoàn chỉnh trước khi lớp con tiếp tục.
Tại sao phải gọi constructor lớp cha trước?
- Đối tượng của lớp con là một phần mở rộng của lớp cha.
- Các field và trạng thái của lớp cha cần được thiết lập đúng trước khi lớp con sử dụng hoặc override chúng.
- Nếu không gọi
super()rõ ràng, Java sẽ tự động gọisuper()không tham số (nếu lớp cha có). Nếu lớp cha không có constructor mặc định → lỗi compile!
Ví dụ thực tế với kế thừa:
Java
public class Vehicle {
protected String brand;
protected int year;
public Vehicle(String brand, int year) {
this.brand = brand;
this.year = year;
System.out.println("Khởi tạo Vehicle: " + brand + " năm " + year);
}
// Constructor không tham số cho lớp cha
public Vehicle() {
this("Unknown Brand", 2025);
}
}
public class Car extends Vehicle {
private int doors;
private boolean electric;
public Car(String brand, int year, int doors, boolean electric) {
super(brand, year); // BẮT BUỘC gọi constructor lớp cha đầu tiên
this.doors = doors;
this.electric = electric;
System.out.println("Khởi tạo Car: " + doors + " cửa, điện: " + electric);
}
public Car(String brand, int year) {
super(brand, year); // gọi super với tham số
this.doors = 4;
this.electric = false;
}
public Car() {
super(); // gọi constructor không tham số của lớp cha
this.doors = 5;
this.electric = true;
}
}
Kết quả khi chạy:
Java
Car c = new Car("Tesla", 2025, 4, true);
// Output:
// Khởi tạo Vehicle: Tesla năm 2025
// Khởi tạo Car: 4 cửa, điện: true
Kết hợp this() và super() trong constructor chaining
Trong lớp con, bạn có thể kết hợp cả hai:
- Gọi
this()để chaining trong lớp con. - Sau đó (ngầm hoặc rõ ràng) gọi
super()để khởi tạo lớp cha.
Ví dụ kết hợp:
Java
public class ElectricCar extends Car {
private double batteryCapacity;
public ElectricCar(String brand, int year, double batteryCapacity) {
this(brand, year, 4, true, batteryCapacity); // chaining trong lớp con
}
public ElectricCar(String brand, int year, int doors, boolean electric, double batteryCapacity) {
super(brand, year, doors, electric); // gọi constructor của lớp cha (Car)
this.batteryCapacity = batteryCapacity;
}
}
Best practices sử dụng this và super để tránh lỗi (năm 2026)
- Luôn đặt
this()hoặcsuper()ở dòng đầu tiên – nếu không sẽ lỗi compile. - Nếu lớp cha không có constructor mặc định, lớp con bắt buộc phải gọi
super(…)với tham số phù hợp. - Tránh vòng lặp vô hạn khi dùng
this()(constructor A gọi B, B gọi lại A). - Sử dụng
this.fieldđể phân biệt rõ field và tham số khi tên trùng nhau. - Trong Java 2026 (JDK 26), constructor chaining vẫn hoạt động mượt mà với virtual threads – không gây deadlock nếu bạn không thực hiện blocking I/O nặng trong constructor (best practice: giữ constructor nhẹ, chỉ khởi tạo trạng thái).
- Kết hợp với record classes (nếu phù hợp): record tự động sinh constructor compact, nhưng bạn vẫn có thể dùng
this()trong compact constructor để validation.
Tóm lại:
this() giúp chaining trong lớp → code DRY, dễ bảo trì.
super() đảm bảo kế thừa đúng thứ tự → tránh lỗi trạng thái không hợp lệ.
6. Constructor Trong Kế Thừa Và Đa Hình

Khi tạo đối tượng của lớp con, Java luôn gọi constructor của lớp cha trước, sau đó mới đến lớp con – đây là nguyên tắc cốt lõi để tránh trạng thái không nhất quán.
Constructor trong kế thừa: Chuỗi gọi từ lớp con lên lớp cha (với super())
Quy tắc cơ bản:
- Constructor của lớp con phải gọi constructor của lớp cha (trực tiếp hoặc ngầm).
- Lời gọi
super(…)phải là dòng đầu tiên trong constructor của lớp con. - Nếu bạn không viết
superrõ ràng, Java sẽ tự động chènsuper()không tham số (gọi constructor mặc định của lớp cha). - Nếu lớp cha không có constructor mặc định (tức là chỉ có constructor có tham số), lớp con bắt buộc phải gọi
super(…)với tham số phù hợp → nếu không sẽ lỗi compile!
Ví dụ hoàn chỉnh minh họa chuỗi gọi constructor:
Java
// Lớp cha - chỉ có constructor có tham số (KHÔNG có constructor mặc định)
public class Animal {
protected String species;
protected int age;
public Animal(String species, int age) {
if (age < 0) {
throw new IllegalArgumentException("Tuổi không thể âm!");
}
this.species = species;
this.age = age;
System.out.println("Khởi tạo Animal: " + species + ", tuổi " + age);
}
}
// Lớp con - bắt buộc phải gọi super() với tham số
public class Dog extends Animal {
private String breed;
private boolean isTrained;
// Constructor đầy đủ
public Dog(String species, int age, String breed, boolean isTrained) {
super(species, age); // BẮT BUỘC gọi constructor lớp cha đầu tiên
this.breed = breed;
this.isTrained = isTrained;
System.out.println("Khởi tạo Dog: giống " + breed + ", đã huấn luyện: " + isTrained);
}
// Constructor chaining trong lớp con + gọi super
public Dog(String breed) {
super("Chó", 2); // cung cấp giá trị mặc định cho lớp cha
this.breed = breed;
this.isTrained = false;
System.out.println("Khởi tạo Dog mặc định: giống " + breed);
}
// Constructor không tham số - vẫn phải gọi super
public Dog() {
super("Chó", 1); // bắt buộc gọi super vì lớp cha không có default constructor
this.breed = "Lai";
this.isTrained = false;
}
}
Kết quả khi chạy:
Java
Dog d1 = new Dog("Husky", true);
// Output:
// Khởi tạo Animal: Chó, tuổi 2
// Khởi tạo Dog: giống Husky, đã huấn luyện: true
Dog d2 = new Dog();
// Output:
// Khởi tạo Animal: Chó, tuổi 1
// Khởi tạo Dog mặc định: giống Lai
→ Thấy rõ: lớp cha luôn khởi tạo trước, đảm bảo trạng thái của species và age đã sẵn sàng trước khi lớp con tiếp tục.
Xử lý constructor trong interface và abstract class
- Interface: Không có constructor (vì interface không thể tạo đối tượng trực tiếp). Interface chỉ định nghĩa hành vi (method), không có trạng thái khởi tạo.
- Abstract class: Có thể có constructor (cả mặc định và có tham số). Abstract class thường dùng constructor để khởi tạo các field chung cho tất cả lớp con. Lớp con kế thừa abstract class vẫn phải tuân thủ quy tắc gọi
super()như trên.
Ví dụ ngắn gọn:
Java
public abstract class Shape {
protected String color;
public Shape(String color) {
this.color = color != null ? color : "Black";
System.out.println("Khởi tạo Shape với màu: " + color);
}
}
public class Circle extends Shape {
private double radius;
public Circle(double radius, String color) {
super(color); // gọi constructor của abstract class
this.radius = radius;
}
}
Ứng dụng đa hình (polymorphism) với constructor trong Java
Trong đa hình, constructor không bị override (vì constructor không phải method thông thường).
Tuy nhiên, constructor giúp thiết lập trạng thái ban đầu chính xác, sau đó các method override mới hoạt động đúng theo kiểu thực tế của đối tượng.
Ví dụ minh họa đa hình + constructor:
Java
public class Animal {
public Animal() {
System.out.println("Constructor Animal");
}
public void makeSound() {
System.out.println("Tiếng kêu chung");
}
}
public class Cat extends Animal {
public Cat() {
super(); // gọi constructor lớp cha
System.out.println("Constructor Cat");
}
@Override
public void makeSound() {
System.out.println("Meo meo!");
}
}
public class Dog extends Animal {
public Dog() {
super();
System.out.println("Constructor Dog");
}
@Override
public void makeSound() {
System.out.println("Gâu gâu!");
}
}
// Sử dụng đa hình
Animal animal1 = new Cat(); // Output: Constructor Animal → Constructor Cat
animal1.makeSound(); // Output: Meo meo!
Animal animal2 = new Dog(); // Output: Constructor Animal → Constructor Dog
animal2.makeSound(); // Output: Gâu gâu!
Lợi ích trong đa hình:
- Constructor đảm bảo trạng thái cơ bản (từ lớp cha) luôn được thiết lập trước.
- Method override hoạt động đúng trên đối tượng thực tế (runtime type).
- Hỗ trợ thiết kế Liskov Substitution Principle (LSP) – lớp con có thể thay thế lớp cha mà không phá vỡ logic.
Tóm tắt năm 2026:
- Luôn nhớ: lớp cha khởi tạo trước lớp con.
- Nếu lớp cha không có default constructor → lớp con bắt buộc gọi
super(args). - Constructor + đa hình giúp code an toàn, dễ mở rộng trong các hệ thống lớn (Spring, microservices).
- Kết hợp với
ffina,sealed classes, và virtual threads để viết code hiện đại, hiệu suất cao.
7. Constructor Nâng Cao: Private Và Copy Constructor

Private Constructor Trong Java – Kiểm soát khởi tạo đối tượng
Private constructor là constructor có access modifier private, nghĩa là chỉ có thể được gọi từ bên trong chính lớp đó.
Điều này ngăn chặn hoàn toàn việc tạo đối tượng từ bên ngoài bằng từ khóa new, buộc người dùng phải sử dụng các phương thức tĩnh (static factory methods) để lấy instance.
Tại sao cần private constructor?
- Ngăn chặn việc tạo nhiều instance không mong muốn (ví dụ: trong Singleton).
- Đảm bảo tính duy nhất hoặc kiểm soát chặt chẽ logic khởi tạo.
- Thường dùng trong Singleton pattern, utility classes (chỉ chứa static methods), hoặc builder pattern (khi builder là inner class).
Ứng dụng phổ biến nhất: Singleton Pattern (Lazy Initialization)
Java
public class DatabaseConnection {
// Instance duy nhất, volatile để an toàn với multi-threading
private static volatile DatabaseConnection instance;
// Private constructor → ngăn tạo từ ngoài
private DatabaseConnection() {
// Khởi tạo kết nối (giả lập)
System.out.println("Khởi tạo kết nối database...");
// Có thể thêm heavy initialization ở đây
}
// Static factory method - điểm truy cập duy nhất
public static DatabaseConnection getInstance() {
if (instance == null) { // Double-checked locking cho hiệu suất
synchronized (DatabaseConnection.class) {
if (instance == null) {
instance = new DatabaseConnection();
}
}
}
return instance;
}
public void query(String sql) {
System.out.println("Thực thi: " + sql);
}
}
Cách sử dụng:
Java
DatabaseConnection db = DatabaseConnection.getInstance(); // OK
DatabaseConnection db2 = new DatabaseConnection(); // LỖI compile! Constructor private
Ứng dụng khác của private constructor:
- Utility classes (ví dụ:
Math,Collections): Không cần tạo instance → private constructor + tất cả method làstatic.
Java
public final class MathUtils {
private MathUtils() { // Ngăn tạo instance
throw new AssertionError("Không thể khởi tạo utility class!");
}
public static int add(int a, int b) { return a + b; }
}
Copy Constructor Trong Java – Tạo bản sao đối tượng an toàn
Copy constructor là một constructor đặc biệt nhận tham số là một instance của chính lớp đó, dùng để tạo bản sao sâu (deep copy) hoặc bản sao nông (shallow copy) tùy thiết kế.
Cú pháp cơ bản:
Java
public ClassName(ClassName other) {
// Sao chép các field từ other sang this
}
Ví dụ thực tế (với deep copy):
Java
public class Person {
private String name;
private int age;
private Address address; // Object phức tạp
** // Constructor thông thường
public Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
// Copy constructor - deep copy
public Person(Person other) {
this.name = other.name; // String immutable → safe
this.age = other.age;
// Deep copy cho object mutable
this.address = new Address(other.address.getCity(), other.address.getStreet());
}
// Getter + Setter...
}
public class Address {
private String city;
private String street;
public Address(String city, String street) {
this.city = city;
this.street = street;
}
// Copy constructor cho Address
public Address(Address other) {
this.city = other.city;
this.street = other.street;
}
// Getter...
}
Cách sử dụng:
Person p1 = new Person("Ngọc Linh", 25, new Address("HCMC", "Nguyễn Huệ"));
Person p2 = new Person(p1); // Tạo bản sao từ p1
p2.getAddress().setCity("Hà Nội"); // Không ảnh hưởng đến p1 (deep copy)
So sánh Copy Constructor với phương thức clone()
| Tiêu chí | Copy Constructor | Phương thức clone() |
|---|---|---|
| An toàn kiểu | An toàn (compile-time check) | Không an toàn (cần cast, dễ lỗi runtime) |
| Yêu cầu interface | Không cần | Phải implement Cloneable |
| Deep vs Shallow copy | Dễ kiểm soát (tự code deep copy) | Mặc định shallow copy, dễ quên deep copy |
| Xử lý exception | Không throw CloneNotSupportedException | Phải xử lý exception |
| Khuyến nghị năm 2026 | Ưu tiên – rõ ràng, dễ debug, immutable-friendly | Tránh dùng (ngoại trừ legacy code) |
Lý do copy constructor tốt hơn:
- Không phụ thuộc vào
Cloneable(một marker interface lỗi thời). - Dễ dàng thực hiện deep copy mà không quên field nào.
- Tích hợp tốt với immutable objects và record classes (Java hiện đại).
- Tránh các vấn đề phức tạp khi override
clone()trong kế thừa.
Tóm tắt Constructor nâng cao năm 2026:
- Private constructor → kiểm soát khởi tạo (Singleton, utility class).
- Copy constructor → sao chép đối tượng an toàn, thay thế tốt cho
clone(). - Kết hợp cả hai để thiết kế class thread-safe, immutable, và dễ test trong môi trường virtual threads + microservices.
8. Best Practices Constructor Trong Java Năm 2026
Năm 2026, với JDK 26 đã ổn định, constructor không chỉ là công cụ khởi tạo đối tượng mà còn là nền tảng để viết code an toàn, hiệu suất cao, dễ bảo trì và phù hợp với các xu hướng hiện đại như immutable design, virtual threads, Spring Boot 3.x+, và record classes. Dưới đây là các best practices quan trọng nhất mà mọi lập trình viên Java nên áp dụng.
1. Ưu tiên Constructor Injection cho Dependency Injection (Spring Boot & Modern Frameworks)
- Tại sao? Constructor injection rõ ràng, dễ test (không cần setter), hỗ trợ immutable, và là cách khuyến nghị chính thức của Spring từ Spring Boot 2.0+.
- Cách làm: Chỉ dùng constructor để inject dependency, tránh field/setter injection.
Java
@Service
public class UserService {
private final UserRepository userRepo;
private final EmailSender emailSender;
public UserService(UserRepository userRepo, EmailSender emailSender) {
this.userRepo = userRepo;
this.emailSender = emailSender;
}
}
2. Sử dụng final cho các field được khởi tạo trong constructor → Tạo Immutable Objects
- Lợi ích: Đảm bảo trạng thái không thay đổi sau khởi tạo → an toàn multi-threading (đặc biệt với virtual threads), dễ debug, và tuân thủ immutability (một trong những best practice lớn nhất năm 2026).
- Cách làm: Khai báo field là
finalvà chỉ gán giá trị trong constructor.
Java
public class User {
private final String id;
private final String name;
private final LocalDate birthDate;
public User(String id, String name, LocalDate birthDate) {
this.id = Objects.requireNonNull(id, "ID không được null");
this.name = name;
this.birthDate = birthDate;
}
}
3. Tránh thực hiện công việc nặng (heavy computation, I/O, API call) trong constructor
- Tại sao? Constructor nên nhẹ và nhanh để tránh làm chậm việc tạo đối tượng, đặc biệt khi tạo hàng triệu instance với virtual threads.
- Giải pháp: Chỉ khởi tạo trạng thái cơ bản. Công việc phức tạp → chuyển sang lazy initialization (getter) hoặc phương thức riêng.
Ví dụ xấu (tránh):
Java
public class ReportGenerator {
public ReportGenerator() {
// Heavy: gọi API, đọc file lớn...
fetchDataFromExternalService();
}
}
Ví dụ tốt:
Java
public class ReportGenerator {
private List<Data> data;
public ReportGenerator() {
// Chỉ khởi tạo nhẹ
}
public List<Data> getData() {
if (data == null) {
data = fetchDataFromExternalService(); // lazy
}
return data;
}
}
4. Sử dụng Builder Pattern hoặc Static Factory Method khi constructor có quá nhiều tham số
- Vấn đề: Constructor với 5+ tham số → khó đọc, dễ nhầm thứ tự, khó mở rộng.
- Giải pháp tốt nhất năm 2026:
- Builder Pattern (Lombok
@Builderhoặc tự viết). - Static factory methods (như
of(),valueOf()).
Ví dụ với Builder (sử dụng Lombok cho gọn):
Java
@Builder
@ToString
public class Order {
private final String id;
private final Customer customer;
private final LocalDate orderDate;
private final List<Item> items;
private final PaymentMethod payment;
// ... nhiều field nữa
}
Sử dụng:
Java
Order order = Order.builder()
.id("ORD-123")
.customer(cust)
.orderDate(LocalDate.now())
.items(itemsList)
.payment(PaymentMethod.CREDIT_CARD)
.build();
5. Giới hạn số lượng constructor overloading (tối đa 3-4)
- Lý do: Quá nhiều overload → gây nhầm lẫn, khó bảo trì.
- Giải pháp: Tập trung logic vào một constructor chính (có đầy đủ tham số), các constructor khác dùng
this()để chaining.
Constructor với Record Classes trong Java hiện đại (JDK 26)Record classes (từ Java 14, ổn định và được nâng cấp liên tục) là cách tốt nhất để tạo immutable data class với constructor tối giản.
Ví dụ compact constructor:
Java
public record Point(int x, int y) {
// Compact constructor: tự động gán + thêm validation
public Point {
if (x < 0 || y < 0) {
throw new IllegalArgumentException("Tọa độ không thể âm");
}
}
}
- Record tự động sinh: constructor đầy đủ, getter, equals(), hashCode(), toString().
- Giảm boilerplate cực kỳ → khuyến khích dùng thay class thông thường khi chỉ cần data.
Cập nhật & Dự đoán tính năng constructor trong Java 25+ / 26- Java 25 (đã ra mắt): Cải tiến record classes, hỗ trợ constructor tốt hơn cho pattern matching.
- Java 26 (2026): Dự kiến preview hoặc stable cho constructor với pattern matching (deconstruction patterns) – cho phép destructuring trực tiếp trong constructor, giúp code ngắn gọn hơn khi xử lý dữ liệu phức tạp.
Tóm tắt Best Practices Constructor 2026- Constructor injection cho DI.
final+ immutable design.- Giữ constructor nhẹ, tránh heavy work.
- Builder / Static factory khi nhiều tham số.
- Giới hạn overloading, ưu tiên chaining.
- Ưu tiên record classes cho data object.
- Luôn validate input để tăng security (tránh null, giá trị không hợp lệ).
9. Ví Dụ Thực Tế Và Case Study Constructor Java

- Ứng dụng web (Servlet/Spring Controller): Khởi tạo dependencies và tài nguyên.
- Framework Spring (Constructor Injection & Chaining): Cách Spring xử lý constructor để setup beans một cách thông minh.
Những ví dụ dưới đây dựa trên thực tế năm 2026 (JDK 26 đã GA từ tháng 3/2026, Spring Boot 4.x+), nơi constructor kết hợp tốt với immutable design, virtual threads, và constructor injection là best practice chuẩn.
Ví dụ constructor Java trong ứng dụng web (Spring Controller)
Trong ứng dụng web (đặc biệt Spring Boot), constructor thường được dùng để inject dependencies (services, repositories) một cách rõ ràng. Điều này giúp:
- Đối tượng luôn ở trạng thái hợp lệ ngay từ đầu (không null dependencies).
- Dễ unit test (không cần reflection).
- Hỗ trợ immutable fields với
final.
Ví dụ thực tế: UserController trong ứng dụng quản lý người dùng
Java
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
private final NotificationService notificationService;
// Constructor injection – best practice Spring Boot 2026
public UserController(UserService userService,
NotificationService notificationService) {
this.userService = Objects.requireNonNull(userService, "UserService không được null");
this.notificationService = notificationService;
}
@PostMapping
public ResponseEntity<UserDTO> createUser(@RequestBody UserCreateRequest request) {
User user = userService.createUser(request);
notificationService.sendWelcomeEmail(user.getEmail());
return ResponseEntity.ok(UserDTO.from(user));
}
}
→ Constructor đảm bảo UserService luôn tồn tại trước khi controller xử lý request. Nếu thiếu dependency → lỗi ngay lúc khởi tạo bean (không phải runtime).
Trong Servlet cổ điển (ít dùng hơn nhưng vẫn thấy ở legacy):
Constructor khởi tạo connection pool hoặc config chung:
Java
public class LegacyServlet extends HttpServlet {
private final DataSource dataSource;
public LegacyServlet() {
// Khởi tạo connection pool (thường dùng JNDI)
this.dataSource = lookupDataSourceFromJNDI();
}
}
Case Study: Constructor có tham số trong dự án lớn (E-commerce)
Trong dự án e-commerce lớn (hàng triệu sản phẩm, microservices), constructor có tham số giúp Product class luôn hợp lệ khi load từ DB hoặc tạo mới.
Ví dụ Product class với validation & immutable:
Java
public class Product {
private final String id;
private final String name;
private final BigDecimal price;
private final Category category;
public Product(String id, String name, BigDecimal price, Category category) {
this.id = Objects.requireNonNull(id, "ID sản phẩm không được null");
this.name = validateName(name);
this.price = validatePrice(price);
this.category = Objects.requireNonNull(category, "Category không được null");
}
private String validateName(String name) {
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("Tên sản phẩm không được rỗng");
}
return name.trim();
}
private BigDecimal validatePrice(BigDecimal price) {
if (price == null || price.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Giá phải lớn hơn 0");
}
return price;
}
// Getter only → immutable
}
Lợi ích trong dự án lớn:
- Khi tạo Product từ DB (qua JPA/Entity): Constructor validation ngăn sản phẩm "lậu" (giá âm, tên rỗng) vào hệ thống.
- An toàn concurrency: Với virtual threads (JDK 26), immutable Product không cần lock khi chia sẻ giữa threads.
- Dễ mở rộng: Microservice OrderService dùng constructor này để tạo item trong giỏ hàng.
Constructor Chaining & Injection trong Spring Framework
Spring khuyến khích mạnh mẽ constructor injection (từ Spring Boot 2.0+ đến 4.x năm 2026). Khi một bean có nhiều constructor, Spring sẽ:
- Ưu tiên constructor có @Autowired (nếu có).
- Nếu chỉ có một constructor, tự động dùng nó (không cần @Autowired).
- Đây là dạng "chaining logic" do container quyết định để chọn constructor phù hợp nhất.
Ví dụ Service với nhiều constructor (Spring tự chọn):
Java
@Service
public class OrderService {
private final OrderRepository orderRepo;
private final PaymentGateway paymentGateway;
private final NotificationService notificationService; // Optional
// Constructor chính – mandatory dependencies
public OrderService(OrderRepository orderRepo, PaymentGateway paymentGateway) {
this.orderRepo = orderRepo;
this.paymentGateway = paymentGateway;
this.notificationService = null; // Optional
}
// Constructor overload – với optional dependency
@Autowired // Spring sẽ chọn cái này nếu có notificationService bean
public OrderService(OrderRepository orderRepo,
PaymentGateway paymentGateway,
NotificationService notificationService) {
this(orderRepo, paymentGateway); // chaining đến constructor chính
this.notificationService = notificationService;
}
public void processOrder(Order order) {
// Logic...
if (notificationService != null) {
notificationService.sendOrderConfirmation(order);
}
}
}
→ Spring tự động chọn constructor phù hợp dựa trên beans có sẵn → linh hoạt mà vẫn an toàn.
Debug và Test Constructor trong môi trường 2026
Unit Test với JUnit 5+ & Mockito:
Java
@Test
void testConstructor_ThrowsException_WhenInvalidPrice() {
assertThrows(IllegalArgumentException.class,
() -> new Product("P1", "Laptop", new BigDecimal("-10"), Category.ELECTRONICS));
}
Integration Test với Spring Boot Test:
Java
@SpringBootTest
class UserControllerTest {
@Autowired
private UserController controller;
@Test
void contextLoads() {
// Kiểm tra constructor injection thành công → bean khởi tạo OK
assertNotNull(controller);
}
}
Debug: Trong IntelliJ IDEA (hoặc VS Code), đặt breakpoint trong constructor → dễ theo dõi thứ tự chaining, super(), và giá trị inject.
❓ Câu hỏi thường gặp
8 câu hỏi
Kết luận
Constructor trong Java không chỉ là "cơ bản" – nó là nền tảng cho code sạch, an toàn và hiệu suất cao.
Năm 2026, với Java 25 mang đến Flexible Constructor Bodies và record classes mạnh mẽ hơn, bạn có công cụ tuyệt vời để xây dựng ứng dụng hiện đại.
Hãy thực hành ngay hôm nay: Tạo một lớp với chaining, private constructor, và thử record! Bạn sẽ thấy code mình "pro" lên trông thấy.
Cảm ơn bạn đã đọc đến đây! Nếu có câu hỏi, comment bên dưới nhé. Happy coding!

Lê Đình Đài
- Kinh nghiệm 5 năm vận hành Shopee & TikTok Shop
- Xây shop thời trang nữ từ 0đ lên doanh thu 5 tỷ/tháng
Founder của dinhdai.tech - Nơi chia sẻ kiến thức, công cụ AI miễn phí và giải pháp tối ưu cho seller. Sứ mệnh của tôi là giúp mọi người kinh doanh hiệu quả hơn với công nghệ.