
Chào các bạn Gen Z "Code-Warriors"! Anh Creyt đây, hôm nay chúng ta sẽ cùng khám phá một khái niệm mà nhiều bạn hay lơ là nhưng lại cực kỳ quan trọng trong thế giới OOP: protected.
Tưởng tượng thế này: Bạn có một căn nhà (class cha), trong đó có những bí mật gia truyền (thuộc tính/phương thức protected). Những bí mật này không phải là chuyện riêng tư "tuyệt mật" của bạn (như private), nhưng cũng không phải là thứ bạn muốn "rao bán" cho cả thế giới biết (như public). Nó là của riêng gia đình bạn, để con cháu (subclasses) có thể kế thừa và phát huy.
Và đặc biệt hơn, những người hàng xóm thân thiết ở cùng khu phố (các class trong cùng package) cũng có thể "biết chuyện" một chút. Còn những người lạ hoắc, ở tận đẩu tận đâu (các class khác package, không phải con cháu) thì... miễn bàn!
Đó chính là protected – nó đứng giữa private (chỉ mình tôi) và public (ai cũng biết), tạo ra một "vùng đệm" cho những thứ bạn muốn chia sẻ với "gia đình" và "hàng xóm thân cận".
Protected là gì và để làm gì?
Trong Java, từ khóa protected là một trong bốn "access modifier" (bộ điều chỉnh truy cập) giúp bạn kiểm soát ai có thể truy cập vào các thành phần (thuộc tính, phương thức, constructor) của một class.
Cụ thể, khi bạn đánh dấu một thành phần là protected, nó có thể được truy cập bởi:
- Các class con (subclasses), bất kể chúng ở package nào. Đây là điểm mạnh nhất của
protected– nó sinh ra để phục vụ tính kế thừa! - Các class khác trong cùng package. Đúng vậy, đây là điểm mà nhiều bạn hay quên. Nếu một class nằm cùng package với class chứa thành phần
protected, nó có thể truy cập thành phần đó, ngay cả khi nó không phải là class con.
Nó dùng để làm gì ư? Đơn giản là để bạn xây dựng các thư viện, framework mà ở đó bạn muốn cung cấp một số "điểm mở rộng" cho các developer khác (thông qua kế thừa) mà không làm lộ toẹt hết các chi tiết triển khai nội bộ. Nó giúp duy trì sự đóng gói (encapsulation) ở một mức độ vừa phải, linh hoạt hơn private nhưng an toàn hơn public.
Code Ví Dụ Minh Họa Rõ Ràng
Để bạn dễ hình dung, anh Creyt đã chuẩn bị một ví dụ "chuẩn không cần chỉnh" với các package khác nhau để thấy rõ sự khác biệt:
1. Class cha: Vehicle (trong package com.creyt.vehicles)
// Package: com.creyt.vehicles
package com.creyt.vehicles;
public class Vehicle {
protected String brand; // Thuộc tính protected
public Vehicle(String brand) {
this.brand = brand;
}
protected void startEngine() { // Phương thức protected
System.out.println(brand + " engine started. Vroom vroom!");
}
public void drive() {
startEngine(); // Class cha tự gọi phương thức protected của mình
System.out.println(brand + " is driving.");
}
}
2. Class con: Car (cùng package, kế thừa Vehicle)
// Package: com.creyt.vehicles (cùng package với Vehicle)
package com.creyt.vehicles;
public class Car extends Vehicle {
public Car(String brand) {
super(brand);
}
public void honk() {
System.out.println(brand + " says: Beep beep!");
this.startEngine(); // Class con truy cập phương thức protected của cha
System.out.println("Car is ready to go!");
}
}
3. Class con: Motorcycle (khác package, kế thừa Vehicle)
// Package: com.creyt.bikes (package khác)
package com.creyt.bikes;
import com.creyt.vehicles.Vehicle; // Import class cha
public class Motorcycle extends Vehicle {
public Motorcycle(String brand) {
super(brand);
}
public void wheelie() {
System.out.println(brand + " is doing a wheelie!");
this.startEngine(); // Class con (khác package) truy cập được phương thức protected của cha
System.out.println("Motorcycle is having fun!");
}
}
4. Class khác: Garage (cùng package với Vehicle, không phải class con)
// Package: com.creyt.vehicles (cùng package với Vehicle, không phải class con)
package com.creyt.vehicles;
public class Garage {
public void serviceVehicle(Vehicle v) {
System.out.println("Servicing " + v.brand + " in the garage.");
v.startEngine(); // Cùng package => truy cập được!
System.out.println("Vehicle serviced!");
}
}
5. Class khác: MechanicShop (khác package, không phải class con)
// Package: com.creyt.services (package khác, không phải class con)
package com.creyt.services;
import com.creyt.vehicles.Vehicle; // Import class Vehicle
public class MechanicShop {
public void diagnoseVehicle(Vehicle v) {
System.out.println("Diagnosing vehicle in mechanic shop.");
// LỖI BIÊN DỊCH: v.startEngine();
// Không thể truy cập startEngine() vì:
// 1. MechanicShop không phải là class con của Vehicle.
// 2. MechanicShop không nằm trong cùng package với Vehicle.
System.out.println("Diagnosis complete!");
}
}
6. Class MainApp để chạy thử tất cả các trường hợp trên:
// Package: com.creyt.app (Main method để chạy thử)
package com.creyt.app;
import com.creyt.vehicles.Car;
import com.creyt.vehicles.Vehicle;
import com.creyt.vehicles.Garage;
import com.creyt.bikes.Motorcycle;
import com.creyt.services.MechanicShop;
public class MainApp {
public static void main(String[] args) {
System.out.println("--- Testing protected access ---");
Car myCar = new Car("Honda Civic");
myCar.honk(); // Car (subclass, same package) can access startEngine()
System.out.println("Car brand: " + myCar.brand); // Car can access protected field
Motorcycle myBike = new Motorcycle("Yamaha R1");
myBike.wheelie(); // Motorcycle (subclass, different package) can access startEngine()
// System.out.println("Bike brand: " + myBike.brand); // LỖI BIÊN DỊCH: Không truy cập được brand trực tiếp từ MainApp
// Vì MainApp không phải subclass của Vehicle, cũng không cùng package.
// Tuy nhiên, myBike (Motorcycle) có thể truy cập brand của chính nó thông qua this.brand.
Garage myGarage = new Garage();
myGarage.serviceVehicle(myCar); // Garage (same package, not subclass) can access startEngine()
MechanicShop myShop = new MechanicShop();
// myShop.diagnoseVehicle(myBike); // Dòng này sẽ gây lỗi biên dịch nếu bỏ comment ở class MechanicShop
// Vì MechanicShop không phải subclass, khác package.
// Một ví dụ khác để làm rõ hơn:
class MyCustomCar extends Car { // Inner class, subclass của Car (cũng là subclass của Vehicle)
public MyCustomCar(String brand) {
super(brand);
}
public void customStart() {
this.startEngine(); // Truy cập protected từ subclass (MyCustomCar)
System.out.println("Custom car started with extra flair!");
}
}
MyCustomCar customCar = new MyCustomCar("Tesla Model S");
customCar.customStart();
// Thử truy cập từ một class không liên quan, khác package
// Vehicle genericVehicle = new Vehicle("Generic");
// genericVehicle.startEngine(); // LỖI BIÊN DỊCH: startEngine() is protected.
// MainApp không phải subclass, không cùng package.
}
}
Giải thích nhanh:
CarvàMotorcyclelà con củaVehicle, nên dù ở cùng package hay khác package, chúng đều có thể gọistartEngine()và truy cậpbrandcủa cha.Garagenằm cùng package vớiVehicle, nên nó cũng có thể gọistartEngine()và truy cậpbrandcủaVehiclethông qua đối tượngVehicle.MechanicShopnằm khác package và không phải con củaVehicle, nên nó hoàn toàn không thể gọistartEngine()hay truy cậpbrandcủaVehicle.

Mẹo (Best Practices) để ghi nhớ và dùng thực tế
- "Bí mật gia tộc, không phải bí mật quốc gia!": Hãy nhớ
protectedkhông phải làprivate. Nó cho phép con cái và hàng xóm "biết chuyện". Đừng dùng nó cho những dữ liệu nhạy cảm mà bạn không muốn bất kỳ ai ngoài class đó biết. - Dùng khi nào? Khi bạn thiết kế một class mà bạn mong đợi nó sẽ được kế thừa, và bạn muốn cung cấp một số phương thức/thuộc tính nội bộ để các class con có thể tùy biến hoặc sử dụng, nhưng không muốn lộ ra cho toàn bộ thế giới bên ngoài.
- Kế thừa là chìa khóa: Mục đích chính của
protectedlà để hỗ trợ tính kế thừa. Nếu bạn không có ý định cho class của mình được kế thừa, hoặc không có nhu cầu chia sẻ nội bộ với con cháu, thìprivatehoặcdefault(package-private) có thể là lựa chọn tốt hơn. - Mẹo nhớ "level" quyền truy cập:
private: Chỉ mình tôi (within the class).default(không ghi gì): Tôi và hàng xóm (within the package).protected: Tôi, hàng xóm và con cái (within the package OR by subclasses).public: Ai cũng biết, ai cũng xài (everywhere).
Ví dụ thực tế các ứng dụng/website đã ứng dụng
protected được sử dụng rất nhiều trong các framework và thư viện lớn để tạo ra các điểm mở rộng (extension points) cho người dùng mà vẫn giữ được sự đóng gói:
- Framework Android: Bạn thường thấy các phương thức lifecycle của
ActivitynhưonCreate(),onStart(),onResume()... được đánh dấu làprotected. Điều này cho phép bạn (khi extendActivity) ghi đè (override) chúng để thêm logic của riêng bạn (ví dụ: khởi tạo UI trongonCreate), nhưng không cho phép bất kỳ class nào khác gọi trực tiếp chúng từ bên ngoài (vì chúng không phảipublic). Đây là một ví dụ kinh điển về việc sử dụngprotectedđể hỗ trợ kế thừa. - Các thư viện tiện ích lớn: Khi bạn xây dựng một thư viện mà bạn muốn người khác có thể mở rộng, bạn có thể dùng
protectedcho các phương thức "hook" (điểm móc nối) mà các developer có thể override để thay đổi hành vi của thư viện mà không cần phải hiểu sâu hết mọi thứ bên trong. Điều này giúp thư viện vừa mạnh mẽ vừa dễ mở rộng. - Mẫu thiết kế (Design Patterns): Trong nhiều Design Patterns như Template Method,
protectedthường được sử dụng để định nghĩa các bước của một thuật toán mà các lớp con có thể triển khai hoặc ghi đè, trong khi giữ nguyên cấu trúc tổng thể của thuật toán.
Thử nghiệm đã từng và hướng dẫn nên dùng cho case nào
Với kinh nghiệm của anh Creyt, protected là một công cụ mạnh mẽ nhưng cần dùng đúng chỗ. Nó giống như việc bạn trao chìa khóa phụ cho con cái và người thân tín, không phải ai bạn cũng đưa.
Nên dùng protected khi:
- Bạn muốn tạo một "API nội bộ" cho các class con của mình. Tức là, bạn muốn các class con có thể truy cập và tùy chỉnh một phần hành vi của class cha, nhưng không muốn expose (phơi bày) phần đó ra công chúng.
- Bạn đang thiết kế một hệ thống phân cấp class (class hierarchy) và muốn kiểm soát chặt chẽ hơn việc truy cập giữa cha và con. Nó giúp bạn tạo ra một giao diện nhất quán cho các class con mà không làm mất đi tính đóng gói.
- Bạn muốn cung cấp các phương thức "hook" cho các class con để chúng có thể ghi đè và thay đổi logic mà không cần phải sửa đổi code của class cha.
Tránh dùng protected khi:
- Nếu bạn chỉ muốn class đó tự dùng (chẳng hạn, biến trạng thái nội bộ, phương thức helper chỉ dùng trong class đó) -> dùng
private. - Nếu bạn muốn mọi class đều có thể truy cập, không có giới hạn -> dùng
public. - Nếu bạn chỉ muốn các class trong cùng package truy cập và không có ý định kế thừa từ bên ngoài package -> cân nhắc
default(package-private). Nhiều khidefaultlà đủ và an toàn hơnprotected.
Thử nghiệm thực tế để "cảm" được nó:
Anh Creyt khuyên các bạn hãy tự tạo một hệ thống class đơn giản trên IDE của mình:
- Tạo một package
com.mycompany.animals. - Trong đó, tạo class
Animalvới một phương thứcprotected void makeSound()và một thuộc tínhprotected String species. - Tạo class
DogvàCatkế thừaAnimal(cũng trongcom.mycompany.animals). OverridemakeSound()và truy cậpspeciestừ chúng. - Tạo một class
Zootrong cùng package (com.mycompany.animals) và thử truy cậpmakeSound()vàspeciescủaAnimalthông qua một đối tượngAnimalhoặcDog. - Tạo một package mới:
com.mycompany.vet. - Trong đó, tạo class
VetClinic. Thử tạo một đối tượngAnimal(hoặcDog,Cat) và xem điều gì xảy ra khi bạn cố gắng truy cập các thành phầnprotectedđó.
Bạn sẽ thấy rõ ràng ranh giới truy cập của protected ngay lập tức! Việc tự tay code và thử nghiệm sẽ giúp bạn khắc sâu kiến thức hơn bất kỳ bài giảng nào. Chúc các bạn code "ngon"!
Thuộc Series: Java – OOP
Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!