
package 声明必须在文件最顶行,否则 javac 直接报错
Java 编译器对 package 的位置极其严格:它必须是源文件中第一个非注释、非空行。一旦前面有 import、类定义、甚至一行带空格的注释,javac 就会抛出 class, interface, or enum expected 或更隐晦的 package not found 错误。
常见踩坑点:
- IDE 自动生成的 license header 注释没删,导致
package不是首行 - 用 IDE 快捷键移动类时,把
package拖到了中间 - 从其他项目复制代码,忘了检查包声明位置
验证方式很简单:打开 .java 文件,按 Ctrl+Home(或 Cmd+↑),光标应直接落在 package 关键字上。
default 访问权限(不写修饰符)只对同包类可见,跨模块即失效
很多人误以为 default 是“对所有类开放”,其实它只在编译期按物理包路径判断——只要不在同一个 package 下,哪怕在同一个 JAR 里,也访问不了。JDK 9+ 模块系统进一步收紧了这点:即使两个包名相同,若分属不同模块且未 opens 或 exports,反射都拿不到 default 成员。
立即学习“Java免费学习笔记(深入)”;
实际影响:
- 单元测试类(通常在
test目录下独立包路径)无法直接访问生产代码的default方法,必须提升为protected或加测试友好的public构造器 - Spring Boot 的
@Configuration类若依赖default工具方法,而该方法在另一个 module 中,启动会失败(NoClassDefFoundError 或 IllegalAccessError) - 使用
javac --module-path编译时,default成员不会被模块系统“导出”,外部模块看不见
protected 不等于“子类可用”,它还允许同包访问
protected 的真实语义是“本类 + 同包 + 子类(无论在哪)”,不是单纯的“继承可见”。这意味着:一个 protected 方法,即使没被继承,只要调用方和定义方在同一个 package 下,就能直接调用——这常被当成“封装松动”的隐患。
设计建议:
- 如果真想限制为“仅子类可用”,别用
protected,改用private+protected的 hook 方法组合(比如模板方法模式) - 避免在工具类中滥用
protected静态方法:它会让同包下任意类都能调用,违背工具类“明确入口”的初衷 - Maven 多模块项目中,父子模块若共用包名(如都用
com.example.util),protected会意外打通边界,此时应靠模块拆分 +requires控制依赖,而非依赖访问修饰符
模块化(module-info.java)不替代包访问控制,而是叠加一层导出约束
module-info.java 的 exports 只控制“哪些包能被其他模块看到”,不改变包内成员自身的访问权限。比如你 exports com.example.api,但里面某个类的方法是 default,那么其他模块依然不能调用那个方法——除非它被声明为 public。
关键事实:
-
exports com.example.api和exports com.example.api to com.example.client效果不同:后者只允许指定模块访问,前者全放开;但两者都不让default成员变 public -
opens仅影响反射访问(如 JSON 序列化、JUnit 参数化),不影响普通方法调用 - 如果模块未声明
requires,即使目标类是public,编译期就报错package is not visible
所以,模块化和包访问控制是两层事:包决定“谁能在源码里写点什么”,模块决定“谁能在 classpath/modulepath 上看到这个包”。漏掉任何一层,运行时都可能崩。
最易被忽略的是:IDE 有时缓存旧的模块图,改了 module-info.java 却没触发完整 rebuild,导致行为和预期不符——遇到奇怪的 NoClassDefFoundError,先 mvn clean compile 看看。










