Segurança de tipo: elenco não verificado


266

No meu arquivo de contexto do aplicativo Spring, tenho algo como:

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

Na classe java, a implementação se parece com:

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

No Eclipse, vejo um aviso que diz:

Segurança de tipo: elenco não verificado de Object para HashMap

O que eu fiz errado? Como resolvo o problema?


Eu criei uma rotina para verificar o elenco com o HashMap parametrizado, o que elimina o aviso de elenco desmarcado: link Eu diria que esta é a solução "correta", mas se vale ou não a pena ser discutível. :)
skiphoppy


Respostas:


249

Bem, primeiro de tudo, você está desperdiçando memória com a nova HashMapchamada de criação. Sua segunda linha desconsidera completamente a referência a este hashmap criado, disponibilizando-o para o coletor de lixo. Portanto, não faça isso, use:

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

Em segundo lugar, o compilador está reclamando que você lança o objeto para a HashMapsem verificar se é a HashMap. Mas, mesmo se você fizesse:

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

Você provavelmente ainda receberia esse aviso. O problema é que getBeanretorna Object, então não se sabe qual é o tipo. Convertê-lo para HashMapdiretamente não causaria o problema com o segundo caso (e talvez não houvesse um aviso no primeiro caso, não tenho certeza de quão pedante é o compilador Java com avisos para o Java 5). No entanto, você está convertendo para a HashMap<String, String>.

HashMaps são realmente mapas que pegam um objeto como chave e têm um objeto como valor, HashMap<Object, Object>se você preferir. Portanto, não há garantia de que, quando você obtiver seu bean, ele possa ser representado como HashMap<String, String>porque você poderia ter, HashMap<Date, Calendar>porque a representação não genérica retornada pode ter qualquer objeto.

Se o código for compilado e você puder executar String value = map.get("thisString");sem erros, não se preocupe com esse aviso. Mas se o mapa não for completamente de chaves de string para valores de string, você receberá um ClassCastExceptionem tempo de execução, porque os genéricos não podem impedir que isso aconteça nesse caso.


12
Isso foi há um tempo atrás, mas eu estava procurando uma resposta sobre o tipo de verificação de um Set <CustomClass> antes da transmissão, e você não pode obter um genérico parametrizado. por exemplo, if (event.getTarget instanceof Set <CustomClass>) Você só pode digitar check a genérico com a? e isso não removerá o aviso de elenco. por exemplo, if (event.getTarget instanceof Set <?>)
garlicman

315

O problema é que um elenco é uma verificação de tempo de execução - mas devido ao tipo de apagamento, em tempo de execução não há realmente nenhuma diferença entre um HashMap<String,String>e HashMap<Foo,Bar>para qualquer outro Fooe Bar.

Use @SuppressWarnings("unchecked")e segure seu nariz. Ah, e faça campanha para genéricos reificados em Java :)


14
Vou levar os genéricos reificados de Java sobre o NSMutableWhatever sem tipo, que parece um salto de dez anos para trás, em qualquer dia da semana. Pelo menos Java está tentando.
Dan Rosenstark

12
Exatamente. Se você insistir na verificação de tipos, isso só poderá ser feito com o HashMap <?,?> E isso não removerá o aviso, pois é o mesmo que não verificar os tipos genéricos. Não é o fim do mundo, mas é chato que você seja pego suprimindo um aviso ou vivendo com ele.
garlicman

5
@JonSkeet O que é um genérico reificado?
SasQ

89

Como as mensagens acima indicam, a Lista não pode ser diferenciada entre a List<Object>e a List<String>ou List<Integer>.

Resolvi esta mensagem de erro para um problema semelhante:

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

com o seguinte:

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

Explicação: A primeira conversão de tipo verifica se o objeto é uma Lista sem se preocupar com os tipos contidos nela (pois não podemos verificar os tipos internos no nível da Lista). A segunda conversão agora é necessária porque o compilador sabe apenas que a lista contém algum tipo de objeto. Isso verifica o tipo de cada objeto na lista conforme ele é acessado.


3
Você está certo, meu amigo. Em vez de transmitir a lista, basta iterá-la e converter cada elemento, o aviso não aparecerá, impressionante.
Juan Isaza

2
Isso eliminou a advertência, mas ainda não estou confiante: P
mumair

1
Sim sente como vendar o compilador, mas não em tempo de execução: D Então eu não vejo nenhuma diferença entre este e @SuppressWarnings ( "unchecked")
channae

1
Fantástico! A principal diferença do uso do @SupressWarning é que o uso da anotação elimina o aviso do IDE e das ferramentas de análise de código, mas se você estiver usando a compilação do sinalizador -Werror, o erro ainda será causado. Usando essa abordagem, ambos os avisos são corrigidos.
Edu Costa

30

Um aviso é exatamente isso. Um aviso. Às vezes, os avisos são irrelevantes, às vezes não. Eles são usados ​​para chamar sua atenção para algo que o compilador acha que pode ser um problema, mas pode não ser.

No caso de elencos, sempre haverá um aviso neste caso. Se você tem certeza absoluta de que um elenco específico será seguro, considere adicionar uma anotação como esta (não tenho certeza da sintaxe) antes da linha:

@SuppressWarnings (value="unchecked")

14
-1: um aviso nunca deve ser aceito. Ou suprima esse tipo de aviso ou corrija-o. Chegará o momento em que você terá muitos avisos e não verá o relevante uma vez.
Ezdazuzena # 16/13

10
Você realmente não pode evitar avisos de elenco de classe ao transmitir genéricos parametrizados, ou seja, Mapa, portanto esta é a melhor resposta para a pergunta original.
muttonUp

9

Você está recebendo esta mensagem porque getBean retorna uma referência de objeto e a está transmitindo para o tipo correto. O Java 1.5 fornece um aviso. Essa é a natureza do uso do Java 1.5 ou melhor com código que funciona assim. Spring tem a versão typesafe

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

na sua lista de tarefas.


6

Se você realmente deseja se livrar dos avisos, uma coisa que você pode fazer é criar uma classe que se estenda a partir da classe genérica.

Por exemplo, se você está tentando usar

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

Você pode criar uma nova classe como essa

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

Então quando você usa

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

O compilador sabe quais são os tipos (não mais genéricos) e não haverá aviso. Isso pode nem sempre ser a solução perfeita, alguns podem argumentar que esse tipo de derrota o propósito das classes genéricas, mas você ainda está reutilizando todo o mesmo código da classe genérica, mas está declarando, em tempo de compilação, que tipo você quer usar


3

A solução para evitar o aviso desmarcado:

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

Parece um hack, não uma solução.
Malwinder Singh

1
- A classe serializável MyMap não declara um campo serialVersionUID final estático do tipo long: {
Ulterior

1

Outra solução, se você estiver lançando muito o mesmo objeto e não quiser colocar seu código em lixo @SupressWarnings("unchecked"), seria criar um método com a anotação. Dessa forma, você centraliza o elenco e, com sorte, reduz a possibilidade de erro.

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

1

O código abaixo causa o aviso de segurança de tipo

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

Gambiarra

Crie um novo objeto de mapa sem mencionar os parâmetros, porque o tipo de objeto mantido na lista não é verificado.

Etapa 1: criar um novo mapa temporário

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

Etapa 2: instanciar o mapa principal

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

Etapa 3: Iterar o mapa temporário e definir os valores no mapa principal

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

0

O que eu fiz errado? Como resolvo o problema?

Aqui :

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

Você usa um método legado que geralmente não queremos usar, pois retorna Object:

Object getBean(String name) throws BeansException;

O método a favor de obter (para singleton) / criar (para protótipo) um bean de uma fábrica de beans é:

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

Usando-o como:

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

será compilado, mas ainda com um aviso de conversão desmarcado, pois Mapnem todos os objetos são necessariamente Map<String, String>objetos.

Mas <T> T getBean(String name, Class<T> requiredType) throws BeansException;não é suficiente nas classes genéricas do bean, como coleções genéricas, pois isso requer especificar mais de uma classe como parâmetro: o tipo de coleção e seu (s) tipo (s) genérico (s).

Nesse tipo de cenário e em geral, uma abordagem melhor não é usar BeanFactorymétodos diretamente , mas deixar a estrutura injetar o bean.

A declaração do bean:

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

A injeção de feijão:

@Autowired
@Qualifier("someMap")
Map<String, String> someMap;
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.