首页 / Java 入门教程 / 包(package)与 import 机制

Java 入门教程

包(package)与 import 机制

本教程共 100 篇 · 第 7 篇 · 更新于 2026-08-05 · 约 5 分钟阅读

JavaJava 入门教程packageimport命名空间classpath

7. 包(package)与 import 机制

本节目标:理解包为什么存在、怎么声明、目录该怎么放,并掌握 import 的三种写法和编译器的类名查找规则。

撞名字的麻烦

假设小明写了个 Person 类,小红也写了个 Person 类。

现在小白的项目要同时用这两个。编译器懵了:Person p = new Person(); 到底指谁?

更常见的情况是撞上 JDK 自带的类。你写了个工具类叫 Arrays,而 JDK 里本来就有 java.util.Arrays

Java 的解法是包(package)——给每个类加一个「姓氏」。

完整类名 = 包名 + 类名

一个类的真实身份是完整类名(也叫全限定名):

所在包完整类名
小明的 Personmingming.Person
小红的 Personhonghong.Person
你的 Arraysmr.junmr.jun.Arrays
JDK 的 Arraysjava.utiljava.util.Arrays

平时写的 Person 只是简写。JVM 只认完整类名,只要包名不同,就是两个毫不相干的类。

声明包

在源文件的第一行(注释除外)写 package

package com.example.util;   // 必须是第一条语句

public class Greeter {
    public static void sayHi(String name) {
        System.out.println("Hi, " + name);
    }
}
```bash

规则很死:

- 一个文件**最多一条** `package` 语句
- 必须放在所有代码之前
- 不写就属于「默认包」

> [!WARNING]
> 默认包(不写 `package`)极易撞名,而且**默认包里的类无法被有包名的类导入**。除了学语法时的临时文件,正式代码一律写包名。

## 包名怎么起

约定俗成的两条:

**全部小写。** `com.example.util`,不要写成 `com.Example.Util`。

**用倒置域名保证唯一。** 公司域名是 `example.com`,包名就以 `com.example` 打头。

常见例子:

```text
org.apache.commons.lang3
com.google.gson
com.fasterxml.jackson.databind

后面的层级按功能自己分:com.example.user.servicecom.example.order.dao

Note

包没有父子关系。 java.utiljava.util.zip 看着像父子,实际是两个毫不相干的包,不存在任何继承或访问上的特殊照顾。这是个高频误解。

另外,别给自己的类起 StringSystemList 这类和标准库撞车的名字,找起来会很痛苦。

目录结构必须对上

包名的每一段对应一层目录,这是硬性要求。

com.example.util.Greeter 的源文件必须放在:

src/com/example/util/Greeter.java
```java

完整演示一遍。建两个文件:

`src/com/example/util/Greeter.java`:

```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("小明");
    }
}
```bash

编译(`-d out` 把产物放进 `out` 目录,目录层级会自动建好):

```bash
javac -d out src/com/example/util/Greeter.java src/com/example/app/Main.java

运行时要用完整类名

java -cp out com.example.app.Main
```text

```text
Hi, 小明

注意最后那条命令——不是 java Main,而是 java com.example.app.Main。带了包名的类,启动时必须报全名。

编译产物的目录结构和源码是镜像的:

out/
└── com/
    └── example/
        ├── app/
        │   └── Main.class
        └── util/
            └── Greeter.class
```bash

## import 的三种写法

要用别的包里的类,有三条路。

### 写完整类名

```java
package com.example.app;

public class Main {
    public static void main(String[] args) {
        com.example.util.Greeter.sayHi("小明");   // 全名,又长又啰嗦
    }
}

能用,但每次都写一长串太痛苦。

单类型导入(推荐)

import com.example.util.Greeter;
```bash

之后写简单类名 `Greeter` 即可。**这是日常最常用的写法。**

### 按需导入(星号)

```java
import com.example.util.*;   // 导入该包下所有类

这里的 * 有两个容易误会的点:

  1. 不包括子包。 import java.util.* 不会导入 java.util.zip 下的类。
  2. 不影响性能。 编译器只是多了个查找范围,不会把整包塞进 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
    }
}
```text

```text
3.141592653589793
5

用得不多。它省了几个字符,代价是读代码的人不知道 abs 从哪来。

比较合理的使用场景是单元测试里的断言方法(assertEquals 之类),以及数学计算密集的代码。

Tip

判断该不该用静态导入,问自己一句:去掉类名之后,别人还看得出这个方法属于谁吗? 看不出就别用。

编译器怎么找类

看到一个简单类名,编译器按这个顺序查:

  1. 是完整类名吗?是就直接用。
  2. 当前包里有没有这个类?
  3. import 进来的包里有没有?
  4. java.lang 包里有没有?

四轮都找不到,报错「找不到符号」。

第 4 条解释了一个现象:StringSystemMathInteger 这些类从来不用导入。它们都在 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,自动可用
    }
}
```bash

> [!NOTE]
> 自动导入的只有 `java.lang` 这一层。`java.lang.reflect`、`java.lang.annotation` 这些子包仍需手动导入——又一次印证「包没有父子关系」。

## 两个同名类怎么办

同时要用 `java.util.Date` 和 `java.sql.Date` 时,**只能 import 一个**,另一个写全名:

```java
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
```bash

要是两个都 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` 由编译器自动导入,它的子包不算在内;同名类冲突时只能导一个,另一个写全名。