包(package)与 import 机制
本教程共 100 篇 · 第 7 篇 · 更新于 2026-08-05 · 约 5 分钟阅读
本节目标:理解包为什么存在、怎么声明、目录该怎么放,并掌握
import的三种写法和编译器的类名查找规则。
撞名字的麻烦
假设小明写了个 Person 类,小红也写了个 Person 类。
现在小白的项目要同时用这两个。编译器懵了:Person p = new Person(); 到底指谁?
更常见的情况是撞上 JDK 自带的类。你写了个工具类叫 Arrays,而 JDK 里本来就有 java.util.Arrays。
Java 的解法是包(package)——给每个类加一个「姓氏」。
完整类名 = 包名 + 类名
一个类的真实身份是完整类名(也叫全限定名):
| 类 | 所在包 | 完整类名 |
|---|---|---|
| 小明的 Person | ming | ming.Person |
| 小红的 Person | hong | hong.Person |
| 你的 Arrays | mr.jun | mr.jun.Arrays |
| JDK 的 Arrays | java.util | java.util.Arrays |
平时写的 Person 只是简写。JVM 只认完整类名,只要包名不同,就是两个毫不相干的类。
声明包
在源文件的第一行(注释除外)写 package:
package com.example.util; // 必须是第一条语句
public class Greeter {
public static void sayHi(String name) {
System.out.println("Hi, " + name);
}
}
规则很死:
- 一个文件最多一条
package语句 - 必须放在所有代码之前
- 不写就属于「默认包」
Warning默认包(不写
package)极易撞名,而且默认包里的类无法被有包名的类导入。除了学语法时的临时文件,正式代码一律写包名。
包名怎么起
约定俗成的两条:
全部小写。 com.example.util,不要写成 com.Example.Util。
用倒置域名保证唯一。 公司域名是 example.com,包名就以 com.example 打头。
常见例子:
org.apache.commons.lang3
com.google.gson
com.fasterxml.jackson.databind
后面的层级按功能自己分:com.example.user.service、com.example.order.dao。
Note包没有父子关系。
java.util和java.util.zip看着像父子,实际是两个毫不相干的包,不存在任何继承或访问上的特殊照顾。这是个高频误解。
另外,别给自己的类起 String、System、List 这类和标准库撞车的名字,找起来会很痛苦。
目录结构必须对上
包名的每一段对应一层目录,这是硬性要求。
com.example.util.Greeter 的源文件必须放在:
src/com/example/util/Greeter.java
完整演示一遍。建两个文件:
src/com/example/util/Greeter.java:
package com.example.util;
public class Greeter {
public static void sayHi(String name) {
System.out.println("Hi, " + name);
}
}
src/com/example/app/Main.java:
package com.example.app;
import com.example.util.Greeter; // 导入另一个包里的类
public class Main {
public static void main(String[] args) {
Greeter.sayHi("小明");
}
}
编译(-d out 把产物放进 out 目录,目录层级会自动建好):
javac -d out src/com/example/util/Greeter.java src/com/example/app/Main.java
运行时要用完整类名:
java -cp out com.example.app.Main
Hi, 小明
注意最后那条命令——不是 java Main,而是 java com.example.app.Main。带了包名的类,启动时必须报全名。
编译产物的目录结构和源码是镜像的:
out/
└── com/
└── example/
├── app/
│ └── Main.class
└── util/
└── Greeter.class
import 的三种写法
要用别的包里的类,有三条路。
写完整类名
package com.example.app;
public class Main {
public static void main(String[] args) {
com.example.util.Greeter.sayHi("小明"); // 全名,又长又啰嗦
}
}
能用,但每次都写一长串太痛苦。
单类型导入(推荐)
import com.example.util.Greeter;
之后写简单类名 Greeter 即可。这是日常最常用的写法。
按需导入(星号)
import com.example.util.*; // 导入该包下所有类
这里的 * 有两个容易误会的点:
- 不包括子包。
import java.util.*不会导入java.util.zip下的类。 - 不影响性能。 编译器只是多了个查找范围,不会把整包塞进 class 文件。
不推荐用它,原因是可读性:一屏 * 之后,你说不清 Arrays 到底来自哪个包。IDE 也默认帮你展开成单类型导入。
静态导入
import static 可以直接导入类的静态成员,用的时候不写类名:
import static java.lang.Math.PI;
import static java.lang.Math.abs;
public class StaticImportDemo {
public static void main(String[] args) {
System.out.println(PI); // 不用写 Math.PI
System.out.println(abs(-5)); // 不用写 Math.abs
}
}
3.141592653589793
5
用得不多。它省了几个字符,代价是读代码的人不知道 abs 从哪来。
比较合理的使用场景是单元测试里的断言方法(assertEquals 之类),以及数学计算密集的代码。
Tip判断该不该用静态导入,问自己一句:去掉类名之后,别人还看得出这个方法属于谁吗? 看不出就别用。
编译器怎么找类
看到一个简单类名,编译器按这个顺序查:
- 是完整类名吗?是就直接用。
- 当前包里有没有这个类?
import进来的包里有没有?java.lang包里有没有?
四轮都找不到,报错「找不到符号」。
第 4 条解释了一个现象:String、System、Math、Integer 这些类从来不用导入。它们都在 java.lang 包里,编译器自动帮你导了。
public class AutoImport {
public static void main(String[] args) {
String s = "hi"; // java.lang.String,自动可用
System.out.println(s); // java.lang.System,自动可用
System.out.println(Math.max(1, 2)); // java.lang.Math,自动可用
}
}
Note自动导入的只有
java.lang这一层。java.lang.reflect、java.lang.annotation这些子包仍需手动导入——又一次印证「包没有父子关系」。
两个同名类怎么办
同时要用 java.util.Date 和 java.sql.Date 时,只能 import 一个,另一个写全名:
import java.util.Date;
public class DateConflict {
public static void main(String[] args) {
Date d1 = new Date(); // java.util.Date
java.sql.Date d2 = new java.sql.Date(0L); // 写全名区分
System.out.println(d1.getClass().getName());
System.out.println(d2.getClass().getName());
}
}
java.util.Date
java.sql.Date
要是两个都 import,编译器会直接报「引用不明确」。
常用基础包一览
刚上手时,你会反复碰到下面这几个包。提前认识一下,看到 import 就不慌了:
| 包 | 用途 | 典型类 |
|---|---|---|
java.lang | 语言核心,自动导入 | String、System、Math、Integer |
java.util | 工具与集合 | ArrayList、HashMap、Scanner、Arrays |
java.io | 输入输出 | File、InputStream、PrintWriter |
java.time | 日期时间(Java 8 引入) | LocalDate、LocalDateTime、Duration |
java.math | 高精度数值 | BigDecimal、BigInteger |
java.net | 网络 | URL、Socket、HttpURLConnection |
记住一个规律:包名越短,越是核心基础。 java.lang 最短,因为它是整个语言的地基,所以才被编译器自动导入。
Tip写代码时不必死记包名。IDE 在你敲出
ArrayList的瞬间就会提示要不要import,按一下回车就补好了。但理解「为什么需要 import」这件事,能帮你在报错时快速定位问题——多半就是缺了某条import。
小结
包是 Java 的命名空间,完整类名 = 包名 + 类名,JVM 只认完整类名。
package 必须是文件第一条语句,包名全小写、用倒置域名,包之间没有父子关系。
包名的每一段对应一层目录,运行带包的类要用完整类名启动。
import 优先用单类型导入;* 不含子包也不影响性能,但伤可读性。
java.lang 由编译器自动导入,它的子包不算在内;同名类冲突时只能导一个,另一个写全名。