Como abordar avisos de elenco não verificados?

611

O Eclipse está me enviando um aviso do seguinte formulário:

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

Isso é de uma chamada para uma API que eu não tenho controle sobre o que retorna Object:

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

Eu gostaria de evitar avisos do Eclipse, se possível, pois teoricamente eles indicam pelo menos um possível problema de código. Ainda não encontrei uma boa maneira de eliminar esta. Posso extrair a linha única envolvida em um método por si só e adicioná @SuppressWarnings("unchecked")-lo, limitando assim o impacto de ter um bloco de código no qual ignoro os avisos. Alguma opção melhor? Não quero desativar esses avisos no Eclipse.

Antes de eu entrar no código, era mais simples, mas ainda provocava avisos:

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

O problema estava em outro lugar quando você tentava usar o hash, recebia avisos:

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.
skiphoppy
fonte
Se você estiver usando o HttpSession assim, consulte o artigo de Brian Goetz sobre o assunto: ibm.com/developerworks/library/j-jtp09238.html
Tom Hawtin - tackline em
Se um elenco não verificado é inevitável, uma boa idéia é associá-lo firmemente a algo que represente logicamente seu tipo (como um enumou até mesmo exemplos Class<T>), para que você possa dar uma olhada e saber que é seguro.
Philip Guin
4
Related / dupe: Type safety: elenco não verificado
blahdiblah 11/03
3
possível duplicata de segurança
Raedwald 17/13
Eu acrescentaria, descobri que só era possível adicionar @SuppressWarnings ("desmarcado") no nível do método que contém o código incorreto. Então, eu quebrei o código para uma rotina em que eu tinha que fazer isso. Eu sempre pensei que você poderia fazer isso imediatamente acima da linha em questão.
JGFMK 15/01

Respostas:

557

A resposta óbvia, é claro, não é fazer o elenco desmarcado.

Se for absolutamente necessário, tente pelo menos limitar o escopo da @SuppressWarningsanotação. De acordo com seus Javadocs , ele pode acessar variáveis ​​locais; dessa forma, nem afeta todo o método.

Exemplo:

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

Não há como determinar se o Maprealmente deve ter os parâmetros genéricos <String, String>. Você deve saber de antemão quais devem ser os parâmetros (ou descobrirá quando receber um ClassCastException). É por isso que o código gera um aviso, porque o compilador não pode saber se é seguro.

Michael Myers
fonte
112
+1 por indicar que ele pode usar variáveis ​​locais. O Eclipse oferece apenas adicioná-lo a todo o método ...
thSoft
17
O Eclipse 3.7 (Indigo) possui suporte para adicionar desmarcadas a variáveis ​​locais.
sweetfa
78
O aviso não é apenas porque o compilador não sabe que o elenco é seguro. Por exemplo, String s = (String) new Object() ;não recebe aviso, mesmo que o compilador não saiba que o elenco é seguro. O aviso é porque o compilador (a) não sabe que a conversão é segura E (b) não gerará uma verificação completa do tempo de execução no ponto da conversão. Haverá uma verificação de que é um Hashmap, mas não haverá uma verificação de que é um HashMap<String,String>.
Theodore Norvell 28/02
9
Infelizmente, mesmo que o elenco e o aviso sejam para a atribuição , a anotação deve seguir a declaração da variável ... Portanto, se a declaração e a atribuição estiverem em lugares diferentes (por exemplo, fora e dentro de um bloco 'try', respectivamente) , O Eclipse agora gera dois avisos: a conversão não verificada original e um novo diagnóstico de "anotação desnecessária".
Ti Strga
6
Uma solução alternativa para a anotação que precisa acompanhar a declaração da variável local, que pode estar em um escopo diferente em uma linha diferente da conversão real, é criar uma variável local dentro do escopo da conversão especificamente para executar a conversão na mesma linha como a declaração. Em seguida, atribua essa variável à variável real que está em um escopo diferente. Este é o método que usei também para suprimir o aviso em uma conversão para uma variável de instância, pois a anotação também não pode ser aplicada aqui.
Jeff Lockhart
169

Infelizmente, não há ótimas opções aqui. Lembre-se, o objetivo de tudo isso é preservar a segurança do tipo. " Java Generics " oferece uma solução para lidar com bibliotecas herdadas não genéricas, e existe uma em particular chamada "técnica de loop vazio" na seção 8.2. Basicamente, faça o elenco inseguro e suprima o aviso. Em seguida, percorra o mapa da seguinte maneira:

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

Se um tipo inesperado for encontrado, você obterá um tempo de execução ClassCastException, mas pelo menos isso acontecerá próximo à origem do problema.

Julien Chastang
fonte
6
Resposta muito, muito melhor que a fornecida pelo skiphoppy, por várias razões: 1) Esse código é muito, muito mais curto. 2) Esse código realmente lança ClassCastException conforme o esperado. 3) Este código não faz uma cópia completa do mapa de origem. 4) Os loops podem ser facilmente agrupados em um método separado usado em uma declaração, o que removeria facilmente o impacto no desempenho no código de produção.
Stijn de Witt
6
Não existe a possibilidade de o compilador Java ou o JIT decidir que os resultados desse código não estão sendo usados ​​e "otimizá-lo" ao não compilá-lo?
RenniePet
1
Não é realmente um código morto se ele pode gerar uma exceção. Não sei o suficiente sobre os compiladores JIT em uso hoje para garantir que nenhum deles estragaria tudo, mas sinto-me bastante confiante em dizer que eles não deveriam.
GrandOpener 29/05
3
Isso ainda não garante a segurança do tipo, pois o mesmo mapa ainda está sendo usado. Originalmente, ele pode ter sido definido como Mapa <Objeto, Objeto>, que apenas contém Strings e Numbers e, posteriormente, se um Boolean for adicionado, o usuário desse código terá uma surpresa confusa e bastante difícil de rastrear. A única maneira de garantir a segurança do tipo é copiá-lo em um novo mapa com o tipo solicitado, que garante o que é permitido entrar nele.
user2219808
112

Uau; Acho que descobri a resposta para minha própria pergunta. Só não tenho certeza se vale a pena! :)

O problema é que o elenco não está marcado. Então, você tem que verificar você mesmo. Você não pode simplesmente verificar um tipo parametrizado com instanceof, porque as informações do tipo parametrizado não estão disponíveis no tempo de execução, pois foram apagadas no tempo de compilação.

Mas, você pode executar uma verificação em todos os itens do hash, com instanceof, e, ao fazer isso, pode construir um novo hash que seja seguro para o tipo. E você não provocará nenhum aviso.

Graças a mmyers e Esko Luontola, parametrizei o código que originalmente escrevi aqui, para que ele possa ser agrupado em uma classe de utilitário em algum lugar e usado para qualquer HashMap parametrizado. Se você deseja entendê-lo melhor e não estiver muito familiarizado com os genéricos, incentive a visualização do histórico de edições desta resposta.

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

Isso dá muito trabalho, possivelmente com muito pouca recompensa ... Não sei se vou usá-lo ou não. Agradeço qualquer comentário sobre se as pessoas acham que vale a pena ou não. Além disso, eu gostaria de receber sugestões de melhorias: há algo melhor que eu posso fazer além de lançar AssertionErrors? Existe algo melhor que eu poderia jogar? Devo torná-lo uma exceção marcada?

skiphoppy
fonte
68
esse material é confuso, mas acho que tudo o que você fez foi negociado ClassCastExceptions por AssertionErrors.
Dustin Getz
59
Cara, isso definitivamente não vale a pena! Imagine o pobre coitado que precisa voltar e modificar algum código com essa bagunça. Não gosto de suprimir avisos, mas acho que esse é o mal menor aqui.
Craig B
69
Não é apenas uma bagunça feia e confusa (quando você não pode evitar comentários copiosos podem orientar o programador de manutenção); iterar sobre todos os elementos da coleção transforma a conversão de uma operação O (1) para O (n). Isso é algo que nunca seria esperado e pode facilmente se transformar em uma desaceleração horrível e misteriosa.
Dan Is Fiddling Por Firelight
22
@ DanNeely você está correto. Em geral, ninguém deve fazer isso.
Skiphoppy
4
Alguns comentários ... a assinatura do método está errada porque não "lança" nada, apenas copia o mapa existente em um novo mapa. Além disso, provavelmente poderia ser refatorado para aceitar qualquer mapa e não confiar no próprio HashMap (por exemplo, pegue Map e retorne Map na assinatura do método, mesmo que o tipo interno seja HashMap). Você realmente não precisa fazer a conversão ou o armazenamento em um novo mapa - se você não lançar um erro de afirmação, o mapa fornecido terá os tipos certos dentro dele a partir de agora. Criar um novo mapa com os tipos genéricos é inútil, pois você ainda pode torná-lo bruto e colocar o que quer que seja.
MetroidFan2002
51

Nas Preferências do Eclipse, acesse Java-> Compilador-> Erros / Avisos-> Tipos genéricos e marque a Ignore unavoidable generic type problemscaixa de seleção.

Isso satisfaz a intenção da pergunta, ou seja,

Eu gostaria de evitar avisos do Eclipse ...

se não o espírito.

Dave
fonte
1
Ah, obrigado por isso :) " uses unchecked or unsafe operations." Estava recebendo um erro javac, mas a adição @SuppressWarnings("unchecked")deixou o Eclipse infeliz, alegando que a supressão era desnecessária. Desmarcar esta caixa faz com que o Eclipse javacse comporte da mesma maneira que eu queria. Suprimir explicitamente o aviso no código é muito mais claro do que suprimi-lo em qualquer lugar dentro do Eclipse.
dimo414
26

Você pode criar uma classe de utilitário como a seguir e usá-la para suprimir o aviso não verificado.

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

Você pode usá-lo da seguinte maneira:

import static Objects.uncheckedCast;
...

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

Um pouco mais de discussão sobre isso está aqui: http://cleveralias.blogs.com/thought_spearmints/2006/01/suppresswarning.html

Esko Luontola
fonte
18
não com voto negativo, mas o invólucro não adiciona exatamente nada além de suprimir o aviso.
Dustin Getz
3
+1, pois esta solução não desperdiça preciosas linhas de código.
Tino
1
@ErikE Too much. Monitores maiores e de alta resolução muito mais caros para dar espaço a todas aquelas linhas desperdiçadas, uma mesa maior para colocar todos esses monitores maiores, uma sala maior para colocar a mesa maior e um chefe perspicaz.
Tino
1
@ErikE Scrollbars, para vi? Você está de brincadeira?
Tino
21

Isso é difícil, mas aqui estão os meus pensamentos atuais:

Se a sua API retornar Objeto, não haverá nada que você possa fazer - não importa o quê, você estará lançando o objeto às cegas. Você permite que o Java execute ClassCastExceptions ou você mesmo pode verificar cada elemento e lançar Assertions ou IllegalArgumentExceptions ou algo parecido, mas essas verificações em tempo de execução são todas equivalentes. Você precisa suprimir o tempo de compilação não verificado, independentemente do que faz em tempo de execução.

Eu preferiria fazer a conversão às cegas e permitir que a JVM execute sua verificação de tempo de execução para mim, pois "sabemos" o que a API deve retornar e geralmente queremos assumir que a API funciona. Use genéricos em qualquer lugar acima do elenco, se precisar deles. Você realmente não está comprando nada lá, pois ainda possui o elenco de blinds únicos, mas pelo menos pode usar genéricos a partir daí para que a JVM possa ajudá-lo a evitar transmissões de blinds em outras partes do seu código.

Nesse caso em particular, presumivelmente, você pode ver a chamada para SetAttribute e ver o tipo de entrada; portanto, apenas ocultar o tipo para o mesmo na saída não é imoral. Adicione um comentário referenciando o SetAttribute e pronto.

Dustin Getz
fonte
16

Aqui está um exemplo abreviado que evita o aviso de "elenco não verificado" empregando duas estratégias mencionadas em outras respostas.

  1. Passe a Classe do tipo de interesse como parâmetro em tempo de execução ( Class<T> inputElementClazz). Então você pode usar:inputElementClazz.cast(anyObject);

  2. Para conversão de tipo de uma coleção, use o curinga? em vez de um tipo genérico T para reconhecer que você realmente não sabe que tipo de objetos esperar do código legado ( Collection<?> unknownTypeCollection). Afinal, é isso que o aviso de "elenco não verificado" quer nos dizer: não podemos ter certeza de que recebemos um Collection<T>, então a coisa mais honesta a fazer é usar a Collection<?>. Se for absolutamente necessário, uma coleção de um tipo conhecido ainda pode ser construída ( Collection<T> knownTypeCollection).

O código legado com interface no exemplo abaixo possui um atributo "input" no StructuredViewer (StructuredViewer é um widget de árvore ou tabela, "input" é o modelo de dados por trás dele). Essa "entrada" pode ser qualquer tipo de coleção 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();
}

Naturalmente, o código acima pode gerar erros de tempo de execução se usarmos o código herdado com os tipos de dados incorretos (por exemplo, se definirmos uma matriz como a "entrada" do StructuredViewer em vez de uma coleção Java).

Exemplo de chamada do método:

dragFinishedStrategy.dragFinished(viewer, Product.class);
StaticNoiseLog
fonte
13

No mundo da Sessão HTTP, você realmente não pode evitar a transmissão, já que a API é escrita dessa maneira (apenas recebe e retorna Object).

Com um pouco de trabalho, você pode facilmente evitar o elenco desmarcado. Isso significa que ele se transformará em um elenco tradicional, dando um ClassCastExceptiondireito no caso de um erro). Uma exceção desmarcada pode se transformar em um CCEa qualquer momento mais tarde, em vez do ponto do elenco (essa é a razão pela qual é um aviso separado).

Substitua o HashMap por uma classe dedicada:

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

Em seguida, faça a conversão para essa classe em vez de Map<String,String>e tudo será verificado no local exato em que você escreve seu código. Não é inesperado ClassCastExceptionsmais tarde.

Joachim Sauer
fonte
Esta é realmente uma resposta útil.
GPrathap
10

No Android Studio, se você deseja desativar a inspeção, pode usar:

//noinspection unchecked
Map<String, String> myMap = (Map<String, String>) deserializeMap();
Jan Moravec
fonte
2
Isso funciona no IntelliJ IDE também
neXus
8

Nesse caso em particular, eu não armazenaria o Maps diretamente no HttpSession, mas uma instância da minha própria classe, que por sua vez contém um Mapa (um detalhe de implementação da classe). Então você pode ter certeza de que os elementos no mapa são do tipo certo.

Mas se você quiser verificar se o conteúdo do mapa é do tipo correto, use um código como este:

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);
    }
}
Esko Luontola
fonte
1
Impressionante; Acho que posso combinar isso com a minha resposta para parametrizá-lo e evitar a necessidade de suprimir completamente os avisos!
Skiphoppy:
1
+1 provavelmente melhor receita (fácil de entender e manter) a fazê-lo com segurança com tempo de execução verifica
Tino
8

A função de utilitário Objects.Unchecked na resposta acima de Esko Luontola é uma ótima maneira de evitar a confusão de programas.

Se você não deseja o SuppressWarnings em um método inteiro, o Java o força a colocá-lo em um local. Se você precisar de um elenco para um membro, pode levar a um código como este:

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

O uso do utilitário é muito mais limpo e ainda é óbvio o que você está fazendo:

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

NOTA: Acho importante acrescentar que, às vezes, o aviso realmente significa que você está fazendo algo errado, como:

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

O que o compilador está dizendo é que essa conversão NÃO será verificada no tempo de execução; portanto, nenhum erro de tempo de execução será gerado até você tentar acessar os dados no contêiner genérico.

Vou eu
fonte
5

A supressão de aviso não é uma solução. Você não deve fazer uma transmissão de dois níveis em uma declaração.

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;
}
abbas
fonte
1
Sua pergunta de cinco anos? Você precisa fazer tanto trabalho? Dado que o Java tem o tipo apagamento, o segundo mapa de hash deve ser idêntico ao primeiro no tempo de execução; Eu acho que seria mais eficiente e evitaria a cópia se você apenas repetisse as entradas e verificasse que eram todas instâncias de strings. Ou, TBH, inspecione a origem do JAR do servlet que você está usando e verifique se ele apenas coloca seqüências de caracteres.
Rup
1
Até hoje eu estou vendo esse aviso em projetos. Seu problema não era a verificação do tipo, mas um aviso causado por um "put" em um mapa não transmitido.
abbas
2

Um palpite rápido se você postar seu código pode dizer com certeza, mas você pode ter feito algo ao longo das linhas de

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

que produzirá o aviso quando você precisar fazer

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

pode valer a pena olhar

Genéricos na linguagem de programação Java

se você não estiver familiarizado com o que precisa ser feito.

Mark Davidson
fonte
1
Infelizmente, não é uma situação tão fácil. Código adicionado.
Skiphoppy,
1
Eu vim aqui procurando uma resposta para um problema um pouco diferente: e você me disse exatamente o que eu precisava! Obrigado!
Staticsan 17/09/10
2

Talvez eu tenha entendido mal a pergunta (um exemplo e algumas linhas ao redor seriam legais), mas por que você nem sempre usa uma interface apropriada (e Java5 +)? Não vejo nenhuma razão pela qual você queira converter para a em HashMapvez de a Map<KeyType,ValueType>. Na verdade, não consigo imaginar nenhum motivo para definir o tipo de uma variável em HashMapvez de Map.

E por que a fonte é uma Object? É um tipo de parâmetro de uma coleção herdada? Nesse caso, use genéricos e especifique o tipo que você deseja.

phihag
fonte
2
Tenho certeza de que mudar para o Map nesse caso não mudaria nada, mas obrigado pela dica de programação, que pode mudar a maneira como faço algumas coisas, para melhor. A origem do objeto é uma API sobre a qual eu não tenho controle (código adicionado).
Skiphoppy,
2

Se eu tiver que usar uma API que não ofereça suporte a genéricos ... eu tento isolar essas chamadas em rotinas de wrapper com o mínimo de linhas possível. Em seguida, uso a anotação SuppressWarnings e também adiciono os lançamentos de segurança de tipo ao mesmo tempo.

Essa é apenas uma preferência pessoal para manter as coisas o mais organizadas possível.

Fortyrunner
fonte
2

Pegue este, é muito mais rápido do que criar um novo HashMap, se ele já for um, mas ainda seguro, pois cada elemento é verificado em relação ao seu tipo ...

@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);
    }
jfreundo
fonte
key.isAssignableFrom(e.getKey().getClass())pode ser escrito comokey.isInstance(e.getKey())
user102008
1

Basta verificar antes de lançá-lo.

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

E para quem pergunta, é bastante comum receber objetos em que você não tem certeza do tipo. Muitas implementações "SOA" herdadas passam por vários objetos nos quais você nem sempre deve confiar. (Os horrores!)

EDIT Alterou o código de exemplo uma vez para corresponder às atualizações do pôster e, após alguns comentários, vejo que instanceof não funciona muito bem com genéricos. No entanto, alterar a verificação para validar o objeto externo parece funcionar bem com o compilador de linha de comando. Exemplo revisado agora publicado.

Rick
fonte
8
Infelizmente, os genéricos tornam isso impossível. Não é apenas um HashMap, é um HashMap com informações de tipo. E se eu eliminar essa informação, enviarei os avisos para outro lugar.
Skiphoppy,
1

Quase todo problema em Ciência da Computação pode ser resolvido adicionando um nível de indireção *, ou algo assim.

Portanto, introduza um objeto não genérico de nível superior que a Map. Sem contexto, não parecerá muito convincente, mas de qualquer maneira:

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

* Exceto muitos níveis de indireção.

Tom Hawtin - linha de orientação
fonte
1
A citação é atribuída ao falecido professor David Wheeler. en.wikipedia.org/wiki/…
Stephen C
1

Aqui está uma maneira de lidar com isso quando substituo a equals()operação.

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

Isso parece funcionar no Java 8 (mesmo compilado com -Xlint:unchecked)

Jim Daehn
fonte
0

Se você tiver certeza de que o tipo retornado por session.getAttribute () é HashMap, não será possível converter para esse tipo exato, mas confie apenas na verificação do HashMap genérico

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

O Eclipse surpreenderá os avisos, mas é claro que isso pode levar a erros de tempo de execução que podem ser difíceis de depurar. Eu uso essa abordagem apenas em contextos não críticos à operação.

Lukas Normantas
fonte
0

Duas maneiras, uma que evita a marca completamente, a outra usando um método utilitário impertinente, mas agradável.
O problema é coleções pré-genéricas ...
Eu acredito que a regra geral é: "converter objetos uma coisa de cada vez" - o que isso significa ao tentar usar classes brutas em um mundo genérico é que, porque você não sabe o que está neste mapa <?,?> (e, de fato, a JVM pode até achar que nem é um mapa!), é óbvio quando você pensa sobre isso que não pode convertê-lo. Se você tivesse um Map <String,?> Map2, o HashSet <String> keys = (HashSet <String>) map2.keySet () não emitirá um aviso, apesar de ser um "ato de fé" para o compilador (porque ele pode vir a ser um TreeSet) ... mas só é um único ato de fé.

PS com a objeção de que a iteração como na minha primeira maneira "é chata" e "leva tempo", a resposta é "sem dor, sem ganho": é garantido que uma coleção genérica contenha Map.Entry <String, String> se nada outro. Você tem que pagar por esta garantia. Ao usar genéricos sistematicamente, esse pagamento, maravilhosamente, assume a forma de conformidade com a codificação, não o tempo da máquina!
Uma escola de pensamento pode dizer que você deve definir as configurações do Eclipse para cometer erros de verificação não verificados, em vez de avisos. Nesse caso, você teria que usar o meu primeiro caminho.

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


}
microfone roedor
fonte
o fato de você não ter privilégios para comentários não permite editar as respostas de outras pessoas para adicionar seus comentários; você edita as respostas de outras pessoas para aprimorá-las na formatação, sintaxe, ..., para não adicionar sua opinião sobre elas. Quando você atingir 50 representantes, poderá comentar em qualquer lugar; nesse meio tempo, tenho certeza de que pode resistir (ou, se realmente não puder, escrever seus comentários nas respostas existentes em sua postagem). (nota para os outros: eu escrevo isso porque eu vi - e rejeitou - suas propostas Comments-edições para outras mensagens nas ferramentas de moderação)
Matteo Italia
-1

Isso faz os avisos desaparecerem ...

 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;
}
morno
fonte
1
Não, não faz. Na verdade, isso cria dois avisos onde primeiro houve um.
Stijn de Witt
Ah ok. Não sei por que pensei isso.
Lukewm
-3

Solução: Desative este aviso no Eclipse. Não @SuppressWarnings, apenas desative-o completamente.

Várias das "soluções" apresentadas acima estão fora de linha, tornando o código ilegível com o objetivo de suprimir um aviso bobo.

Marc Riehm
fonte
9
Posso perguntar por que? desativar um aviso globalmente ocultará outros lugares onde esse problema é real. adicionar um @SuppressWarningsnão torna o código ilegível.
MByD 28/02