Как решить проблемы с предупреждениями о небезопасном приведении типов?
Описание проблемы:
Я получаю предупреждение в Eclipse следующего вида:
Безопасность типов: Непроверенное приведение из Object в HashMap
Это связано с вызовом API, над которым у меня нет контроля, которое возвращает объект типа Object:
HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {
HashMap<String, String> theHash = (HashMap<String, String>) session.getAttribute("attributeKey");
return theHash;
}
Я хотел бы избежать предупреждений Eclipse, если это возможно, так как они теоретически указывают как минимум на потенциальную проблему в коде. Пока не нашел хорошего способа избавиться от этого предупреждения. Я могу вынести строку, вызывающую предупреждение, в отдельный метод и добавить аннотацию @SuppressWarnings("unchecked")
к этому методу, тем самым ограничив влияние блока кода, где я игнорирую предупреждения. Есть ли лучшие варианты? Я не хочу отключать эти предупреждения в Eclipse.
Ранее код выглядел проще, но все равно вызывал предупреждения:
HashMap getItems(javax.servlet.http.HttpSession session) {
HashMap theHash = (HashMap) session.getAttribute("attributeKey");
return theHash;
}
Проблема заключалась в том, что при попытке использовать хеш, вы также получали предупреждения:
HashMap items = getItems(session);
items.put("this", "that");
// Безопасность типов: Метод put(Object, Object) принадлежит сырому типу HashMap. Обращения к обобщенному типу HashMap<K,V> должны быть параметризованы.
Вопрос:
Как лучше устранить предупреждение о безопасности типов в Eclipse при работе с возвращаемыми объектами типа Object из API, над которым у меня нет контроля?
5 ответ(ов)
Похоже, я сам пришел к ответу на свой вопрос, но не уверен, стоит ли это того! 😃
Проблема в том, что приведение типа не проверяется. Поэтому приходится делать это самостоятельно. Нельзя просто использовать instanceof
для проверки параметризованного типа, так как информация о параметризированном типе недоступна в момент выполнения: она была устранена на этапе компиляции.
Тем не менее, вы можете произвести проверку для каждого элемента в хэш-карте с помощью instanceof
, и таким образом создать новый хэш, который будет типобезопасным. И вы не вызовете никаких предупреждений.
Благодаря mmyers и Esko Luontola я параметризовал код, который изначально написал здесь, чтобы его можно было обернуть в утилитный класс и использовать для любой параметризованной HashMap
. Если вы хотите лучше понять код и не очень знакомы с генериками, я рекомендую посмотреть историю правок этого ответа.
public static <K, V> HashMap<K, V> castHash(HashMap input,
Class<K> keyClass,
Class<V> valueClass) {
HashMap<K, V> output = new HashMap<K, V>();
if (input == null)
return output;
for (Object key: input.keySet().toArray()) {
if ((key == null) || (keyClass.isAssignableFrom(key.getClass()))) {
Object value = input.get(key);
if ((value == null) || (valueClass.isAssignableFrom(value.getClass()))) {
K k = keyClass.cast(key);
V v = valueClass.cast(value);
output.put(k, v);
} else {
throw new AssertionError(
"Не удается привести к HashMap<"+ keyClass.getSimpleName()
+", "+ valueClass.getSimpleName() +">"
+", значение "+ value +" не является "+ valueClass.getSimpleName()
);
}
} else {
throw new AssertionError(
"Не удается привести к HashMap<"+ keyClass.getSimpleName()
+", "+ valueClass.getSimpleName() +">"
+", ключ "+ key +" не является " + keyClass.getSimpleName()
);
}
}
return output;
}
Это требует значительных усилий, возможно, за очень небольшую награду... Я не уверен, буду ли я это использовать. Буду признателен за мнения: стоит ли это того? Также буду рад предложениям по улучшению: есть ли что-то лучше, чем выбрасывать AssertionError
? Могу ли я выбрасывать что-то еще? Должен ли я сделать это проверяемым исключением?
В настройках Eclipse перейдите в раздел Java → Компилятор → Ошибки/Предупреждения → Общие типы и установите флажок Игнорировать непредотвратимые проблемы с обобщенными типами
.
Это поможет решить поставленную задачу, а именно:
Я хотел бы избежать предупреждений Eclipse...
хоть и не совсем соответствует духу вопроса.
Вы можете создать вспомогательный класс, аналогичный приведенному ниже, и использовать его для подавления предупреждения о необработанном типе.
public class Objects {
/**
* Помогает избежать использования {@code @SuppressWarnings({"unchecked"})} при приведении к обобщенному типу.
*/
@SuppressWarnings({"unchecked"})
public static <T> T uncheckedCast(Object obj) {
return (T) obj;
}
}
Использовать его можно следующим образом:
import static Objects.uncheckedCast;
...
HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {
return uncheckedCast(session.getAttribute("attributeKey"));
}
Некоторую дополнительную информацию можно найти здесь: http://cleveralias.blogs.com/thought_spearmints/2006/01/suppresswarning.html
Эти вопросы действительно сложные, но вот мои текущие мысли:
Если ваш API возвращает объект типа Object, то вы ничего не сможете с этим сделать — в любом случае вам придётся выполнять неявное приведение типа. Вы можете позволить Java выбрасывать исключения ClassCastException или проверить каждый элемент самостоятельно и выбрасывать AssertionError или IllegalArgumentException, но все эти временные проверки эквивалентны. Вам нужно подавить временные неявные приведения типов, что бы вы ни делали во время выполнения.
Я бы предпочёл просто выполнить неявное приведение и позволить JVM выполнять проверку во время выполнения за меня, поскольку мы "знаем", что должен возвращать API и обычно готовы предполагать, что он работает. Используйте обобщения (generics) везде выше приведения, если они вам нужны. Вы на самом деле ничего не выигрываете в этом случае, так как всё равно у вас останется одно неявное приведение, но, по крайней мере, вы сможете использовать обобщения на более высоком уровне, так что JVM сможет помочь вам избежать неявных приведений в других частях вашего кода.
В данном конкретном случае, предположительно, вы можете видеть вызов метода SetAttribute и знаете, какого типа объект передаётся, поэтому просто выполнить неявное приведение к тому же типу при выходе не является чем-то аморальным. Добавьте комментарий, ссылающийся на SetAttribute, и забудьте об этом.
Вот укороченный пример, который избегает предупреждения "unchecked cast" с помощью двух стратегий, упомянутых в других ответах.
- Передавайте класс интересующего типа в качестве параметра во время выполнения (
Class<T> inputElementClazz
). Затем вы можете использовать:inputElementClazz.cast(anyObject);
- Для приведения типов коллекции используйте подстановочный знак
?
, вместо обобщенного типаT
, чтобы подтвердить, что вы действительно не знаете, какие объекты ожидать от устаревшего кода (Collection<?> unknownTypeCollection
). В конце концов, именно это предупреждение "unchecked cast" пытается нам сообщить: мы не можем быть уверены, что получаемCollection<T>
, поэтому правильный подход — использоватьCollection<?>
. Если это абсолютно необходимо, коллекцию известного типа всё равно можно создать (Collection<T> knownTypeCollection
).
Устаревший код, взаимодействующий в приведенном ниже примере, имеет атрибут "input" в StructuredViewer
(StructuredViewer — это виджет дерева или таблицы, "input" — это модель данных, стоящая за ним). Этот "input" может быть любой Java коллекцией.
public void dragFinished(StructuredViewer structuredViewer, Class<T> inputElementClazz) {
IStructuredSelection selection = (IStructuredSelection) structuredViewer.getSelection();
// устаревший код возвращает объект из getFirstElement,
// разработчик знает/надеется, что это тип inputElementClazz, но компилятор не может знать
T firstElement = inputElementClazz.cast(selection.getFirstElement());
// устаревший код возвращает объект из getInput, поэтому мы обрабатываем его как Collection<?>
Collection<?> unknownTypeCollection = (Collection<?>) structuredViewer.getInput();
// для некоторых операций нам даже не нужна коллекция с известными типами
unknownTypeCollection.remove(firstElement);
// ничто не мешает нам создать коллекцию известного типа, если это действительно необходимо
Collection<T> knownTypeCollection = new ArrayList<T>();
for (Object object : unknownTypeCollection) {
T aT = inputElementClazz.cast(object);
knownTypeCollection.add(aT);
System.out.println(aT.getClass());
}
structuredViewer.refresh();
}
Естественно, приведенный выше код может вызывать ошибки выполнения, если мы используем устаревший код с неверными типами данных (например, если мы установим массив в качестве "input" для StructuredViewer
, вместо Java коллекции).
Пример вызова метода:
dragFinishedStrategy.dragFinished(viewer, Product.class);
Как создать обобщённый массив в Java?
Что такое PECS (Producer Extends Consumer Super)?
Как получить экземпляр класса обобщенного типа T?
Получить обобщённый тип класса во время выполнения
Вывод типа с помощью рефлексии для лямбд в Java 8