package 与 import
引语:项目越做越大,小崔的代码目录堆了几十个
.java文件,类名冲突频频发生——他写了三个叫Utils的类,编译器都不知道该用哪个。前端开发黄俪建议按功能分包,白歌说:"不仅要把类放进包,还要用好import——Java 的包机制就是代码的'邮政编码'。"
package:命名空间与物理组织
什么是 package
package 是 Java 的命名空间机制,用于将相关的类和接口组织在一起,同时避免命名冲突。它同时解决了两个问题:
- 逻辑层面:提供了类名的命名空间,
com.feixiang.hr.Employee和com.feixiang.tech.Employee是两个不同的类 - 物理层面:规定了
.java和.class文件在磁盘上的目录结构
package 声明规则
| 规则 | 说明 | 示例 |
|---|---|---|
| 必须首行 | package 语句必须是 Java 源文件的第一条非注释语句 | package com.feixiang.hr; |
| 只能一条 | 每个源文件只能有一条 package 语句 | —— |
| 对应目录 | 包名必须与目录结构一致 | com.feixiang.hr → com/feixiang/hr/ |
| 命名规范 | 全部小写,用 . 分隔层级 | com.feixiang.hr.util |
| 域名倒置 | 建议使用公司域名的倒置作为根包名 | com.feixiang 来自 feixiang.com |
飞翔科技包结构
飞翔科技项目源码目录结构:
src/
└── com/
└── feixiang/
├── hr/ ← 人力资源模块
│ ├── Employee.java
│ ├── HRService.java
│ ├── InternalAuditor.java
│ └── util/
│ └── SalaryHelper.java
├── tech/ ← 技术部模块
│ ├── TechManager.java
│ └── Department.java
├── finance/ ← 财务模块
│ └── FinanceReport.java
└── common/ ← 公共模块
└── CompanyConstant.java
对应的 package 声明:
// com/feixiang/hr/Employee.java
package com.feixiang.hr;
// com/feixiang/hr/util/SalaryHelper.java
package com.feixiang.hr.util;
// com/feixiang/common/CompanyConstant.java
package com.feixiang.common;
import:引入外部类
为什么需要 import
如果不使用 import,每次使用其他包的类都必须写全限定类名(Fully Qualified Name):
// ❌ 没有 import,代码非常冗长
public class App {
public static void main(String[] args) {
com.feixiang.hr.Employee emp = new com.feixiang.hr.Employee();
com.feixiang.hr.HRService service = new com.feixiang.hr.HRService();
java.util.List<com.feixiang.hr.Employee> list = new java.util.ArrayList<>();
}
}
使用 import 后:
// ✅ 有 import,代码简洁清晰
import com.feixiang.hr.Employee;
import com.feixiang.hr.HRService;
import java.util.List;
import java.util.ArrayList;
public class App {
public static void main(String[] args) {
Employee emp = new Employee();
HRService service = new HRService();
List<Employee> list = new ArrayList<>();
}
}
import 的三种形式
| 形式 | 语法 | 说明 | 示例 |
|---|---|---|---|
| 单类导入 | import 包名.类名; | 明确导入一个类 | import com.feixiang.hr.Employee; |
| 通配符导入 | import 包名.*; | 导入该包下所有类(不含子包) | import java.util.*; |
| 静态导入 | import static 包名.类名.成员; | 导入静态成员,可直接使用 | import static java.lang.Math.PI; |
通配符导入的注意事项
// 通配符只导入当前包下的类,不包括子包
import com.feixiang.hr.*;
// ✅ 导入了 com.feixiang.hr.Employee
// ✅ 导入了 com.feixiang.hr.HRService
// ❌ 没有导入 com.feixiang.hr.util.SalaryHelper(子包不会自动导入)
import com.feixiang.hr.util.*; // 需要单独导入子包
最佳实践:尽量使用单类导入而非通配符。单类导入让依赖关系一目了然,避免了不同包中有同名类时的歧义。大多数 IDE(IntelliJ IDEA、Eclipse)默认使用单类导入。
静态导入(static import)
定义
静态导入(JDK 1.5 引入)允许直接使用类的静态成员(静态字段和静态方法),无需写类名前缀。
语法对比
| 方式 | 语法 | 使用时的写法 |
|---|---|---|
| 常规导入 | import com.feixiang.common.CompanyConstant; | CompanyConstant.COMPANY_NAME |
| 静态导入字段 | import static com.feixiang.common.CompanyConstant.*; | COMPANY_NAME(直接使用) |
| 静态导入方法 | import static java.lang.Math.*; | sqrt(16) 而非 Math.sqrt(16) |
完整示例:飞翔科技常量与工具类
package com.feixiang.hr;
// 静态导入:直接使用常量和静态方法
import static com.feixiang.common.CompanyConstant.COMPANY_NAME;
import static com.feixiang.common.CompanyConstant.MAX_EMPLOYEES;
import static java.lang.Math.round;
/**
* HR 管理类 —— 展示静态导入的用法
*/
public class HRDashboard {
public void printDashboard(int currentEmployees) {
// ✅ 静态导入后可直接使用常量名
System.out.println("===== " + COMPANY_NAME + " HR 仪表盘 =====");
// ✅ 直接使用静态方法
double ratio = (double) currentEmployees / MAX_EMPLOYEES * 100;
System.out.println("在职人数:" + currentEmployees + " / " + MAX_EMPLOYEES);
System.out.println("满编率:" + round(ratio) + "%");
if (currentEmployees >= MAX_EMPLOYEES) {
System.out.println("【警告】编制已满,暂停招聘!");
}
}
}
测试代码:
package com.feixiang;
import com.feixiang.hr.HRDashboard;
public class DashboardTest {
public static void main(String[] args) {
HRDashboard dashboard = new HRDashboard();
dashboard.printDashboard(4500); // 接近 5000 上限
}
}
运行输出:
===== 飞翔科技 HR 仪表盘 =====
在职人数:4500 / 5000
满编率:90%
静态导入的陷阱
// ❌ 过度使用静态导入,代码可读性极差
import static java.lang.Math.*;
import static java.lang.System.*;
import static java.util.Arrays.*;
public class Messy {
public static void main(String[] args) {
out.println(sqrt(25)); // sqrt 是哪来的?out 是哪来的?
out.println(abs(-42)); // 看代码的人一头雾水
}
}
纠正:静态导入只用于高频使用的常量和明确的工具方法。当有歧义时,退回使用类名前缀。
// ✅ 合理使用
import static com.feixiang.common.CompanyConstant.COMPANY_NAME;
import static java.lang.Math.PI; // PI 语义明确,不会产生歧义
public class Clean {
public static void main(String[] args) {
System.out.println(COMPANY_NAME); // 公司名:语义清晰
System.out.println(Math.sqrt(25)); // Math.sqrt 保留前缀,可读性更好
}
}
java.lang 包的自动导入
java.lang 包是 Java 的核心语言包,编译器会自动导入,无需手动写 import。
以下常用类都属于 java.lang 包,可以直接使用:
| 类别 | 常用类 |
|---|---|
| 基本类型包装 | Integer、Long、Double、Boolean、Character |
| 字符串 | String、StringBuilder、StringBuffer |
| 系统相关 | System、Runtime |
| 数学运算 | Math、StrictMath |
| 异常与错误 | Exception、Error、Throwable、RuntimeException |
| 反射 | Class |
| 线程 | Thread、Runnable |
// ✅ 无需 import,直接使用
public class AutoImportDemo {
public static void main(String[] args) {
String msg = "Hello 飞翔科技"; // java.lang.String
System.out.println(msg); // java.lang.System
int num = Integer.parseInt("100"); // java.lang.Integer
Thread current = Thread.currentThread(); // java.lang.Thread
}
}
不同包同名类的处理
当两个不同的包中有同名类时,不能同时使用通配符导入,必须至少有一个使用全限定类名。
// ====== com/feixiang/hr/DateUtils.java ======
package com.feixiang.hr;
public class DateUtils {
public static String formatHireDate() {
return "入职日期格式化(HR版本)";
}
}
// ====== com/feixiang/tech/DateUtils.java ======
package com.feixiang.tech;
public class DateUtils {
public static String formatProjectDate() {
return "项目日期格式化(技术版本)";
}
}
处理方案
// ❌ 编译错误:对 DateUtils 的引用不明确
import com.feixiang.hr.*;
import com.feixiang.tech.*;
public class App {
public static void main(String[] args) {
// DateUtils.formatHireDate(); // 报错!不知道用哪个 DateUtils
}
}
方案一:导入一个,另一个用全限定类名
// ✅ 明确指定
import com.feixiang.hr.DateUtils;
public class App {
public static void main(String[] args) {
System.out.println(DateUtils.formatHireDate()); // 使用导入的
System.out.println(com.feixiang.tech.DateUtils.formatProjectDate()); // 全限定名
}
}
方案二:两个都用全限定类名
// ✅ 避免歧义,代码意图最明确
public class App {
public static void main(String[] args) {
System.out.println(com.feixiang.hr.DateUtils.formatHireDate());
System.out.println(com.feixiang.tech.DateUtils.formatProjectDate());
}
}
完整示例:飞翔科技多模块协作
场景
小崔需要在 HR 模块的代码中使用技术部和公共模块的类。本示例展示 package 声明、import 导入、静态导入的综合应用。
代码
// ====== com/feixiang/common/CompanyConstant.java ======
package com.feixiang.common;
public class CompanyConstant {
public static final String COMPANY_NAME = "飞翔科技";
public static final String HR_EMAIL = "hr@feixiang.com";
public static final int STANDARD_WORK_HOURS = 8;
}
// ====== com/feixiang/tech/Department.java ======
package com.feixiang.tech;
public class Department {
private String deptName;
public Department(String deptName) {
this.deptName = deptName;
}
public String getDeptName() {
return deptName;
}
}
// ====== com/feixiang/hr/Employee.java ======
package com.feixiang.hr;
public class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() { return name; }
public double getSalary() { return salary; }
}
// ====== com/feixiang/hr/HRService.java ======
package com.feixiang.hr;
// 常规导入
import com.feixiang.tech.Department; // 导入技术部类
import java.util.Date; // 导入 JDK 类
// 静态导入
import static com.feixiang.common.CompanyConstant.COMPANY_NAME;
import static com.feixiang.common.CompanyConstant.HR_EMAIL;
/**
* HR 服务类 —— 综合展示 import 的各种用法
*/
public class HRService {
// java.lang 包自动导入,无需 import
private String serviceName;
public HRService(String serviceName) {
this.serviceName = serviceName;
}
/**
* 处理员工入职
*/
public void onboardEmployee(Employee emp, Department dept) {
System.out.println("===== " + COMPANY_NAME + " 入职通知 ====="); // 静态导入的常量
System.out.println("HR 服务:" + serviceName);
System.out.println("新员工:" + emp.getName());
System.out.println("部门:" + dept.getDeptName()); // 来自 com.feixiang.tech
System.out.println("入职日期:" + new Date()); // java.util.Date
System.out.println("如有疑问请邮件:" + HR_EMAIL); // 静态导入的常量
// java.lang.String 自动导入
String message = "欢迎 " + emp.getName() + " 加入 " + COMPANY_NAME + "!";
System.out.println(message);
}
}
测试代码
// ====== com/feixiang/Main.java ======
package com.feixiang;
import com.feixiang.hr.Employee;
import com.feixiang.hr.HRService;
import com.feixiang.tech.Department;
public class Main {
public static void main(String[] args) {
// 创建各模块对象
Employee emp = new Employee("小崔", 28000.00);
Department techDept = new Department("技术部-后端组");
HRService hr = new HRService("飞翔科技HR系统 v1.0");
// 执行入职流程
hr.onboardEmployee(emp, techDept);
}
}
运行输出
===== 飞翔科技 入职通知 =====
HR 服务:飞翔科技HR系统 v1.0
新员工:小崔
部门:技术部-后端组
入职日期:Sun Dec 21 10:00:00 CST 2025
如有疑问请邮件:hr@feixiang.com
欢迎 小崔 加入 飞翔科技!
import 编译原理简述
编译器查找类的优先级:
- 当前类所在的包
- 单类导入(
import com.feixiang.hr.Employee;) - 通配符导入(
import com.feixiang.hr.*;) java.lang包(自动导入)
易错场景
反例一:package 语句不在首行
// ❌ 编译错误!package 必须在首行
import java.util.Date;
package com.feixiang.hr; // 错误:package 必须是第一条语句
public class Employee {
}
纠正:
// ✅ package 第一行,import 紧随其后
package com.feixiang.hr;
import java.util.Date;
public class Employee {
}
反例二:混淆通配符导入与子包
// ❌ 错误认知:import com.feixiang.* 会导入 com.feixiang.hr.Employee
import com.feixiang.*;
public class App {
public static void main(String[] args) {
// ❌ 编译错误:找不到符号
// Employee emp = new Employee();
}
}
纠正:通配符只导入当前包下的类,不递归导入子包。
// ✅ 导入具体包
import com.feixiang.hr.Employee;
public class App {
public static void main(String[] args) {
Employee emp = new Employee("白歌", 35000);
}
}
反例三:静态导入导致可读性灾难
// ❌ 静态导入所有 Math 成员,不清楚 PI 从哪里来
import static java.lang.Math.*;
public class Circle {
public double area(double r) {
return PI * r * r; // PI 从哪来的?必须翻到文件顶部才知道
}
}
纠正:
// ✅ 保留类名前缀或精确静态导入
import static java.lang.Math.PI; // 精确导入,语义明确
public class Circle {
public double area(double r) {
return PI * r * r; // 文件顶部就能看到 PI 的来源
}
}
面试考点
Q1:package 语句有什么规则?
① package 语句必须是 Java 源文件的第一条非注释语句;② 每个源文件只能有一句 package 声明;③ 包名必须与目录结构一致(
com.feixiang.hr对应com/feixiang/hr/);④ 包名全部小写,采用域名倒置的命名规范;⑤ 如果没有 package 声明,类属于无名包(default package),不推荐在实际项目中使用。
Q2:import java.util.* 会导入 java.util 子包中的类吗?
不会。通配符
*只匹配当前包下的直接类,不会递归导入子包。例如import java.util.*不会导入java.util.regex.Pattern,需要单独写import java.util.regex.*或import java.util.regex.Pattern。
Q3:import static 和普通 import 有什么区别?
普通
import导入的是类,使用时仍需写类名.静态成员;import static导入的是类的静态成员(字段或方法),使用时可以省略类名前缀。例如import static java.lang.Math.PI后可以直接写PI。静态导入适合高频使用的常量,但过度使用会降低可读性。
Q4:如果两个包中都有同名的 DateUtils 类,如何处理?
① 只导入其中一个,另一个使用全限定类名;② 两个都用全限定类名。不能同时使用通配符导入两个包,编译器会报"引用不明确"错误。
Q5:java.lang 包为什么不需要 import?
java.lang是 Java 的核心语言包,包含了 Java 语言最基础的类(String、System、Math、Thread等)。编译器会隐式地为每个源文件添加import java.lang.*,因此无需手动导入。这是 Java 语言规范的硬性规定(JLS 7.3)。