Class và object trong Java: bản thiết kế và thực thể heap
Phân biệt class và object, cơ chế cấp phát heap với từ khóa new, quản lý biến reference trên stack và so sánh bằng toán tử == với equals.
TL;DR: Class là khuôn mẫu thiết kế định nghĩa các thuộc tính và hành vi, còn object là thực thể cụ thể được sinh ra trên heap qua từ khóa new. Biến tham chiếu trên stack chỉ lưu địa chỉ ô nhớ trỏ tới object. Toán tử == so sánh địa chỉ tham chiếu, trong khi phương thức .equals() mặc định cũng so địa chỉ trừ khi được ghi đè để so sánh giá trị nội dung. Ta đã làm quen với việc gọi phương thức ở bài khai báo method.
Module 4 khép lại với một class NumberUtils chỉ có method tĩnh: class ở đó chỉ là nơi gom nhóm các hàm dùng chung. Từ bài này, class giữ vai trò hoàn toàn khác: nó là bản thiết kế cho một kiểu đối tượng mới với trạng thái riêng. Bạn không chỉ gọi method mà còn tạo ra các object, mỗi object có field riêng, sống trên heap, được biến reference trỏ đến.
Bài này giải thích class là gì, object là gì, cơ chế JVM cấp phát object trên heap, và phân tích sâu sự khác biệt giữa == và .equals().
1. Khuôn bánh và chiếc bánh phản ánh quan hệ giữa class và object ra sao?
Bạn có một chiếc khuôn bánh trung thu bằng gỗ: đó chính là class. Chiếc khuôn mô tả hình dạng, kích thước, hoa văn. Khi bạn ép bột vào khuôn sẽ cho ra một chiếc bánh: đó là object. Khuôn chỉ có 1, nhưng bánh có thể đúc hàng ngàn chiếc với các loại nhân khác nhau (đậu xanh, thập cẩm).
| Đời thường | Java |
|---|---|
| Khuôn bánh | class Cake |
| Một chiếc bánh đã đúc | Một Cake object |
| Nhân bánh (đậu xanh, thập cẩm) | Field value của object |
| Hành động cắn một miếng | Method |
Class = template, object = instance đúc từ template. 1 class tạo ra N objects. Mỗi object có bản sao field riêng và dùng chung các method.
Thử ngẫmnếu class là khuôn bánh, thì trong code bạn đang viết, class nào đang đóng vai khuôn và field nào là phần nhân khác nhau giữa các object của nó?
2. Cách khai báo class và khởi tạo đối tượng bằng từ khóa new thế nào?
// Class khai bao 1 lan trong file
public class Book {
String title; // field
String author;
int year;
}
Dùng class — tạo object bằng new:
public class App {
public static void main(String[] args) {
Book b1 = new Book();
b1.title = "Clean Code";
b1.author = "Robert C. Martin";
b1.year = 2008;
Book b2 = new Book();
b2.title = "Effective Java";
b2.author = "Joshua Bloch";
b2.year = 2018;
System.out.println(b1.title); // Clean Code
System.out.println(b2.title); // Effective Java
}
}
b1 và b2 là 2 object khác nhau, cùng kiểu Book. Sửa b1.title không đụng b2.title — mỗi object có bản sao field riêng.
2.1 new làm gì bên dưới?
Khi bạn viết new Book(), JVM chạy 3 bước:
- Cấp phát memory — tìm chỗ trên heap đủ chứa tất cả field của
Book. Field chưa gán lấy giá trị mặc định (nullcho reference,0cho int,falsecho boolean). - Chạy constructor — method đặc biệt khởi tạo object (bài sau).
- Trả về reference — địa chỉ của object trên heap được gán vào biến
b1.

Biến b1, b2 nằm trên stack frame — chúng chỉ giữ địa chỉ (reference). Object nằm trên heap. Đây là cơ chế đã học ở Module 2 — giờ nhìn lại ở tầng OOP.
Thử ngẫmbước 1 của new luôn cấp phát heap trước khi bước 2 chạy constructor. Nếu constructor ném exception giữa chừng, object đã cấp phát đó còn nằm trên heap không, và ai dọn nó?
3. Field chưa gán nhận giá trị mặc định nào trong Java?
Field không được gán nhận giá trị mặc định theo kiểu:
| Kiểu | Default |
|---|---|
int, long, short, byte, char | 0 |
double, float | 0.0 |
boolean | false |
Bất kỳ reference type (String, Book, int[], ...) | null |
Khác với local variable: local không có default, phải gán trước khi đọc nếu không compiler sẽ báo lỗi:
int x;
System.out.println(x); // COMPILE ERROR — variable x might not have been initialized
public class Foo {
int x; // OK — field tu dong la 0
}
Lý do: local nằm trong stack frame được cấp phát nhanh, không có bước zero-out. Field trong object được JVM zero-out khi cấp phát bộ nhớ theo đặc tả của ngôn ngữ.
4. Những hệ quả khi object được cấp phát trên heap là gì?
4.1 Nhiều reference cùng trỏ 1 object
Book a = new Book();
a.title = "Java";
Book b = a; // copy reference
b.title = "Python";
System.out.println(a.title); // "Python" — a va b tro cung object
Đây là bản chất pass-by-value: b = a copy reference (địa chỉ ô nhớ), không nhân bản object. Sửa object qua reference này thì reference kia cũng thấy thay đổi.
4.2 Object không còn ai reference được GC thu hồi
Book a = new Book();
a = null; // khong ai giu reference den object cu nua
// JVM se don object cu o lan GC tiep theo
Bạn không cần gọi free() hay delete như trong C/C++. Bộ dọn rác (Garbage Collector - GC) tự động phát hiện các object không còn reachable để thu hồi bộ nhớ.
4.3 Toán tử == so địa chỉ, .equals() so nội dung
Book a = new Book(); a.title = "Java";
Book b = new Book(); b.title = "Java";
System.out.println(a == b); // false — 2 object khac, dia chi khac
System.out.println(a.equals(b)); // false — default equals() cua Object chi so == (dia chi)
Chú ý: equals() mặc định từ class Object chưa so sánh nội dung. Muốn 2 cuốn sách cùng thông tin trả về true, bạn phải override equals().
5. Quy tắc khai báo nhiều class trong một file mã nguồn là gì?
Java cho phép nhiều class trong 1 file, nhưng chỉ có duy nhất 1 public class, và tên file phải trùng khớp với public class đó:
// File: App.java
public class App {
public static void main(String[] args) {
Book b = new Book();
b.title = "Java";
System.out.println(b.title);
}
}
class Book { // package-private, khong public
String title;
String author;
int year;
}
Quy tắc này giúp compiler và classloader dễ dàng định vị bytecode tương ứng. Trong thực tế dự án, mỗi class nên được đặt ở một file riêng biệt.
6. Instance method khác biệt gì so với static method?
Method không có từ khóa static là instance method và phải được gọi qua một đối tượng:
public class Book {
String title;
int year;
public void printInfo() { // instance method
System.out.println(title + " (" + year + ")");
}
public int yearsOld() {
return java.time.Year.now().getValue() - year;
}
}
// Su dung:
Book b = new Book();
b.title = "Java";
b.year = 2010;
b.printInfo(); // goi method qua object
System.out.println(b.yearsOld());
Trong thân instance method, bạn truy cập field trực tiếp bằng tên (title, year). JVM ngầm hiểu đó là field của đối tượng đang thực thi lời gọi hàm.
6.1 So sánh instance method và static method
public class Book {
String title;
public static void hello() { System.out.println("Hello"); } // static
public void printTitle() { System.out.println(title); } // instance
}
Book.hello(); // goi static qua class — OK
Book.printTitle(); // COMPILE ERROR — printTitle can 1 object
new Book().printTitle(); // OK — goi qua object
Thử ngẫmmột method không đụng field nào của object, chỉ tính toán từ tham số truyền vào: theo bạn nên khai báo nó static hay instance, và vì sao?
7. Những cạm bẫy thường gặp khi làm việc với class và object là gì?
❌ Nhầm 1: Gọi field instance qua tên class.
Book.title = "Java"; // COMPILE ERROR — title la instance field
✅ Khởi tạo object trước bằng new, sau đó truy cập: b.title = "Java".
❌ Nhầm 2: Khởi tạo object nhưng không gán vào biến tham chiếu.
new Book(); // object tao xong lap tuc eligible cho GC vi khong co bien tro toi
✅ Lưu reference vào biến: Book b = new Book();.
❌ Nhầm 3: So sánh hai object riêng biệt bằng toán tử ==.
if (user1 == user2) { ... } // chi so sanh dia chi tham chieu
✅ Override và sử dụng phương thức .equals().
❌ Nhầm 4: Đọc field đối tượng khi chưa khởi tạo dẫn đến NullPointerException.
public class User {
String name; // default null
int len = name.length(); // NPE tai runtime
}
✅ Khởi tạo giá trị field trong constructor hoặc gán giá trị mặc định an toàn.
❌ Nhầm 5: Khai báo nhiều public class trong cùng một file mã nguồn.
public class App { ... }
public class Book { ... } // COMPILE ERROR
✅ Chỉ giữ 1 public class trùng tên file, hoặc tách mỗi class ra từng file riêng.
8. Oracle JLS quy định chi tiết về lifecycle của class và object ra sao?
Spec / reference chính thức:
- JLS §8 — Classes: toàn bộ quy tắc về khai báo class, field, method và constructor.
- JLS §4.12.5 — Initial Values of Variables: giá trị mặc định của field và biến local.
- JLS §15.9 — Class Instance Creation Expressions: thứ tự thực thi của biểu thức
new. - JVMS §2.5.3 — Heap: định nghĩa vùng nhớ heap và trách nhiệm của GC.
Ghi chú: JLS §15.9 mô tả chuỗi bước của new: đánh giá tham số, cấp phát heap, gán giá trị mặc định cho field, sau đó mới gọi constructor.
9. Những điểm cốt lõi nào cần ghi nhớ về class và object?
- Class là khuôn mẫu thiết kế trên mã nguồn; Object là thực thể cụ thể chứa dữ liệu riêng được cấp phát trên heap.
- Biến tham chiếu trên stack chỉ lưu địa chỉ ô nhớ: gán
b = achỉ sao chép địa chỉ chứ không tạo ra object mới. - Field của object được JVM tự động gán giá trị mặc định (0, false, null), khác với biến local bắt buộc khởi tạo trước khi dùng.
- Toán tử
==so sánh địa chỉ ô nhớ; muốn so sánh nội dung logic giữa các đối tượng phải ghi đè phương thức.equals().
10. Tự kiểm tra mức độ hiểu biết về class và object
- Q1Đoạn sau in gì?
Book a = new Book(); a.title = "A"; Book b = a; b.title = "B"; System.out.println(a.title); - Q2Đoạn sau compile không?
public class Book { int year; } public class App { public static void main(String[] args) { int x; System.out.println(x); Book b = new Book(); System.out.println(b.year); } } - Q3Đoạn sau in gì và tại sao?
Book a = new Book(); Book b = new Book(); System.out.println(a == b); System.out.println(a.equals(b)); - Q4Đoạn sau có gì khác thường? Mỗi khi chạy main tạo bao nhiêu object?
new Book(); new Book(); Book b = new Book(); - Q5Vì sao file
App.javakhông thể chứa 2 class đềupublic?
Bài tiếp theo: Constructor và this — khởi tạo object đúng cách
Bài này có giúp bạn hiểu bản chất không?
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Mini-challenge: thư viện NumberUtils
Bài tiếpConstructor và từ khóa this trong Java: khởi tạo object
Lộ trình Junior Java Backend Developer
Chặng 3/21 · OOP: class, kế thừa, đa hình, abstraction — bài 1/16 của chặng
Thảo luận & góp ý
Hỏi đáp về bài này