你编写了几个 Java 类,一切都很顺利。然后你又编写了几个。接着你的项目就有 200 个文件,你再也找不到任何东西了。两个类拥有相同的名称,编译器开始报错。这正是包所解决的问题。
Java 中的包是一个命名空间。它将相关的类、接口和枚举分组到类似文件夹的结构中。Java 中的每个类都存在于某个包中。即使是简单的 HelloWorld 类也有一个包,即使你没有显式地编写它。它会被放入臭名昭著的“默认包”中,这对于学校项目来说没问题,但对于任何实际项目来说都非常糟糕。
Oracle 官方教程解释道:“包是对相关类型的分组,提供访问保护和命名空间管理。”这是一种教科书式的说法,意思是“包防止你的代码自相冲突。”
为什么包存在
三个实际原因。
首先,名称冲突。你编写了一个 User 类。你导入的某个库也拥有一个 User 类。如果没有包,Java 无法区分它们。使用包后,一个是 com.yourcompany.model.User,另一个是 org.somelibrary.model.User。它们可以和平共存。
其次,访问控制。Java 有四种访问级别:private、package-private(默认)、protected 和 public。初学者最容易忽略 package-private。它表示“对同一包中的任何类可访问,但对包外不可访问”。这不是 bug,而是一种设计工具。你可以对外部世界隐藏实现细节,同时让它们对包内的相关类可见。
第三,可发现性。当所有与账单相关的类都位于 com.yourcompany.billing 中时,新开发者就知道去哪里查找。这与在计算机上将文件组织到文件夹中的逻辑相同。
命名约定
Java 包名采用反向域名约定。如果你拥有 jamilur.dev,你的包名以 dev.jamilur 开头。如果你在拥有 example.com 的公司工作,则以 com.example 开头。
官方 Java 文档推荐:“包名使用全小写以避免与类或接口名称冲突。”
以下是真实项目的示例。
-
java.lang-- 核心 Java 语言类 -
java.util-- 工具类,如集合 -
org.springframework.boot-- Spring Boot -
com.mysql.cj.jdbc-- MySQL JDBC 驱动程序 -
dev.jamilur.ecommerce.model-- 假设的电商模型
每个点代表一个目录。dev.jamilur.ecommerce.model 在磁盘上映射为 dev/jamilur/ecommerce/model/。
声明包
将其放在 Java 文件的最顶部,任何 import 之前。
package dev.jamilur.bkash.app;
Enter fullscreen mode Exit fullscreen mode
就是这样。文件必须位于与包名匹配的文件夹结构中:dev/jamilur/bkash/app/。
如果你忘记了包声明,Java 会将你的类放入默认包。不要在真实应用程序中这样做。默认包无法被命名包中的类导入,这意味着你无法在项目其他部分使用自己的代码。Baeldung 对为什么默认包在生产环境中很危险有很好的解释。
真实示例
让我们为孟加拉电商应用构建一个小型项目结构。
src/
dev/
jamilur/
ecommerce/
model/
Product.java
Customer.java
Order.java
service/
PaymentService.java
ShippingService.java
controller/
OrderController.java
Enter fullscreen mode Exit fullscreen mode
Product.java 文件以如下开头:
package dev.jamilur.ecommerce.model;
public class Product {
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
}
Enter fullscreen mode Exit fullscreen mode
而 PaymentService.java 文件以如下开头:
package dev.jamilur.ecommerce.service;
import dev.jamilur.ecommerce.model.Order;
public class PaymentService {
public boolean processPayment(Order order) {
// payment logic here
return true;
}
}
Enter fullscreen mode Exit fullscreen mode
注意 import 语句。当你想使用另一个包中的类时,需要导入它。Order 类位于 model 包中。PaymentService 类位于 service 包中。import 语句桥接了这个差距。
导入与通配符
你有三种导入方式。
显式导入(大多数时候使用这种)。
import dev.jamilur.ecommerce.model.Order;
import dev.jamilur.ecommerce.model.Customer;
Enter fullscreen mode Exit fullscreen mode
通配符导入(方便但有争议)。
import dev.jamilur.ecommerce.model.*;
Enter fullscreen mode Exit fullscreen mode
这会导入 model 包中的所有类。它节省了输入。但缺点是会失去清晰度。查看你文件的读者无法知道你具体使用了哪些类。包括Google 的 Java 风格指南在内的许多风格指南都建议不要使用通配符导入。你的 IDE 很可能会自动展开它们。
静态导入(很少使用,需谨慎)。
import static java.lang.Math.PI;
import static java.lang.Math.sqrt;
Enter fullscreen mode Exit fullscreen mode
现在你可以写 PI 而不是 Math.PI,写 sqrt(16) 而不是 Math.sqrt(16)。这对常量和工具方法很方便,但如果过度使用可能会使代码更难阅读。
包私有访问
以下是大多数教程跳过的访问修饰符。
当你声明类、方法或字段时不使用任何访问修饰符(没有 public、没有 private、没有 protected),它就会获得包私有访问权限。同一包中的任何内容都可以看到它,包外的内容则无法看到。
package dev.jamilur.ecommerce.service;
class PaymentGatewayHelper {
// no 'public' keyword
// only classes in the same package can use this
String encryptData(String raw) {
return "encrypted_" + raw;
}
}
Enter fullscreen mode Exit fullscreen mode
这对于不应成为公共 API 一部分的内部辅助类很有用。Spring 大量使用这种模式。你可以用 @Transactional 注解一个服务方法并将其设为包私有,Spring 的代理仍然可以拦截它,因为它位于同一包中。但外部代码无法直接调用它。
包与模块
Java 9 引入了模块,模块就像增强版的包。一个模块捆绑多个包,并声明它导出哪些包以及需要哪些其他模块。
日常项目不需要模块。包对于大多数应用程序来说已经足够。模块增加了复杂度,只有在大型系统中才值得。Java 平台模块系统(JPMS)在官方 JMod 文档中有介绍,如果你想稍后探索的话。
初学者常见困惑
包名会影响性能吗?不会。包纯粹是组织性的。它们没有运行时成本。
不同包中的两个文件可以拥有相同的类名吗?可以。这正是包存在的主要原因之一。com.example.User 和 com.other.User 是两个完全不同的类。
我必须匹配包和文件夹结构吗?是的,对于标准 Java 编译。编译器期望如此。Maven 和 Gradle 等现代构建工具会强制执行这一点。
我可以稍后更改包名吗?可以,但这会破坏所有 import 和所有引用。你的 IDE 可以自动重构它。但在共享团队中,更改包名会产生合并冲突和混乱。请尽早选择你的包结构。
快速总结
- 包是一个将相关 Java 类型分组的命名空间。
- 包名遵循反向域名约定:
com.example.project.module。 - 包声明是 Java 文件中的第一行。
- 你使用
import关键字从其他包导入类。 - 为了清晰,优先使用显式导入而非通配符导入。
- 包私有访问(无修饰符)仅允许在同一包内访问。
- 包映射到文件系统上的目录。
- 模块(Java 9+)是包之上的额外层,用于大型系统。
包并不令人兴奋。但它们是可扩展项目与一堆无人能导航的文件之间的区别。尽早正确设置包结构可以避免日后头疼。
基于 dev.java/learn:Packages
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.