【问题标题】:Check generic type before casting at runtime在运行时强制转换之前检查泛型类型
【发布时间】:2018-05-16 15:13:35
【问题描述】:

我们的任务是创建一个对象结构来评估表达式(例如“1 + 2”或“true & false”)。提供了解析器,以及用于创建表达式对象的工厂接口。

我们对不同的表达式使用泛型:Expression<Integer> 返回一个整数,Expression<Boolean> 返回一个布尔值,等等。

我们面临的问题是提供的接口使用了原始类型Expression,例如:

public Expression createSumExpression(Expression left, Expression right);

只有当操作数的类型为Expression<Integer>Expression<Double> 时才定义求和表达式。我们如何检查这个? 由于类型擦除,类型信息在运行时不可用,如果不正确只会导致ClassCastException

我可以修改所有内容,除了 createSumExpressioncreateAndExpression 函数。


这是演示问题的一段简化代码。它可以工作,但看起来不是很好,并且会给出多个警告。

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;
    // ...
}

【问题讨论】:

  • 你能修改Expression本身吗?
  • @JornVernee 是的,我可以修改Expression
  • IMO,这是接口签名的概念问题。如果createSumExpression 只接受IntegerDouble,则方法的签名应该是public Expression createSumExpression(Expression&lt;? extends Number&gt; left, Expression&lt;? extends Number&gt; right); 以防止出现任何异常。而且由于不能修改,所以没有"clear"解决方案。

标签: java generics casting


【解决方案1】:

在这种情况下,泛型并不是真正有用,因为大多数类型将在运行时从输入字符串派生,而泛型是编译时的事情。没有办法提前知道类型。它只在像这样的简单示例中真正有用。

所以我的建议是放弃泛型并实现自己的动态类型系统。例如:

enum Type {
    Integer,
    Boolean;
}

class ScriptObject {
    private final Object value;
    private final Type type;

    private ScriptObject(Object value, Type type) {
        this.value = value;
        this.type = type;
    }

    public static ScriptObject of(boolean b) {
        return new ScriptObject(b, Type.Boolean);
    }

    public static ScriptObject of(int i) {
        return new ScriptObject(i, Type.Integer);
    }

    public int asInt() {
        return (int) value;
    }

    public boolean asBoolean() {
        return (boolean) value;
    }

    public static boolean areType(Type type, ScriptObject...objects) {
        for(ScriptObject o : objects) {
            if(o.type != type)
                return false;
        }

        return true;
    }

    @Override
    public String toString() {
        return value.toString();
    }

}

abstract class Expression {
    public abstract ScriptObject getValue();
}

class BasicExpression extends Expression {
    private final ScriptObject value;

    public BasicExpression(ScriptObject value) {
        this.value = value;
    }

    @Override
    public ScriptObject getValue() {
        return value;
    }

}

abstract class BinaryExpression extends Expression {
    private final Expression left;
    private final Expression right;

    public BinaryExpression (Expression l, Expression r) {
        this.left = l;
        this.right = r;
    }

    @Override
    public ScriptObject getValue() {        
        return operation(left.getValue(), right.getValue());
    }

    protected abstract ScriptObject operation(ScriptObject left, ScriptObject right);

}

转换您的示例将如下所示:

public static void main(String[] args) {
    Expression left1 = new BasicExpression(ScriptObject.of(42));
    Expression right1 = new BasicExpression(ScriptObject.of(3));     
    Expression sum = createSumExpression(left1, right1);
    System.out.printf("%s + %s = %s%n",left1.getValue(), right1.getValue(), sum.getValue());

    Expression left2 = new BasicExpression(ScriptObject.of(true));
    Expression right2 = new BasicExpression(ScriptObject.of(false));
    Expression and = createAndExpression(left2, right2);
    System.out.printf("%s && %s = %s%n",left2.getValue(), right2.getValue(), and.getValue());

    createAndExpression(left1, right2).getValue(); // fails with: Can not apply '&' to '42' and 'false' 
}

private static Expression createSumExpression(Expression left, Expression right) {
    return new BinaryExpression(left, right) {
        @Override
        protected ScriptObject operation(ScriptObject left, ScriptObject right) {
            if(!ScriptObject.areType(Type.Integer, left, right)) {
                throw new RuntimeException("Can not apply '+' to '" + left + "' and '" + right + "'");
            }
            return ScriptObject.of(left.asInt() + right.asInt());
        }
    };
}

private static Expression createAndExpression(Expression left, Expression right) {
    return new BinaryExpression(left, right) {
        @Override
        protected ScriptObject operation(ScriptObject left, ScriptObject right) {
            if(!ScriptObject.areType(Type.Boolean, left, right)) {
                throw new RuntimeException("Can not apply '&' to '" + left + "' and '" + right + "'");
            }
            return ScriptObject.of(left.asBoolean() && right.asBoolean());
        }
    };
}

我试图使示例保持简单,但有些注意事项是,您可能希望创建一堆用于类型检查的辅助函数,并使用自定义异常类型,可能是一个已检查的异常类型,然后您可以捕获并输出它是某处的消息,而不仅仅是崩溃。

您必须自己进行类型检查,这需要更多的工作,但好处是您可以完全决定何时(甚至是否)检查类型,以及提供什么样的错误消息。

还有为类型创建转换机制的空间,因此您可以尝试将其转换为所需的类型,而不是只检查类型是否是您想要的,这不是一个功能(除了 auto-( un)boxing) Java 的类型系统。

【讨论】:

  • 这是有道理的。非常感谢您的全面回答!
【解决方案2】:

如果您希望在编译时类型安全而不检查由于可能的运行时失败而导致的异常,您还可以定义多个工厂。

如果为某种类型调用无效的方法,仍然会抛出异常,但不能创建错误类型的表达式。

例如,您可以有一个仅处理 Integer 表达式的 ExpressionFactory

private static class IntegerExpressionFactory implements IExpressionFactory<Expression<Integer>> {

    @Override
    public Expression<Integer> createSumExpression(Expression<Integer> left, Expression<Integer> right) {
        return new BinaryExpression<>(left, right) {
            @Override
            protected Integer operation(Expression<Integer> left, Expression<Integer> right) {
                return left.getValue() + right.getValue();
            }
        };
    }

    @Override
    public Expression<Integer> createAndExpression(Expression<Integer> left, Expression<Integer> right) {
        throw new UnsupportedOperationException("Can't perform AND operation on integer expressions.");
    }
}

它将对您现有的代码产生以下影响:

public static void main(String[] args) {
    IntegerExpressionFactory integerExpressionFactory = new IntegerExpressionFactory();
    Expression<Integer> left1 = new BasicExpression<>(42);
    Expression<Integer> right1 = new BasicExpression<>(3);
    Expression<Integer> sum = integerExpressionFactory.createSumExpression(left1, right1);
    System.out.printf("%d + %d = %d%n", left1.getValue(), right1.getValue(), sum.getValue()); // 42 + 3 = 45

    integerExpressionFactory.createAndExpression(left1, right1); // UnsupportedOperationException

    Expression<Boolean> left2 = new BasicExpression<>(true);
    Expression<Boolean> right2 = new BasicExpression<>(false);
    integerExpressionFactory.createAndExpression(left2, right2); // Does not compile
}

它不像自定义类型系统那样灵活,您可以在其中组合多种类型的操作,但它是一种更直接的实现。

【讨论】:

    【解决方案3】:

    因为您无法在运行时访问该信息,所以如果没有大量非常难看的 try/catch 块,就无法明确知道泛型的类型。因此,请在方法签名之前添加 @SuppressWarnings("unchecked") 行,并确保清理输入。

    @SuppressWarnings("unchecked")
    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();
            }
        };
    }
    
    @SuppressWarnings("unchecked")
    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();
            }
        };
    }
    

    @SuppressWarnings("unchecked") 将告诉 java 编译器不要担心未经检查的转换并继续编译。

    清理您的输入,以便仅将 Expression&lt;Integer&gt;Expression&lt;Double&gt;createSumExpression() 一起使用。表达式的boolean 侧也是如此

    【讨论】:

    • 这并不能解决问题,因为我们无法控制createSumExpression() 的调用方式。
    • 这不会解决运行时异常ClassCastException。我不明白这一点。
    • 解决类转换异常的方法是从一开始就永远不要遇到它。如果您从不尝试添加布尔值或对整数进行按位运算,您将不会遇到ClassCastExeption。如果您只向“创建方法”提供他们想要的数据,那么您就可以了。如果你真的想要,你可以尝试在 try/catch 块中转换变量,但你仍然有不匹配的类。
    猜你喜欢
    • 1970-01-01
    • 2014-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-20
    • 1970-01-01
    相关资源
    最近更新 更多