Jak reagować na niezaznaczone ostrzeżenia dotyczące rzutowania?


610

Eclipse wyświetla mi ostrzeżenie o następującej formie:

Bezpieczeństwo typu: Niezaznaczone rzutowanie z Object na HashMap

Wynika to z wywołania interfejsu API, nad którym nie mam kontroli, który zwraca obiekt:

HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {
  HashMap<String, String> theHash = (HashMap<String, String>)session.getAttribute("attributeKey");
  return theHash;
}

Jeśli to możliwe, chciałbym unikać ostrzeżeń Eclipse, ponieważ teoretycznie wskazują one przynajmniej na potencjalny problem z kodem. Jednak nie znalazłem jeszcze dobrego sposobu na wyeliminowanie tego. Mogę wyodrębnić pojedynczy wiersz związany z metodą i dodać ją @SuppressWarnings("unchecked")do tej metody, ograniczając w ten sposób wpływ bloku kodu, w którym ignoruję ostrzeżenia. Jakieś lepsze opcje? Nie chcę wyłączać tych ostrzeżeń w Eclipse.

Zanim przyszedłem do kodu, było to prostsze, ale wciąż prowokowało ostrzeżenia:

HashMap getItems(javax.servlet.http.HttpSession session) {
  HashMap theHash = (HashMap)session.getAttribute("attributeKey");
  return theHash;
}

Problem polegał na tym, że gdy próbowałeś użyć skrótu, pojawiały się ostrzeżenia:

HashMap items = getItems(session);
items.put("this", "that");

Type safety: The method put(Object, Object) belongs to the raw type HashMap.  References to generic type HashMap<K,V> should be parameterized.

Jeśli używasz HttpSession w ten sposób, sprawdź artykuł Briana Goetza na ten temat: ibm.com/developerworks/library/j-jtp09238.html
Tom Hawtin - tackline

Jeśli nie można uniknąć niezaznaczonej obsady, dobrym pomysłem jest ścisłe powiązanie jej z czymś, co logicznie reprezentuje jej typ (np. enumLub nawet przypadki Class<T>), abyś mógł na nią spojrzeć i wiedzieć, że jest bezpieczny.
Philip Guin,



Dodałbym, znalazłem, że mogę dodać @SuppressWarnings („niezaznaczone”) na poziomie metody, który zawiera kod naruszający. Więc złamałem kod do rutyny, w której musiałem to zrobić. Zawsze myślałem, że możesz to zrobić bezpośrednio powyżej linii, o której mowa.
JGFMK

Odpowiedzi:


557

Oczywistą odpowiedzią nie jest oczywiście wykonanie niezatwierdzonej obsady.

Jeśli jest to absolutnie konieczne, spróbuj przynajmniej ograniczyć zakres @SuppressWarningsadnotacji. Według Javadocs może on zmieniać zmienne lokalne; w ten sposób nie wpływa to nawet na całą metodę.

Przykład:

@SuppressWarnings("unchecked")
Map<String, String> myMap = (Map<String, String>) deserializeMap();

Nie ma sposobu, aby ustalić, czy Mapnaprawdę powinny mieć ogólne parametry <String, String>. Musisz wcześniej wiedzieć, jakie powinny być parametry (lub dowiesz się, kiedy otrzymasz a ClassCastException). Dlatego kod generuje ostrzeżenie, ponieważ kompilator nie może wiedzieć, czy jest bezpieczny.


112
+1 za wskazanie, że może on dotyczyć zmiennych lokalnych. Eclipse oferuje tylko dodanie go do całej metody ...
thSoft

17
Eclipse 3.7 (Indigo) obsługuje dodawanie niezaznaczonych do zmiennych lokalnych.
sweetfa

78
Ostrzeżenie to nie tylko dlatego, że kompilator nie wie, że rzutowanie jest bezpieczne. Na przykład String s = (String) new Object() ;nie dostaje ostrzeżenia, mimo że kompilator nie wie, że rzutowanie jest bezpieczne. Ostrzeżenie jest spowodowane tym, że kompilator (a) nie wie, że rzutowanie jest bezpieczne ORAZ (b) nie wygeneruje pełnej kontroli czasu działania w punkcie rzutowania. Zostanie sprawdzony, czy to jest Hashmap, ale nie będzie czeku, że to jest HashMap<String,String>.
Theodore Norvell,

9
Niestety, mimo że rzutowanie i ostrzeżenie dotyczą przypisania , adnotacja musi przejść do deklaracji zmiennej ... Więc jeśli deklaracja i przypisanie znajdują się w różnych miejscach (powiedzmy odpowiednio na zewnątrz i wewnątrz bloku „try”) , Eclipse generuje teraz dwa ostrzeżenia: oryginalny niezaznaczony rzut i nową diagnostykę „niepotrzebnych adnotacji”.
Ti Strga

6
Obejściem adnotacji, która musi towarzyszyć deklaracji zmiennej lokalnej, która może znajdować się w innym zakresie w innym wierszu niż faktyczna rzutowanie, jest utworzenie zmiennej lokalnej w zakresie rzutowania, aby wykonać rzutowanie w tym samym wierszu jako deklaracja. Następnie przypisz tę zmienną do zmiennej rzeczywistej, która ma inny zakres. Jest to metoda, której użyłem również do zignorowania ostrzeżenia o rzutowaniu na zmienną instancji, ponieważ adnotacji nie można tutaj zastosować.
Jeff Lockhart,

167

Niestety nie ma tutaj świetnych opcji. Pamiętaj, że celem tego wszystkiego jest zachowanie bezpieczeństwa typu. „ Java Generics ” oferuje rozwiązanie do radzenia sobie z nieogenerowanymi bibliotekami starszego typu, aw szczególności w części 8.2 nazywa się ją „techniką pustej pętli”. Zasadniczo wykonaj niebezpieczną obsadę i ukryj ostrzeżenie. Następnie przejdź przez mapę w następujący sposób:

@SuppressWarnings("unchecked")
Map<String, Number> map = getMap();
for (String s : map.keySet());
for (Number n : map.values());

Jeśli napotkasz nieoczekiwany typ, otrzymasz środowisko wykonawcze ClassCastException, ale przynajmniej stanie się to blisko źródła problemu.


6
Znacznie lepsza odpowiedź niż ta dostarczona przez skiphoppy, z wielu powodów: 1) Ten kod jest znacznie, znacznie krótszy. 2) Ten kod faktycznie zgłasza ClassCastException zgodnie z oczekiwaniami. 3) Ten kod nie wykonuje pełnej kopii mapy źródłowej. 4) Pętle można łatwo owinąć osobną metodą stosowaną w asercie, co z łatwością usunęłoby spadek wydajności w kodzie produkcyjnym.
Stijn de Witt

6
Czy nie ma możliwości, że kompilator Java lub kompilator JIT zdecyduje, że wyniki tego kodu nie są używane i „zoptymalizuje” go, nie kompilując?
RenniePet

1
To naprawdę nie jest martwy kod, jeśli potencjalnie może wygenerować wyjątek. Nie wiem wystarczająco dużo o używanych obecnie kompilatorach JIT, aby zagwarantować, że żaden z nich nie zepsuje tego, ale jestem dość pewny, mówiąc, że nie powinni.
GrandOpener

3
To wciąż nie gwarantuje bezpieczeństwa typu, ponieważ wciąż używana jest ta sama mapa. Mógł być pierwotnie zdefiniowany jako Mapa <Obiekt, Obiekt>, który akurat ma ciągi i liczby, a później, jeśli zostanie dodany logiczny, użytkownik tego kodu otrzyma mylącą i raczej trudną do prześledzenia niespodziankę. Jedynym sposobem na zagwarantowanie bezpieczeństwa typu jest skopiowanie go na nową mapę z żądanym typem, który gwarantuje to, co jest dozwolone.
user2219808,

112

Łał; Myślę, że wymyśliłem odpowiedź na moje własne pytanie. Po prostu nie jestem pewien, czy warto! :)

Problem polega na tym, że obsada nie jest sprawdzana. Musisz to sprawdzić sam. Nie można po prostu sprawdzić sparametryzowanego typu za pomocą instanceof, ponieważ informacje o sparametryzowanym typie są niedostępne w czasie wykonywania, ponieważ zostały usunięte podczas kompilacji.

Ale możesz wykonać sprawdzenie każdego elementu w skrócie za pomocą instanceof, a dzięki temu możesz zbudować nowy skrót, który jest bezpieczny dla typu. I nie wywołasz żadnych ostrzeżeń.

Dzięki mmyers i Esko Luontoli sparametryzowałem kod, który tu pierwotnie napisałem, aby można go było gdzieś zawinąć w klasę użyteczności i wykorzystać w dowolnym sparametryzowanym HashMap. Jeśli chcesz to lepiej zrozumieć i nie znasz zbyt dobrze ogólnych, zachęcam do przejrzenia historii edycji tej odpowiedzi.

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(
                "Cannot cast to HashMap<"+ keyClass.getSimpleName()
                +", "+ valueClass.getSimpleName() +">"
                +", value "+ value +" is not a "+ valueClass.getSimpleName()
            );
        }
    } else {
        throw new AssertionError(
            "Cannot cast to HashMap<"+ keyClass.getSimpleName()
            +", "+ valueClass.getSimpleName() +">"
            +", key "+ key +" is not a " + keyClass.getSimpleName()
        );
    }
  }
  return output;
}

To dużo pracy, być może za bardzo małą nagrodę ... Nie jestem pewien, czy z niej skorzystam, czy nie. Będę wdzięczny za wszelkie komentarze dotyczące tego, czy ludzie uważają, że warto, czy nie. Będę również wdzięczny za sugestie dotyczące ulepszeń: czy jest coś lepszego, co mogę zrobić oprócz wyrzucenia błędów AssertionErrors? Czy jest coś lepszego, co mógłbym rzucić? Czy powinienem zrobić z tego sprawdzony wyjątek?


68
te rzeczy są mylące, ale myślę, że wszystko, co zrobiłeś, to handel ClassCastExceptions dla AssertionErrors.
Dustin Getz

59
Koleś, to zdecydowanie nie jest tego warte! Wyobraź sobie biednego soka, który musi wrócić i zmodyfikować jakiś kod z tym bałaganem. Nie lubię tłumić ostrzeżeń, ale myślę, że to mniejsze zło tutaj.
Craig B,

69
Nie chodzi tylko o to, że jest to brzydki, mylący bałagan (kiedy nie można uniknąć jednego obszernego komentarza, można przejść przez programistę) iteracja nad każdym elementem w kolekcji zmienia rzutowanie z operacji O (1) na operację O (n). Jest to coś, czego nigdy nie można się spodziewać i może łatwo przerodzić się w straszne spowolnienie tajemnicy.
Dan Is Fiddling By Firelight,

22
@ Dan Tylko masz rację. Zasadniczo nikt nigdy nie powinien tego robić.
skiphoppy,

4
Niektóre komentarze ... podpis metody jest nieprawidłowy, ponieważ nie „rzuca” tego cholerstwa, po prostu kopiuje istniejącą mapę do nowej mapy. Ponadto prawdopodobnie można by to refaktoryzować, aby zaakceptować dowolną mapę i nie polegać na samym HashMapie (tj. Weź Mapę i zwróć Mapę w podpisie metody, nawet jeśli typ wewnętrzny to HashMap). Tak naprawdę nie musisz robić rzutowania ani przechowywania na nowej mapie - jeśli nie rzucisz błędu asercji, to na danej mapie są teraz odpowiednie typy. Tworzenie nowej mapy z typami ogólnymi jest bezcelowe, ponieważ nadal możesz ją przygotować na surowo i umieścić wszystko.
MetroidFan2002

51

W Preferencjach Eclipse przejdź do Java-> Kompilator-> Błędy / Ostrzeżenia-> Typy ogólne i zaznacz pole Ignore unavoidable generic type problemswyboru.

To spełnia cel pytania, tj

Chciałbym uniknąć ostrzeżeń Eclipse ...

jeśli nie duch.


1
Ach, dziękuję za to :) Wystąpił uses unchecked or unsafe operations.błąd „ ” javac, ale dodanie @SuppressWarnings("unchecked")spowodowało, że Eclipse był niezadowolony, twierdząc, że tłumienie było niepotrzebne. Odznaczenie tego pola powoduje, że Eclipse javaczachowuje się tak samo, i tego właśnie chciałem. Jawne pomijanie ostrzeżenia w kodzie jest znacznie wyraźniejsze niż pomijanie go wszędzie w środowisku Eclipse.
dimo414

26

Możesz utworzyć klasę narzędzia taką jak poniżej i użyć jej, aby ukryć niesprawdzone ostrzeżenie.

public class Objects {

    /**
     * Helps to avoid using {@code @SuppressWarnings({"unchecked"})} when casting to a generic type.
     */
    @SuppressWarnings({"unchecked"})
    public static <T> T uncheckedCast(Object obj) {
        return (T) obj;
    }
}

Możesz użyć tego w następujący sposób:

import static Objects.uncheckedCast;
...

HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {
      return uncheckedCast(session.getAttribute("attributeKey"));
}

Więcej dyskusji na ten temat znajduje się tutaj: http://cleveralias.blogs.com/thought_spearmints/2006/01/suppresswarning.html


18
nie głosowanie w dół, ale opakowanie nie dodaje dokładnie nic, tylko tłumi ostrzeżenie.
Dustin Getz

3
+1, ponieważ to rozwiązanie nie marnuje cennych wierszy kodu.
Tino

1
@ErikE Za dużo. Znacznie droższe, większe i o wyższej rozdzielczości monitory, aby zwolnić miejsce dla wszystkich zmarnowanych linii, większe biurko, na którym można umieścić wszystkie te większe monitory, większe pomieszczenie, w którym można umieścić większe biurko, i wnikliwy szef ..
Tino

1
@ErikE Paski przewijania, dla vi? Żartujesz?
Tino

21

Te rzeczy są trudne, ale oto moje aktualne myśli:

Jeśli Twój interfejs API zwróci obiekt, to nic nie możesz zrobić - bez względu na wszystko, będziesz ślepo rzucał obiektem. Zezwalasz Javie na zgłaszanie wyjątków ClassCastExcept lub możesz sprawdzać każdy element osobno i zgłaszać Assertions lub IllegalArgumentExceptions lub niektóre z nich, ale wszystkie te kontrole środowiska wykonawczego są równoważne. Musisz wyłączyć czas kompilacji bez zaznaczenia rzutowania bez względu na to, co robisz w czasie wykonywania.

Wolę tylko ślepe rzutowanie i pozwolić JVM wykonać dla mnie sprawdzenie środowiska wykonawczego, ponieważ „wiemy”, co powinien zwrócić interfejs API i zazwyczaj jesteśmy gotowi założyć, że interfejs API działa. Jeśli potrzebujesz, używaj ogólnych elementów nad obsadą. Tak naprawdę nic tam nie kupujesz, ponieważ nadal masz pojedynczą obsadę w ciemno, ale przynajmniej możesz użyć odtąd ogólnych, aby JVM pomógł ci uniknąć rzucania w ciemno w innych częściach twojego kodu.

W tym konkretnym przypadku, przypuszczalnie możesz zobaczyć wezwanie do SetAttribute i zobaczyć, że typ się zbliża, więc po prostu ślepe rzucenie tego samego typu na wyjście nie jest niemoralne. Dodaj komentarz odnoszący się do SetAttribute i gotowe.


16

Oto skrócony przykład, który pozwala uniknąć ostrzeżenia o „niesprawdzonej obsadzie” dzięki zastosowaniu dwóch strategii wymienionych w innych odpowiedziach.

  1. Przekaż klasę typu zainteresowania jako parametr w runtime ( Class<T> inputElementClazz). Następnie możesz użyć:inputElementClazz.cast(anyObject);

  2. Do rzutowania typu na kolekcję użyj symbolu wieloznacznego? zamiast ogólnego typu T, aby potwierdzić, że rzeczywiście nie wiesz, jakiego rodzaju obiektów można oczekiwać po starszym kodzie ( Collection<?> unknownTypeCollection). W końcu to właśnie mówi nam ostrzeżenie „niezaznaczonej obsady”: nie możemy być pewni, że otrzymamy Collection<T>, więc uczciwą rzeczą jest użycie Collection<?>. Jeśli jest to absolutnie potrzebne, nadal można zbudować kolekcję znanego typu ( Collection<T> knownTypeCollection).

Starszy kod z interfejsem w poniższym przykładzie ma atrybut „input” w StructuredViewer (StructuredViewer to widget drzewa lub tabeli, „input” to model danych za nim). To „wejście” może być dowolnym rodzajem kolekcji Java.

public void dragFinished(StructuredViewer structuredViewer, Class<T> inputElementClazz) {
    IStructuredSelection selection = (IStructuredSelection) structuredViewer.getSelection();
    // legacy code returns an Object from getFirstElement,
    // the developer knows/hopes it is of type inputElementClazz, but the compiler cannot know
    T firstElement = inputElementClazz.cast(selection.getFirstElement());

    // legacy code returns an object from getInput, so we deal with it as a Collection<?>
    Collection<?> unknownTypeCollection = (Collection<?>) structuredViewer.getInput();

    // for some operations we do not even need a collection with known types
    unknownTypeCollection.remove(firstElement);

    // nothing prevents us from building a Collection of a known type, should we really need one
    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();
}

Oczywiście powyższy kod może powodować błędy w czasie wykonywania, jeśli użyjemy starszego kodu z niewłaściwymi typami danych (np. Jeśli ustawimy tablicę jako „wejście” StructuredViewer zamiast kolekcji Java).

Przykład wywołania metody:

dragFinishedStrategy.dragFinished(viewer, Product.class);

13

W świecie sesji HTTP naprawdę nie można uniknąć przesyłania, ponieważ interfejs API jest zapisywany w ten sposób (tylko pobiera i zwraca Object).

Przy odrobinie pracy można łatwo ominąć niesprawdzoną obsadę ”. Oznacza to, że zmieni się w tradycyjną obsadę, dając ClassCastExceptionprawo do niej w przypadku błędu). Niesprawdzony wyjątek może CCEpóźniej zmienić się w punkt obsady (dlatego jest to osobne ostrzeżenie).

Zamień HashMap na dedykowaną klasę:

import java.util.AbstractMap;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Attributes extends AbstractMap<String, String> {
    final Map<String, String> content = new HashMap<String, String>();

    @Override
    public Set<Map.Entry<String, String>> entrySet() {
        return content.entrySet();
    }

    @Override
    public Set<String> keySet() {
        return content.keySet();
    }

    @Override
    public Collection<String> values() {
        return content.values();
    }

    @Override
    public String put(final String key, final String value) {
        return content.put(key, value);
    }
}

Następnie rzuć na tę klasę zamiast Map<String,String>i wszystko zostanie sprawdzone dokładnie w miejscu, w którym napiszesz swój kod. Nieoczekiwane ClassCastExceptionspóźniej.


To jest naprawdę pomocna odpowiedź.
GPrathap,

10

W Android Studio, jeśli chcesz wyłączyć inspekcję, możesz użyć:

//noinspection unchecked
Map<String, String> myMap = (Map<String, String>) deserializeMap();

2
Działa to również w IntelliJ IDE
neXus

8

W tym konkretnym przypadku nie zapisywałbym Map bezpośrednio w HttpSession, ale zamiast tego wystąpienie mojej własnej klasy, która z kolei zawiera Mapę (szczegół implementacji klasy). Wtedy możesz być pewien, że elementy na mapie są odpowiedniego typu.

Ale jeśli mimo wszystko chcesz sprawdzić, czy zawartość mapy jest odpowiedniego typu, możesz użyć takiego kodu:

public static void main(String[] args) {
    Map<String, Integer> map = new HashMap<String, Integer>();
    map.put("a", 1);
    map.put("b", 2);
    Object obj = map;

    Map<String, Integer> ok = safeCastMap(obj, String.class, Integer.class);
    Map<String, String> error = safeCastMap(obj, String.class, String.class);
}

@SuppressWarnings({"unchecked"})
public static <K, V> Map<K, V> safeCastMap(Object map, Class<K> keyType, Class<V> valueType) {
    checkMap(map);
    checkMapContents(keyType, valueType, (Map<?, ?>) map);
    return (Map<K, V>) map;
}

private static void checkMap(Object map) {
    checkType(Map.class, map);
}

private static <K, V> void checkMapContents(Class<K> keyType, Class<V> valueType, Map<?, ?> map) {
    for (Map.Entry<?, ?> entry : map.entrySet()) {
        checkType(keyType, entry.getKey());
        checkType(valueType, entry.getValue());
    }
}

private static <K> void checkType(Class<K> expectedType, Object obj) {
    if (!expectedType.isInstance(obj)) {
        throw new IllegalArgumentException("Expected " + expectedType + " but was " + obj.getClass() + ": " + obj);
    }
}

1
Niesamowite; Myślę, że mogę to połączyć z moją odpowiedzią, aby sparametryzować i uniknąć konieczności całkowitego tłumienia ostrzeżeń!
skiphoppy

1
+1 prawdopodobnie najlepszy przepis (łatwy do zrozumienia i utrzymania), aby zrobić to bezpiecznie dzięki kontroli czasu wykonywania
Tino

8

Funkcja narzędzia Objects.Unchecked w powyższej odpowiedzi autorstwa Esko Luontoli to świetny sposób na uniknięcie bałaganu w programie.

Jeśli nie chcesz, aby SuppressWarnings dotyczyła całej metody, Java zmusza cię do umieszczenia jej na lokalnym. Jeśli potrzebujesz rzutowania na członka, może to prowadzić do kodu w następujący sposób:

@SuppressWarnings("unchecked")
Vector<String> watchedSymbolsClone = (Vector<String>) watchedSymbols.clone();
this.watchedSymbols = watchedSymbolsClone;

Korzystanie z tego narzędzia jest znacznie czystsze i nadal jest oczywiste, co robisz:

this.watchedSymbols = Objects.uncheckedCast(watchedSymbols.clone());

UWAGA: Uważam, że ważne jest, aby dodać, że czasami ostrzeżenie naprawdę oznacza, że ​​robisz coś nie tak, jak:

ArrayList<Integer> intList = new ArrayList<Integer>();
intList.add(1);
Object intListObject = intList; 

 // this line gives an unchecked warning - but no runtime error
ArrayList<String> stringList  = (ArrayList<String>) intListObject;
System.out.println(stringList.get(0)); // cast exception will be given here

Kompilator mówi ci, że to rzutowanie NIE zostanie sprawdzone w czasie wykonywania, więc żaden błąd środowiska wykonawczego nie zostanie zgłoszony, dopóki nie spróbujesz uzyskać dostępu do danych w ogólnym kontenerze.


5

Eliminacja ostrzeżeń nie jest rozwiązaniem. Nie powinieneś wykonywać rzutowania na dwa poziomy w jednym zestawieniu.

HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {

    // first, cast the returned Object to generic HashMap<?,?>
    HashMap<?, ?> theHash = (HashMap<?, ?>)session.getAttribute("attributeKey");

    // next, cast every entry of the HashMap to the required type <String, String>
    HashMap<String, String> returingHash = new HashMap<>();
    for (Entry<?, ?> entry : theHash.entrySet()) {
        returingHash.put((String) entry.getKey(), (String) entry.getValue());
    }
    return returingHash;
}

1
Jego pięcioletnie pytanie? Czy potrzebujesz tyle pracy? Biorąc pod uwagę, że Java ma typ kasowania, druga mapa hash powinna być identyczna jak pierwsza w czasie wykonywania; Myślę, że byłoby to bardziej wydajne i uniknęło kopiowania, gdybyś po prostu iterował wpisy i sprawdził, że wszystkie były instancjami ciągów. Lub, TBH, sprawdź źródło używanego pliku JAR serwletu i sprawdź, czy zawsze wstawia ciągi.
Rup

1
Do dziś widzę to ostrzeżenie w projektach. Jego problemem nie była weryfikacja typu, ale ostrzeżenie wywołane przez „umieszczenie” na niepublikowanej mapie.
abbas

2

Szybkie zgadnięcie, jeśli opublikujesz swój kod, może powiedzieć na pewno, ale być może zrobiłeś coś podobnego do

HashMap<String, Object> test = new HashMap();

która wyświetli ostrzeżenie, gdy musisz to zrobić

HashMap<String, Object> test = new HashMap<String, Object>();

warto na to spojrzeć

Ogólne w języku programowania Java

jeśli nie wiesz, co należy zrobić.


1
Niestety sytuacja nie jest taka łatwa. Kod został dodany.
skiphoppy

1
Przybyłem tutaj, szukając odpowiedzi na nieco inny problem: i powiedziałeś mi dokładnie, czego potrzebowałem! Dzięki!
staticsan

2

Być może źle zrozumiałem pytanie (przykład i kilka otaczających linii byłoby miło), ale dlaczego nie zawsze używasz odpowiedniego interfejsu (i Java5 +)? Nie widzę powodu, dla którego kiedykolwiek chciałbyś rzucić na HashMapzamiast zamiast Map<KeyType,ValueType>. W rzeczywistości nie wyobrażam sobie żadnego powodu, aby ustawić typ zmiennej na HashMapzamiast Map.

A dlaczego źródłem jest Object? Czy jest to typ parametru starej kolekcji? Jeśli tak, użyj ogólnych i podaj żądany typ.


2
Jestem prawie pewien, że przejście na Mapę w tym przypadku niczego nie zmieni, ale dzięki za wskazówkę programistyczną, która może zmienić sposób, w jaki robię niektóre rzeczy, na lepsze. Źródłem obiektu jest interfejs API, nad którym nie mam kontroli (dodany kod).
skiphoppy

2

Jeśli muszę użyć interfejsu API, który nie obsługuje generycznych. Próbuję izolować te wywołania w procedurach otoki z jak najmniejszą liczbą wierszy. Następnie używam adnotacji SuppressWarnings i jednocześnie dodam rzutowania dotyczące bezpieczeństwa typu.

To jest tylko osobista preferencja, aby zachować porządek.


2

Weźmy to, jest to znacznie szybsze niż tworzenie nowej HashMap, jeśli jest już jedna, ale nadal jest bezpieczna, ponieważ każdy element jest sprawdzany pod względem typu ...

@SuppressWarnings("unchecked")
public static <K, V> HashMap<K, V> toHashMap(Object input, Class<K> key, Class<V> value) {
       assert input instanceof Map : input;

       for (Map.Entry<?, ?> e : ((HashMap<?, ?>) input).entrySet()) {
           assert key.isAssignableFrom(e.getKey().getClass()) : "Map contains invalid keys";
           assert value.isAssignableFrom(e.getValue().getClass()) : "Map contains invalid values";
       }

       if (input instanceof HashMap)
           return (HashMap<K, V>) input;
       return new HashMap<K, V>((Map<K, V>) input);
    }

key.isAssignableFrom(e.getKey().getClass())można zapisać jakokey.isInstance(e.getKey())
użytkownik102008,

1

Po prostu sprawdź to, zanim rzucisz.

Object someObject = session.getAttribute("attributeKey");
if(someObject instanceof HashMap)
HashMap<String, String> theHash = (HashMap<String, String>)someObject;  

A dla każdego, kto pyta, dość powszechne jest otrzymywanie obiektów, w których nie jesteś pewien tego typu. Wiele starszych implementacji „SOA” przekazuje różne obiekty, którym nie zawsze należy ufać. (Horrory!)

EDYCJA Raz zmieniłem przykładowy kod, aby pasował do aktualizacji plakatu, a po kilku komentarzach widzę, że instanceof nie działa dobrze z generycznymi. Jednak zmiana sprawdzania w celu sprawdzenia poprawności zewnętrznego obiektu wydaje się dobrze współgrać z kompilatorem wiersza poleceń. Poprawiony przykład jest teraz opublikowany.


8
Niestety, generycy uniemożliwiają to. To nie tylko HashMap, to HashMap z informacjami o typie. A jeśli wyeliminuję te informacje, po prostu przekażę ostrzeżenia w inne miejsce.
skiphoppy

1

Prawie każdy problem w informatyce można rozwiązać, dodając poziom pośredni * lub coś w tym rodzaju.

Więc wprowadź nie-ogólny obiekt, który jest na wyższym poziomie niż Map. Bez kontekstu nie będzie to wyglądało zbyt przekonująco, ale w każdym razie:

public final class Items implements java.io.Serializable {
    private static final long serialVersionUID = 1L;
    private Map<String,String> map;
    public Items(Map<String,String> map) {
        this.map = New.immutableMap(map);
    }
    public Map<String,String> getMap() {
        return map;
    }
    @Override public String toString() {
        return map.toString();
    }
}

public final class New {
    public static <K,V> Map<K,V> immutableMap(
        Map<? extends K, ? extends V> original
    ) {
        // ... optimise as you wish...
        return Collections.unmodifiableMap(
            new HashMap<String,String>(original)
        );
    }
}

static Map<String, String> getItems(HttpSession session) {
    Items items = (Items)
        session.getAttribute("attributeKey");
    return items.getMap();
}

* Z wyjątkiem zbyt wielu poziomów pośrednich.


1
Cytat przypisuje się zmarłemu profesorowi Davidowi Wheelerowi. en.wikipedia.org/wiki/…
Stephen C

1

Oto jeden ze sposobów, w jaki sobie z tym radzę, gdy zastępuję equals()operację.

public abstract class Section<T extends Section> extends Element<Section<T>> {
    Object attr1;

    /**
    * Compare one section object to another.
    *
    * @param obj the object being compared with this section object
    * @return true if this section and the other section are of the same
    * sub-class of section and their component fields are the same, false
    * otherwise
    */       
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            // this exists, but obj doesn't, so they can't be equal!
            return false;
        }

        // prepare to cast...
        Section<?> other;

        if (getClass() != obj.getClass()) {
            // looks like we're comparing apples to oranges
            return false;
        } else {
            // it must be safe to make that cast!
            other = (Section<?>) obj;
        }

        // and then I compare attributes between this and other
        return this.attr1.equals(other.attr1);
    }
}

Wydaje się, że działa w Javie 8 (nawet skompilowanej -Xlint:unchecked)


0

Jeśli masz pewność, że typ zwrócony przez session.getAttribute () to HashMap, nie możesz typecastować do tego dokładnego typu, ale polegaj tylko na sprawdzeniu ogólnej HashMap

HashMap<?,?> getItems(javax.servlet.http.HttpSession session) {  
    HashMap<?,?> theHash = (HashMap<?,?>)session.getAttribute("attributeKey");
    return theHash;
} 

Eclipse zaskoczy ostrzeżenia, ale oczywiście może to prowadzić do błędów w czasie wykonywania, które mogą być trudne do debugowania. Używam tego podejścia tylko w kontekstach niekrytycznych dla operacji.


0

Dwa sposoby, jeden, który całkowicie omija tag, drugi przy użyciu niegrzecznej, ale miłej metody użytkowej.
Problemem są kolekcje wstępnie uogólnione ...
Wierzę, że ogólna zasada brzmi: „rzucaj obiektami jedna rzecz naraz” - co to oznacza, gdy próbujesz użyć klas surowych w uogólnionym świecie, ponieważ nie wiesz, co znajduje się na tej Mapie <?,?> (i JVM może nawet stwierdzić, że to nawet nie jest Mapa!), to oczywiste, gdy pomyślisz o tym, że nie możesz jej rzucić. Jeśli miałeś Map <String,?> Map2, to HashSet <String> keys = (HashSet <String>) map2.keySet () nie daje ostrzeżenia, mimo że jest to „akt wiary” dla kompilatora (ponieważ może się okazać TreeSet) ... ale to tylko pojedynczy akt wiary.

PS na zastrzeżenie, że iteracja jak na mój pierwszy sposób „jest nudna” i „wymaga czasu”, odpowiedź brzmi „bez bólu nie ma zysku”: uogólniona kolekcja zawiera Map.Entry <String, String> i nic jeszcze. Musisz zapłacić za tę gwarancję. Przy systematycznym stosowaniu leków generycznych ta płatność przybiera formę kodowania zgodności, a nie czasu maszynowego!
Jedna ze szkół myśli mogłaby powiedzieć, że powinieneś ustawić ustawienia Eclipse, aby zamiast takich ostrzeżeń pojawiały się takie niesprawdzone błędy rzutowania. W takim przypadku musiałbyś skorzystać z mojej pierwszej drogi.

package scratchpad;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

public class YellowMouse {

    // First way

    Map<String, String> getHashMapStudiouslyAvoidingSuppressTag(HttpSession session) {
      Map<?, ?> theHash = (Map<?, ?>)session.getAttribute("attributeKey");

      Map<String, String> yellowMouse = new HashMap<String, String>();
      for( Map.Entry<?, ?> entry : theHash.entrySet() ){
        yellowMouse.put( (String)entry.getKey(), (String)entry.getValue() );
      }

      return yellowMouse;
    }


    // Second way

    Map<String, String> getHashMapUsingNaughtyButNiceUtilityMethod(HttpSession session) {
      return uncheckedCast( session.getAttribute("attributeKey") );
    }


    // NB this is a utility method which should be kept in your utility library. If you do that it will
    // be the *only* time in your entire life that you will have to use this particular tag!!

    @SuppressWarnings({ "unchecked" })
    public static synchronized <T> T uncheckedCast(Object obj) {
        return (T) obj;
    }


}

fakt, że nie masz uprawnień do komentowania, nie pozwala na edytowanie odpowiedzi innych osób w celu dodawania komentarzy; edytujesz odpowiedzi innych, aby poprawić ich formatowanie, składnię, ..., aby nie dodawać do nich opinii. Kiedy osiągniesz 50 powtórzeń, będziesz mógł komentować wszędzie, tymczasem jestem pewien, że możesz się oprzeć (lub, jeśli naprawdę nie możesz, napisać komentarzy do istniejących odpowiedzi w poście). (uwaga dla innych: Piszę to, ponieważ widziałem - i odrzuciłem - jego proponowane komentarze-zmiany do innych postów w narzędziach do moderacji)
Matteo Italia

-1

To powoduje, że ostrzeżenia znikają ...

 static Map<String, String> getItems(HttpSession session) {
        HashMap<?, ?> theHash1 = (HashMap<String,String>)session.getAttribute("attributeKey");
        HashMap<String,String> theHash = (HashMap<String,String>)theHash1;
    return theHash;
}

1
Nie, nie ma. W rzeczywistości tworzy to dwa ostrzeżenia, w których pierwsze było jedno.
Stijn de Witt

Ach, okej. Nie jestem pewien, dlaczego tak myślałem.
lukewm

-3

Rozwiązanie: Wyłącz to ostrzeżenie w Eclipse. Nie @SuppressWarnings, po prostu całkowicie go wyłącz.

Kilka przedstawionych powyżej „rozwiązań” jest zupełnie nie na miejscu, przez co kod jest nieczytelny ze względu na głupie ostrzeżenie.


9
Czy mogę zapytać dlaczego? globalne wyłączenie ostrzeżenia spowoduje ukrycie innych miejsc, w których ten problem jest prawdziwy. dodanie a @SuppressWarningswcale nie czyni kodu nieczytelnym.
MByD
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.