Bezpieczeństwo typu: Niesprawdzona obsada

265

W moim pliku kontekstowym aplikacji wiosennej mam coś takiego:

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

W klasie java implementacja wygląda następująco:

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

W Eclipse widzę ostrzeżenie:

Bezpieczeństwo typu: Niezaznaczone rzutowanie z Object na HashMap

Co zrobiłem źle? Jak rozwiązać problem?

Zrodzony ze smoka
źródło
Wymyśliłem procedurę sprawdzania rzutowania na sparametryzowaną HashMap, która eliminuje niesprawdzone ostrzeżenie o rzutowaniu: link Powiedziałbym, że jest to „poprawne” rozwiązanie, ale czy jego wartość może być dyskusyjna. :)
skiphoppy

Odpowiedzi:

248

Po pierwsze, marnujesz pamięć dzięki nowemu HashMapwezwaniu do stworzenia. Druga linia całkowicie pomija odniesienie do utworzonej mapy skrótów, dzięki czemu jest ona dostępna dla śmieciarza. Więc nie rób tego, użyj:

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

Po drugie, kompilator narzeka, że ​​rzuciłeś obiekt na obiekt HashMapbez sprawdzania, czy jest to obiekt HashMap. Ale nawet jeśli miałbyś zrobić:

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

Prawdopodobnie nadal dostaniesz to ostrzeżenie. Problemem jest getBeanpowrót Object, więc nie wiadomo, jaki jest typ. Przekształcenie go w tryb HashMapbezpośredni nie spowodowałoby problemu z drugim przypadkiem (i być może nie byłoby ostrzeżenia w pierwszym przypadku, nie jestem pewien, jak pedantyczny jest kompilator Java z ostrzeżeniami dla Java 5). Konwertujesz go jednak na HashMap<String, String>.

HashMaps to tak naprawdę mapy, które przyjmują obiekt jako klucz i mają obiekt jako wartość, HashMap<Object, Object>jeśli wolisz. Dlatego nie ma gwarancji, że po otrzymaniu fasoli może ona być reprezentowana jako, HashMap<String, String>ponieważ można ją uzyskać, HashMap<Date, Calendar>ponieważ zwracana reprezentacja nieogólna może zawierać dowolne obiekty.

Jeśli kod się skompiluje i możesz wykonać String value = map.get("thisString");bez błędów, nie przejmuj się tym ostrzeżeniem. Ale jeśli mapa nie zawiera w pełni kluczy łańcuchowych do wartości łańcuchowych, pojawi się ClassCastExceptionw czasie wykonywania, ponieważ ogólne nie mogą tego zablokować.

MetroidFan2002
źródło
12
To było jakiś czas temu, ale szukałem odpowiedzi na sprawdzanie typu Set <CustomClass> przed rzutowaniem, a nie można instancji na sparametryzowanym generic. np. if (event.getTarget instanceof Set <CustomClass>) Możesz wpisać check generic tylko za pomocą? i to nie usunie ostrzeżenia o obsadzie. np. if (event.getTarget instanceof Set <?>)
czosnek
315

Problem polega na tym, że rzutowanie jest sprawdzaniem środowiska wykonawczego - ale ze względu na kasowanie typu, w czasie wykonywania nie ma właściwie różnicy między a HashMap<String,String>a HashMap<Foo,Bar>dla innych Fooi Bar.

Użyj @SuppressWarnings("unchecked")i przytrzymaj nos. Aha, i kampania dla zreifikowanych generyków w Javie :)

Jon Skeet
źródło
14
Zajmę się sprawdzonymi rodzajami języka Java zamiast niewymienionego NSMutable, niezależnie od tego, co wydaje się być dziesięcioletnim skokiem wstecz w każdy dzień tygodnia. Przynajmniej Java próbuje.
Dan Rosenstark,
12
Dokładnie. Jeśli nalegasz na sprawdzenie typu, można to zrobić tylko za pomocą HashMap <?,?>, A to nie usunie ostrzeżenia, ponieważ jest takie samo, jak nie sprawdzanie typów ogólnych. To nie koniec świata, ale irytujące, że przyłapano Cię na tłumieniu ostrzeżenia lub życiu z nim.
czosnnik
5
@JonSkeet Co to jest generic reified?
SasQ,
89

Jak wskazują powyższe komunikaty, listy nie można rozróżnić między a List<Object>i a List<String>lub List<Integer>.

Rozwiązałem ten komunikat o błędzie dotyczący podobnego problemu:

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

z następującymi:

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

Objaśnienie: Pierwsza konwersja typu sprawdza, czy obiekt jest Listą, nie dbając o typy przechowywane w nim (ponieważ nie możemy zweryfikować typów wewnętrznych na poziomie Listy). Druga konwersja jest teraz wymagana, ponieważ kompilator wie tylko, że Lista zawiera jakieś obiekty. To weryfikuje typ każdego obiektu na liście, gdy jest on dostępny.

Larry Landry
źródło
3
masz rację mój przyjacielu. Zamiast rzutować listę, po prostu iteruj ją i rzucaj każdy element, ostrzeżenie nie pojawi się, niesamowite.
juan Isaza,
2
To usunęło ostrzeżenie, ale nadal nie jestem pewien: P
mumair
1
Tak, czuję się jak zasłanianie oczu kompilatorem, ale nie środowisko uruchomieniowe: D Więc nie widzę żadnej różnicy między tym a @SuppressWarnings („niezaznaczone”)
channae
1
To cudownie! Główna różnica w korzystaniu z @SupressWarning polega na tym, że użycie adnotacji eliminuje ostrzeżenie z IDE i narzędzi do analizy kodu, ale jeśli używasz kompilacji flag -Werror, to jeszcze się nie powiedzie. Przy takim podejściu oba ostrzeżenia są naprawione.
Edu Costa
30

Ostrzeżenie jest właśnie takie. Ostrzeżenie. Czasami ostrzeżenia są nieistotne, a czasem nie. Służą do zwrócenia uwagi na coś, co kompilator uważa za problem, ale może nie być.

W przypadku rzutów zawsze będzie to ostrzeżenie. Jeśli masz absolutną pewność, że konkretna obsada będzie bezpieczna, powinieneś rozważyć dodanie takiej adnotacji (nie jestem pewien co do składni) tuż przed wierszem:

@SuppressWarnings (value="unchecked")
David M. Karr
źródło
14
-1: ostrzeżenie nigdy nie powinno być akceptowane. Lub stłum tego rodzaju ostrzeżenia lub napraw je. Nadejdzie chwila, w której będziesz mieć wiele ostrzeżeń i nie zobaczysz ich raz.
ezdazuzena
10
Naprawdę nie można uniknąć ostrzeżeń o rzucaniu klas podczas rzutowania sparametryzowanych leków generycznych, tj. Mapy, więc jest to najlepsza odpowiedź na pierwotne pytanie.
baranina Do
9

Otrzymujesz ten komunikat, ponieważ getBean zwraca odwołanie do obiektu i rzutujesz go na poprawny typ. Java 1.5 wyświetla ostrzeżenie. Taka jest natura używania Java 1.5 lub lepszej z kodem, który działa w ten sposób. Wiosna ma bezpieczną wersję

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

na liście rzeczy do zrobienia.

David Nehme
źródło
6

Jeśli naprawdę chcesz pozbyć się ostrzeżeń, jedną rzeczą, którą możesz zrobić, to stworzyć klasę, która wywodzi się z klasy ogólnej.

Na przykład, jeśli próbujesz użyć

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

Możesz stworzyć nową taką klasę

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

Następnie, kiedy używasz

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

Kompilator NIE wie, jakie są typy (już nie ogólne) i nie będzie ostrzeżenia. Nie zawsze może to być idealne rozwiązanie, niektórzy mogą argumentować, że tego rodzaju porażka jest celem klas ogólnych, ale nadal używasz całego tego samego kodu z klasy ogólnej, po prostu deklarujesz w czasie kompilacji, jakiego typu chcesz użyć.

Królik
źródło
3

Rozwiązanie pozwalające uniknąć niesprawdzonego ostrzeżenia:

class MyMap extends HashMap<String, String> {};
someMap = (MyMap)getApplicationContext().getBean("someMap");
ochakov
źródło
Wygląda na hack, a nie rozwiązanie.
Malwinder Singh,
1
- Klasa MyMap, którą można serializować, nie deklaruje statycznego końcowego pola serialVersionUID typu: {
Ulterior
1

Inne rozwiązanie, jeśli często rzucasz ten sam obiekt i nie chcesz zaśmiecać swojego kodu @SupressWarnings("unchecked") , byłoby stworzenie metody z adnotacją. W ten sposób scentralizujesz obsadę i, mam nadzieję, zmniejszysz prawdopodobieństwo błędu.

@SuppressWarnings("unchecked")
public static List<String> getFooStrings(Map<String, List<String>> ctx) {
    return (List<String>) ctx.get("foos");
}
Jeremy
źródło
1

Poniższy kod powoduje Ostrzeżenie o bezpieczeństwie typu

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

Obejście

Utwórz nowy obiekt mapy bez podawania parametrów, ponieważ typ obiektu znajdującego się na liście nie jest weryfikowany.

Krok 1: Utwórz nową tymczasową mapę

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

Krok 2: Utwórz główną mapę

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

Krok 3: Powtórz tymczasową mapę i ustaw wartości w głównej mapie

 for(Map.Entry<?, ?> entry :myInputObj.entrySet()){
        myInput.put((String)entry.getKey(),entry.getValue()); 
    }
Andy
źródło
0

Co zrobiłem źle? Jak rozwiązać problem?

Tutaj:

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

Używasz starszej metody, której zazwyczaj nie chcemy używać, ponieważ zwraca Object:

Object getBean(String name) throws BeansException;

Metodą preferowania uzyskania (dla singletona) / utworzenia (dla prototypu) fasoli z fabryki fasoli jest:

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

Używając go, takiego jak:

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

będzie się kompilować, ale nadal z nie zaznaczonym ostrzeżeniem o konwersji, ponieważ wszystkie Mapobiekty niekoniecznie są Map<String, String>obiektami.

Ale <T> T getBean(String name, Class<T> requiredType) throws BeansException;to nie wystarczy w klasach ogólnych komponentu bean, takich jak kolekcje ogólne, ponieważ wymaga to podania więcej niż jednej klasy jako parametru: typu kolekcji i jej ogólnych typów.

W tego rodzaju scenariuszu i ogólnie lepszym podejściem nie jest stosowanie BeanFactorymetod bezpośrednich , ale umożliwienie ramce wstrzyknięcia fasoli.

Deklaracja fasoli:

@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;
    }
}

Wstrzyknięcie fasoli:

@Autowired
@Qualifier("someMap")
Map<String, String> someMap;
davidxxx
źródło