Chuyển tới nội dung chính

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.

com/mycompany/project/Main.java
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ý doGiải thích
Tổ chức codeNhóm các class liên quan: com.shop.model, com.shop.service, com.shop.controller
Tránh xung đột tênHai class cùng tên Date có thể tồn tại: java.util.Datejava.sql.Date
Kiểm soát truy cậpAccess 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):

Animal.java
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;
Package name phải khớp thư mục

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ứcPackage prefix
Googlecom.google.*
Apacheorg.apache.*
Spring Frameworkorg.springframework.*
Dự án cá nhâncom.yourname.project.*
// Ví dụ thực tế
package com.techshop.model; // Domain: techshop.com
package org.university.library; // Domain: university.org
Quy tắc đặt tên
  • 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<>();
Wildcard import và best practice

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.*Date bị 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(...)
Khi nào dùng Static Import

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

PackageNội dungVí dụ
java.langCác class cốt lõi (tự động import)String, System, Math, Object
java.utilCollections, utilitiesList, Map, ArrayList, Optional
java.ioInput/OutputFile, InputStream, BufferedReader
java.nioNew I/OPath, Files, Channel
java.timeDate/Time API (Java 8+)LocalDate, LocalTime, Duration
java.util.streamStream APIStream, Collectors
java.util.concurrentConcurrencyExecutorService, Future, Lock

Access Modifiers và Package

Package ảnh hưởng trực tiếp đến access control trong Java:

ModifierCùng classCùng packageSubclass khác pkgMọ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:

com/shop/model/Product.java
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);
}
}
com/shop/model/Order.java
package com.shop.model;

public class Order {
// ✅ OK - cùng package com.shop.model
Product product = new Product();
}
com/shop/service/OrderService.java
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
}
Khi nào dùng default access?
  • 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
Hiểu lầm phổ biến

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.shopcom.shop.modelhai 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
vn/olhub/shop/service/ProductService.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

Nâng cao — Java Platform Module System (JPMS)

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

AspectClasspath (Pre-Java 9)Modulepath (Java 9+)
OrganizationJAR files, foldersModules (modular JARs)
DependenciesImplicit (tất cả visible)Explicit (khai báo trong module-info)
EncapsulationYếu (mọi public class visible)Mạnh (chỉ exported packages visible)
Conflict resolutionKhô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

  1. Strong encapsulation: Chỉ export những gì cần thiết
  2. Reliable configuration: Dependencies rõ ràng, kiểm tra lúc startup
  3. Smaller runtime: jlink tạo custom JRE chỉ với modules cần thiết
  4. Better performance: JVM tối ưu dựa trên module graph
💡 Cách nhớ: Modules như "giao diện công khai"
  • 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
Modules và packages khác nhau
  • 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

📖 Theo JLS §6.6.1 (Access Control)

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
}
🔥 Bẫy OCP: protected vs default

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
🔥 Bẫy OCP: Static import conflicts

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

🔥 Bẫy OCP: import java.util.* 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 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:

com/app/model/User.java
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;
}
}
com/app/model/UserHelper.java
package com.app.model;

public class UserHelper {
public static void printUser(User user) {
System.out.println(user.name + " - " + user.age);
}
}
com/app/service/UserService.java
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. UserHelper cùng package com.app.model với User, nên truy cập được field name (default access).
  • UserService.process()Compile error ở dòng user.name. UserService ở package com.app.service, khác package với User (com.app.model). Field name có default access nên không thể truy cập từ package khác. Field user.age thì OK vì có public access.

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 package model, Order quản lý nó
  • PriceCalculator → default: Logic tính giá là internal, chỉ OrderService gọi
  • SmtpEmailService → default: Implementation cụ thể, bên ngoài chỉ cần biết EmailService interface

Tóm tắt

Khái niệmĐiểm chính
PackageTổ chức code, tránh xung đột tên, kiểm soát access
Package statementDòng đầu tiên trong file, phải khớp cấu trúc thư mục
ImportCho phép dùng class từ package khác mà không viết tên đầy đủ
Static importImport static members, dùng khi gọi nhiều lần
java.langTự động import, chứa String, System, Math...
Default accessChỉ cùng package, KHÔNG xuyên sub-package
Naming conventionReverse domain name, toàn lowercase

Đọc thêm