OOP — Class và Object¶
1. Khái niệm¶
Class là bản thiết kế (blueprint) mô tả một loại đối tượng: nó có những dữ liệu gì (fields) và có thể làm gì (methods).
Object là một thực thể cụ thể được tạo ra từ class đó. Một class có thể tạo ra vô số object.
// Class — bản thiết kế
class Dog {
String name;
int age;
void bark() {
System.out.println(name + " says: Woof!");
}
}
// Object — thực thể cụ thể
Dog rex = new Dog();
Dog luna = new Dog();
// rex và luna là hai object riêng biệt, cùng từ class Dog
2. Tại sao quan trọng¶
OOP là nền tảng của Java và hầu hết backend framework hiện đại:
- Spring Boot hoạt động dựa trên bean — object được Spring quản lý
- JPA/Hibernate map class Java thành bảng database
- Design patterns (Factory, Strategy, Builder...) đều xây trên class hierarchy
Hiểu rõ class/object, static/instance, và ba method toString() / equals() / hashCode() là điều kiện tối thiểu để làm việc với bất kỳ Java codebase thực tế nào.
3. Anatomy của một Class¶
Fields — dữ liệu¶
Fields (còn gọi là instance variables) lưu trạng thái của mỗi object.
Mỗi object có bản sao riêng của các instance fields — thay đổi field của object này không ảnh hưởng object kia.
Methods — hành vi¶
Methods định nghĩa những gì object có thể làm.
class Product {
String name;
double price;
int quantity;
double totalValue() {
return price * quantity;
}
void restock(int amount) {
quantity += amount;
}
}
Naming conventions¶
| Thành phần | Convention | Ví dụ |
|---|---|---|
| Class | PascalCase | BankAccount, ProductService |
| Field | camelCase | firstName, accountBalance |
| Method | camelCase, động từ | deposit(), calculateTax() |
| Constant | UPPER_SNAKE_CASE | MAX_RETRY, DEFAULT_TIMEOUT |
4. Object Creation — new và Heap¶
Tạo object¶
Ba việc xảy ra theo thứ tự:
- Cấp phát Heap — JVM tìm chỗ trống trên Heap, cấp phát memory cho object
- Khởi tạo giá trị mặc định — fields được gán giá trị default (
0,null,false) - Trả về reference — địa chỉ object trên Heap được lưu vào biến
ptrên Stack
Giá trị default của fields¶
| Kiểu | Default |
|---|---|
int, long, short, byte |
0 |
double, float |
0.0 |
boolean |
false |
char |
' ' |
Object (String, array, ...) |
null |
Local variable không có default
Fields của object có giá trị mặc định. Nhưng local variable trong method thì không — truy cập trước khi gán sẽ gây lỗi compile.
Nhiều reference, một object¶
Product a = new Product();
Product b = a; // b trỏ vào CÙNG object với a — không phải bản sao
a.price = 100;
System.out.println(b.price); // 100 — cùng object
5. this keyword¶
this là reference trỏ về chính object đang thực thi method. Dùng khi tên field bị che khuất bởi tham số cùng tên.
class Circle {
double radius;
void setRadius(double radius) {
this.radius = radius; // this.radius = field; radius = tham số
}
double area() {
return Math.PI * radius * radius; // this ở đây không bắt buộc
}
}
Khi nào cần this, khi nào không?
this bắt buộc khi tên tham số trùng với tên field. Ngoài ra this là tùy chọn — Java tự hiểu radius là field nếu không có local variable cùng tên. Đừng lạm dụng this ở mọi chỗ — code sẽ dài và khó đọc hơn mà không thêm giá trị gì.
6. Static vs Instance¶
Instance members¶
Gắn với từng object cụ thể. Mỗi object có bản sao riêng.
class Counter {
int count = 0; // instance field — mỗi object có riêng
void increment() { count++; }
}
Counter c1 = new Counter();
Counter c2 = new Counter();
c1.increment();
System.out.println(c1.count); // 1
System.out.println(c2.count); // 0 — không bị ảnh hưởng
Static members¶
Gắn với class, dùng chung bởi tất cả object. Tồn tại trong Metaspace.
class Counter {
static int totalCreated = 0; // dùng chung, sống trong Metaspace
int count = 0;
Counter() {
totalCreated++; // mỗi lần tạo object mới, tăng counter chung
}
}
Counter c1 = new Counter();
Counter c2 = new Counter();
System.out.println(Counter.totalCreated); // 2 — gọi qua tên class
Static method¶
Không có this. Không thể truy cập instance field trực tiếp.
class MathUtils {
static int square(int n) {
return n * n; // không cần object
}
}
int result = MathUtils.square(5); // gọi qua tên class
| Instance | Static | |
|---|---|---|
| Gắn với | Object | Class |
| Memory | Heap (mỗi object) | Metaspace (dùng chung) |
| Gọi qua | Object reference | Tên class |
Truy cập this |
Có | Không |
| Dùng khi | Cần state của object | Utility, không cần state |
Anti-pattern: gọi static qua object reference
7. toString(), equals(), hashCode()¶
Ba method này được kế thừa từ Object — class gốc của mọi class trong Java. Default implementation thường không hữu ích — hầu hết class nên override cả ba.
toString()¶
Default trả về ClassName@hexHashCode — vô nghĩa với người đọc.
class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override
public String toString() {
return "Point(" + x + ", " + y + ")";
}
}
Point p = new Point(3, 4);
System.out.println(p); // Point(3, 4) — Java tự gọi toString()
Override toString() để debug dễ hơn
System.out.println(obj), string concatenation "value: " + obj, và logger đều tự gọi toString(). Object không override → log vô nghĩa như com.example.Point@7ef88735.
equals()¶
Default so sánh địa chỉ (giống ==) — hai object khác nhau luôn false dù cùng dữ liệu.
class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override
public boolean equals(Object o) {
if (this == o) return true; // cùng địa chỉ
if (!(o instanceof Point other)) return false; // khác kiểu
return x == other.x && y == other.y; // so sánh dữ liệu
}
}
Point p1 = new Point(3, 4);
Point p2 = new Point(3, 4);
System.out.println(p1 == p2); // false — khác địa chỉ
System.out.println(p1.equals(p2)); // true — cùng dữ liệu
hashCode()¶
Contract: nếu a.equals(b) là true thì a.hashCode() == b.hashCode() phải là true.
class Point {
int x, y;
@Override
public boolean equals(Object o) { /* ... */ }
@Override
public int hashCode() {
return Objects.hash(x, y); // kết hợp hash của các fields dùng trong equals()
}
}
Quy tắc: override equals() → bắt buộc override hashCode()
Nếu chỉ override equals(): hai object bằng nhau theo equals nhưng hashCode khác nhau → HashSet lưu cả hai như object riêng biệt, HashMap không tìm được key đã put vào. Bug này không có compile error hay exception — chỉ ra kết quả sai.
Cách nhanh nhất¶
IntelliJ: Alt+Insert → Generate → equals() and hashCode() → tự sinh code chuẩn.
Hoặc dùng Record (Java 16+) — tự động có toString(), equals(), hashCode():
record Point(int x, int y) {} // xong, không cần viết gì thêm
Point p1 = new Point(3, 4);
Point p2 = new Point(3, 4);
System.out.println(p1.equals(p2)); // true
System.out.println(p1); // Point[x=3, y=4]
8. Code ví dụ¶
Đã kiểm chứng
Bản đầy đủ có thể compile: OopDemo.java
static— field này sống trong Metaspace, dùng chung bởi tất cả instance. Tăng mỗi khi constructor chạy.accountIdđược gán từtotalAccountssau khi tăng — mỗi tài khoản có ID tự động, duy nhất.staticmethod — gọi quaBankAccount.getTotalAccounts(), không cần object. Chỉ truy cập được static member.- Hai
BankAccountbằng nhau nếu cùngaccountId— không phụ thuộc vàobalancehayowner.
9. Lỗi thường gặp¶
Lỗi 1 — Gọi instance method qua tên class¶
Dog.bark(); // ❌ lỗi compile — bark() là instance method, cần object
Dog rex = new Dog();
rex.bark(); // ✅
Lỗi 2 — Quên new, dùng null reference¶
Product p;
p.price = 100; // ❌ NullPointerException — p chưa trỏ vào object nào
Product p = new Product(); // ✅ tạo object trước
p.price = 100;
Lỗi 3 — Override equals() nhưng quên hashCode()¶
class User {
String email;
@Override
public boolean equals(Object o) {
if (!(o instanceof User u)) return false;
return email.equals(u.email);
}
// ❌ quên hashCode()
}
Set<User> users = new HashSet<>();
users.add(new User("a@example.com"));
users.contains(new User("a@example.com")); // false — bug! HashSet dùng hashCode để định vị bucket
Lỗi 4 — Gọi static member qua object reference¶
Counter c = new Counter();
int n = c.totalCreated; // ❌ hoạt động nhưng misleading
int n = Counter.totalCreated; // ✅ rõ ràng đây là class-level data
Lỗi 5 — Nhầm nhiều reference là nhiều object¶
Product a = new Product();
Product b = a; // b không phải bản sao — cùng object với a
b.price = 999;
System.out.println(a.price); // 999 — a và b cùng object
10. Câu hỏi phỏng vấn¶
Q1: Class và Object khác nhau như thế nào?
Class là bản thiết kế định nghĩa cấu trúc và hành vi — tồn tại ở compile time, metadata lưu trong Metaspace. Object là thực thể được tạo ra từ class lúc runtime — sống trên Heap. Một class có thể tạo ra vô số object, mỗi object có bản sao riêng của instance fields nhưng dùng chung bytecode của các method.
Q2: == và equals() khác nhau thế nào?
==so sánh địa chỉ (reference equality) —truechỉ khi hai biến trỏ vào cùng một object trên Heap.equals()so sánh nội dung (value equality) — hành vi do class định nghĩa khi override. Defaultequals()từObjectcũng so sánh địa chỉ, nên phải override nếu muốn so sánh theo dữ liệu.
Q3: Tại sao phải override hashCode() khi đã override equals()?
Contract Java: hai object bằng nhau theo
equals()phải có cùnghashCode().HashMapvàHashSetdùnghashCode()để xác định bucket trước, sau đó mới dùngequals()để phân biệt trong bucket. Vi phạm contract — overrideequals()mà không overridehashCode()— khiến hai object bằng nhau có thể rơi vào bucket khác nhau, làmHashSet.contains()vàHashMap.get()trả về kết quả sai dù không có exception.
Q4: this dùng để làm gì?
thislà reference trỏ vào object đang thực thi method. Dùng để: (1) phân biệt field với tham số cùng tên, (2) gọi constructor khác trong cùng class bằngthis(...), (3) truyền object hiện tại làm argument cho method khác. Không thể dùngthistrong static method vì static method không gắn với bất kỳ object nào.
Q5: Static field và instance field lưu ở đâu trong JVM memory?
Instance field lưu trên Heap — mỗi object có bản sao riêng, tồn tại cho đến khi object bị GC thu hồi. Static field lưu trong Metaspace (Method Area) — dùng chung cho tất cả instance của class, tồn tại suốt vòng đời của class trong JVM.
11. Tài liệu tham khảo¶
| Tài liệu | Nội dung |
|---|---|
| JLS §8 — Class Declarations | Đặc tả chính thức về class |
| Oracle Tutorial — Classes | Hướng dẫn chính thức |
| JEP 395 — Records | Record — tự động toString/equals/hashCode |
| Effective Java — Joshua Bloch | Item 10: equals(), Item 11: hashCode(), Item 12: toString() |
| Head First Java — Sierra & Bates | Chapter 2: A Trip to Objectville |