Isso surgiu como uma pergunta que fiz em uma entrevista recentemente, como algo que o candidato gostaria de ver adicionado à linguagem Java. É comumente identificado como uma dor que o Java não reificou os genéricos , mas, quando pressionado, o candidato não poderia realmente me dizer o tipo de coisas que ele poderia ter alcançado se eles estivessem lá.
Obviamente, como os tipos brutos são permitidos em Java (e verificações inseguras), é possível subverter os genéricos e acabar com um List<Integer>
que (por exemplo) realmente contém String
s. Isso claramente poderia se tornar impossível se as informações de tipo fossem reificadas; mas deve haver mais do que isso !
As pessoas poderiam postar exemplos de coisas que realmente gostariam de fazer , se houvesse genéricos reificados disponíveis? Quero dizer, obviamente você poderia obter o tipo de a List
em tempo de execução - mas o que você faria com ele?
public <T> void foo(List<T> l) {
if (l.getGenericType() == Integer.class) {
//yeah baby! err, what now?
EDITAR : Uma atualização rápida para isso, pois as respostas parecem estar principalmente preocupadas com a necessidade de passar um Class
como um parâmetro (por exemplo EnumSet.noneOf(TimeUnit.class)
). Eu estava procurando mais por algo onde isso simplesmente não fosse possível . Por exemplo:
List<?> l1 = api.gimmeAList();
List<?> l2 = api.gimmeAnotherList();
if (l1.getGenericType().isAssignableFrom(l2.getGenericType())) {
l1.addAll(l2); //why on earth would I be doing this anyway?
fonte
findClass()
teria que ignorar a parametrização, masdefineClass()
não poderia). E como sabemos, The Powers That Be considera a compatibilidade com versões anteriores primordial.Respostas:
Nas poucas vezes em que me deparei com essa "necessidade", no final das contas tudo se resume a esta construção:
Isso funciona em C #, supondo que
T
tenha um construtor padrão . Você pode até obter o tipo de tempo de execuçãotypeof(T)
e obter os construtoresType.GetConstructor()
.A solução Java comum seria passar o
Class<T>
argumento as.(não precisa necessariamente ser passado como argumento do construtor, já que um argumento de método também é bom, o acima é apenas um exemplo, também
try-catch
é omitido por questões de brevidade)Para todas as outras construções de tipo genérico, o tipo real pode ser facilmente resolvido com um pouco de ajuda de reflexão. As perguntas e respostas abaixo ilustram os casos de uso e possibilidades:
fonte
T.class
ouT.getClass()
, de forma que você possa acessar todos os seus campos, construtores e métodos. Também torna a construção impossível.O que mais comumente me incomoda é a incapacidade de tirar proveito do envio múltiplo em vários tipos genéricos. O seguinte não é possível e há muitos casos em que seria a melhor solução:
fonte
public void my_method(List input) {}
. No entanto, nunca me deparei com essa necessidade, simplesmente porque não teriam o mesmo nome. Se eles tiverem o mesmo nome, eu questiono sepublic <T extends Object> void my_method(List<T> input) {}
não é uma ideia melhor.myStringsMethod(List<String> input)
emyIntegersMethod(List<Integer> input)
até mesmo se a sobrecarga para tal caso fosse possível em Java.<algorithm>
em C ++.A segurança do tipo vem à mente. O downcasting para um tipo parametrizado sempre será inseguro sem genéricos reificados:
Além disso, as abstrações vazariam menos - pelo menos aquelas que podem estar interessadas em informações de tempo de execução sobre seus parâmetros de tipo. Hoje, se você precisa de qualquer tipo de informação de tempo de execução sobre o tipo de um dos parâmetros genéricos, você também deve transmiti-la
Class
. Dessa forma, sua interface externa depende de sua implementação (se você usa RTTI sobre seus parâmetros ou não).fonte
ParametrizedList
que copia os dados nos tipos de verificação da coleção de origem. É um pouco parecido,Collections.checkedList
mas pode ser semeado com uma coleção para começar.T.class.getAnnotation(MyAnnotation.class)
(ondeT
é um tipo genérico) sem alterar a interface externa.Você seria capaz de criar matrizes genéricas em seu código.
fonte
String[] strings = new String[1]; Object[] objects = strings; objects[0] = new Object();
Compila bem em ambos os idiomas. Executa não muito bem.Esta é uma pergunta antiga, há uma tonelada de respostas, mas acho que as respostas existentes estão erradas.
"reificado" significa apenas real e geralmente significa apenas o oposto de apagamento de tipo.
O grande problema relacionado ao Java Generics:
void method(List<A> l)
emethod(List<B> l)
. Isso ocorre devido ao apagamento do tipo, mas é extremamente mesquinho.fonte
deserialize(thingy, List<Integer>.class)
A serialização seria mais direta com a reificação. O que nós queremos é
O que temos que fazer é
parece feio e funciona mal.
Também há casos em que seria muito útil dizer algo como
Essas coisas não picam com frequência, mas picam quando acontecem.
fonte
Minha exposição ao Java Geneircs é bastante limitada e, além dos pontos que outras respostas já mencionaram, há um cenário explicado no livro Java Generics and Collections , de Maurice Naftalin e Philip Walder, onde os genéricos reificados são úteis.
Visto que os tipos não são reificáveis, não é possível ter exceções parametrizadas.
Por exemplo, a declaração do formulário abaixo não é válida.
Isto é porque a captura cláusula verifica se a exceção lançada corresponde a um determinado tipo. Esta verificação é igual à verificação realizada pelo teste de instância e, como o tipo não é reificável, a forma de declaração acima é inválida.
Se o código acima fosse válido, o tratamento de exceções da maneira abaixo teria sido possível:
O livro também menciona que, se os genéricos Java forem definidos de maneira semelhante à forma como os modelos C ++ são definidos (expansão), isso pode levar a uma implementação mais eficiente, pois oferece mais oportunidades de otimização. Mas não oferece nenhuma explicação mais do que isso, então qualquer explicação (dicas) de pessoas experientes seria útil.
fonte
Os arrays provavelmente funcionariam muito melhor com os genéricos se fossem reificados.
fonte
List<String>
não é umList<Object>
.Eu tenho um wrapper que apresenta um conjunto de resultados jdbc como um iterador (isso significa que posso testar a unidade de operações originadas do banco de dados muito mais facilmente por meio da injeção de dependência).
A API parece
Iterator<T>
onde T é algum tipo que pode ser construído usando apenas strings no construtor. O Repetidor, então, olha para as strings que estão sendo retornadas da consulta sql e tenta combiná-las com um construtor do tipo T.Na forma atual como os genéricos são implementados, também tenho que passar a classe dos objetos que estarei criando a partir do meu conjunto de resultados. Se bem entendi, se os genéricos fossem reificados, eu poderia simplesmente chamar T.getClass () obter seus construtores e, em seguida, não ter que lançar o resultado de Class.newInstance (), o que seria muito mais puro.
Basicamente, acho que torna a escrita de APIs (em oposição a apenas escrever um aplicativo) mais fácil, porque você pode inferir muito mais de objetos e, portanto, menos configuração será necessária ... Eu não apreciei as implicações das anotações até que os vi sendo usados em coisas como spring ou xstream em vez de resmas de configuração.
fonte
Uma coisa boa seria evitar boxing para tipos primitivos (valor). Isso está um tanto relacionado à reclamação de array que outras pessoas levantaram e, nos casos em que o uso de memória é restrito, pode realmente fazer uma diferença significativa.
Existem também vários tipos de problemas ao escrever uma estrutura onde é importante ser capaz de refletir sobre o tipo parametrizado. É claro que isso pode ser contornado passando um objeto de classe em tempo de execução, mas isso obscurece a API e coloca uma carga adicional no usuário do framework.
fonte
new T[]
onde T é do tipo primitivo!Não é que você vai conseguir algo extraordinário. Será apenas mais simples de entender. O apagamento de tipo parece ser um momento difícil para iniciantes e, em última análise, requer a compreensão da maneira como o compilador funciona.
Minha opinião é que os genéricos são simplesmente um extra que economiza muito elenco redundante.
fonte
Algo que todas as respostas aqui perderam e que é constantemente uma dor de cabeça para mim é que, uma vez que os tipos são apagados, você não pode herdar uma interface genérica duas vezes. Isso pode ser um problema quando você deseja fazer interfaces de granulação fina.
fonte
Aqui está um que me pegou hoje: sem reificação, se você escrever um método que aceita uma lista varargs de itens genéricos ... os chamadores podem PENSAR que são à prova de texto, mas acidentalmente passam qualquer coisa velha e explodem seu método.
Parece improvável que isso aconteça? ... Claro, até ... você usar Class como seu tipo de dados. Nesse ponto, seu chamador ficará feliz em enviar muitos objetos Class, mas um simples erro de digitação enviará objetos Class que não aderem a T e ocorrerá um desastre.
(NB: posso ter cometido um erro aqui, mas pesquisando "varargs genéricos" acima parece ser exatamente o que você esperaria. O que torna este um problema prático é o uso de Class, eu acho - chamadores parecem para ser menos cuidadoso :()
Por exemplo, estou usando um paradigma que usa objetos Class como uma chave em mapas (é mais complexo do que um mapa simples - mas conceitualmente é isso que está acontecendo).
por exemplo, isso funciona muito bem em Genéricos Java (exemplo trivial):
por exemplo, sem reificação no Java Generics, este aceita QUALQUER objeto "Class". E é apenas uma pequena extensão do código anterior:
Os métodos acima devem ser escritos milhares de vezes em um projeto individual - portanto, a possibilidade de erro humano aumenta. Depurar erros está provando que "não é divertido". No momento, estou tentando encontrar uma alternativa, mas não tenho muita esperança.
fonte