2013-04-20 2 views
19

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

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

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

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

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

Явная Cast

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: how to return Generic type, Java generic return type все они, кажется, говорят, что такое приведение является опасным, хотя я не вижу большой разницы между тремя, учитывая этот метод, который вы бы предпочли?

Благодаря

ответ

3

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

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

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 ...

1

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

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

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

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

1

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

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

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

Update: «Явная Cast» имеет одно преимущества - абонент (пользователь контейнера) получает напоминание что существует классный риск!

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

3

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

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

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

    //getters and setters 
} 

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

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

1

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

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

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

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

3

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

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

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 не компилируется ...).

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

Смежные вопросы