Тип безпеки: Неперевірений склад


265

У своєму контекстному файлі весняних програм у мене є щось на кшталт:

<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 я бачу попередження, яке говорить:

Безпека типу: Незареєстрована передача від Object до HashMap

Що я зробив не так? Як вирішити проблему?


Я придумав рутину насправді перевірити відтінок параметризованих HashMap, що виключає безперешкодне попередження проголошення: посилання Я б сказав , що це «правильне» рішення, але буде чи ні його цінність може бути спірним. :)
skiphoppy


Відповіді:


248

Ну, по-перше, ви витрачаєте пам'ять за допомогою нового HashMapдзвінка про створення. Ваш другий рядок повністю ігнорує посилання на цю створену хеш-карту, роблячи її потім доступною для сміттєзбірника. Отже, не робіть цього, використовуйте:

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

По-друге, компілятор скаржиться на те, що ви віддаєте об'єкт на а, HashMapне перевіряючи, чи це a 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>якщо ви хочете. Таким чином, немає гарантії, що коли ви отримаєте свою квасолю, вона може бути представлена ​​як а, HashMap<String, String>тому що ви могли мати її, HashMap<Date, Calendar>тому що неповернене представлення, яке повертається, може мати будь-які об'єкти.

Якщо код компілюється і ви можете виконувати String value = map.get("thisString");без будь-яких помилок, не переживайте за це попередження. Але якщо карта не є повністю рядковими клавішами до значень рядків, ви отримаєте час ClassCastExceptionвиконання, оскільки генерики не можуть блокувати це в цьому випадку.


12
Це було деякий час тому, але я шукав відповідь на тип перевірки Set <CustomClass> перед складанням, і ви не можете примірникof на параметризованому загальному. наприклад, якщо (event.getTarget instanceof Set <CustomClass>) Ви можете перевірити лише загальний з допомогою? і це не видалить попередження про виклик. Наприклад, якщо (event.getTarget instanceof Set <?>)
часник

315

Проблема полягає в тому, що амплуа - це перевірка часу виконання, але через стирання типу під час виконання фактично немає різниці між a HashMap<String,String>і HashMap<Foo,Bar>для будь-яких інших Fooта Bar.

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


14
Я візьму перероблені дженерики Java через нетипізований NSMutableWever, який відчуває себе десятирічним стрибком назад у будь-який день тижня. Принаймні, Java намагається.
Dan Rosenstark

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);

Пояснення: Перетворення першого типу підтверджує, що об'єктом є Список, не піклуючись про типи, що містяться в межах (оскільки ми не можемо перевірити внутрішні типи на рівні Списку). Тепер потрібне друге перетворення, оскільки компілятор знає лише, що Список містить якісь об'єкти. Це підтверджує тип кожного об'єкта в Списку по мірі його доступу.


3
ти прав мій друг. Замість того, щоб кидати список, просто повторіть його та передайте кожен елемент, попередження не з’явиться, дивним.
Джуан Ісаза

2
Це зняло попередження, але все ще я не впевнений: P
mumair

1
Так, мені здається, що компілятор із зав'язаними очима, але не час виконання: D Так що я не бачу різниці між цим та @SuppressWarnings ("невірно")
chanae

1
Це круто! Основна відмінність використання @SupressWarning полягає в тому, що використання анотації виключає попередження з ваших інструментів аналізу IDE та коду, але якщо ви використовуєте компіляцію прапора -Werror, у вас з’явиться помилка. За допомогою цього підходу виправлені обидва попередження.
Еду Коста

30

Попередження - саме це. Попередження. Іноді попередження не мають значення, іноді - ні. Вони використовуються для того, щоб звернути вашу увагу на те, що компілятор вважає, що це може бути проблемою, але може не бути.

У разі закидів у цьому випадку завжди буде попереджатись. Якщо ви абсолютно впевнені, що певний склад буде безпечним, вам слід розглянути можливість додавання примітки на зразок цієї (я не впевнений у синтаксисі) безпосередньо перед рядком:

@SuppressWarnings (value="unchecked")

14
-1: попередження ніколи не слід приймати. Або придушити подібні попередження або виправити. Настане той момент, коли вам доведеться зробити багато попереджень, і відповідні ви не побачите жодного разу.
ezdazuzena

10
Ви не можете уникнути попереджень від класів під час трансляції параметризованих дженериків, тобто Map, тому це найкраща відповідь на початкове запитання.
баранинаУп

9

Ви отримуєте це повідомлення, тому що getBean повертає посилання на Object і ви переносите його в потрібний тип. Java 1.5 дає вам попередження. Така природа використання Java 1.5 або краще з кодом, який працює так. Весна має безпечну версію

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");

Схоже, зламати не рішення.
Malwinder Singh

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();

Обхід

Створіть новий об’єкт Map без згадування параметрів, оскільки тип об'єкта, що міститься у списку, не перевіряється.

Крок 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.