Java Foundations
33/39
Bài 33 / 39~12 phútOOP cơ bản — class, object, encapsulationLộ trình · chặng 03/21Miễn phí… lượt xem

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ườngJava
Khuôn bánhclass Cake
Một chiếc bánh đã đúcMộ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ếngMethod
💡 Cách nhớ

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?

Java
// 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:

Java
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:

  1. 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 (null cho reference, 0 cho int, false cho boolean).
  2. Chạy constructor — method đặc biệt khởi tạo object (bài sau).
  3. Trả về reference — địa chỉ của object trên heap được gán vào biến b1.

Hai vùng nhớ tách bạch: khung Stack chứa hai biến b1 và b2, mỗi biến chỉ giữ một địa chỉ; khung Heap chứa hai object Book riêng biệt với bản field riêng, mỗi biến trỏ sang đúng một object bằng mũi tên nét đứt

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ểuDefault
int, long, short, byte, char0
double, float0.0
booleanfalse
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:

Java
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

Java
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

Java
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

Java
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 đó:

Java
// 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:

Java
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

Java
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.

Java
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.

Java
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ử ==.

Java
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.

Java
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.

Java
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?

📚 Deep Dive Oracle (optional)

Spec / reference chính thức:

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 = a chỉ 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

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Đoạn sau in gì?
    Book a = new Book();
    a.title = "A";
    Book b = a;
    b.title = "B";
    System.out.println(a.title);
  2. 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);
        }
    }
  3. 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));
  4. 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();
  5. Q5
    Vì sao file App.java không thể chứa 2 class đều public?

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.

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

Bài tiếp theo

Constructor và từ khóa this trong Java: khởi tạo object