Encapsulation và Access Modifiers
Sau bài này, bạn sẽ:
- Hiểu Encapsulation và lý do cần bảo vệ dữ liệu trong OOP
- Sử dụng 4 access modifiers (private, default, protected, public) để kiểm soát truy cập
- Áp dụng getters/setters theo JavaBeans convention để truy cập private fields
- Implement data validation trong setters để đảm bảo tính toàn vẹn dữ liệu
- Áp dụng best practices cho encapsulation: private fields, public methods, immutable fields
Bài trước: Constructor và Methods — Đã học về constructors và method overloading. Bài này sẽ tìm hiểu cách bảo vệ dữ liệu và kiểm soát truy cập với encapsulation.
Encapsulation là gì?
Encapsulation (đóng gói) là một trong 4 trụ cột của OOP, có nghĩa là:
- Gói gọn dữ liệu (fields) và methods liên quan vào một class
- Che giấu chi tiết triển khai bên trong
- Kiểm soát truy cập vào dữ liệu qua public methods
Hãy nghĩ về một chiếc máy ATM:
- Bên trong (private): Cơ chế đếm tiền, két sắt, vi xử lý - bạn KHÔNG thể truy cập trực tiếp
- Bên ngoài (public): Màn hình, bàn phím, khe thẻ - giao diện công khai
- Bạn chỉ tương tác qua giao diện, không cần biết cách hoạt động bên trong
Tại sao cần Encapsulation?
1. Bảo vệ dữ liệu (Data Protection)
// ❌ Không có encapsulation - Ai cũng sửa được
class BankAccount {
public double balance; // public - nguy hiểm!
}
BankAccount account = new BankAccount();
account.balance = -1000; // ❌ Số dư âm!? Không hợp lý!
// ✅ Có encapsulation - Kiểm soát được
class BankAccount {
private double balance; // private - bảo vệ
public void deposit(double amount) {
if (amount > 0) { // Validation
balance += amount;
} else {
System.out.println("Invalid amount!");
}
}
public double getBalance() {
return balance;
}
}
BankAccount account = new BankAccount();
account.deposit(-1000); // ✅ Bị từ chối: "Invalid amount!"
2. Che giấu triển khai (Implementation Hiding)
class PasswordManager {
private String password; // Che giấu
public void setPassword(String password) {
// Hash password trước khi lưu (implementation detail)
this.password = hashPassword(password);
}
private String hashPassword(String password) {
// Logic phức tạp - người dùng không cần biết
return "hashed_" + password;
}
public boolean verifyPassword(String inputPassword) {
return hashPassword(inputPassword).equals(password);
}
}
3. Dễ bảo trì và thay đổi
Nếu cần thay đổi cách lưu trữ dữ liệu, chỉ cần sửa bên trong class mà không ảnh hưởng code bên ngoài.
class Employee {
// Trước: lưu full name
private String fullName;
// Sau: tách thành firstName và lastName
private String firstName;
private String lastName;
// Public method không đổi - code bên ngoài không bị ảnh hưởng
public String getFullName() {
return firstName + " " + lastName;
}
}
Access Modifiers (Phạm vi truy cập)
Java có 4 access modifiers để kiểm soát ai có thể truy cập vào fields, methods, constructors:
| Modifier | Trong class | Trong package | Subclass (khác package) | Mọi nơi |
|---|---|---|---|---|
| private | ✅ | ❌ | ❌ | ❌ |
| default (no modifier) | ✅ | ✅ | ❌ | ❌ |
| protected | ✅ | ✅ | ✅ | ❌ |
| public | ✅ | ✅ | ✅ | ✅ |
1. private
Chỉ truy cập được từ bên trong cùng class.
class Person {
private String ssn; // Social Security Number - rất nhạy cảm!
public void setSSN(String ssn) {
// Có thể thêm validation, logging, encryption...
this.ssn = ssn;
}
private void internalMethod() {
System.out.println("Only callable inside Person class");
}
}
// Sử dụng
Person person = new Person();
// person.ssn = "123-45-6789"; // ❌ Compile error: ssn is private
// person.internalMethod(); // ❌ Compile error
person.setSSN("123-45-6789"); // ✅ OK
- Fields: Hầu hết mọi trường hợp (best practice)
- Helper methods: Methods chỉ dùng bên trong class
- Dữ liệu nhạy cảm: Password, SSN, card number...
2. default (package-private)
Truy cập được từ cùng package, KHÔNG có từ khóa.
// File: com/company/model/Product.java
package com.company.model;
class Product { // default access
String name; // default access
double price; // default access
void display() { // default access
System.out.println(name + ": $" + price);
}
}
// File: com/company/model/Store.java (cùng package)
package com.company.model;
class Store {
void test() {
Product product = new Product(); // ✅ OK - cùng package
product.name = "Laptop"; // ✅ OK
}
}
// File: com/company/Main.java (khác package)
package com.company;
import com.company.model.Product;
class Main {
void test() {
// Product product = new Product(); // ❌ Compile error
}
}
Default access còn gọi là package-private - chỉ accessible trong cùng package.
3. protected
Truy cập được từ:
- Cùng package
- Subclass (class con) kể cả khác package
// File: Animal.java
package animals;
public class Animal {
protected String name; // protected
protected void eat() {
System.out.println(name + " is eating");
}
}
// File: Dog.java (cùng package)
package animals;
public class Dog extends Animal {
public void bark() {
System.out.println(name + " is barking"); // ✅ OK - subclass
eat(); // ✅ OK
}
}
// File: Cat.java (khác package nhưng là subclass)
package pets;
import animals.Animal;
public class Cat extends Animal {
public void meow() {
System.out.println(name + " is meowing"); // ✅ OK - subclass
eat(); // ✅ OK
}
}
// File: Main.java (khác package, không phải subclass)
package main;
import animals.Animal;
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
// animal.name = "Leo"; // ❌ Compile error
}
}
protected sẽ rõ hơn khi học Inheritance (kế thừa) ở module tiếp theo.
4. public
Truy cập được từ mọi nơi.
// File: MathUtils.java
package utils;
public class MathUtils {
public static final double PI = 3.14159; // public constant
public static int add(int a, int b) {
return a + b;
}
}
// File: Main.java (bất kỳ package nào)
package com.myapp;
import utils.MathUtils;
public class Main {
public static void main(String[] args) {
System.out.println(MathUtils.PI); // ✅ OK
System.out.println(MathUtils.add(5, 3)); // ✅ OK
}
}
- API methods: Methods cần được gọi từ bên ngoài
- Constants:
public static finalconstants - Entry point:
public static void main() - Utility classes: Math, Collections, Arrays...
Getters và Setters
Getters và Setters là public methods dùng để truy cập và sửa đổi private fields.
Quy ước đặt tên (JavaBeans Convention)
// Getter: get + FieldName (camelCase)
public DataType getFieldName() {
return fieldName;
}
// Setter: set + FieldName (camelCase)
public void setFieldName(DataType fieldName) {
this.fieldName = fieldName;
}
// Boolean getter: is + FieldName
public boolean isActive() {
return active;
}
Ví dụ cơ bản
class Student {
// Private fields
private String name;
private int age;
private double gpa;
private boolean active;
// Getter cho name
public String getName() {
return name;
}
// Setter cho name
public void setName(String name) {
this.name = name;
}
// Getter cho age
public int getAge() {
return age;
}
// Setter cho age
public void setAge(int age) {
this.age = age;
}
// Getter cho gpa
public double getGpa() {
return gpa;
}
// Setter cho gpa
public void setGpa(double gpa) {
this.gpa = gpa;
}
// Boolean getter (is thay vì get)
public boolean isActive() {
return active;
}
// Setter cho active
public void setActive(boolean active) {
this.active = active;
}
}
// Sử dụng
Student student = new Student();
student.setName("Nguyễn Văn An");
student.setAge(20);
student.setGpa(3.5);
student.setActive(true);
System.out.println(student.getName()); // Nguyễn Văn An
System.out.println(student.isActive()); // true
Hầu hết IDEs có shortcut tự động generate getters/setters:
- IntelliJ IDEA:
Alt + Insert(Windows) /Cmd + N(Mac) - Eclipse:
Alt + Shift + S - VS Code: Extension "Java Code Generators"
Data Validation trong Setters
Lợi ích lớn nhất của encapsulation: validate dữ liệu trước khi gán.
Ví dụ: BankAccount với Validation
class BankAccount {
private String accountNumber;
private String ownerName;
private double balance;
private boolean active;
// Constructor
public BankAccount(String accountNumber, String ownerName) {
this.accountNumber = accountNumber;
this.ownerName = ownerName;
this.balance = 0.0;
this.active = true;
}
// Getter
public double getBalance() {
return balance;
}
// Không có setter cho balance - chỉ thay đổi qua deposit/withdraw
// Deposit với validation
public void deposit(double amount) {
if (!active) {
System.out.println("Account is inactive!");
return;
}
if (amount <= 0) {
System.out.println("Amount must be positive!");
return;
}
balance += amount;
System.out.println("Deposited: $" + amount);
}
// Withdraw với validation
public void withdraw(double amount) {
if (!active) {
System.out.println("Account is inactive!");
return;
}
if (amount <= 0) {
System.out.println("Amount must be positive!");
return;
}
if (amount > balance) {
System.out.println("Insufficient funds!");
return;
}
balance -= amount;
System.out.println("Withdrawn: $" + amount);
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
System.out.println("Account " + (active ? "activated" : "deactivated"));
}
public String getAccountNumber() {
return accountNumber;
}
public String getOwnerName() {
return ownerName;
}
public void setOwnerName(String ownerName) {
if (ownerName == null || ownerName.trim().isEmpty()) {
System.out.println("Owner name cannot be empty!");
return;
}
this.ownerName = ownerName;
}
}
Ví dụ: Person với Age Validation
class Person {
private String name;
private int age;
private String email;
// Getter
public int getAge() {
return age;
}
// Setter với validation
public void setAge(int age) {
if (age < 0) {
System.out.println("Age cannot be negative!");
return;
}
if (age > 150) {
System.out.println("Age is unrealistic!");
return;
}
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
// Basic email validation
if (email == null || !email.contains("@")) {
System.out.println("Invalid email format!");
return;
}
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
if (name == null || name.trim().isEmpty()) {
System.out.println("Name cannot be empty!");
return;
}
// Capitalize first letter
this.name = name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase();
}
}
- Luôn validate dữ liệu trong setters
- Không cho setter nếu field không được thay đổi từ bên ngoài (ví dụ: balance, id)
- Read-only fields: Chỉ có getter, không có setter
Ví dụ tổng hợp: Employee với Full Encapsulation
class Employee {
// Private fields - KHÔNG thể truy cập trực tiếp từ bên ngoài
private String employeeId;
private String name;
private String department;
private double salary;
private int age;
private boolean active;
// Constructor
public Employee(String employeeId, String name, String department, double salary, int age) {
this.employeeId = employeeId;
setName(name); // Dùng setter để validate
setDepartment(department);
setSalary(salary);
setAge(age);
this.active = true;
}
// Getters
public String getEmployeeId() {
return employeeId; // Read-only - không có setter
}
public String getName() {
return name;
}
public String getDepartment() {
return department;
}
public double getSalary() {
return salary;
}
public int getAge() {
return age;
}
public boolean isActive() {
return active;
}
// Setters với validation
public void setName(String name) {
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("Name cannot be empty!");
}
this.name = name;
}
public void setDepartment(String department) {
if (department == null || department.trim().isEmpty()) {
throw new IllegalArgumentException("Department cannot be empty!");
}
this.department = department;
}
public void setSalary(double salary) {
if (salary < 0) {
throw new IllegalArgumentException("Salary cannot be negative!");
}
this.salary = salary;
}
public void setAge(int age) {
if (age < 18 || age > 65) {
throw new IllegalArgumentException("Age must be between 18 and 65!");
}
this.age = age;
}
public void setActive(boolean active) {
this.active = active;
}
// Business methods
public void giveRaise(double percentage) {
if (percentage < 0 || percentage > 50) {
throw new IllegalArgumentException("Raise percentage must be between 0 and 50!");
}
double increase = salary * (percentage / 100);
salary += increase;
System.out.println("Salary raised by " + percentage + "% to $" + salary);
}
public void promote(String newDepartment, double salaryIncrease) {
setDepartment(newDepartment);
if (salaryIncrease > 0) {
salary += salaryIncrease;
}
System.out.println(name + " promoted to " + newDepartment);
}
public void displayInfo() {
System.out.println("=== Employee Information ===");
System.out.println("ID: " + employeeId);
System.out.println("Name: " + name);
System.out.println("Department: " + department);
System.out.println("Salary: $" + salary);
System.out.println("Age: " + age);
System.out.println("Status: " + (active ? "Active" : "Inactive"));
}
}
// Sử dụng
public class Main {
public static void main(String[] args) {
Employee emp = new Employee("E001", "Nguyễn Văn An", "IT", 2000, 28);
emp.displayInfo();
// Thay đổi qua setters
emp.setName("Nguyễn Văn Bình");
emp.giveRaise(10); // 10% raise
// emp.employeeId = "E999"; // ❌ Compile error - private
// emp.salary = -1000; // ❌ Compile error - private
// emp.setSalary(-1000); // ✅ Compiles nhưng throws exception
}
}
Best Practices cho Encapsulation
1. Luôn đặt fields là private
// ❌ Bad
public class Product {
public String name;
public double price;
}
// ✅ Good
public class Product {
private String name;
private double price;
// Public getters/setters
}
2. Cung cấp public methods để truy cập
class BankAccount {
private double balance;
// Thay vì setBalance(), dùng business methods
public void deposit(double amount) { /* ... */ }
public void withdraw(double amount) { /* ... */ }
public double getBalance() { return balance; }
}
3. Validate dữ liệu trong setters
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Invalid age: " + age);
}
this.age = age;
}
4. Không phải field nào cũng cần setter
class Order {
private final String orderId; // Immutable - không có setter
private LocalDateTime orderDate; // Không có setter - tự động set
public Order(String orderId) {
this.orderId = orderId;
this.orderDate = LocalDateTime.now();
}
public String getOrderId() { return orderId; }
public LocalDateTime getOrderDate() { return orderDate; }
}
5. Return copies cho mutable objects
class Team {
private List<String> members = new ArrayList<>();
// ❌ Bad - trả về reference gốc
public List<String> getMembers() {
return members; // Caller có thể modify!
}
// ✅ Good - trả về copy
public List<String> getMembers() {
return new ArrayList<>(members);
}
// Collections.unmodifiableList — wrapper approach
public List<String> getMembers() {
return Collections.unmodifiableList(members);
}
// Java 10+: List.copyOf() — simpler alternative
public List<String> getMembersCopy() {
return List.copyOf(members);
}
}
6. Sử dụng final cho immutable fields
class Configuration {
private final String appName; // Không thể thay đổi sau khi khởi tạo
private final int maxConnections;
public Configuration(String appName, int maxConnections) {
this.appName = appName;
this.maxConnections = maxConnections;
}
// Chỉ có getters, không có setters
public String getAppName() { return appName; }
public int getMaxConnections() { return maxConnections; }
}
Immutable Objects (Đối tượng bất biến)
Immutable object là object không thể thay đổi state sau khi được tạo.
Hãy nghĩ về giấy chứng nhận kết hôn:
- Sau khi cấp, không thể sửa đổi thông tin (ngày tháng, tên...)
- Muốn thay đổi phải làm mới hoàn toàn (tạo object mới)
Tại sao Immutable Objects quan trọng?
- Thread-safe: Không cần đồng bộ (synchronized) khi dùng trong multi-threading
- Cacheable: Có thể cache an toàn (String pool)
- Hashable: Dùng làm key trong HashMap
- Predictable: Không thay đổi bất ngờ
- Secure: Không thể bị modify sau khi tạo
5 quy tắc tạo Immutable Class
Joshua Bloch khuyến nghị 5 quy tắc để tạo immutable class:
1. Không cung cấp methods thay đổi state (mutators/setters)
2. Class không thể bị extend (declare final hoặc private constructor)
3. Tất cả fields là final
4. Tất cả fields là private
5. Đảm bảo exclusive access đến mutable components (defensive copying)
Ví dụ: Immutable Person class
// ❌ Mutable class
class MutablePerson {
private String name;
private int age;
public MutablePerson(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) { this.name = name; } // Mutator
public void setAge(int age) { this.age = age; }
public String getName() { return name; }
public int getAge() { return age; }
}
// ✅ Immutable class
public final class ImmutablePerson { // Rule 2: final class
private final String name; // Rule 3, 4: private final
private final int age;
public ImmutablePerson(String name, int age) {
this.name = name;
this.age = age;
}
// Rule 1: Only getters, no setters
public String getName() { return name; }
public int getAge() { return age; }
// Thay vì setters, tạo object mới
public ImmutablePerson withName(String newName) {
return new ImmutablePerson(newName, this.age);
}
public ImmutablePerson withAge(int newAge) {
return new ImmutablePerson(this.name, newAge);
}
}
// Sử dụng
ImmutablePerson person = new ImmutablePerson("An", 20);
// person.setAge(21); // ❌ Compile error - không có setter
// Tạo object mới với giá trị thay đổi
ImmutablePerson olderPerson = person.withAge(21);
System.out.println(person.getAge()); // 20 (không đổi)
System.out.println(olderPerson.getAge()); // 21
Defensive Copying cho Mutable Fields
Nếu class chứa mutable objects (như Date, List, Array), phải copy để bảo vệ.
import java.util.*;
// ❌ Không defensive copy - KHÔNG immutable
class BadImmutableEvent {
private final String name;
private final Date eventDate;
public BadImmutableEvent(String name, Date eventDate) {
this.name = name;
this.eventDate = eventDate; // ❌ Lưu reference trực tiếp
}
public Date getEventDate() {
return eventDate; // ❌ Trả về reference trực tiếp
}
}
// Test
Date date = new Date();
BadImmutableEvent event = new BadImmutableEvent("Conference", date);
date.setTime(0); // ❌ Thay đổi date bên ngoài → thay đổi event!
System.out.println(event.getEventDate()); // Thu Jan 01 07:00:00 ICT 1970
// ✅ Có defensive copy - THẬT SỰ immutable
final class GoodImmutableEvent {
private final String name;
private final Date eventDate;
public GoodImmutableEvent(String name, Date eventDate) {
this.name = name;
// Defensive copy trong constructor
this.eventDate = new Date(eventDate.getTime());
}
public Date getEventDate() {
// Defensive copy trong getter
return new Date(eventDate.getTime());
}
public String getName() {
return name; // String is immutable, no need to copy
}
}
// Test
Date date = new Date();
GoodImmutableEvent event = new GoodImmutableEvent("Conference", date);
date.setTime(0); // ✅ Không ảnh hưởng event
System.out.println(event.getEventDate()); // Giữ nguyên giá trị gốc
Ví dụ: Immutable với Collections
import java.util.*;
final class ImmutableStudent {
private final String name;
private final int age;
private final List<String> courses; // Mutable type
public ImmutableStudent(String name, int age, List<String> courses) {
this.name = name;
this.age = age;
// Defensive copy: Tạo ArrayList mới
this.courses = new ArrayList<>(courses);
}
public String getName() { return name; }
public int getAge() { return age; }
public List<String> getCourses() {
// Collections.unmodifiableList — wrapper approach
return Collections.unmodifiableList(courses);
}
// Hoặc trả về defensive copy
public List<String> getCoursesCopy() {
return new ArrayList<>(courses);
}
// Java 10+: List.copyOf() — simpler alternative
public List<String> getCoursesImmutable() {
return List.copyOf(courses);
}
}
// Test
List<String> courses = new ArrayList<>(Arrays.asList("Math", "Physics"));
ImmutableStudent student = new ImmutableStudent("An", 20, courses);
// ✅ Thay đổi list gốc không ảnh hưởng student
courses.add("Chemistry");
System.out.println(student.getCourses()); // [Math, Physics]
// ✅ Không thể modify qua getter
// student.getCourses().add("Biology"); // UnsupportedOperationException
// Java 10+ alternative
List<String> immutableCopy = List.copyOf(courses);
// immutableCopy.add("C"); // ❌ UnsupportedOperationException
Record Classes (Java 16+)
Java 16 giới thiệu records - cách ngắn gọn để tạo immutable data carriers.
Cú pháp Record
// ❌ Trước Java 16: Phải viết rất nhiều boilerplate code
final class PersonOld {
private final String name;
private final int age;
public PersonOld(String name, int age) {
this.name = name;
this.age = age;
}
public String name() { return name; }
public int age() { return age; }
@Override
public boolean equals(Object obj) { /* ... */ }
@Override
public int hashCode() { /* ... */ }
@Override
public String toString() { /* ... */ }
}
// ✅ Java 16+: Record tự động generate tất cả
public record Person(String name, int age) {
// Tất cả đã có sẵn:
// - private final fields
// - Constructor
// - Getters (name(), age())
// - equals(), hashCode(), toString()
}
Ví dụ Record
record Student(String name, int age, String studentId) {
// Compact constructor - validation
public Student {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Name cannot be empty");
}
}
// Custom method
public boolean isAdult() {
return age >= 18;
}
}
// Sử dụng
Student student = new Student("An", 20, "S001");
System.out.println(student.name()); // An (getter tự động)
System.out.println(student.age()); // 20
System.out.println(student.isAdult()); // true
System.out.println(student); // Student[name=An, age=20, studentId=S001]
// ❌ Immutable - không thể thay đổi
// student.age = 21; // Compile error - no setter
Đặc điểm Records:
- Tự động immutable (final class, final fields)
- Tự động generate constructor, getters,
equals(),hashCode(),toString() - Không có setters
- Không thể extend (records are final)
- Có thể implement interfaces
JavaBeans Convention
JavaBeans là một chuẩn (convention) cho Java classes, đặc biệt dùng trong frameworks (Spring, JSF, JPA...).
Quy tắc JavaBeans
- Public no-arg constructor
- Private fields
- Public getters và setters theo naming convention:
- Getter:
get+ PropertyName (camelCase) - Setter:
set+ PropertyName (camelCase) - Boolean getter:
is+ PropertyName
- Getter:
- Serializable (optional nhưng khuyến nghị)
Ví dụ JavaBean
import java.io.Serializable;
public class ProductBean implements Serializable {
// 1. Private fields
private String productId;
private String name;
private double price;
private boolean available;
// 2. No-arg constructor (bắt buộc)
public ProductBean() {
}
// 3. Getters và setters theo convention
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
// Boolean property: is + PropertyName
public boolean isAvailable() {
return available;
}
public void setAvailable(boolean available) {
this.available = available;
}
}
Tại sao cần JavaBeans convention?
- Reflection: Frameworks dùng reflection để tự động gọi getters/setters
- Serialization: Lưu trữ và truyền tải objects
- Property binding: UI frameworks bind data tự động
- ORM: JPA/Hibernate map objects to database
class Parent {
protected void display() {
System.out.println("Parent display");
}
}
class Child extends Parent {
// ✅ OK - can be more accessible (protected → public)
@Override
public void display() {
System.out.println("Child display");
}
// ❌ COMPILE ERROR - cannot be more restrictive (protected → private)
/*
@Override
private void display() {
System.out.println("Child display");
}
*/
}
Quy tắc OCP: Overriding method không thể có access modifier hạn chế hơn method gốc.
Thứ tự từ hạn chế nhất đến ít hạn chế nhất:
private < default < protected < public
Cho phép:
protected→protected✅protected→public✅public→public✅
Không cho phép:
public→protected❌protected→default❌default→private❌
Bài tập thực hành
Bài 1: Circle Class
Tạo class Circle với encapsulation đầy đủ:
- Private field:
radius - Constructor nhận radius
- Getter/Setter cho radius (validate: radius > 0)
- Methods:
getArea(),getCircumference(),getDiameter()
Bài 2: Book Class với Validation
Tạo class Book:
- Private fields:
title,author,isbn,price,publishYear - Validation:
titlevàauthorkhông được rỗngprice>= 0publishYeartừ 1800 đến năm hiện tạiisbnphải có 13 ký tự
- Tạo getters/setters phù hợp
Bài 3: BankAccount với Transaction History
Mở rộng class BankAccount:
- Private field:
List<String> transactionHistory - Methods
deposit()vàwithdraw()thêm transaction vào history - Getter cho history trả về unmodifiable copy
- Method
printHistory()in lịch sử giao dịch
Bài 4: So sánh với và không có Encapsulation
Viết 2 versions của class Student:
- Không có encapsulation: public fields
- Có encapsulation: private fields + getters/setters
Demo sự khác biệt về data safety và validation.
Tổng kết
Key Takeaways
- Encapsulation: Đóng gói data và che giấu implementation
- Lợi ích:
- Bảo vệ dữ liệu
- Validation
- Dễ bảo trì
- Loose coupling
- 4 Access Modifiers:
private: Chỉ trong class- default: Trong package
protected: Package + subclasspublic: Mọi nơi
- Getters/Setters: Public methods để truy cập private fields
- Best Practices:
- Fields luôn private
- Validate trong setters
- Không phải field nào cũng cần setter
- Return copies cho mutable objects
Bài tiếp theo
Trong bài tiếp theo, chúng ta sẽ tìm hiểu về this và static Keywords - hai từ khóa quan trọng trong Java OOP.
Bài 5: this và static Keywords →
Đọc thêm
"Encapsulation is about hiding the internal state and requiring all interaction to be performed through an object's methods." — Gang of Four, Design Patterns