Backend

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

SSite Admin
7 tháng 08, 2026 5 phút đọc 54 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 Java — Ngày 58: Optional

Optional là công cụ cho kiểu trả về — không field, không tham số, không bọc collection; chuỗi map/flatMap/filter thay kim tự tháp if; orElse luôn tính tham số còn orElseGet lười, orElseThrow cho “không có là lỗi” — và các anti-pattern isPresent + get, orElse(null).

23 thg 9, 20269 phút9
99 Ngày Spring — Ngày 58: Spring Boot Actuator

Boot 3 mặc định chỉ lộ health; bản đồ endpoint an toàn và nguy hiểm; HealthIndicator tự viết với liveness tách khỏi readiness cho Kubernetes, info từ build và git, metrics Micrometer với tag hữu hạn — và ba lớp khóa: cổng riêng, SecurityFilterChain với EndpointRequest, exclude env/heapdump/threaddump.

23 thg 9, 202610 phút5
So sánh EasyExcel và Apache Fesod: khác biệt thật nằm ở đâu?

Fesod là hậu duệ của EasyExcel, cùng engine SAX/SXSSF. Khác biệt kiểm chứng được: vòng đời dự án, POI 5.2.5 vs 5.5.1, các API mới (numRows, csv(), headerMergeStrategy) và độ bền với file xấu — không phải hiệu năng gấp nhiều lần.

23 thg 9, 20268 phút1