Java Foundations/Class và Object — bản thiết kế vs sản phẩm đúc ra
33/39
Bài 33 / 39~18 phútOOP cơ bản — class, object, encapsulationMiễn phí lượt xem

Class và Object — bản thiết kế vs sản phẩm đúc ra

Phân biệt class (bản thiết kế) và object (instance), từ khóa new, object sống trên heap và reference trên stack, `==` vs `.equals()` lần nữa nhìn từ góc OOP.

Module 4 khép lại với một class NumberUtils chỉ có method tĩnh — class ở đó chỉ là "cái sọt chứa hàm". Từ bài này, class thay vai trò 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 — bạn tạo ra các object, mỗi object có field riêng, sống trên heap, được reference trỏ đến.

Bài này giải thích class là gì, object là gì (và hai thứ đó không giống nhau), cơ chế JVM cấp phát object trên heap, và một lần nữa nhìn == vs .equals() — nhưng lần này bạn hiểu sâu vì sao chúng khác nhau.

1. Analogy — khuôn bánh và chiếc bánh

Bạn có khuôn bánh trung thu bằng gỗ — đó là class. Khuôn mô tả hình dạng, kích thước, hoa văn. Bạn ép bột vào khuôn, cho ra 1 chiếc bánh — đó là object. Khuôn chỉ có 1; bánh có thể đúc hàng ngàn chiếc, mỗi chiếc nhân khác nhau (đậu xanh, thập cẩm), thậm chí ăn dở dang khác nhau.

Đời thườngJava
Khuôn bánhclass Cake
Một chiếc bánh đã đúcMột Cake object
Nhân bánh (đậu xanh vs thập cẩm)Field value của object
Hành động "cắn một miếng"Method
💡 Cách nhớ

Class = template, object = instance đúc từ template. 1 class → N objects. Mỗi object có bản sao field riêng và có thể gọi method chung.

2. Khai báo class và tạo object

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

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
    }
}

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

3. Giá trị mặc định của field

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, không compiler báo error:

int x;
System.out.println(x);   // COMPILE ERROR — variable x might not have been initialized

public class Foo {
    int x;   // OK — field dong tu dong 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 — spec yêu cầu (phần của "object initialization").

4. Object sống trên heap — hệ quả

4.1 Nhiều reference cùng trỏ 1 object

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à đúng pass-by-value: b = a copy reference (địa chỉ), không copy object. Sửa object qua reference nào cũng thấy qua reference khác.

4.2 Object không còn ai reference → GC thu hồi

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 gọi free() hay delete như C/C++. Garbage Collector (GC) tự phát hiện object không reachable và thu hồi memory. Chi tiết ở module Memory & GC.

4.3 == so địa chỉ, .equals() so nội dung

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() default không so nội dung. Muốn 2 Book cùng nội dung ra true, bạn phải override equals(). Sẽ đào sâu ở bài toString / equals / hashCode.

5. Nhiều class trong 1 file

Java cho phép nhiều class trong 1 file, nhưng chỉ 1 public class, và tên file phải trùng tên public class:

// 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;
}

Rule này do compiler áp đặt — classpath / classloader dựa vào tên file khi tìm class bytecode. Thực tế, mỗi class quan trọng nên có file riêng (convention + dễ tìm).

6. Instance method — method thuộc về object

Module 4 dùng static hết. Giờ bạn viết method không static — gọi qua object:

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 body của instance method, bạn truy cập field trực tiếp bằng tên (title, year) — Java hiểu ngầm "field của object đang gọi method này". Tương đương this.title, this.year — bài sau nói rõ this.

6.1 Instance method ≠ static method

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

Book b = new Book();
b.hello();                   // OK nhung KHONG nen — goi static qua instance gay hieu lam

Gọi static qua instance vẫn compile được nhưng IDE cảnh báo: nên gọi qua class để code rõ.

7. Pitfall tổng hợp

Nhầm 1: Tưởng class là object.

Book.title = "Java";   // COMPILE ERROR — title la instance field, khong goi qua class

new Book() để tạo object trước, rồi gán qua reference: b.title = "Java".

Nhầm 2: new rồi quên gán vào biến.

new Book();   // object duoc tao roi mat luon — khong ai giu reference

✅ Lưu reference vào biến nếu cần dùng sau: Book b = new Book();.

Nhầm 3: So sánh object bằng ==.

if (user1 == user2) { ... }   // so dia chi — gan nhu luon false

✅ Override + dùng .equals(). Bài sau.

Nhầm 4: Tưởng đọc null field cho crash ngay lúc khai báo.

public class User {
    String name;   // default null
    int len = name.length();   // NPE tai runtime
}

✅ Khởi tạo field phụ thuộc nhau trong constructor hoặc lazy.

Nhầm 5: Viết nhiều public class trong 1 file.

// File: App.java
public class App { ... }
public class Book { ... }   // COMPILE ERROR

✅ Chỉ 1 public class trùng tên file. Các class khác bỏ public hoặc tách file.

8. 📚 Deep Dive Oracle

📚 Deep Dive Oracle (optional)

Spec / reference chính thức:

Ghi chú: JLS §15.9 mô tả chi tiết chuỗi bước của new: evaluate arguments → allocate → initialize fields to default → call constructor. Hiểu chuỗi này giúp bạn debug các bug kiểu "field == null trong constructor" (field được set default trước khi constructor chạy).

9. Tóm tắt

  • Class = template. Object = instance đúc từ class. 1 class → N objects, mỗi object field riêng.
  • new ClassName() cấp phát object trên heap, trả reference về biến trên stack.
  • Field không gán lấy giá trị mặc định (0, false, null). Local variable thì bắt buộc gán trước khi đọc.
  • Biến kiểu reference giữ địa chỉ, không giữ object. Gán b = a copy reference → 2 biến trỏ cùng object.
  • == so địa chỉ. .equals() default cũng so địa chỉ — cần override để so nội dung.
  • GC tự thu hồi object không còn reference. Bạn không gọi free().
  • Instance method gọi qua object (obj.method()); static method gọi qua class (Class.method()).
  • 1 file Java chỉ có 1 public class và tên file phải trùng.

10. Tự kiểm tra

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 đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên

Bài tiếp theo

Constructor và `this` — khởi tạo object đúng cách