Проверьте универсальный тип перед кастом во время выполнения - PullRequest
0 голосов
/ 16 мая 2018

Нам было дано задание создать структуру объекта для оценки выражений (например, «1 + 2» или «true & false»).Предоставляется синтаксический анализатор, а также фабричный интерфейс для создания объектов выражений.

Мы используем обобщенные значения для различных выражений: Expression<Integer> возвращает целое число, Expression<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;
    // ...
}

Ответы [ 3 ]

0 голосов
/ 16 мая 2018

Обобщения не очень полезны в этом случае, так как большинство типов будут получены из входной строки во время выполнения, а обобщения - это время компиляции. Там нет никакого способа узнать типы заранее каких-либо способов. Это действительно полезно только в простом примере, подобном этому.

Поэтому я предлагаю отказаться от обобщений и реализовать собственную динамическую систему типов. Например что-то вроде:

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) бокс) системы типов Java.

0 голосов
/ 16 мая 2018

Если вы предпочитаете безопасность типов во время компиляции без проверки исключений из-за возможных сбоев во время выполнения, вы также можете определить несколько фабрик.

Исключения по-прежнему могут создаваться при вызове недопустимого метода дляопределенного типа, но вы не можете создать выражение неправильных типов.

Например, у вас может быть ExpressionFactory, который обрабатывает только Integer выражений.

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.");
    }
}

Itбудет иметь следующий эффект на ваш существующий код:

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
}

Она менее гибкая, чем настраиваемая система ввода, в которой вы можете комбинировать операции нескольких типов, но это более простая реализация.

0 голосов
/ 16 мая 2018

Поскольку вы не можете получить доступ к этой информации во время выполнения, нет способа окончательно узнать, что это за тип универсального, без большого количества блоков 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<Integer> и Expression<Double> использовались только с createSumExpression().и то же самое относится к boolean стороне выражений

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...