基于节点的Java解释器如何处理不同数据类型的二元运算问题
解决方案
核心问题说明
Java的方法重载是编译时静态绑定,只会根据参数的编译时类型匹配方法,你传入的lresult和rresult编译时都是LiteralNode类型,所以不管运行时实际是哪个子类,都会触发兜底的通用方法,这是当前问题的根本原因。要实现运行时根据两个操作数的实际类型匹配处理逻辑,有两种成熟的落地方案:
方案1:双重访问者模式(双分派)
通过两次动态绑定依次确定两个操作数的实际类型,完全避免instanceof判断,逻辑和类型强绑定。
实现步骤
- 给
LiteralNode抽象类新增分派方法:
public abstract class LiteralNode extends Node { public abstract Object getValue(); // 第一次分派:确定当前操作数类型,接收第二个操作数 public abstract LiteralNode acceptAdd(LiteralNode other); // 第二次分派:确定第二个操作数类型,接收已确定类型的第一个操作数 public abstract LiteralNode acceptAddSecond(IntegerNode first); public abstract LiteralNode acceptAddSecond(DoubleNode first); public abstract LiteralNode acceptAddSecond(StringNode first); // 新增自定义类型时在此处对应加方法即可 }
- 每个具体Literal子类实现上述分派方法,示例
IntegerNode实现:
public class IntegerNode extends LiteralNode { private Integer value; // 原有构造方法、getValue、execute逻辑保持不变 @Override public LiteralNode acceptAdd(LiteralNode other) { // 触发第二次分派,传入已确定类型的当前对象 return other.acceptAddSecond(this); } @Override public LiteralNode acceptAddSecond(IntegerNode first) { // 两个整数相加逻辑 return new IntegerNode(first.getValue() + this.value); } @Override public LiteralNode acceptAddSecond(DoubleNode first) { // 浮点数加整数逻辑 return new DoubleNode(first.getValue() + this.value); } @Override public LiteralNode acceptAddSecond(StringNode first) { // 字符串加整数的错误处理 throw new IllegalArgumentException("String类型不支持与Integer做加法运算"); } }
DoubleNode、StringNode按相同规则实现所有accept方法即可,每种类型组合的运算逻辑直接写在对应实现里。
3. 修改AdditionNode的执行逻辑:
public LiteralNode execute() { LiteralNode lresult = left.execute(); LiteralNode rresult = right.execute(); return lresult.acceptAdd(rresult); }
原有的Operations类的静态重载方法可以全部移除,逻辑都拆分到各个节点类的实现中。
方案2:类型标记查表法
通过类型枚举标记+Map存储运算逻辑,所有处理逻辑集中管理,不需要修改节点类结构。
实现步骤
- 定义类型枚举,给
LiteralNode加类型标记方法:
public enum NodeType { INTEGER, DOUBLE, STRING, CUSTOM_TYPE } public abstract class LiteralNode extends Node { public abstract Object getValue(); public abstract NodeType getType(); }
每个具体子类实现getType返回对应的枚举值,比如IntegerNode返回NodeType.INTEGER。
2. 在Operations类中维护运算逻辑查表:
import java.util.Map; import java.util.HashMap; import java.util.function.BiFunction; public final class Operations { // Key为两个操作数的类型组合,Value为对应的运算处理函数 private static final Map<Map.Entry<NodeType, NodeType>, BiFunction<LiteralNode, LiteralNode, LiteralNode>> ADD_HANDLERS = new HashMap<>(); static { // 注册Integer+Integer的处理逻辑 ADD_HANDLERS.put(Map.entry(NodeType.INTEGER, NodeType.INTEGER), (a, b) -> { Integer aVal = (Integer) a.getValue(); Integer bVal = (Integer) b.getValue(); return new IntegerNode(aVal + bVal); }); // 注册Double+Integer的处理逻辑 ADD_HANDLERS.put(Map.entry(NodeType.DOUBLE, NodeType.INTEGER), (a, b) -> { Double aVal = (Double) a.getValue(); Integer bVal = (Integer) b.getValue(); return new DoubleNode(aVal + bVal); }); // 其他支持的运算组合按需注册即可 } public static LiteralNode add(LiteralNode a, LiteralNode b) { Map.Entry<NodeType, NodeType> key = Map.entry(a.getType(), b.getType()); BiFunction<LiteralNode, LiteralNode, LiteralNode> handler = ADD_HANDLERS.get(key); if (handler == null) { throw new IllegalArgumentException(String.format("不支持%s与%s的加法运算", a.getType(), b.getType())); } return handler.apply(a, b); } }
AdditionNode的原有调用逻辑不需要修改,直接调用Operations.add(lresult, rresult)即可。
方案选型建议
- 如果你的节点类型数量少,但运算种类多(加减乘除、比较、逻辑运算等),优先选双重访问者模式,逻辑按类型拆分,避免单类代码过度臃肿。
- 如果你的节点类型数量多,但运算种类少,优先选查表法,所有逻辑集中管理,新增类型或运算时不需要修改现有节点类的代码。
内容的提问来源于stack exchange,提问作者GC Wang
相关产品推荐
相关产品推荐

