Java Foundations/Constructor và `this` — khởi tạo object đúng cách
34/39
Bài 34 / 39~16 phútOOP cơ bản — class, object, encapsulationMiễn phí lượt xem

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

Constructor là gì, default constructor, overload constructor, this() chaining, this. để phân biệt field với parameter, và vì sao không return được trong constructor.

Bài trước bạn tạo object bằng new Book() rồi gán từng field một dòng. Nhưng bắt người dùng class phải nhớ gán đủ 5 field mỗi lần là thiết kế kém — dễ quên, dễ để object ở trạng thái dở. Constructor là cơ chế Java đảm bảo object luôn khởi tạo đúng, đầy đủ, ngay khi new.

Bài này giải thích cú pháp constructor, default constructor mà Java cấp miễn phí (và khi nào nó biến mất), overload + chaining bằng this(...), và vì sao this.field = field là pattern phổ biến ai cũng gặp.

1. Analogy — đơn đặt hàng máy tính

Khi mua máy tính lắp ráp, bạn điền form đặt hàng: CPU, RAM, ổ cứng, card đồ họa. Cửa hàng không giao máy "rỗng" rồi bảo bạn tự cắm từng linh kiện — họ lắp xong trả máy hoàn chỉnh. Form = tham số constructor; máy hoàn chỉnh = object vừa tạo.

Đời thườngJava
Form đặt hàngParameter constructor
Cửa hàng lắp rápBody constructor
Máy đã hoàn chỉnh trao tayObject vừa new xong
💡 Cách nhớ

Constructor = method đặc biệt chạy một lần khi object được tạo. Mục đích: đảm bảo object ra đời ở trạng thái hợp lệ, sẵn sàng dùng.

2. Cú pháp constructor

public class Book {
    String title;
    String author;
    int year;

    // Constructor — khong return type, ten trung ten class
    public Book(String title, String author, int year) {
        this.title = title;
        this.author = author;
        this.year = year;
    }
}

// Su dung
Book b = new Book("Clean Code", "Robert C. Martin", 2008);
System.out.println(b.title);   // Clean Code

Đặc điểm của constructor:

  • Không có return type (không void, không int, không gì cả).
  • Tên trùng tên class — kể cả chữ hoa/chữ thường.
  • Gọi bằng new ClassName(args...).
  • Có thể có nhiều constructor (overload — bài sau).

2.1 Vì sao không có return type?

Constructor không return giá trị — nó chỉ khởi tạo object mà new sẽ trả về. Nếu bạn viết public void Book(...), Java hiểu đó là method bình thường tên Book, không phải constructor. Compile được nhưng object tạo qua new Book(...) sẽ không chạy method đó, dễ bug khó lần.

public class Book {
    // Khong phai constructor — day la method ten "Book"
    public void Book(String title) {
        this.title = title;
    }
}

Book b = new Book();        // goi default constructor, title van null
b.Book("Java");              // goi method Book — gan duoc nhung phai goi tay

3. Default constructor — Java cho không

Nếu class không khai báo constructor nào, Java tự động thêm một constructor public không tham số:

public class Empty { }

// Ngam gan voi:
public class Empty {
    public Empty() { }
}

Empty e = new Empty();   // OK

Constructor mặc định không làm gì — chỉ để bạn có thể new object. Field vẫn lấy giá trị default (0, false, null).

Nhưng nếu bạn khai báo bất kỳ constructor nào — dù chỉ có 1 phiên bản — Java không thêm default nữa:

public class Book {
    String title;

    public Book(String title) { this.title = title; }
}

Book a = new Book("Java");   // OK
Book b = new Book();          // COMPILE ERROR — khong co constructor khong tham so

Muốn cả 2 form, khai báo cả 2:

public Book() { }
public Book(String title) { this.title = title; }
⚠️ Default constructor biến mất là lỗi phổ biến khi refactor

Bạn viết class có default constructor, framework dùng (vd Jackson JSON deserialize yêu cầu no-arg constructor). Rồi bạn thêm constructor public Book(String title) — Java không generate default nữa — framework fail với "no default constructor". Luôn thêm lại public Book() {} nếu cần.

4. this — reference tới object đang khởi tạo / đang chạy method

Trong constructor và instance method, từ khóa this trỏ tới chính object đang chạy.

public class Book {
    String title;

    public Book(String title) {
        this.title = title;   // this.title = field; title = parameter
    }
}

Đặt this vào đúng vị trí trong trình tự new thì rõ hơn: object đã tồn tại và đã có đủ field trước khi dòng đầu tiên của constructor chạy — this chỉ là cái tên để gọi object đó.

Trình tự new: cấp vùng nhớ trên heap với field ở giá trị mặc định, constructor gán giá trị thật qua this, rồi trả địa chỉ về biến

Parameter title shadow field title. Trong body, viết title ám chỉ parameter. Dùng this.title để ép hiểu "field của object này".

Pattern this.x = x cực kỳ phổ biến trong Java — đặt parameter cùng tên field cho rõ ý, dùng this. phân biệt. Không phải anti-pattern.

4.1 Có thể bỏ this. khi không bị shadow

public class Book {
    String title;

    public Book(String t) {
        title = t;         // OK — khong co shadow, title hieu la field
        this.title = t;    // cung OK nhung du thua
    }
}

Nhưng nhiều team có rule "luôn dùng this. cho field access" để code nhất quán và tránh nhầm với local. Convention tuỳ codebase.

5. Overload constructor

Có nhiều constructor với signature khác nhau:

public class Book {
    String title;
    String author;
    int year;

    public Book(String title) {
        this.title = title;
        this.author = "Unknown";
        this.year = 0;
    }

    public Book(String title, String author) {
        this.title = title;
        this.author = author;
        this.year = 0;
    }

    public Book(String title, String author, int year) {
        this.title = title;
        this.author = author;
        this.year = year;
    }
}

Người gọi chọn constructor phù hợp với lượng thông tin họ có:

new Book("Java");
new Book("Java", "Bloch");
new Book("Java", "Bloch", 2018);

Vấn đề: 3 constructor có code lặp. Constructor 1 và 2 thực chất chỉ là "gọi constructor 3 với default cho phần thiếu". Java cho phép gọi constructor khác bằng this(...).

6. this(...) — constructor chaining

public class Book {
    String title;
    String author;
    int year;

    public Book(String title) {
        this(title, "Unknown", 0);         // goi constructor 3
    }

    public Book(String title, String author) {
        this(title, author, 0);             // goi constructor 3
    }

    public Book(String title, String author, int year) {
        this.title = title;
        this.author = author;
        this.year = year;
    }
}

Rule:

  • this(...) phải là statement đầu tiên trong constructor.
  • Chỉ 1 this(...) mỗi constructor (không chain vòng).
  • Cấm gọi this(...) sau code khác, cấm gọi nhiều lần.

Lý do rule "phải đầu tiên": Java đảm bảo chain constructor chạy theo thứ tự từ cha đến con (khi có kế thừa — module sau). Cho phép code chạy trước this(...) sẽ phá đảm bảo đó.

7. Cấm return <value> trong constructor

public class Book {
    public Book(String title) {
        if (title == null) return;   // OK — return; (void return) hop le
        // return false;              // COMPILE ERROR — constructor khong tra gia tri
        this.title = title;
    }
}

return; (không giá trị) được phép để thoát constructor sớm. return <value>; thì không. Vì constructor không có return type — không có gì để trả.

Nhưng pattern "return sớm để bỏ qua khởi tạo" hay dẫn đến object dở dang — object đã được cấp memory, chạy constructor nửa chừng, rồi về với reference hợp lệ nhưng field chưa gán đủ. Thay vì return sớm, nên ném exception:

public Book(String title) {
    if (title == null) throw new IllegalArgumentException("title must not be null");
    this.title = title;
}

Constructor ném exception → new không trả reference → caller nhận exception, object không bao giờ "tồn tại".

8. Cơ chế bên dưới — constructor compile thành method <init>

Bytecode không có khái niệm "constructor" riêng. Compiler sinh method tên đặc biệt <init> cho mỗi constructor. new = cấp memory + gọi invokespecial <init>.

new Book
dup
ldc "Java"
invokespecial Book.<init>(String)V
  • new — cấp memory, để reference trên operand stack.
  • dup — nhân đôi reference (1 để trả về, 1 để pass vào <init>).
  • invokespecial — gọi <init> với argument "Java". Return type V = void.

Hiểu điều này giải thích tại sao constructor không là instance method đầy đủ (không override được, không polymorphic) — invokespecial dispatch tĩnh, khác invokevirtual.

9. Pitfall tổng hợp

Nhầm 1: Thêm void vào trước tên class.

public void Book(String title) { this.title = title; }   // day la method, khong phai constructor

✅ Constructor không có return type. Xóa void.

Nhầm 2: Thêm constructor rồi quên giữ default.

public Book(String title) { ... }
// Framework Jackson goi new Book() -> crash

✅ Thêm public Book() { } lại nếu cần.

Nhầm 3: Gán parameter cho chính nó.

public Book(String title) { title = title; }   // tu gan — field van null

this.title = title;.

Nhầm 4: this(...) không phải statement đầu tiên.

public Book(String t) {
    System.out.println("init");
    this(t, 0);   // COMPILE ERROR
}

✅ Chuyển this(...) lên đầu.

Nhầm 5: Throw trong constructor rồi field ở trạng thái dở.

public Book(String title) {
    this.title = title;
    if (title.length() > 100) throw new IllegalArgumentException(...);
    // neu throw, object khong duoc return -> khong van de voi caller
    // nhung neu co this register vao static list truoc, se leak
}

✅ Validate trước khi gán/register. Constructor ném exception → caller không lấy được reference → an toàn.

10. 📚 Deep Dive Oracle

📚 Deep Dive Oracle (optional)

Spec / reference chính thức:

Ghi chú: JLS §8.8.9 nói default constructor kế thừa access modifier từ classpublic class Foo → default constructor public; class Foo (package-private) → default constructor package-private. Chi tiết quan trọng khi viết library công khai.

11. Tóm tắt

  • Constructor = method đặc biệt khởi tạo object. Cùng tên class, không return type.
  • Gọi bằng new ClassName(args...). JVM cấp memory → gọi constructor → trả reference.
  • Nếu không khai báo constructor nào, Java cho default no-arg. Khai báo constructor khác → default biến mất.
  • this trỏ tới object hiện tại. this.field = field là pattern chuẩn để phân biệt field với parameter cùng tên.
  • Overload constructor được — signature khác nhau.
  • this(...) gọi constructor khác cùng class. Phải là statement đầu tiên.
  • return; void được phép; return <value>; không được.
  • Validate trong constructor bằng throw exception, không return sớm — tránh object dở dang.
  • Bytecode: constructor compile thành <init>, gọi bằng invokespecial.

12. Tự kiểm tra

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Đoạn sau có chạy đúng ý không?
    public class User {
        String name;
        public void User(String name) {
            this.name = name;
        }
    }
    
    User u = new User("Alice");
    System.out.println(u.name);
  2. Q2
    Đoạn sau in gì?
    public class Box {
        int x;
        int y;
        public Box() { this(10, 20); }
        public Box(int x, int y) { this.x = x; this.y = y; }
    }
    
    Box a = new Box();
    Box b = new Box(5, 6);
    System.out.println(a.x + "," + a.y + " / " + b.x + "," + b.y);
  3. Q3
    Đoạn sau compile không?
    public class Foo {
        int x;
        public Foo(int x) {
            System.out.println("init");
            this(0);
        }
        public Foo() { this.x = 0; }
    }
  4. Q4
    Vì sao đoạn sau không giúp caller "chọn bỏ" object?
    public class Order {
        int total;
        public Order(int total) {
            if (total < 0) return;
            this.total = total;
        }
    }
    
    Order o = new Order(-5);
    System.out.println(o.total);
  5. Q5
    Một class Jackson dùng để deserialize JSON cần constructor thế nào? Code sau có crash không?
    public class User {
        public String name;
        public User(String name) { this.name = name; }
    }
    
    // Jackson: objectMapper.readValue("{\"name\":\"A\"}", User.class)

Bài tiếp theo: Field vs local — lifecycle và initialization order

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

Field, local, static — ba loại biến, ba lifecycle