1. Getter và Setter là gì
Nếu hình dung đối tượng như một két sắt, thì các trường private là nội dung của két, còn getter và setter là chìa khóa đến từng ngăn. Getter cho phép xem bên trong có gì, còn setter — đặt vào đó thứ gì mới một cách cẩn thận (nhưng chỉ khi bạn không bỏ, chẳng hạn, một con nhím vào thay vì tài liệu).
Getter
Getter là phương thức public (public) trả về giá trị của trường private (private). Thông thường tên của nó bắt đầu bằng get + tên trường viết hoa chữ cái đầu.
public class Person {
private String name; // Trường private
// Getter cho trường name
public String getName() {
return name;
}
}
Setter
Setter là phương thức public (public) cho phép thay đổi giá trị của trường private. Tên của nó bắt đầu bằng set + tên trường viết hoa chữ cái đầu.
public class Person {
private String name;
// Setter cho trường name
public void setName(String name) {
this.name = name;
}
}
Dành cho các trường boolean
Đối với các trường kiểu boolean, thông lệ là dùng tiền tố is trong getter:
private boolean active;
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
2. Cú pháp và quy ước đặt tên
Java là ngôn ngữ nghiêm ngặt, nhưng không hề “khó tính”. Có những quy ước lâu đời giúp mã của bạn dễ hiểu với các lập trình viên khác (và với chính bạn sau một tháng).
- Getter: public Type getFieldName()
- Setter: public void setFieldName(Type value)
- Getter cho boolean: public boolean isFieldName()
Tên trường trong phương thức được viết hoa chữ cái đầu: nếu trường là age, thì phương thức sẽ là getAge() và setAge().
Quy ước này tuân theo phong cách JavaBeans, nhờ đó IDE, thư viện và framework có thể tự động tìm các getter và setter của bạn. Ví dụ, nếu bạn dùng Spring hoặc JavaFX, các phương thức này sẽ được gọi một cách “ma thuật” khi cần.
3. Ví dụ mã
Hãy lấy một dự án học tập — một ứng dụng “danh bạ” đơn giản (tương tự sổ điện thoại) — và thêm vào đó các getter và setter đúng chuẩn.
Ví dụ: lớp Contact với các trường private và getter/setter
public class Contact {
private String name;
private String phone;
private int age;
private boolean favorite;
// Getter
public String getName() {
return name;
}
public String getPhone() {
return phone;
}
public int getAge() {
return age;
}
public boolean isFavorite() {
return favorite;
}
// Setter
public void setName(String name) {
this.name = name;
}
public void setPhone(String phone) {
this.phone = phone;
}
public void setAge(int age) {
// Ví dụ kiểm tra hợp lệ: tuổi không được âm
if (age < 0) {
System.out.println("Tuổi không thể là số âm!");
return;
}
this.age = age;
}
public void setFavorite(boolean favorite) {
this.favorite = favorite;
}
}
Sử dụng trong ứng dụng
Contact friend = new Contact();
friend.setName("Ivan Ivanov");
friend.setPhone("+1-999-123-45-67");
friend.setAge(25);
friend.setFavorite(true);
System.out.println("Tên: " + friend.getName());
System.out.println("Điện thoại: " + friend.getPhone());
System.out.println("Tuổi: " + friend.getAge());
System.out.println("Yêu thích: " + (friend.isFavorite() ? "Có" : "Không"));
Ví dụ kiểm tra hợp lệ trong setter
Lưu ý rằng trong setter setAge chúng ta thêm một kiểm tra đơn giản: nếu tuổi âm, chúng ta không thay đổi trường và in cảnh báo. Đây là cách đơn giản để bảo vệ đối tượng khỏi dữ liệu không hợp lệ.
4. Best practices: cách làm đúng
Không phải mọi trường đều cần setter public
Đôi khi một trường chỉ nên cho phép đọc — chẳng hạn, mã định danh duy nhất được thiết lập khi tạo đối tượng và không thay đổi nữa. Khi đó đơn giản là không viết setter:
public class Contact {
private final int id; // final — không thể thay đổi sau khi khởi tạo
public Contact(int id) {
this.id = id;
}
public int getId() {
return id;
}
// Không có setId!
}
Hãy dùng getter/setter để kiểm soát truy cập và kiểm tra hợp lệ
public void setName(String name) {
if (name == null || name.trim().isEmpty()) {
System.out.println("Tên không được rỗng!");
return;
}
this.name = name;
}
Không để lộ trực tiếp các đối tượng nội bộ có thể thay đổi
Nếu bạn có một trường — ví dụ, mảng số điện thoại:
private String[] phones;
Đừng trả về trực tiếp qua getter:
public String[] getPhones() {
return phones; // Không nên!
}
Mã như vậy cho phép mã bên ngoài thay đổi mảng tùy ý — vi phạm nguyên tắc đóng gói!
Đúng hơn: trả về bản sao của mảng:
public String[] getPhones() {
return Arrays.copyOf(phones, phones.length); // Trả về bản sao
}
Hoặc chỉ cần clone:
public String[] getPhones() {
return phones.clone();
}
Giữ getter và setter rõ ràng và đơn giản
- Đừng viết logic nghiệp vụ phức tạp trong getter/setter — nhiệm vụ của chúng đơn giản: kiểm soát truy cập và, khi cần, kiểm tra hợp lệ.
- Nếu trường không nên thay đổi — đừng viết setter.
- Nếu trường không nên truy cập từ bên ngoài — đừng viết getter.
5. Tạo getter/setter tự động trong IDE
Viết accessor thủ công không thú vị chút nào, đặc biệt khi lớp có cả chục trường. May mắn là IDE hiện đại (ví dụ, IntelliJ IDEA, Eclipse, VS Code với plugin) có thể tạo tự động.
Trong IntelliJ IDEA
- Mở lớp, đặt con trỏ bên trong thân lớp.
- Nhấn Alt + Insert (hoặc Code -> Generate...).
- Chọn Getter and Setter.
- Chọn các trường cần thiết và nhấn OK.
Voilà! Getter và setter của bạn sẽ xuất hiện như có phép thuật.
Trong Eclipse
- Mở lớp.
- Nhấp chuột phải — Source — Generate Getters and Setters...
- Chọn các trường và nhấn OK.
Trong VS Code (với Java Extension Pack)
- Mở file lớp.
- Trong Command Palette (Ctrl+Shift+P) gõ Generate getters and setters.
- Làm theo hướng dẫn.
6. Nâng cấp ứng dụng của bạn: đóng gói trong thực tiễn
Trong các bài giảng trước bạn đã xây dựng một ứng dụng lưu trữ danh bạ đơn giản. Giờ chúng ta có thể cải tiến nó bằng cách đặt các trường ở chế độ private và chỉ cung cấp truy cập qua getter/setter.
Trước đây (ví dụ xấu):
public class Contact {
public String name;
public String phone;
public int age;
}
Vấn đề: bất kỳ mã nào cũng có thể làm như sau:
Contact c = new Contact();
c.age = -1000; // Giờ thì chúng ta có ma cà rồng trong danh bạ!
Sau đó (ví dụ tốt):
public class Contact {
private String name;
private String phone;
private int age;
public void setAge(int age) {
if (age < 0) {
System.out.println("Tuổi không thể là số âm!");
return;
}
this.age = age;
}
public int getAge() {
return age;
}
// Các getter/setter còn lại...
}
Bây giờ không thể vô tình (hoặc cố ý) làm hỏng đối tượng từ bên ngoài.
7. Getter/setter cho thuộc tính tính toán và bất biến
Đôi khi giá trị không được lưu trong trường mà được tính toán ngay khi cần:
public class Rectangle {
private int width;
private int height;
public int getArea() {
return width * height;
}
}
Không cần setter cho diện tích — không thể thiết lập trực tiếp, chỉ có thể thay đổi chiều rộng hoặc chiều cao.
8. Getter và setter: lỗi thường gặp
Lỗi số 1: Getter/setter vi phạm đóng gói.
Nếu getter trả về tham chiếu đến đối tượng nội bộ có thể thay đổi (ví dụ, một danh sách), mã bên ngoài có thể sửa nó, bỏ qua mọi kiểm tra. Điều này làm suy yếu ý tưởng đóng gói.
Lỗi số 2: Setter không kiểm tra hợp lệ dữ liệu.
Nếu setter chỉ gán giá trị mà không kiểm tra, bạn có thể nhận được trạng thái không hợp lệ của đối tượng (ví dụ, tuổi âm hoặc tên rỗng).
Lỗi số 3: Tạo setter tự động cho mọi trường.
IDE có thể tạo setter cho tất cả các trường, nhưng không phải lúc nào cũng đúng! Ví dụ, với mã định danh (id) thì không cần setter.
Lỗi số 4: Logic phức tạp trong getter/setter.
Getter và setter nên đơn giản. Nếu xuất hiện logic nghiệp vụ phức tạp, hãy tách nó sang phương thức riêng.
Lỗi số 5: Vi phạm quy ước đặt tên.
Nếu đặt tên getter là fetchName() thay vì getName(), một số framework và thư viện sẽ không nhận diện được nó.
GO TO FULL VERSION