Packages
Package là cơ chế tổ chức code cơ bản nhất trong Java. Mọi file Java trong dự án chuyên nghiệp đều bắt đầu bằng package statement — hiểu packages giúp bạn hiểu cách Java tổ chức code và tại sao import hoạt động như vậy.
Package là gì?
Theo Oracle: "A package is a grouping of related types providing access protection and name space management."
Package trong Java tương tự thư mục (folder) trên máy tính — giúp tổ chức files liên quan vào cùng một nhóm.
package com.mycompany.project; // Dòng đầu tiên trong mọi file Java
import java.util.List; // Import class từ package khác
public class Main {
public static void main(String[] args) {
System.out.println("Hello from com.mycompany.project!");
}
}
Tại sao cần Package?
| Lý do | Giải thích |
|---|---|
| Tổ chức code | Nhóm các class liên quan: com.shop.model, com.shop.service, com.shop.controller |
| Tránh xung đột tên | Hai class cùng tên Date có thể tồn tại: java.util.Date và java.sql.Date |
| Kiểm soát truy cập | Access modifier protected và default (package-private) phụ thuộc vào package |
| Tái sử dụng | Đóng gói code thành thư viện (library/JAR) theo package |
Cấu trúc Package
Package Statement
Dòng package phải là dòng đầu tiên trong file Java (trước mọi import và class declaration):
package com.zoo.animals; // Package declaration
public class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
}
Mối quan hệ Package — Thư mục
Package name phải khớp với cấu trúc thư mục:
src/
└── com/
└── zoo/
├── animals/
│ ├── Animal.java → package com.zoo.animals;
│ ├── Dog.java → package com.zoo.animals;
│ └── Cat.java → package com.zoo.animals;
└── services/
└── FeedService.java → package com.zoo.services;
Nếu file Animal.java nằm trong thư mục com/zoo/animals/ nhưng khai báo package com.zoo; → compile error. Package name và đường dẫn thư mục phải hoàn toàn khớp.
Naming Convention
Oracle khuyến nghị dùng tên miền ngược (reverse domain name) làm prefix:
| Tổ chức | Package prefix |
|---|---|
com.google.* | |
| Apache | org.apache.* |
| Spring Framework | org.springframework.* |
| Dự án cá nhân | com.yourname.project.* |
// Ví dụ thực tế
package com.techshop.model; // Domain: techshop.com
package org.university.library; // Domain: university.org
- Toàn bộ chữ thường (lowercase):
com.myapp.utils✓,com.myApp.Utils✗ - Dùng tên miền ngược:
vn.olhub.java(domain: java.olhub.vn) - Tránh dùng từ khóa Java: không dùng
package com.class.new; - Nếu tên miền bắt đầu bằng số: thêm underscore
com._123shop
Import Statements
Import cơ bản
import cho phép sử dụng class từ package khác mà không cần viết tên đầy đủ:
// Không có import - phải viết tên đầy đủ (Fully Qualified Name)
java.util.List<String> names = new java.util.ArrayList<>();
java.util.Map<String, Integer> scores = new java.util.HashMap<>();
// Với import - gọn hơn nhiều
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
List<String> names = new ArrayList<>();
Map<String, Integer> scores = new HashMap<>();
Wildcard Import
Import tất cả classes trong một package:
import java.util.*; // Import TẤT CẢ classes trong java.util
List<String> list = new ArrayList<>();
Map<String, Integer> map = new HashMap<>();
Set<String> set = new HashSet<>();
import java.util.* tiện nhưng có nhược điểm:
- Khó đọc: Không biết class nào thực sự được dùng
- Xung đột tên:
import java.util.*+import java.sql.*→Datebị ambiguous
Best practice: Import cụ thể từng class. IDE như IntelliJ tự động thêm import cho bạn.
Static Import
Import static members (static methods, static fields) để gọi trực tiếp mà không cần tên class:
// Không có static import
double area = Math.PI * Math.pow(radius, 2);
// Với static import
import static java.lang.Math.PI;
import static java.lang.Math.pow;
double area = PI * pow(radius, 2); // Gọn hơn
// Ví dụ khác: JUnit tests thường dùng static import
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
assertEquals(expected, actual); // Thay vì Assertions.assertEquals(...)
Dùng khi method/constant được gọi rất nhiều lần trong file: Math.PI, Math.sqrt(), assertion methods trong tests. Không nên lạm dụng vì code khó đọc khi không biết method thuộc class nào.
Name Collision — Khi 2 packages có cùng tên class
Khi import 2 classes cùng tên từ packages khác nhau, Java không biết dùng class nào → compile error!
import java.util.Date;
import java.sql.Date; // ❌ Compile error! Ambiguous import
// Giải pháp: Dùng fully qualified name cho một trong hai
import java.util.Date;
Date utilDate = new Date(); // java.util.Date
java.sql.Date sqlDate = new java.sql.Date(System.currentTimeMillis()); // Fully qualified
Package java.lang — Package đặc biệt
java.lang là package duy nhất được tự động import — bạn không cần viết import java.lang.*:
// Các class này dùng được trực tiếp, không cần import
String name = "Java"; // java.lang.String
System.out.println("Hello"); // java.lang.System
Integer num = Integer.valueOf(42); // java.lang.Integer
Object obj = new Object(); // java.lang.Object
Các package quan trọng trong Java
| Package | Nội dung | Ví dụ |
|---|---|---|
java.lang | Các class cốt lõi (tự động import) | String, System, Math, Object |
java.util | Collections, utilities | List, Map, ArrayList, Optional |
java.io | Input/Output | File, InputStream, BufferedReader |
java.nio | New I/O | Path, Files, Channel |
java.time | Date/Time API (Java 8+) | LocalDate, LocalTime, Duration |
java.util.stream | Stream API | Stream, Collectors |
java.util.concurrent | Concurrency | ExecutorService, Future, Lock |
Access Modifiers và Package
Package ảnh hưởng trực tiếp đến access control trong Java:
| Modifier | Cùng class | Cùng package | Subclass khác pkg | Mọi nơi |
|---|---|---|---|---|
| public | ✓ | ✓ | ✓ | ✓ |
| protected | ✓ | ✓ | ✓ | ✗ |
| (default) | ✓ | ✓ | ✗ | ✗ |
| private | ✓ | ✗ | ✗ | ✗ |
Default Access (Package-Private)
Khi không khai báo access modifier, class/method chỉ truy cập được từ cùng package:
package com.shop.model;
class Product { // default access - chỉ thấy trong com.shop.model
String name; // default access field
void display() { // default access method
System.out.println(name);
}
}
package com.shop.model;
public class Order {
// ✅ OK - cùng package com.shop.model
Product product = new Product();
}
package com.shop.service;
import com.shop.model.Product; // ❌ Compile error!
public class OrderService {
// Product có default access, không thể truy cập từ package khác
}
- Internal implementation classes mà chỉ dùng trong package
- Helper classes không cần expose ra bên ngoài
- Giúp tạo package boundary rõ ràng — chỉ expose public API
Sub-packages
Java không có khái niệm "sub-package" về mặt access control:
package com.shop; // Package A
package com.shop.model; // Package B — HOÀN TOÀN KHÁC Package A
Nhiều người nghĩ com.shop.model là "sub-package" của com.shop nên class default access trong com.shop sẽ thấy được từ com.shop.model. SAI!
Với Java, com.shop và com.shop.model là hai package hoàn toàn riêng biệt. Default access chỉ hoạt động trong cùng đúng package, không xuyên sub-package.
Ví dụ thực tế: Tổ chức dự án Java
Cấu trúc package tiêu chuẩn cho web application:
src/main/java/
└── vn/
└── olhub/
└── shop/
├── model/ → Entities, DTOs
│ ├── Product.java
│ ├── User.java
│ └── Order.java
├── repository/ → Data access
│ ├── ProductRepo.java
│ └── UserRepo.java
├── service/ → Business logic
│ ├── ProductService.java
│ └── OrderService.java
├── controller/ → API endpoints
│ └── ProductController.java
└── util/ → Utilities
└── StringUtils.java
package vn.olhub.shop.service;
import vn.olhub.shop.model.Product;
import vn.olhub.shop.repository.ProductRepo;
public class ProductService {
private final ProductRepo repo;
public ProductService(ProductRepo repo) {
this.repo = repo;
}
public Product findById(long id) {
return repo.findById(id);
}
}
Java Module System (Java 9+) — Tổng quan
Phần này giới thiệu module system của Java 9+. Nếu bạn mới bắt đầu, có thể bỏ qua và quay lại sau.
Java 9 giới thiệu Java Platform Module System (JPMS) — cách tổ chức code ở level cao hơn packages.
Module là gì?
Module là một nhóm packages liên quan, với khai báo rõ ràng:
- Exports: Packages nào được expose ra ngoài
- Requires: Modules nào cần thiết
Structure:
my-app/
├── module-info.java ← Module descriptor
└── com/
└── myapp/
├── Main.java
└── util/
└── Helper.java
File module-info.java:
module com.myapp {
// Export packages cho external modules
exports com.myapp;
exports com.myapp.util;
// Require modules khác
requires java.sql;
requires java.logging;
}
Classpath vs Modulepath
| Aspect | Classpath (Pre-Java 9) | Modulepath (Java 9+) |
|---|---|---|
| Organization | JAR files, folders | Modules (modular JARs) |
| Dependencies | Implicit (tất cả visible) | Explicit (khai báo trong module-info) |
| Encapsulation | Yếu (mọi public class visible) | Mạnh (chỉ exported packages visible) |
| Conflict resolution | Không có (first wins) | Module system kiểm tra |
| JVM flag | -classpath / -cp | --module-path / -p |
Example: Classpath
# Tất cả JARs visible cho nhau
java -cp "lib/*:app.jar" com.myapp.Main
Example: Modulepath
# Chỉ exported packages visible
java --module-path mods --module com.myapp/com.myapp.Main
Benefits of Modules
- Strong encapsulation: Chỉ export những gì cần thiết
- Reliable configuration: Dependencies rõ ràng, kiểm tra lúc startup
- Smaller runtime:
jlinktạo custom JRE chỉ với modules cần thiết - Better performance: JVM tối ưu dựa trên module graph
- Package: Tổ chức classes
- Module: Tổ chức packages, định nghĩa "public API" cho external modules
- Giống như một thư viện chỉ expose public methods, ẩn internal implementation
Unnamed Module (Legacy support):
- Code không có
module-info.java→ chạy trong "unnamed module" - Có thể access tất cả packages trên classpath (legacy behavior)
- Modules có thể không access unnamed module
- Package: Namespace cho classes, kiểm soát access (public, protected, default, private)
- Module: Kiểm soát visibility của toàn bộ packages — ngay cả public classes cũng không visible nếu package không exported
Access Modifier và Package Interaction
Access modifiers không chỉ phụ thuộc class hierarchy mà còn phụ thuộc package.
Default Access (Package-Private) — Chi tiết
Khi không có access modifier, class/member có default access (package-private) — chỉ accessible từ cùng package.
Quy tắc:
public: Mọi nơi ✅protected: Cùng package ✅ + subclass (kể cả khác package) ✅- default (no modifier): Chỉ cùng package ✅
private: Chỉ trong cùng class ✅
Example:
// File: com/myapp/model/User.java
package com.myapp.model;
class User { // default access - only visible in com.myapp.model
String name; // default field
}
public class Account {
User user = new User(); // ✅ OK - same package
}
// File: com/myapp/service/UserService.java
package com.myapp.service;
import com.myapp.model.User; // ❌ Compile error! User has default access
public class UserService {
// Cannot use User here - different package
}
protected cho phép access từ subclass khác package, nhưng default thì không!
// File: com/myapp/model/Animal.java
package com.myapp.model;
public class Animal {
protected void makeSound() { // protected
System.out.println("Sound");
}
void eat() { // default access
System.out.println("Eating");
}
}
// File: com/myapp/zoo/Dog.java
package com.myapp.zoo; // Different package!
import com.myapp.model.Animal;
public class Dog extends Animal {
public void test() {
makeSound(); // ✅ OK - protected accessible from subclass
eat(); // ❌ Compile error! - default not accessible
}
}
Static Import Conflicts và Traps
Static Import Wildcards
import static java.lang.Math.*; // Import all static members
double result = sqrt(16); // ✅ No need Math.sqrt()
double pi = PI; // ✅ No need Math.PI
Conflict 1: Ambiguous static members
import static java.lang.Integer.MAX_VALUE;
import static java.lang.Long.MAX_VALUE; // ❌ Compile error! Ambiguous
// Error: reference to MAX_VALUE is ambiguous
System.out.println(MAX_VALUE);
Conflict 2: Static import vs local variable
import static java.lang.Math.PI;
public class Test {
public static void main(String[] args) {
double PI = 3.14; // ✅ Local variable shadows static import
System.out.println(PI); // 3.14 (not Math.PI!)
}
}
Conflict 3: Multiple static imports với same method name
import static java.util.Collections.max;
import static java.lang.Math.max; // ❌ Ambiguous!
// Giải pháp: Chỉ import một, gọi full name cho cái còn lại
import static java.util.Collections.max;
int result = max(list); // Collections.max()
int result2 = Math.max(a, b); // Must use full name
Wildcard Import Không Import Sub-packages
import java.util.*; // Import classes in java.util
List<String> list = new ArrayList<>(); // ✅ OK - ArrayList in java.util
Map<String, Integer> map = new HashMap<>(); // ✅ OK
// ❌ java.util.regex.Pattern NOT imported!
Pattern pattern = Pattern.compile("\\d+"); // Compile error!
// Phải import explicitly:
import java.util.regex.Pattern;
Lý do: Wildcard import chỉ import classes trong package, không import sub-packages.
java.util
├── ArrayList.class ← Imported by java.util.*
├── HashMap.class ← Imported by java.util.*
└── regex/ ← Sub-package NOT imported
└── Pattern.class ← NOT imported by java.util.*
Giải pháp:
import java.util.*; // Import java.util classes
import java.util.regex.*; // Import java.util.regex classes
Lỗi thường gặp
Lỗi 1: Quên package statement
// ❌ Không có package declaration
public class Main { }
// → Class thuộc "default package", không thể import từ package khác
// ✅ Luôn khai báo package
package com.myapp;
public class Main { }
Tại sao: Default package không có tên → không thể viết import cho nó. Chỉ dùng cho demo/test nhỏ.
Lỗi 2: Import package nhầm
// ❌ Dùng java.util.Date khi cần java.sql.Date
import java.util.Date; // Date cho general purpose
// vs
import java.sql.Date; // Date cho database
Tại sao: Hai class cùng tên nhưng khác package, khác mục đích. Đọc kỹ import khi gặp class trùng tên.
Lỗi 3: Nghĩ sub-package kế thừa access
// ❌ Nghĩ com.shop.model "thuộc" com.shop
package com.shop;
class Helper { } // default access
package com.shop.model;
// Helper không thể truy cập ở đây — khác package!
Bài tập
Bài 1: Tổ chức Package [Cơ bản]
Cho các class sau, hãy tổ chức chúng vào package phù hợp:
Student,Teacher,Course(data models)StudentService,CourseService(business logic)StringHelper,DateHelper(utilities)
Xem lời giải
com/
└── school/
├── model/
│ ├── Student.java → package com.school.model;
│ ├── Teacher.java → package com.school.model;
│ └── Course.java → package com.school.model;
├── service/
│ ├── StudentService.java → package com.school.service;
│ └── CourseService.java → package com.school.service;
└── util/
├── StringHelper.java → package com.school.util;
└── DateHelper.java → package com.school.util;
Bài 2: Import và Access [Trung bình]
Cho 3 files sau, dự đoán output hoặc compile error:
package com.app.model;
public class User {
String name; // default access
public int age; // public access
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
package com.app.model;
public class UserHelper {
public static void printUser(User user) {
System.out.println(user.name + " - " + user.age);
}
}
package com.app.service;
import com.app.model.User;
public class UserService {
public void process() {
User user = new User("Java", 29);
System.out.println(user.name); // Dòng này có compile error không?
System.out.println(user.age);
}
}
Xem lời giải
UserHelper.printUser()→ OK.UserHelpercùng packagecom.app.modelvớiUser, nên truy cập được fieldname(default access).UserService.process()→ Compile error ở dònguser.name.UserServiceở packagecom.app.service, khác package vớiUser(com.app.model). Fieldnamecó default access nên không thể truy cập từ package khác. Fielduser.agethì OK vì cópublicaccess.
Fix: Thêm public cho field name, hoặc tốt hơn — thêm getter method:
public String getName() { return name; }
Bài 3: Thiết kế Package cho E-commerce [Thách thức]
Thiết kế cấu trúc package cho một ứng dụng e-commerce đơn giản gồm:
- Quản lý sản phẩm (CRUD)
- Quản lý đơn hàng
- Xác thực người dùng
- Gửi email notification
- Utility functions
Yêu cầu: Vẽ cây thư mục, khai báo package cho mỗi file, và giải thích tại sao chọn access modifier cho mỗi class (public vs default).
Xem lời giải
src/main/java/
└── com/
└── ecommerce/
├── model/
│ ├── Product.java → public (dùng khắp nơi)
│ ├── Order.java → public
│ ├── OrderItem.java → default (chỉ Order dùng)
│ └── User.java → public
├── repository/
│ ├── ProductRepository.java → public (interface)
│ └── OrderRepository.java → public (interface)
├── service/
│ ├── ProductService.java → public
│ ├── OrderService.java → public
│ ├── AuthService.java → public
│ └── PriceCalculator.java → default (chỉ OrderService dùng)
├── notification/
│ ├── EmailService.java → public (interface)
│ └── SmtpEmailService.java → default (implementation detail)
└── util/
├── StringUtils.java → public
└── ValidationUtils.java → public
Giải thích access modifier:
OrderItem→ default: Chi tiết đơn hàng chỉ cần trong packagemodel,Orderquản lý nóPriceCalculator→ default: Logic tính giá là internal, chỉOrderServicegọiSmtpEmailService→ default: Implementation cụ thể, bên ngoài chỉ cần biếtEmailServiceinterface
Tóm tắt
| Khái niệm | Điểm chính |
|---|---|
| Package | Tổ chức code, tránh xung đột tên, kiểm soát access |
| Package statement | Dòng đầu tiên trong file, phải khớp cấu trúc thư mục |
| Import | Cho phép dùng class từ package khác mà không viết tên đầy đủ |
| Static import | Import static members, dùng khi gọi nhiều lần |
| java.lang | Tự động import, chứa String, System, Math... |
| Default access | Chỉ cùng package, KHÔNG xuyên sub-package |
| Naming convention | Reverse domain name, toàn lowercase |