99 Ngày Java — Ngày 11: Lớp & đối tượng
Chào mừng đến Giai đoạn 2 — lập trình hướng đối tượng (OOP), vùng đất chiếm trọn 10 ngày tới! Suốt giai đoạn 1, dữ liệu (biến) và hành vi (method Ngày 10) sống tách rời: muốn quản lý 30 sinh viên là phải xoay xở với ba bốn mảng chạy song song — ten[i], diem[i]… rời rạc và mong manh. Ý tưởng lớn của OOP: gom dữ liệu và hành vi của cùng một "thứ" vào chung một nhà — nhà đó tên là lớp (class), và mỗi bản thể đúc từ nó là một đối tượng (object).

Class là khuôn, object là bánh
Ẩn dụ kinh điển nhưng chuẩn nhất: class là khuôn bánh — mô tả hình dáng (field nào) và công dụng (method nào); object là chiếc bánh đúc từ khuôn — mỗi chiếc một số phận, khuôn thì chỉ một:
// SinhVien.java — một KHUÔN mô tả "sinh viên là gì, làm được gì"public class SinhVien {
// Field (trường): dữ liệu mỗi đối tượng tự giữ một bản
String ten;
int namSinh;
double diemTB;
// Method: hành vi — dùng chính dữ liệu của đối tượng đó
void gioiThieu() {
System.out.println("Mình là " + ten + ", sinh năm " + namSinh);
}
boolean duocHocBong() {
return diemTB >= 8.5;
}
}Field là biến khai ngoài method, trong class — mỗi đối tượng giữ một bản riêng.
Method của đối tượng (không còn
staticnhư Ngày 10!) đọc thẳng field của chính đối tượng đó —gioiThieukhông cần tham số vẫn biếttencủa ai.Quy ước: tên class là danh từ, PascalCase (
SinhVien,HoaDon); mỗi classpublicmột file.javatrùng tên — Ngày 02 bạn đã gặp luật này.
new — đúc đối tượng trên heap
Có khuôn rồi thì đúc: từ khóa new xin một vùng nhớ trên heap, dựng đối tượng ở đó, và trả về tham chiếu — đúng bộ máy bạn đã thấy với mảng Ngày 08:
public static void main(String[] args) {
SinhVien an = new SinhVien(); // ĐÚC một đối tượng từ khuôn
an.ten = "An"; // đổ dữ liệu vào field
an.namSinh = 2004;
an.diemTB = 9.0;
SinhVien binh = new SinhVien(); // đúc THÊM một cái — độc lập hoàn toàn
binh.ten = "Bình";
binh.diemTB = 7.0;
an.gioiThieu(); // Mình là An, sinh năm 2004
binh.gioiThieu(); // Mình là Bình, sinh năm 0 ← mặc định!
System.out.println(an.duocHocBong()); // true
System.out.println(binh.duocHocBong()); // false — dữ liệu ai nấy giữ
SinhVien x = an; // KHÔNG đúc mới — chỉ thêm tham chiếu
x.diemTB = 5.0;
System.out.println(an.diemTB); // 5.0 — hai biến, MỘT đối tượng (Ngày 08!)
}Mỗi
newlà một đối tượng độc lập — sửaankhông đụngbinh.Gán biến không đúc thêm bánh:
x = anchỉ copy tham chiếu — hai biến nhìn một đối tượng, sửa quaxthìanthấy ngay. Truyền đối tượng vào method cũng vậy (pass-by-value Ngày 10!).binh.namSinhra0vì field có giá trị mặc định (số về 0,booleanvềfalse, tham chiếu vềnull) — khác hẳn biến local Ngày 10. Nhưng "quên đổ dữ liệu" vẫn là bug — constructor ngày mai sẽ bịt lỗ này.
Stack, heap & vòng đời đối tượng
SinhVien an = new SinhVien();
// STACK (Ngày 10) HEAP// ┌──────────────┐ ┌─────────────────────┐// │ an ──────────┼─────────▶│ SinhVien │// └──────────────┘ │ ten = "An" │// │ namSinh = 2004 │// │ diemTB = 9.0 │// └─────────────────────┘// - Biến local 'an' nằm trên stack, chỉ giữ THAM CHIẾU// - Đối tượng thật nằm trên heap — nơi field sống// - Field CÓ giá trị mặc định (0, 0.0, false, null) — khác biến local!// - Không còn ai trỏ tới? Garbage Collector dọn — hẹn Ngày 72Bức tranh bộ nhớ giờ đã đủ hai mảnh: stack giữ biến local và tham chiếu (Ngày 10), heap giữ đối tượng thật. Đối tượng không còn tham chiếu nào trỏ tới sẽ được Garbage Collector âm thầm dọn dẹp — bạn không bao giờ phải free tay như C/C++; nội tình GC hẹn Ngày 72.
this — chính tôi đây
Ba mươi đối tượng dùng chung một bản code method — vậy khi gioiThieu() chạy, nó biết in ten của ai? Nhờ this — tham chiếu ngầm trỏ về đối tượng đang được gọi:
public class SinhVien {
String ten;
void doiTen(String ten) { // tham số TRÙNG TÊN field (shadowing — Ngày 10)
ten = ten; // ✗ gán tham số cho chính nó — field không đổi!
this.ten = ten; // ✓ this.ten = field CỦA ĐỐI TƯỢNG NÀY
}
boolean cungTen(SinhVien khac) {
return this.ten.equals(khac.ten); // this = "chính tôi" (equals — Ngày 09!)
}
}
// an.doiTen("An Khang") → bên trong method, this chính là an// binh.doiTen("Bình An") → giờ this lại là binh// this là "con trỏ tự thân": method chung một bản, dữ liệu ai nấy xàiBình thường
thisẩn mặt — viếttentrong method là Java tự hiểuthis.ten.Nó buộc phải xuất hiện khi tham số che tên field (shadowing Ngày 10) — pattern sẽ gặp dày đặc trong constructor ngày mai.
thiscòn để trao "chính mình" cho method khác:cungTen(khac)sothis.tenvớikhac.ten— và nhớ bài Ngày 09: so chuỗi bằngequals!
Bài tập nhỏ
Viết class
Sach(tên, tác giả, giá) với methodmoTa()— đúc 3 cuốn, in cả kệ.Thêm
giamGia(double phanTram)sửa fieldgia— gọi trên một cuốn, in lại cả 3 để thấy chỉ cuốn đó đổi.Tạo
Sach a = new Sach(); Sach b = a;— sửab.giarồi ina.gia: giải thích bằng hình stack/heap hôm nay.Viết
boolean reHon(Sach khac)dùngthis— so giá hai cuốn sách.
Kết luận
Cánh cửa OOP đã mở: class gom dữ liệu (field) và hành vi (method) về một mối, new đúc từng đối tượng độc lập trên heap, biến chỉ giữ tham chiếu, và this cho method biết "mình đang phục vụ ai". Nhưng quy trình "đúc xong mới đổ dữ liệu từng field" vừa dài dòng vừa dễ quên — Ngày 12 ta học constructor: nghi thức khai sinh bắt buộc, đảm bảo đối tượng nào chào đời cũng đầy đủ hành trang. Hẹn gặp lại!
Site Admin
Engineer and writer. Building things with TypeScript and distributed systems.
Bình luận (0)
Bạn cần đăng nhập bằng Google để bình luận.
Hãy là người bình luận đầu tiên.


