默认访问权限仅对同一包内类可见,不被子类(跨包)继承,模块化下受exports/opens限制,误用protected会扩大暴露面,反射和mockito等工具受其约束。

默认访问权限到底能被谁访问
Java里不写public、protected或private的类、方法、字段,就是包级别私有(package-private)。它不是“没权限”,而是明确限定:**只对同一个包下的其他类可见**,连子类(哪怕在不同包)都访问不了。
常见错误现象:javac报错error: cannot find symbol或error: <method>() has private access in <class></class></method>,其实根本不是private,而是跨包调用了默认权限成员。
- 同一个
.java文件里的多个类(比如主类+内部工具类)可以互相访问默认成员 - 不同
.java文件但同包(路径一致、package声明相同)可以互访 - 子类继承自默认权限的类?可以——但若子类在不同包,不能访问父类的默认方法/字段
- 模块系统(Java 9+)下,即使同包,如果不在同一
module且没exports,也会被模块边界挡住
为什么protected比默认权限“更开放”却常被误用
很多人以为protected只是“给子类用的”,结果把本该默认访问的工具方法改成protected,反而让其他包的无关子类也能调用,破坏封装边界。
关键区别在于:默认权限只认“包”,protected认“包 + 子类(跨包也行)”。这意味着:
立即学习“Java免费学习笔记(深入)”;
- 如果你只希望同包内协作(比如
com.example.parser里多个解析器共享parseHelper()),就用默认权限——最安全 - 如果你真需要被外部包继承并复用(比如框架提供抽象基类),才用
protected - 把
protected当“比private松一点的私有”来用,是典型误用;它实际比默认权限暴露面更大
IDE和编译器对默认权限的提示容易误导人
IntelliJ 或 Eclipse 常把默认权限成员标为“package-private”,看着像警告;javac本身又完全不报任何提示——导致很多人直到运行时反射失败或编译报错才意识到问题。
真实场景中容易踩的坑:
- 用
Class.forName()加载类后调getDeclaredMethod(),发现拿不到默认方法——因为getDeclaredXXX()只返回本类声明的,但默认方法无法被跨包访问,反射也会受访问控制检查 - Mockito mock 默认权限类时失败,报
Cannot mock/spy because : final class之类错误,其实是它底层生成子类失败(默认类可被继承,但默认构造器不可见) - Maven 多模块项目中,
test目录下同包名的测试类,看似“同包”,但因src/test/java和src/main/java是不同源根,JVM 视为不同保护域,不能访问默认成员
什么时候该主动避免默认权限
默认权限不是万能的“默认选项”,它在现代 Java 工程中越来越容易引发隐性耦合。
以下情况建议显式声明,而不是依赖默认:
- 模块化项目(
module-info.java存在):默认权限成员不会被自动导出,外部包即使同名也无法访问,不如直接上public+exports控制 - 使用 Lombok 的
@Data或@Getter:它生成的 getter/setter 默认也是默认权限,若实体要被其他包使用,得加access = AccessLevel.PUBLIC - Spring Bean 注入:
@Service类的默认权限方法,若被@Async或@Transactional代理,可能因 CGLIB 生成子类失败而静默失效
包结构越复杂,越不能靠“默认”偷懒。一个package-info.java里没写exports,一个module-info.java里没配opens,再合理的默认权限也会在运行时突然卡住。










