java访问者模式需accept和visit两个方法,因java仅支持单分派:accept按接收者类型分派(第一次),visit按参数类型分派(第二次),共同模拟双重分派;缺一不可,否则无法正确绑定运行时方法。

为什么Java里访问者模式非要写两个 accept 和 visit 方法?
因为Java只有单分派,accept 负责按接收者类型分派(第一次),visit 再按参数类型分派(第二次)——合起来才模拟出双重分派。不这么拆,编译器就只能在调用时看到参数的静态类型,没法根据实际运行时类型选对方法。
常见错误是只写 visit,忘了在元素类里加 accept,结果永远调不到具体实现;或者 accept 里传错 this 类型,比如写成 visitor.visit(this) 却没给 visit 声明对应重载,直接编译报错 no suitable method found。
-
accept必须定义在被访问的元素接口/基类中,且每个子类都要覆写,调用visitor.visit(this) -
visit方法必须在访问者接口中一一声明,参数类型要和每个元素子类严格匹配 - 新增元素类时,要同步改访问者接口 + 所有实现类,这是模式最痛的扩展点
Visitor 接口要不要用泛型?Visitor<t></t> 会踩什么坑?
别用。泛型会让 visit 方法签名变得不可预测,尤其当多个元素类返回不同类型时,访问者实现类会迅速变成类型擦除地狱。JDK 自带的 FileVisitor 也没用泛型,就是靠方法重载撑住的。
典型翻车场景:定义了 visit(FileElement e) 和 visit(DbElement e),再想让 visit 返回值统一为 T,结果发现每个实现类得写一堆 return (T) result,运行时 ClassCastException 隐患很大。
- 返回值统一用
void或基础类型(如boolean控制是否继续遍历)更稳妥 - 如果真需要不同返回值,用访问者内部字段暂存,而不是靠泛型推导
- IDE 自动生成重载方法时,注意检查参数类型是否精确匹配元素类,别漏掉
final子类
遇到 NullPointerException 在 visitor.visit(xxx) 这一行,怎么快速定位?
八成是 visitor 实例为空,不是元素为空。访问者模式里,元素对象通常由结构持有、生命周期明确,但访问者常是临时构造或依赖注入失败,容易忽略判空。
另一个可能是元素类的 accept 方法里写了 if (visitor != null) visitor.visit(this),但忘了在访问者接口里定义对应 visit 方法,导致编译不过,开发者手动删了该重载,又没删调用,结果运行时报 AbstractMethodError —— 表象像 NPE,实则是链接错误。
- 在
accept开头加Objects.requireNonNull(visitor),把问题提前暴露在明确位置 - 用 IDE 的 “Find Usages” 查
Visitor接口所有visit方法,确认每个元素子类都有对应实现 - 单元测试里故意传
null访问者,看是否抛预期异常,而不是静默失败
用 instanceof + 强转替代访问者模式行不行?
小结构、少操作时可以,但只要涉及三个以上元素类型、两种以上操作(比如“导出JSON”+“校验权限”+“生成SQL”),硬写 if (x instanceof A) { ... } else if (x instanceof B) { ... } 就会失控:逻辑散落、无法复用、改一个操作要扫全量判断块。
性能上,现代 JVM 对 instanceof 优化很好,但访问者模式的虚方法调用也有内联机会,两者差异微乎其微;真正拖慢的是维护成本——每次加新元素,你得去每个操作里补 else if,而访问者只要改接口+一个实现类。
- 临时脚本、POC 阶段用
instanceof更快上手 - 一旦操作逻辑开始复用(比如多个地方都要“计算权重”),立刻抽成访问者
- 注意:
instanceof判断后强转,务必用同一变量,避免二次instanceof检查浪费
访问者最难的不是写对那几个方法,而是决定“哪些行为该放进 Visitor,哪些该留在元素内部”。边界模糊时,先写个 toString() 或 toDto() 这种纯数据转换,再慢慢把业务规则移进来——动得越晚,抽象越准。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











