抽象类不强制子类补全访问修饰符,导致新方法或重写方法默认包级可见,违反接口契约中方法必须public的要求,引发跨包调用失败。

因为抽象容器类(如 AbstractList、AbstractMap)本身只提供骨架实现,不控制子类成员的访问权限,而 Java 的可见性规则默认是包级(default),不是 public 或 protected。开发者在继承时往往只关注功能逻辑,却忘了显式声明方法或字段的修饰符,导致子类行为意外受限。
抽象类不传递可见性约束
抽象类中的方法大多是 public 或 protected,但它的子类里新添加的方法、重写的方法、或者新增字段,默认没有修饰符时就是包内可见。比如你扩展 AbstractSequentialList 写了一个自定义链表:
- 如果没加
public就直接写void addFirst(E e),那这个方法对外不可见,即使它很有用; - 如果重写了
get(int index)却漏写@Override public E get(int index),只写了E get(int index),那它就变成包级方法,外部调用失败; - 抽象类本身不会强制你补全修饰符,编译器也不会报错——直到别人调用不到时才发现。
继承链中可见性只能变宽,不能变窄
Java 规定子类重写父类方法时,访问权限不能比父类更严格。例如父类是 public E get(int),子类就不能改成 protected 或 private。但反过来,如果你新加一个工具方法,比如 findNode(int),不加修饰符就默认只能同包访问——而多数业务代码不在同一包,等于白写。
- 常见疏忽:把本该
public的构造方法写成默认(无修饰符),导致外部无法 new 实例; - 另一个典型:把关键的
size()或isEmpty()重写后没加public,结果集合接口调用失败; - 抽象类的文档一般不强调“你必须补全修饰符”,容易被当成纯逻辑任务忽略。
接口契约与实现类可见性脱节
集合框架的设计哲学是“面向接口编程”。比如你实现了 List 接口,用户按 List<string> list = new YourCustomList();</string> 使用。但如果 YourCustomList 里关键方法是包级或 protected,那它虽然编译通过,实际运行时无法响应 list.add(...) 这样的接口调用(因为接口方法是 public,实现必须也是 public)。
- 接口方法签名隐含
public,但抽象基类不帮你自动补上; - IDE 不会高亮提醒“你重写的方法缺 public”,尤其在覆盖多个抽象/模板方法时容易漏;
- 测试常在同包下写,测得通,上线跨包调用就抛
IllegalAccessError或编译失败。
本质上这不是抽象类的问题,而是 Java 可见性机制和集合框架分层设计共同带来的“静默陷阱”——它不报错,但让类变得不可用。补全修饰符不是形式主义,是兑现接口承诺的必要动作。











