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

Java 入门教程

包(package)与 import 机制

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

JavaJava 入门教程packageimport命名空间classpath

本节目标:理解包为什么存在、怎么声明、目录该怎么放,并掌握 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);
    }
}

规则很死:

  • 一个文件最多一条 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.servicecom.example.order.dao

Note

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

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

目录结构必须对上

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

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.*;   // 导入该包下所有类

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

  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
    }
}
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,自动可用
    }
}
Note

自动导入的只有 java.lang 这一层。java.lang.reflectjava.lang.annotation 这些子包仍需手动导入——又一次印证「包没有父子关系」。

两个同名类怎么办

同时要用 java.util.Datejava.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语言核心,自动导入StringSystemMathInteger
java.util工具与集合ArrayListHashMapScannerArrays
java.io输入输出FileInputStreamPrintWriter
java.time日期时间(Java 8 引入)LocalDateLocalDateTimeDuration
java.math高精度数值BigDecimalBigInteger
java.net网络URLSocketHttpURLConnection

记住一个规律:包名越短,越是核心基础。 java.lang 最短,因为它是整个语言的地基,所以才被编译器自动导入。

Tip

写代码时不必死记包名。IDE 在你敲出 ArrayList 的瞬间就会提示要不要 import,按一下回车就补好了。但理解「为什么需要 import」这件事,能帮你在报错时快速定位问题——多半就是缺了某条 import

小结

包是 Java 的命名空间,完整类名 = 包名 + 类名,JVM 只认完整类名。

package 必须是文件第一条语句,包名全小写、用倒置域名,包之间没有父子关系

包名的每一段对应一层目录,运行带包的类要用完整类名启动。

import 优先用单类型导入;* 不含子包也不影响性能,但伤可读性。

java.lang 由编译器自动导入,它的子包不算在内;同名类冲突时只能导一个,另一个写全名。