泛型类型擦除场景下,如何校验表达式工厂方法的操作数类型?
我们需要构建一个用于计算表达式(如"1 + 2"或"true & false")的对象结构,目前已提供了解析器与表达式对象工厂接口。表达式使用泛型设计:Expression<Integer>返回Integer,Expression<Boolean>返回Boolean等。
但当前提供的接口使用了原始类型Expression,例如方法签名public Expression createSumExpression(Expression left, Expression right);,而求和表达式仅支持Expression<Integer>或Expression<Double>类型的操作数。由于泛型类型擦除,运行时无法直接获取类型信息,错误使用时只会抛出ClassCastException。
要求:除createSumExpression和createAndExpression方法外,其余代码均可修改,寻求在不修改指定工厂方法的前提下,实现操作数类型校验的可行方案。
现有简化示例代码
Main.java
public class Main { public static void main(String[] args) { Expression<?> left1 = new BasicExpression<>(42); Expression<?> right1 = new BasicExpression<>(3); Expression<?> sum = createSumExpression(left1, right1); System.out.printf("%d + %d = %d%n",left1.getValue(), right1.getValue(), sum.getValue()); Expression<?> left2 = new BasicExpression<>(true); Expression<?> right2 = new BasicExpression<>(false); Expression<?> and = createAndExpression(left2, right2); System.out.printf("%b & %b = %b%n",left2.getValue(), right2.getValue(), and.getValue()); } private static Expression createSumExpression(Expression left, Expression right) { // Raw types because of given interface return new BinaryExpression<Integer,Expression<Integer>,Expression<Integer>>(left, right) { @Override protected Integer operation(Expression<Integer> left, Expression<Integer> right) { return left.getValue() + right.getValue(); } }; } private static Expression createAndExpression(Expression left, Expression right) { // Raw types because of given interface return new BinaryExpression<Boolean,Expression<Boolean>,Expression<Boolean>>(left, right) { @Override protected Boolean operation(Expression<Boolean> left, Expression<Boolean> right) { return left.getValue() & right.getValue(); } }; } }
Expression.java
abstract public class Expression<V> { public abstract V getValue(); }
BasicExpression.java
public class BasicExpression<V> extends Expression<V> { public BasicExpression(V value) { this.value = value; } @Override public V getValue() { return value; } private V value; }
BinaryExpression.java
abstract public class BinaryExpression<V, L, R> extends Expression<V> { public BinaryExpression (L l, R r) { this.left = l; this.right = r; } @Override public V getValue() { return operation(left, right); } abstract protected V operation(L left, R right); private L left; private R right; }
提供的工厂接口
/** * @param <E> * Your class for representing an expression. */ public interface IExpressionFactory<E> { public E createSumExpression(E left, E right) throws ModelException; public E createAndExpression(E left, E right) throws ModelException; // ... }
既然不能碰createSumExpression和createAndExpression的方法签名,那我们得换个思路,从表达式对象本身入手,让它能“记住”自己的类型,这样在创建组合表达式的时候就能提前做校验,而不是等到运行时才炸出ClassCastException。
方案1:给表达式加个类型令牌
最简单的办法就是给Expression基类加个Class<V>类型的字段,用来存储表达式值的类型。这样不管泛型怎么擦除,我们都能在运行时拿到明确的类型信息,直接做校验。
先修改Expression.java:
abstract public class Expression<V> { private final Class<V> valueType; protected Expression(Class<V> valueType) { this.valueType = valueType; } public abstract V getValue(); public Class<V> getValueType() { return valueType; } }
然后更新BasicExpression,顺便加个方便调用的构造器:
public class BasicExpression<V> extends Expression<V> { private V value; // 显式传入类型的构造器 public BasicExpression(V value, Class<V> valueType) { super(valueType); this.value = value; } // 自动获取类型的重载构造器,基本类型会自动装箱成包装类 public BasicExpression(V value) { this(value, (Class<V>) value.getClass()); } @Override public V getValue() { return value; } }
接下来就可以在createSumExpression里加校验逻辑了:
private static Expression createSumExpression(Expression left, Expression right) throws ModelException { // 先判断是不是数字类型(Integer/Double都算) boolean isLeftNumeric = Number.class.isAssignableFrom(left.getValueType()); boolean isRightNumeric = Number.class.isAssignableFrom(right.getValueType()); if (!isLeftNumeric || !isRightNumeric) { throw new ModelException("求和表达式只支持数字类型的操作数哦"); } // 再判断类型是否一致,避免Integer加Double这种情况(如果要支持自动转型可以跳过这步) if (!left.getValueType().equals(right.getValueType())) { throw new ModelException("求和表达式的两个操作数类型得一样呀"); } // 针对Integer的情况,Double的话同理加个分支 if (Integer.class.equals(left.getValueType())) { return new BinaryExpression<Integer, Expression<Integer>, Expression<Integer>>( (Expression<Integer>) left, (Expression<Integer>) right) { @Override protected Integer operation(Expression<Integer> left, Expression<Integer> right) { return left.getValue() + right.getValue(); } }; } else if (Double.class.equals(left.getValueType())) { return new BinaryExpression<Double, Expression<Double>, Expression<Double>>( (Expression<Double>) left, (Expression<Double>) right) { @Override protected Double operation(Expression<Double> left, Expression<Double> right) { return left.getValue() + right.getValue(); } }; } else { throw new ModelException("暂时不支持这种数字类型的求和哦"); } }
createAndExpression的校验逻辑类似:
private static Expression createAndExpression(Expression left, Expression right) throws ModelException { if (!Boolean.class.equals(left.getValueType()) || !Boolean.class.equals(right.getValueType())) { throw new ModelException("与表达式只支持布尔类型的操作数哦"); } return new BinaryExpression<Boolean, Expression<Boolean>, Expression<Boolean>>( (Expression<Boolean>) left, (Expression<Boolean>) right) { @Override protected Boolean operation(Expression<Boolean> left, Expression<Boolean> right) { return left.getValue() && right.getValue(); // 这里用逻辑与更符合日常语义,位与&一般用在整数上 } }; }
方案2:加个泛型工具方法提前做检查
除了运行时校验,我们还可以在调用工厂方法前,用一个泛型辅助方法给编译期提个醒,同时兜底运行时校验。比如写个工具方法:
@SuppressWarnings("unchecked") private static <T> Expression<T> checkExprType(Expression expr, Class<T> targetType) throws ModelException { if (!targetType.isAssignableFrom(expr.getValueType())) { throw new ModelException("这个表达式不是" + targetType.getName() + "类型的哦"); } return (Expression<T>) expr; }
然后在Main里调用的时候先做检查:
Expression<Integer> left1 = checkExprType(new BasicExpression<>(42), Integer.class); Expression<Integer> right1 = checkExprType(new BasicExpression<>(3), Integer.class); Expression<?> sum = createSumExpression(left1, right1);
这样如果传错类型,运行时会立刻抛出明确的错误,而不是等到取值的时候才崩溃,调试起来也更方便。
方案3:简化BinaryExpression的泛型结构
当前BinaryExpression的泛型<V, L, R>有点绕,其实L和R都是Expression的子类,而且操作数的类型和结果类型是相关的。我们可以把它改成更简洁的结构:
abstract public class BinaryExpression<V> extends Expression<V> { private final Expression<? extends V> left; private final Expression<? extends V> right; public BinaryExpression(Expression<? extends V> left, Expression<? extends V> right) { super(left.getValueType()); // 假设左右类型一致,或者可以取共同父类 this.left = left; this.right = right; } @Override public V getValue() { return operation(left.getValue(), right.getValue()); } abstract protected V operation(V leftVal, V rightVal); }
这样求和表达式的代码会更简洁:
private static Expression createSumExpression(Expression left, Expression right) throws ModelException { if (!(left.getValueType().equals(Integer.class) && right.getValueType().equals(Integer.class))) { throw new ModelException("求和表达式需要Integer类型的操作数"); } return new BinaryExpression<Integer>((Expression<Integer>) left, (Expression<Integer>) right) { @Override protected Integer operation(Integer leftVal, Integer rightVal) { return leftVal + rightVal; } }; }
这种方式让BinaryExpression的职责更清晰,类型校验也更容易维护。
内容的提问来源于stack exchange,提问作者tttapa

