Backend

99 Ngày Java — Ngày 11: Lớp & đối tượng

SSite Admin
7 tháng 08, 2026 5 phút đọc 14 lượt xem
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).

Sketchnote Ngày 11: lớp và đối tượng trong Java — khuôn đúc, field, method, new, heap và this

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 static như Ngày 10!) đọc thẳng field của chính đối tượng đó — gioiThieu không cần tham số vẫn biết ten của ai.

  • Quy ước: tên class là danh từ, PascalCase (SinhVien, HoaDon); mỗi class public một file .java trù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 new là một đối tượng độc lập — sửa an không đụng binh.

  • Gán biến không đúc thêm bánh: x = an chỉ copy tham chiếu — hai biến nhìn một đối tượng, sửa qua x thì an thấy ngay. Truyền đối tượng vào method cũng vậy (pass-by-value Ngày 10!).

  • binh.namSinh ra 0field có giá trị mặc định (số về 0, boolean về 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 72

Bứ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ài
  • Bình thường this ẩn mặt — viết ten trong method là Java tự hiểu this.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.

  • this còn để trao "chính mình" cho method khác: cungTen(khac) so this.ten với khac.ten — và nhớ bài Ngày 09: so chuỗi bằng equals!

Bài tập nhỏ

  • Viết class Sach (tên, tác giả, giá) với method moTa() — đúc 3 cuốn, in cả kệ.

  • Thêm giamGia(double phanTram) sửa field gia — 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ửa b.gia rồi in a.gia: giải thích bằng hình stack/heap hôm nay.

  • Viết boolean reHon(Sach khac) dùng this — 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!

S

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.

Bài viết liên quan

99 Ngày Spring — Ngày 14: Bean Validation — @Valid & bộ constraint chuẩn

Ngày 14 của 99 Ngày Spring: Bean Validation khai báo luật ngay trên DTO — @Valid kích hoạt, bộ constraint chuẩn (@NotBlank vs @NotEmpty vs @NotNull, @Size, @Email, @Min/@Max), bẫy int vs Integer, validate object lồng nhau không tự lan, và lỗi 400 gom một lượt.

10 thg 8, 20265 phút0
99 Ngày Java — Ngày 14: Kế thừa (Inheritance)

Ngày 14 của 99 Ngày Java: extends trao gia tài từ lớp cha, override với @Override và super., thứ tự khai sinh cha trước con sau qua super(...), protected trả nợ Ngày 13, và phép thử is-a vs has-a — khi nào nên composition thay vì kế thừa.

10 thg 8, 20266 phút0
99 Ngày Spring — Ngày 13: @RequestBody & DTO — đừng công khai entity ra API

Ngày 13 của 99 Ngày Spring: @RequestBody + Jackson deserialize JSON thành object, DTO là hợp đồng API — chặn lỗ hổng mass assignment chiều vào và rò rỉ dữ liệu chiều ra, request/response DTO khác nhau là bình thường, và Jackson annotations tinh chỉnh hợp đồng JSON.

9 thg 8, 20266 phút9