1. O que exatamente é genérico?
Antes de discutir a inferência do tipo, devemos revisar o que é genérico. Os genéricos são novos recursos do Java SE 1.5. A essência dos genéricos é um tipo parametrizado, ou seja, o tipo de dados operado é especificado como um parâmetro. Em termos leigos, seriam "variáveis do tipo". Esse tipo de variável pode ser usado na criação de classes, interfaces e métodos. A maneira mais fácil de entender os genéricos do Java é considerá -lo uma sintaxe conveniente que pode economizar algumas operações no Java Type casting :
List <pple> box = new ArrayList <pple> (); box.add (new Apple ()); Apple Apple = box.get (0);
O próprio código acima expressou claramente: a caixa é uma List com objetos da Apple. O método get retorna uma instância do objeto Apple e esse processo não requer conversão de tipo. Não há genéricos, o código acima precisa ser escrito assim:
Apple Apple = (Apple) Box.get (0);
Obviamente, os genéricos não são tão simples quanto o que eu descrevi aqui, mas este não é o protagonista de nossos dias. Os alunos que não entendem os genéricos precisam compensar as lições ~ é claro, os melhores materiais de referência ainda são os documentos oficiais.
2. Problemas causados por genéricos (antes do Java 7)
A maior vantagem dos genéricos é que eles fornecem o tipo de segurança do programa e podem ser compatíveis com versões anteriores. No entanto, também há coisas que tornam os desenvolvedores infelizes. O tipo de genéricos deve ser escrito toda vez que definir. Essa especificação de exibição não apenas parece um pouco detalhada, mas o mais importante é que muitos programadores não estão familiarizados com os genéricos, portanto não podem fornecer parâmetros do tipo correto em muitos casos. Agora, o compilador infere automaticamente os tipos de genéricos de parâmetros, que podem reduzir essa situação e melhorar a legibilidade do código.
3. Melhorias na derivação do tipo de genéricos em Java 7
O uso de tipos genéricos em versões anteriores do Java 7 requer a adição de tipos genéricos em ambos os lados ao declarar e atribuir valores. Por exemplo:
Mapa <string, Integer> map = new Hashmap <string, Integer> ();
Muitas pessoas devem ter sido as mesmas que eu no começo, e ficaram intrigadas com isso: não declarei o tipo de parâmetro na declaração de variável? Por que ainda preciso escrever quando o objeto é inicializado? É também disso que faz os genéricos reclamarem quando apareceram pela primeira vez. No entanto, é gratificante que, enquanto o Java está melhorando, os designers também estejam constantemente melhorando o compilador Java para torná -lo mais inteligente e humanizado. Aqui está o nosso protagonista hoje: digite pushdown ... bem ... não é derivação do tipo, ou seja, inferência de tipo. Quando esse cara aparece, quando ele escreve o código acima, ele pode omitir alegremente os tipos de parâmetros quando a instanciação do objeto é instanciada, e fica assim:
Mapa <string, inteiro> map = new hashmap <> ();
Nesta declaração, o compilador inferirá automaticamente o tipo genérico ao instantar HashMap com base no tipo genérico quando a declaração variável. Novamente, certifique -se de prestar atenção ao "<>" por trás new HashMap . Somente adicionando isso "<>" significa que é inferência de tipo automático, caso contrário, é um HashMap não genérico, e um prompt de aviso será fornecido ao compilar o código-fonte usando o compilador. Este par de colchetes de ângulo "<>" é chamado de "diamante" no documento oficial.
No entanto, a derivação do tipo no momento não está completa (mesmo um produto semi-acabado), porque o tipo de inferência ao criar instâncias genéricas no Java SE 7 é limitado: somente se o tipo parametrizado do construtor for declarado significativamente no contexto, a inferência de tipo pode ser usada, caso contrário, não funcionará. Por exemplo: O exemplo a seguir não pode ser compilado corretamente no Java 7 (mas pode ser compilado no Java 8 agora, porque o tipo genérico é inferido automaticamente com base nos parâmetros do método):
List <String> list = new ArrayList <> (); list.add ("a"); // Como Addall espera obter parâmetros da coleção de tipos <??? estende a string>, a seguinte instrução não pode passar list.addall (new ArrayList <> ());4. Reevolução em Java 8
Na mais recente documentação oficial do Java, podemos ver a definição de derivação do tipo:
A inferência de tipo é a capacidade do compilador Java de analisar cada invocação de método e a declaração correspondente para determinar o argumento (ou argumentos) do tipo que torna a invocação aplicável. O algoritmo de inferência determina os tipos de argumentos e, se disponível, o tipo que o resultado está sendo atribuído ou devolvido. Finalmente, o algoritmo de inferência tenta encontrar o tipo mais específico que funciona com todos os argumentos.
Em resumo, a derivação do tipo refere -se à capacidade do compilador de determinar os tipos de parâmetros necessários com base no método que você chama e na declaração correspondente. E um exemplo também é dado na documentação oficial para explicar:
estático <t> t pick (t a1, t a2) {return a2; } Serializável s = pick ("d", novo ArrayList <String> ()); Aqui, o compilador pode deduzir que o tipo de segundo parâmetro aprovado no método pick é Serializable .
Nas versões Java anteriores, se o exemplo acima puder ser compilado, você precisará escrever isso:
Serializável s = this.
A razão detalhada para escrever isso pode ser vista no capítulo genérico do pensamento de programação Java de Bruce Eckel (quarta edição). Obviamente, este livro é baseado no Java 6, e esta versão não tem conceito de derivação do tipo. Vendo isso, muitas pessoas podem ver claramente o poder da derivação do tipo na versão mais recente. Não está mais limitado ao processo de declaração e instanciação de classes genéricas, mas é estendido a métodos com parâmetros genéricos.
4.1 Tipo de inferência e métodos genéricos
Em relação à derivação do tipo e métodos genéricos na nova versão, o documento também fornece um exemplo um pouco mais complexo. Eu postei aqui. O princípio é o mesmo que o exemplo Serializable acima, para que não entre em detalhes. Se você deseja consolidá -lo, pode dar uma olhada:
classe pública boxdemo {public static <u> void addbox (u u, java.util.list <caixa <u>> caixas) {caixa <u> caixa = new box <> (); box.set (u); caixas.add (caixa); } public static <u> void outputboxes (java.util.list <caixa <u>> caixas) {int contat = 0; para (caixa <u> caixa: caixas) {u boxcontents = box.get (); System.out.println ("Box #" + Counter + "contém [" + boxcontents.toString () + "]"); contador ++; }} public static void main (string [] args) {java.util.ArrayList <Box <Teger>> listOfIntegerBoxes = new java.util.arraylist <> (); BoxDemo. <Teger> addBox (Integer.valueof (10), listOfIntegerboxes); BoxDemo.addbox (Integer.Valueof (20), ListOfIntegerBoxes); BoxDemo.addbox (Integer.Valueof (30), ListOfIntegerBoxes); BoxDemo.OutputBoxes (ListOfIntegerBoxes); }}A saída do código acima é:
A caixa #0 contém [10] a caixa nº 1 contém [20] a caixa 2 contém [30]
Deixe -me mencionar que o foco do método genérico addBox é o tipo de descrição que você não precisa mais exibir na chamada do método na nova versão Java, assim:
BoxDemo. <Teger> addBox (Integer.valueof (10), listOfIntegerboxes);
O compilador pode inferir automaticamente que o tipo de parâmetro é Integer dos parâmetros passados para addBox .
4.2 Tipo de inferência e construtores genéricos de classes genéricas e não genéricas
Bem ... essa pode ser uma frase melhor em inglês: inferência de tipo e construtores genéricos de classes genéricas e não genéricas
De fato, os construtores genéricos não são patentes para classes genéricas. Classes não genéricas também podem ter seus próprios construtores genéricos. Dê uma olhada neste exemplo:
classe myclass <x> {<t> myclass (t t) {// ...}}Se a seguinte instanciação for feita para a classe MyClass:
novo myclass <inteiro> ("") Ok, aqui mostramos que o tipo de parâmetro X da MyClass é Integer e, para o construtor, o compilador deduz que o parâmetro formal t é String com base no objeto String recebido (""). Isso foi implementado na versão Java7. Que melhorias foram feitas em Java8? Após o Java8, podemos escrever essa instanciação de uma classe genérica com um construtor genérico como este:
MyClass <Teger> myObject = new MyClass <> (""); Sim, ainda é o par de colchetes de ângulo (<>), que é chamado de diamante, para que nosso compilador possa deduzir automaticamente que os parâmetros formais x é Integer e t é String . Na verdade, isso é muito semelhante ao nosso exemplo inicial de Map<String,String> , exceto que existe uma genicalização do construtor.
Deve -se notar que a derivação do tipo só pode ser derivada com base no tipo de parâmetro da chamada, tipo de destino (isso será discutido em breve) e o tipo de retorno (se houver uma devolução) e não poderá ser derivado com base em alguns requisitos após o programa.
4.3 Tipo de destino
Como mencionado anteriormente, o compilador pode executar a derivação do tipo com base no tipo de destino. O tipo de alvo de uma expressão refere -se ao tipo de dados correto que o compilador precisa com base em onde a expressão aparece. Por exemplo, este exemplo:
static <t> list <t> emppylist (); list <string> listOne = collection.Emptylist ();
Aqui, a lista <String> é o tipo de destino, porque o que é necessário aqui é List<String> e a lista de retornos Collections.emptyList() List<T> , então o compilador aqui infere que t deve ser String . Tudo bem em Java 7 e 8. No entanto, no Java 7, não pode ser compilado normalmente na seguinte situação:
void ProcessStringList (List <String> stringList) {// Process StringList} ProcessStringList (Coleções.Emptylist ());Neste momento, o Java7 fornecerá esta mensagem de erro:
// list <ject> não pode ser convertido para listar <string>
Motivo: Collections.emptyList() List<T> , e aqui requer um tipo específico, mas como não pode ser inferido a partir da declaração de método de que o que é necessário é String , o compilador fornece um valor Object . Obviamente, List<Object> não pode ser convertida para List<String>. Então, na versão Java7, você precisa chamar esse método como este:
ProcessStringList (Coleções.
No entanto, no Java 8, devido à introdução do conceito de tipo de alvo, é óbvio que o que o compilador precisa é List<String> (ou seja, o tipo de destino aqui), de modo que o compilador infere que o T na List<T> deve ser String , a descrição do processStringList(Collections.emptyList()); está ok.
O uso de tipos de destino é mais óbvio nas expressões Lambda.
Resumir
OK, o exposto acima são algumas idéias pessoais sobre a derivação do tipo em Java. Em resumo, a derivação do tipo cada vez mais perfeita é concluir algum trabalho de conversão de tipo que parece ser natural, mas todo esse trabalho é deixado para o compilador para derivação automática, em vez de permitir que os desenvolvedores o exibissem. Espero que o conteúdo deste artigo seja útil para todos para aprender Java. Se você tiver alguma dúvida, pode deixar uma mensagem para se comunicar.