CodeGym /课程 /JAVA 25 SELF /module-info.java:语法、创建模块

module-info.java:语法、创建模块

JAVA 25 SELF
第 60 级 , 课程 1
可用

1. 什么是 module-info.java,它在哪里?

Java 中的模块总是从文件 module-info.java 开始。这相当于模块的“身份证”,你在其中声明其名称、要导出的包以及对其他模块的依赖。

在哪里找到?
文件 module-info.java 应位于该模块源码目录的根下。例如,如果你的项目结构如下:

project-root/
└── src/
    └── my.module.name/
        ├── module-info.java
        └── com/
            └── example/
                └── api/
                    └── MyClass.java

这里 my.module.name 是模块名(命名规则稍后介绍)。

没有这个文件,模块就不算模块!
如果没有它——那就是一个传统的“老式” Java 项目,即便有一堆目录和类。

2. module-info.java 语法:核心元素

我们先看一个最简单的示例——真的不难,放心!

module my.module.name {
    exports com.example.api;
    requires java.sql;
}

逐段拆解:

module my.module.name { ... }
这是一个名为 my.module.name 的模块声明。模块名是唯一标识,通常与根包一致(例如 com.example.app)。
趣闻:如果你把模块命名为 java.base,编译器可不会高兴。不要尝试冒充标准模块。

exports com.example.api;
这行表示:模块会公开包 com.example.api 中的内容。该包内所有标记为 public 的内容对其他模块可见,其余仅对本模块可见。

requires java.sql;
这里我们向编译器“坦白”:“我的工作需要标准模块 java.sql。”否则,编译器不允许我们使用该模块中的类。

附加关键字(拓展了解)

  • opens <package>; —— 为反射打开包(例如用于 Jackson 一类的序列化库)。
  • uses <service-interface>; —— 声明模块会使用某个服务(接口)。
  • provides <service-interface> with <implementation-class>; —— 声明模块提供该服务的实现。

在本讲中,我们将重点关注 exportsrequires —— 它们在绝大多数教学和生产项目中都用得上。

3. module-info.java 示例

示例 1:最小模块

module com.example.hello {
    exports com.example.hello.api;
}
  • 此模块仅导出包 com.example.hello.api
  • 例如,位于 com.example.hello.internal 的内容将对其他模块隐藏,即使其中有 public 类。

示例 2:带依赖的模块

module com.example.dbclient {
    exports com.example.db.api;
    requires java.sql;
}

我们可以使用 JDBC,但前提是已如实声明对 java.sql 的依赖。

示例 3:导出多个包

module com.example.library {
    exports com.example.library.api;
    exports com.example.library.utils;
}

可以导出任意数量的包(但不要什么都导出——这违背了模块化的意义!)。

4. 限制与规则

模块名

  • 通常与根包一致(例如 com.example.app)。
  • 不能与标准模块名称相同(java.basejava.sql 等)。
  • 不应包含空格、特殊字符,且不能以数字开头等。
  • 建议:使用组织或项目的反向域名以避免冲突。

一模块一份 module-info.java
一个模块中只能有一个这样的文件。如果有两个——编译器会给你来一场“模块风波”。

同一包只能由一个模块导出
同一个包不能由两个不同的模块导出。这就像同一个人持有两本同名护照——官方不会认可。

模块内的包
只能导出在该模块源码结构中实际存在的包。导出不存在的包将导致编译错误。

5. 实践:在项目中创建 module-info.java

假设我们有一个简单项目,目录如下:

project-root/
└── src/
    └── com.example.greetings/
        ├── module-info.java
        └── com/
            └── example/
                └── greetings/
                    ├── api/
                    │   └── Greeter.java
                    └── internal/
                        └── SecretSauce.java

步骤 1:创建 module-info.java

module com.example.greetings {
    exports com.example.greetings.api;
}

步骤 2:尝试在另一个模块中使用 internal 包里的类

假设我们还有第二个模块 com.example.app,想访问 SecretSauce

module com.example.app {
    requires com.example.greetings;
}

import com.example.greetings.internal.SecretSauce; // 错误!

结果:
编译器会说:“包 com.example.greetings.internal 未被模块 com.example.greetings 导出”。即便类 SecretSaucepublic,它对其他模块也是不可见的。
这就是模块层面的真正封装!

步骤 3:尝试不声明 requires

如果在 com.example.app 中没有写 requires com.example.greetings;,却尝试使用 com.example.greetings.api 中的类,编译器会报错:

package com.example.greetings.api is not visible

6. 使用 module-info.java 时的常见错误

错误 1:模块名与项目结构不匹配。
如果你把模块命名为 com.example.app,而目录结构却是 src/main/java/app,编译器就不知道你想要什么。模块名通常与根包一致,目录也应反映这一点。

错误 2:把一切都导出。
只应导出确实需要对其他模块可见的内容。不要因为“更省事”就写 exports com.example;。这会破坏封装性。

错误 3:忘记添加 requires
如果你使用了其他模块或标准库(例如 java.sql)中的类,却忘了声明依赖——就会出现编译错误。

错误 4:在 exports 中导出不存在的包。
如果你写了 exports com.example.foo;,但该包并不存在——编译器会提示你在“导出空气”。

错误 5:未导出包中的 public 类。
如果类被声明为 public,但所在的包没有导出,那么该类只能在模块内部可见。这不算错误,但常常让新手意外。

错误 6:一个模块中有多个 module-info.java
一个模块中应只有一个 module-info.java 文件。如果有两个——编译器将无法构建项目。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION