You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

泛型类型擦除场景下,如何校验表达式工厂方法的操作数类型?

泛型表达式工厂的类型校验方案问题

我们需要构建一个用于计算表达式(如"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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:15:20