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

Java-дженерики и типизация

У меня есть плохо созданный контейнерный объект, который объединяет значения разных типов java (String, Boolean и т.д.)

public class BadlyCreatedClass {
    public Object get(String property) {
        ...;
    }
};

И мы извлекаем из него значения таким образом

String myStr = (String) badlyCreatedObj.get("abc");
Date myDate = (Date) badlyCreatedObj.get("def");

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

Явный ролик

String myStr = (String) badlyCreatedObj.get("abc")
Date myDate = (Date) badlyCreatedObj.get("def");

Использование родового литья

public <X> X genericGet(String property) {

}

public String getString(String property) { 
return genericGet(property); 
}

public Date getDate(String property) { 
return genericGet(property); 
}

Использование Class.cast

<T> T get(String property, Class<T> cls) {
    ;
}

Я рассмотрел несколько связанных вопросов о SO Java generic function: как вернуть общий тип, Java generic return type все они, кажется, говорят, что такое типирование опасно, хотя я не вижу большой разницы между тремя, учитывая этот метод, который вы бы предпочли?

Спасибо

4b9b3361

Ответ 1

Чтобы дать быстрый ответ, не углубляясь в хорошую практику программирования...

Я бы использовал:

private <X> X genericGet(String property) {

}

public String getString(String property) { 
//... checks on property (String specific)...
Object obj = genericGet(property);
//... checks if obj is what is expected and if good return it
return obj; 
}

public Date getDate(String property) { 
//... checks on property (Date specific)...
Object obj = genericGet(property);
//... checks if obj is what is expected and if good return it
return obj
}

уведомить частный genericGet. Таким образом, я могу проверить, является ли свойство get тем, что я ожидаю получить и обработать его правильным образом.

Я могу добавить проверки в getString, зависит от свойства, чтобы убедиться, что ответ будет объектом String.

Я мог бы сделать другие проверки для getDate в свойстве, чтобы убедиться, что это будет дата, которая будет возвращена.

Etc...

Ответ 2

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

Что-то вроде:

class Holder {
    private long id;
    private String name;
    private Date dateofBirth;

    //getters and setters
}

Затем извлекается на основе идентификатора, при этом не требуется никакого литья.

Вы также можете сказать нам, что вы пытаетесь сделать.

Ответ 3

Общий подход к литью заставляет компилятор выпустить предупреждение без предупреждения. Неконтролируемое предупреждение указывает на то, что вопрос о поставке не проверяется (полностью) во время выполнения, то есть может преуспеть, даже если значение не соответствует типу. Это может привести к тому, что переменная будет удерживать значение, не совместимое с его объявленным типом, ситуация, в которой спецификация языка Java вызывает загрязнение кучи.

Следующая программа демонстрирует это:

class Holder {
    Object value;

    Holder(Object value) {
        this.value = value;
    }

    <T> T get() {
        return (T) value;
    }
}

class C<T> {
    T value;

    C(Holder h) {
        value = h.get();
    }
}

public class Test {
    public static void main(String [] args) throws IOException {
        Holder holder = new Holder("Hello");
        C<Integer> c = new C<Integer>(holder);
        System.out.println("I just put a String into a variable of type Integer");

        // much later, possibly in a different part of your program
        c.value.longValue(); // throws ClassCastException
    }
}

Поэтому я настоятельно рекомендую использовать проверенный литой. Проверяются как обычный бросок (ваш первый подход), так и отражающий литье (ваш третий подход). Тем не менее, отражающий эффект не будет работать с параметризованными типами (List<String>.class не компилируется...).

Простейшим и самым гибким безопасным решением является обычный приведение.

Ответ 4

Я бы предпочел использовать общий ролик. Почему?

  • Эксклюзивный отбор всегда сложнее поддерживать. Когда вы читаете код, вы просто не знаете, как этот метод может вернуться. Более того, вероятность того, что метод будет использоваться неверно, а какой-то ClassCastException будет возникать во время runtimme, довольно высока.

  • Приведение класса еще сложнее в обслуживании. По-моему, вы создаете таким образом то, что можно назвать спагетти-кодом.

  • При создании таких методов, как getString и getDate, вы даете очень понятный интерфейс вашему классу. Более того, всегда можно получить объекты других классов, чем String и Date, потому что вы также предоставляете общий метод.

Ответ 5

Как вы уже упоминали, все вышеперечисленные подходы опасны и могут привести к ClassCastException во время выполнения.

Если это действительно необходимо, я бы предпочел подход 'generic cast', поскольку он делает интерфейс явным и саморасширяющимся (сделайте genericGet private в этом случае). Разумеется, вам нужно создать код boilderplate для каждого class в вашем контейнере. Таким образом, преимущество "Class.cast" заключается в том, что вам не нужны эти шаблонные методы.

Заключение: если в контейнере есть определенное количество классов, я бы пошел с "родовым приведением". Если вам необходимо поддерживать бесконечное количество классов, я бы пошел с "Class.cast"

Обновление: "Явное литье" имеет одно преимущество - вызывающий (пользователь контейнера) получает напоминание о том, что существует риск, связанный с классом!

Просто мнение...

Ответ 6

Поскольку все параметры включают типу, они все как-то "небезопасны" и могут терпеть неудачу с ClassCastExceptions.

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

Но вам все равно нужен способ получения "необычных" типов из вашего объекта. Для этого я бы пошел с явным литом или классом. Причина этого в том, что я думаю, что эти два способа - это те, которые в основном используются. Даже если общий вызов метода будет работать нормально, я думаю, что вызовы методов типа obj.genericGet<String>("myproperty"); - это то, о чем не знают все разработчики java. Это редко встречается на практике.

Лично будет добавлять метод getObject() к вспомогательным методам, даже если мне не нравится перемещать тип, переданный пользователю объекта. Преимущество этого в том, что у вас будет согласованный интерфейс, и это то, чего я ожидал бы, если бы увидел такие методы, как getString() или getDate().