Тип безопасности: непроверенный актерский состав


266

В моем весеннем контексте приложения у меня есть что-то вроде:

<util:map id="someMap" map-class="java.util.HashMap" key-type="java.lang.String" value-type="java.lang.String">
    <entry key="some_key" value="some value" />
    <entry key="some_key_2" value="some value" />   
</util:map>

В классе Java реализация выглядит так:

private Map<String, String> someMap = new HashMap<String, String>();
someMap = (HashMap<String, String>)getApplicationContext().getBean("someMap");

В Eclipse я вижу предупреждение, которое гласит:

Безопасность типов: непроверенное приведение от объекта к HashMap

Что я сделал не так? Как мне решить проблему?


Я придумал процедуру, которая фактически проверяет приведение к параметризованному HashMap, которое устраняет необязательное предупреждение о приведении: ссылка Я бы сказал, что это «правильное» решение, но стоит ли оно того, может ли оно быть дискуссионным. :)
skiphoppy


Ответы:


249

Ну, во-первых, вы тратите память на новый HashMapвызов создания. Ваша вторая строка полностью игнорирует ссылку на этот созданный hashmap, делая его доступным для сборщика мусора. Итак, не делайте этого, используйте:

private Map<String, String> someMap = (HashMap<String, String>)getApplicationContext().getBean("someMap");

Во-вторых, компилятор жалуется, что вы приводите объект к HashMapбез проверки, является ли он HashMap. Но, даже если вы должны были сделать:

if(getApplicationContext().getBean("someMap") instanceof HashMap) {
    private Map<String, String> someMap = (HashMap<String, String>)getApplicationContext().getBean("someMap");
}

Вы, вероятно, все еще получите это предупреждение. Проблема в том, что getBeanвозвращается Object, поэтому неизвестно, что это за тип. Преобразование в HashMapнапрямую не вызовет проблемы во втором случае (и, возможно, в первом случае не будет предупреждения, я не уверен, насколько педантичен компилятор Java с предупреждениями для Java 5). Тем не менее, вы конвертируете его в HashMap<String, String>.

HashMaps - это действительно карты, которые принимают объект в качестве ключа и имеют объект в качестве значения, HashMap<Object, Object>если хотите. Таким образом, нет никакой гарантии, что когда вы получите свой bean-компонент, он может быть представлен как, HashMap<String, String>потому что вы можете иметь это, HashMap<Date, Calendar>потому что возвращаемое неуниверсальное представление может иметь любые объекты.

Если код компилируется, и вы можете выполнить его String value = map.get("thisString");без ошибок, не беспокойтесь об этом предупреждении. Но если карта не полностью содержит строковые ключи для строковых значений, вы получите ClassCastExceptionво время выполнения, потому что в этом случае универсальные блоки не могут этого предотвратить.


12
Это было некоторое время назад, но я искал ответ о проверке типа Set <CustomClass> перед приведением, и вы не можете instanceof для параметризованного универсального. например, if (event.getTarget instanceof Set <CustomClass>). Вы можете набирать только универсальный с помощью? и это не снимет предупреждение. например, if (event.getTarget instanceof Set <?>)
garlicman

315

Проблема в том, что приведение является проверкой во время выполнения, но из-за стирания типа во время выполнения на самом деле нет разницы между a HashMap<String,String>и HashMap<Foo,Bar>любым другим Fooи Bar.

Используйте @SuppressWarnings("unchecked")и держите нос. Ох, и кампания за усовершенствованные дженерики на Java :)


14
Я возьму переработанные дженерики Java над нетипизированным NSMutableWh независимо, что похоже на десятилетний скачок назад, в любой день недели. По крайней мере, Java пытается.
Дэн Розенстарк

12
Именно. Если вы настаиваете на проверке типов, это можно сделать только с помощью HashMap <?,?>, И это не приведет к удалению предупреждения, так как оно не проверяет тип универсальных типов. Это не конец света, но раздражает, что вы пойманы, либо подавляете предупреждение, либо живете с ним.
чеснок

5
@JonSkeet Что такое родовой дженерик?
SasQ

89

Как показывают вышеприведенные сообщения, список нельзя различить между a List<Object>и a List<String>или List<Integer>.

Я решил это сообщение об ошибке для аналогичной проблемы:

List<String> strList = (List<String>) someFunction();
String s = strList.get(0);

со следующим:

List<?> strList = (List<?>) someFunction();
String s = (String) strList.get(0);

Объяснение: Первое преобразование типов проверяет, что объект является списком, не заботясь о типах, содержащихся в нем (поскольку мы не можем проверить внутренние типы на уровне списка). Второе преобразование теперь требуется, потому что компилятор знает только, что List содержит какие-то объекты. Это проверяет тип каждого объекта в списке по мере его доступа.


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

2
Это сняло предупреждение, но я все еще не уверен: P
mumair

1
Да, это похоже на завязывание глаза компилятором, но не во время выполнения: D Так что я не вижу никакой разницы между этим и @SuppressWarnings ("unchecked")
channae

1
Это потрясающе! Основное отличие использования @SupressWarning состоит в том, что использование аннотации исключает предупреждение из вашей IDE и инструментов анализа кода, но если вы используете компиляцию с флагом -Werror, вы все равно получите ошибку. При использовании этого подхода оба предупреждения исправляются.
Эду Коста

30

Предупреждение только это. Предупреждение. Иногда предупреждения не имеют значения, иногда нет. Они привыкли обращать ваше внимание на то, что компилятор считает проблемой, но может и не быть.

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

@SuppressWarnings (value="unchecked")

14
-1: предупреждение никогда не должно приниматься. Или подавить такого рода предупреждения или исправить это. Наступит момент, когда вы будете иметь много предупреждений, и вы не увидите соответствующие сообщения.
ездазузена

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

9

Вы получаете это сообщение, потому что getBean возвращает ссылку на объект, и вы приводите его к правильному типу. Java 1.5 дает вам предупреждение. Такова природа использования Java 1.5 или выше с кодом, который работает следующим образом. Spring имеет типобезопасную версию

someMap=getApplicationContext().getBean<HashMap<String, String>>("someMap");

в его списке задач.


6

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

Например, если вы пытаетесь использовать

private Map<String, String> someMap = new HashMap<String, String>();

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

public class StringMap extends HashMap<String, String>()
{
    // Override constructors
}

Затем, когда вы используете

someMap = (StringMap) getApplicationContext().getBean("someMap");

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


3

Решение, чтобы избежать непроверенного предупреждения:

class MyMap extends HashMap<String, String> {};
someMap = (MyMap)getApplicationContext().getBean("someMap");

Похоже, взломать не решение проблемы.
Малвиндер Сингх

1
- Сериализуемый класс MyMap не объявляет статическое окончательное поле serialVersionUID типа long: {
Ulterior

1

Другим решением, если вы часто используете один и тот же объект и не хотите засорять свой код @SupressWarnings("unchecked"), было бы создание метода с аннотацией. Таким образом, вы централизуете приведение и, надеюсь, уменьшаете вероятность ошибки.

@SuppressWarnings("unchecked")
public static List<String> getFooStrings(Map<String, List<String>> ctx) {
    return (List<String>) ctx.get("foos");
}

1

Ниже приведен код причины Тип безопасности Предупреждение

Map<String, Object> myInput = (Map<String, Object>) myRequest.get();

Временное решение

Создайте новый объект карты без упоминания параметров, поскольку тип объекта, который содержится в списке, не проверен.

Шаг 1: Создайте новую временную карту

Map<?, ?> tempMap = (Map<?, ?>) myRequest.get();

Шаг 2: Создание основной карты

Map<String, Object> myInput=new HashMap<>(myInputObj.size());

Шаг 3: Итерация временной карты и установка значений в основную карту

 for(Map.Entry<?, ?> entry :myInputObj.entrySet()){
        myInput.put((String)entry.getKey(),entry.getValue()); 
    }

0

Что я сделал не так? Как мне решить проблему?

Вот :

Map<String,String> someMap = (Map<String,String>)getApplicationContext().getBean("someMap");

Вы используете устаревший метод, который мы обычно не хотим использовать, так как он возвращает Object:

Object getBean(String name) throws BeansException;

Метод для получения (для синглтона) / создания (для прототипа) компонента из фабрики компонентов:

<T> T getBean(String name, Class<T> requiredType) throws BeansException;

Используя это как:

Map<String,String> someMap = app.getBean(Map.class,"someMap");

будет компилироваться, но все еще с предупреждением о непроверенном преобразовании, так как все Mapобъекты не обязательноMap<String, String> объектами.

Но <T> T getBean(String name, Class<T> requiredType) throws BeansException; этого недостаточно в универсальных классах bean-компонентов, таких как универсальные коллекции, поскольку для этого требуется указать более одного класса в качестве параметра: тип коллекции и ее универсальный тип (типы).

В этом сценарии и в целом, лучший подход не использовать напрямую BeanFactory методов, а возможность структуры для внедрения компонента.

Объявление бина:

@Configuration
public class MyConfiguration{

    @Bean
    public Map<String, String> someMap() {
        Map<String, String> someMap = new HashMap();
        someMap.put("some_key", "some value");
        someMap.put("some_key_2", "some value");
        return someMap;
    }
}

Инъекция бобов:

@Autowired
@Qualifier("someMap")
Map<String, String> someMap;
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.