Подтвердить что ты не робот

Общие методы, возвращающие динамические типы объектов

Возможно, вопрос был задан раньше, но, как обычно, второй вы упоминаете слово generic, вы получаете тысячу ответов, объясняющих стирание типа. Я давно прошел эту фазу и много знаю о дженериках и их использовании, но эта ситуация немного более тонкая.

У меня есть контейнер, представляющий ячейку данных в электронной таблице, которая фактически хранит данные в двух форматах: в виде строки для отображения, но и в другом формате, в зависимости от данных (хранимых как объект). Ячейка также содержит трансформатор, который преобразует тип, а также проверяет достоверность для типа (например, IntegerTransformer проверяет, является ли строка допустимым целым числом, и если она возвращает целое число для хранения и наоборот).

Ячейка сама не набирается, так как я хочу изменить формат (например, изменить вторичный формат на float вместо integer или на необработанную строку), не перестраивая объект ячейки новым типом. предыдущая попытка использовала общие типы, но не могла изменить тип, который был определен, когда кодирование получилось очень громоздким с большим отражением.

Вопрос в следующем: как я могу получить данные из моей ячейки в типизированном виде? Я экспериментировал и обнаружил, что использование общего типа может быть выполнено с помощью метода, хотя ограничение не было определено

public class Cell {
    private String stringVal;
    private Object valVal;
    private Transformer<?> trans;
    private Class<?> valClass;

    public String getStringVal(){
        return stringVal;
    }

    public boolean setStringVal(){
        //this not only set the value, but checks it with the transformer that it meets constraints and updates valVal too
    }

    public <T> T getValVal(){
        return (T) valVal;
        //This works, but I don't understand why
    }
}

Бит, который меня отталкивает: это? он не может ничего отличить, нет ввода типа T, который сдерживает его, чтобы соответствовать чему-либо, на самом деле он ничего не говорит нигде. Наличие возвращаемого типа объекта ничего не делает, кроме как придания кастингам сложности.

В моем тесте я установил двойное значение, он сохранил Double (как объект), и когда я сделал Double testdou = testCell.getValVal(); он мгновенно работал, даже без предупреждения о предупреждении. однако, когда я сделал String teststr = testCell.getValVal(), я получил исключение ClassCastException. Неудивительно.

Есть два вида, которые я вижу по этому поводу:

Один: использование undefined Cast to, кажется, не что иное, как бэдж-способ поместить результат внутри метода, а не снаружи после его возвращения. Это очень удобно с точки зрения пользователя, но пользователь метода должен беспокоиться об использовании правильных вызовов: все это происходит, скрывая сложные предупреждения и проверки до времени выполнения, но, похоже, работает.

Вторая точка зрения: мне не нравится этот код: он не чист, это не то качество кода, которое я нормально горжусь письмом. Код должен быть правильным, а не просто работать. Ошибки должны быть пойманы и обработаны, и ожидается, что интерфейсы должны быть надежными, даже если единственным пользователем ожидания является сам, и я всегда предпочитаю гибкий универсальный и многоразовый метод для неудобного. Проблема в том, есть ли нормальный способ сделать это? Является ли это проницательным способом достижения бесприбыльного, все принимающего ArrayList, который возвращает все, что угодно, без кастинга? или здесь что-то мне не хватает. Что-то подсказывает мне, что я не должен доверять этому коду!

возможно, более философский вопрос, чем я предполагал, но я предполагаю, что я прошу.

edit: дальнейшее тестирование.

Я пробовал следующие два интересных фрагмента:

public <T> T getTypedElem() {
    T output = (T) this.typedElem;
    System.out.println(output.getClass());
    return output;
}

public <T> T getTypedElem() {
    T output = null;
    try {
        output = (T) this.typedElem;
        System.out.println(output.getClass());
    } catch (ClassCastException e) {
        System.out.println("class cast caught");
        return null;
    }
    return output;
}

При назначении double to typedElem и попытке поместить его в String я получаю исключение NOT на приведение к, но по возврату, а второй фрагмент не защищает. Выход из getClass - это java.lang.Double, предполагающий, что это динамически выводится из typedElem, но проверки типа уровня компилятора просто вынуждены уйти с пути.

В качестве примечания для обсуждения: существует также функция для получения valClass, что означает возможность выполнения проверки присваивания во время выполнения.

Edit2: result

Подумав о вариантах, которые я отправил с двумя решениями: одно легкое решение, но аннотировало функцию как @depreciated и второе решение, в котором вы передаете ему класс, который вы хотите попробовать сделать. таким образом, это выбор в зависимости от ситуации.

4b9b3361

Ответ 1

Вы можете попробовать маркеры типа:

public <T> T getValue(Class<T> cls) {
    if (valVal == null) return null;
    else {
        if (cls.isInstance(valVal)) return cls.cast(valVal);
        return null;
    }
}

Обратите внимание, что это не делает никакого преобразования (т.е. вы не можете использовать этот метод для извлечения Double, если valVal является экземпляром Float или Integer).

Вы должны получить, кстати, предупреждение компилятора о вашем определении getValVal. Это связано с тем, что кастинг не может быть проверен во время выполнения (Java generics работает с помощью "erasure", что по сути означает, что после компиляции забываются параметры типового типа), поэтому сгенерированный код больше похож:

public Object getValVal() {
    return valVal;
}

Ответ 2

Как вы обнаруживаете, существует ограничение на то, что может быть выражено с помощью системы типа Java, даже с генериками. Иногда существуют отношения между типами определенных значений, которые вы хотели бы утверждать, используя объявления типа, но вы не можете (или, возможно, вы можете, за счет избыточной сложности и долгого, подробного кода). Я думаю, что пример кода в этом сообщении (вопрос и ответы) является хорошей иллюстрацией этого.

В этом случае компилятор Java может сделать больше проверки типов, если вы сохранили представление объекта/строки внутри "трансформатора". (Возможно, вам придется переосмыслить, что это такое: возможно, это не просто "трансформатор".) Поместите обобщенную привязку в свой базовый класс Transformer и сделайте ту же самую привязку к типу "объекта".

Что касается получения значения из ячейки, то нет способа, чтобы проверка типа компилятора помогла вам там, так как значение может быть разных типов (и во время компиляции вы не знаете, какой тип объекта будет хранится в данной ячейке).

Я считаю, что вы также можете сделать что-то похожее на:

public <T> void setObject(Transformer<T> transformer, T object) {}

Если единственный способ установить трансформатор и объект через этот метод, проверка типа компилятора на аргументы предотвратит переход пары несовместимых трансформаторов/объектов в ячейку.

Если я понимаю, что вы делаете, тип Transformer, который вы используете, определяется исключительно типом объекта, который удерживает ячейка, верно? Если это так, вместо того, чтобы устанавливать трансформатор/объект вместе, я бы предоставил сеттер только для объекта и выполнил поиск хэша, чтобы найти соответствующий трансформатор (используя тип объекта как ключ). Хэш-поиск может выполняться каждый раз, когда значение задано, или когда оно преобразуется в строку. В любом случае это сработает.

Это, естественно, делает невозможным передачу неправильного типа Transformer.

Ответ 3

Я думаю, что вы статичный человек, но попробуйте: подумал ли вы об использовании динамического языка, такого как groovy для этой части?

Из вашего описания мне кажется, что типы больше мешают, чем помогают.

В groovy вы можете позволить динамическому тисту Cell.valVal получить легкое преобразование:

class Cell {
  String val
  def valVal
}

def cell = new Cell(val:"10.0")
cell.valVal = cell.val as BigDecimal
BigDecimal valVal = cell.valVal
assert valVal.class == BigDecimal
assert valVal == 10.0

cell.val = "20"
cell.valVal = cell.val as Integer
Integer valVal2 = cell.valVal
assert valVal2.class == Integer
assert valVal2 == 20

Где as все это необходимо для наиболее распространенных преобразований. Вы также можете добавить свой.

Если вам нужно преобразовать другие блоки кода, обратите внимание, что синтаксис Java действителен groovy, за исключением блока do { ... } while()