1. Por que - o motivo da introdução de mecanismos genéricos
Se quisermos implementar uma matriz de string e exigir que ela altere dinamicamente o tamanho, todos pensaremos em usar o ArrayList para agregar objetos de string. No entanto, depois de um tempo, queremos implementar uma variedade de objetos de data cujo tamanho pode ser alterado. Neste momento, certamente esperamos poder reutilizar a implementação do ArrayList para objetos de string que escrevi antes.
Antes do Java 5, a implementação do Arraylist é aproximadamente o seguinte:
public class ArrayList {public Object get (int i) {...} public void add (objeto o) {...} ... objeto privado [] elementData;}A partir do código acima, podemos ver que a função ADD usada para adicionar elementos ao ArrayList recebe um parâmetro do tipo de objeto. O método GET que obtém o elemento especificado da lista de Arraylist também retorna um objeto do tipo objeto. A matriz de objeto Array ElementData armazena o objeto no ArrayList. Ou seja, não importa o tipo de tipo que você coloca na lista de Array, é um objeto dentro dele.
A implementação genérica com base na herança trará dois problemas: a primeira pergunta é sobre o método GET. Toda vez que chamamos o método Get, retornaremos um objeto e cada vez que temos que lançar o tipo para o tipo de que precisamos, que parecerá muito problemático; A segunda pergunta é sobre o método Add. Se adicionarmos um objeto de arquivo ao ArrayList que agregue o objeto String, o compilador não gerará nenhum aviso de erro, que não é o que queremos.
Portanto, a partir do Java 5, o ArrayList pode ser usado para adicionar um parâmetro de tipo (parâmetro de tipo) ao usá -lo. Este parâmetro de tipo é usado para indicar o tipo de elemento no ArrayList. A introdução de parâmetros de tipo resolve os dois problemas mencionados acima, conforme mostrado no código a seguir:
ArrayList <String> s = new ArrayList <String> (); s.add ("abc"); string s = s.get (0); // Não é necessário lançar S.Add (123); // Erro de compilação, você só pode adicionar objetos de string a ele ...No código acima, depois que o compilador "conhece" a sequência de parâmetros de tipo do ArrayList, ele completará a verificação de fundição e tipo para nós.
2. Classes genéricas
A chamada classe genérica é uma classe com um ou mais parâmetros de tipo. Por exemplo:
classe pública par <t, u> {private t primeiro; privado u segundo; Public par (t Primeiro, U segundo) {this.first = primeiro; this.Second = Second; } public t getfirst () {return primeiro; } public u getSecond () {return Second; } public void setFirst (t newValue) {primeiro = newValue; }}No código acima, podemos ver que os parâmetros de tipo do par de classes genéricos são T e U e são colocados em suportes de ângulo após o nome da classe. Aqui, T significa a primeira letra do tipo, que representa o tipo. Comumente usados são e (elemento), k (chave), v (valor), etc. É claro que também é perfeitamente bom não usar essas letras para se referir aos parâmetros do tipo.
Ao instantar uma classe genérica, precisamos apenas substituir o parâmetro de tipo por um tipo específico, como instanciar uma classe <t, u>, podemos fazer isso:
Par <string, número inteiro> par = novo par <string, integer> ();
3. Métodos genéricos
O chamado método genérico é um método com parâmetros de tipo. Pode ser definido em uma classe genérica ou em uma classe normal. Por exemplo:
classe pública Arrayalg {public static <t> t getMiddle (t [] a) {return a [a.length / 2]; }}O método getMiddle no código acima é um método genérico, e o formato definido é que a variável de tipo é colocada após o modificador e antes do tipo de retorno. Podemos ver que os métodos genéricos acima podem ser solicitados a vários tipos de matrizes. Quando se sabe que os tipos dessas matrizes são limitados, embora também possam ser implementados com sobrecarga, a eficiência da codificação é muito menor. O código de exemplo para chamar o método genérico acima é o seguinte:
String [] strings = {"aa", "bb", "cc"};
String middle = Arrayalg.getMiddle (nomes);
4. Limitação de variáveis de tipo
Em alguns casos, classes genéricas ou métodos genéricos desejam limitar ainda mais seus parâmetros de tipo. Por exemplo, se quisermos definir parâmetros de tipo que só podem ser subclasses de uma determinada classe ou apenas classes que implementam uma determinada interface. A sintaxe relevante é a seguinte:
<T estende BoundingType> (BoundingType é uma classe ou interface). Pode haver mais de 1 TypeType, basta usar "&" para se conectar.
5. Entenda a implementação de genéricos
De fato, da perspectiva das máquinas virtuais, não há conceito de "genéricos". Por exemplo, o par de classes genéricas que definimos acima se parece com essa na máquina virtual (ou seja, depois de ser compilada no bytecode):
Public Class Par {Private Object First; objeto privado segundo; Public par (objeto primeiro, objeto segundo) {this.first = primeiro; this.Second = Second; } public Object getFirst () {return primeiro; } public Object getSecond () {return Second; } public void setFirst (objeto newValue) {primeiro = newValue; } public void setSecond (objeto newValue) {Second = newValue; }}A classe acima é obtida pela apagamento do tipo e é o tipo bruto correspondente à classe genérica do par. Tipo Apagamento significa substituir todos os parâmetros de tipo por BoundingType (substitua -o pelo objeto se nenhuma restrição for adicionada).
Podemos simplesmente verificar se depois de compilar par.java, digite "Javap -c -s par" para obter:
A linha com "descritor" na figura acima é a assinatura do método correspondente. Por exemplo, a partir da quarta linha, podemos ver que os dois parâmetros formais do construtor de pares se tornaram objeto após o tipo apagamento.
Como o par de classes genéricas se torna seu tipo bruto na máquina virtual, o método getFirst retorna um objeto de objeto e, da perspectiva do compilador, esse método retorna um objeto do parâmetro de tipo especificado quando instanciamos a classe. De fato, é o compilador que nos ajuda a concluir o trabalho de elenco. Em outras palavras, o compilador converterá a chamada para o método GetFirst na classe genérica do par em duas instruções da máquina virtual:
O primeiro é uma chamada para o método do tipo bruto GetFirst, que retorna um objeto; A segunda instrução lança o objeto retornado para o tipo de parâmetro de tipo que especificamos.
O apagamento do tipo também ocorre em métodos genéricos, como os seguintes métodos genéricos:
estática pública <t estende comparável> t min (t [] a)
Após a compilação, ele se tornará assim após o tipo apagamento:
public static comparável min (comparável [] a)
Tipo Apagamento dos métodos pode causar alguns problemas, considere o seguinte código:
classe DateInterval estende o par <Data, Data> {public void SetSecond (Data Second) {if (Second.comPeto (getFirst ())> = 0) {super.setSecond (Second); }} ...}Depois que o código acima é apagado por tipo, ele se torna:
classe DateInterval estende o par {public void se setSecond (data segundo) {...} ...}Na classe DateInterval, há também um método de segundo conjunto herdado da classe de pares (após o tipo apagamento) da seguinte forma:
Public Void SetSecond (objeto segundo)
Agora, podemos ver que esse método possui diferentes assinaturas de método (diferentes parâmetros formais) do método SetSegund substituído pelo DateInterval, por isso são dois métodos diferentes; no entanto, esses dois métodos não devem ser métodos diferentes (porque é substituído). Considere o seguinte código:
Intervalo DateInterval = novo DateInterval (...); par <Data, Date> par = intervalo; Data Adate = New Date (...); par.setSecond (Adate);
A partir do código acima, podemos ver que o par realmente se refere ao objeto DateInterval, para que o método SetSegund de DateInterval deve ser chamado. O problema aqui é que o tipo de apagamento de conflitos com o polimorfismo.
Vamos resolver por que esse problema ocorre: o par foi declarado anteriormente como par de tipos <data, data>, e essa classe parece ter apenas um método "setSecond (object)" na máquina virtual. Portanto, ao executar, a máquina virtual descobre que o par realmente se refere ao objeto DateInterval, ele chamará o "SetSecond (objeto)" do DateInterval, mas existe apenas o método "SetSecond (Date)" na classe DateInterval.
A solução para esse problema é gerar um método de ponte no DateInterval do compilador:
public void SetSecond (objeto segundo) {SetSecond ((DATE) Second);}6. coisas a serem observadas
(1) Os parâmetros de tipo não podem ser instanciados com tipos básicos
Isto é, a seguinte declaração é ilegal:
Par <int, int> par = novo par <int, int> ();
No entanto, podemos usar o tipo de embalagem correspondente.
(2) não pode jogar ou capturar instâncias de classe genérica
A extensão de classe genérica jogável é ilegal, portanto, instâncias genéricas de classe não podem ser jogadas ou capturadas. Mas é legal usar parâmetros de tipo em declarações de exceção:
public static <t estende throwable> void Dowork (t t) lança t {try {...} catch (arremesso realcause) {t.initcause (realcause); jogue t; }}(3) a matriz parametrizada é ilegal
Em Java, uma matriz de objeto [] pode ser a classe pai de qualquer matriz (porque qualquer matriz pode ser transformada para cima para uma matriz da classe pai que especifica o tipo de elemento quando definida). Considere o seguinte código:
String [] strs = new string [10]; objeto [] objs = strs; obj [0] = nova data (...);
No código acima, atribuímos o elemento da matriz a um objeto que satisfaz o tipo de classe pai (objeto), mas, diferentemente do tipo original (par), ele pode passar no horário de compilação, e uma exceção do ArrayStoreException será lançada em tempo de execução.
Com base nos motivos acima, suponha que o Java nos permita declarar e inicializar uma matriz genérica através da seguinte declaração:
Par <string, string> [] pares = novo par <string, string> [10];
Depois que a máquina virtual executa a apagamento do tipo, os pares se tornam matrizes de pares [] e podemos transformá -la para cima em uma matriz de objeto []. Neste momento, se adicionarmos pares <data, data> Objetos, podemos passar verificações de tempo de compilação e cheques de tempo de execução. Nossa intenção original é apenas deixar esse par de objetos de armazenamento de Array <String, String>, que causarão a localização de erros difíceis. Portanto, o Java não nos permite declarar e inicializar uma matriz genérica através do formulário de declaração acima.
Uma matriz genérica pode ser declarada e inicializada usando a seguinte declaração:
Par <string, string> [] pares = (par <string, string> []) novo par [10];
(4) O tipo variável não pode ser instanciado
As variáveis de tipo não podem ser usadas em formas como "novo t (...)", "novo t [...]", "t.class". A razão pela qual Java nos proíbe de fazer isso é simples. Como há apagamento do tipo, declarações como "novo t (...)" se tornarão "novo objeto (...)", o que geralmente não é o que queremos dizer. Podemos substituir a chamada para "New T [...]" pela seguinte declaração:
matrizes = (t []) novo objeto [n];
(5) As variáveis de tipo não podem ser usadas no contexto estático das classes genéricas
Observe que enfatizamos classes genéricas aqui. Como os métodos genéricos estáticos podem ser definidos em classes comuns, como o método getMiddle na classe Arrayalg mencionada acima. Por razões como regra, considere o seguinte código:
public class People <t> {public static t name; public static t getName () {...}}Sabemos que, ao mesmo tempo, pode haver mais de uma instância da classe <t> na memória. Suponha que exista um objeto <string> de pessoas e pessoas <TEGER> agora na memória, e as variáveis estáticas e os métodos estáticos da classe são compartilhados por todas as instâncias de classe. Portanto, a pergunta é: o tipo de string de nome ou tipo inteiro? Por esse motivo, as variáveis de tipo não são permitidas em Java a serem usadas em contextos estáticos de classes genéricas.
7. Tipo Wildcard
Antes de apresentar o tipo Wildcard, primeiro introduza dois pontos:
(1) Suponha que o aluno seja uma subclasse de pessoas, mas pare <estudante, aluno> não é uma subclasse de pares <pessoas, pessoas>, e não há um relacionamento "IS-A" entre eles.
(2) Existe uma relação "IS-A" entre par <t, t> e seu par original de tipos. Par <t, t> pode ser convertido para parecer em par de qualquer caso.
Agora considere este método:
public static void PrintName (par <pessoas, pessoas> p) {pessoas p1 = p.getfirst (); System.out.println (p1.getName ()); // Suponha que a classe People defina o método GetName Instância}No método acima, queremos poder passar parâmetros de par <estudante, aluno> e emparelhar <pessoas, pessoas> ao mesmo tempo, mas não há relacionamento "IS-A" entre os dois. Nesse caso, o Java nos fornece uma solução: use par <?? estende as pessoas> como o tipo de parâmetro formal. Ou seja, pare <aluno, aluno> e pare <pessoas, pessoas> Ambos podem ser considerados subclasses de par <? estende as pessoas>.
O código que se parece com "<? Extende BoundingType>" é chamado de limitação do subtipo de caracteres curinga. Correspondente a isso é a limitação do super tipo de caracteres curinga, o formato é o seguinte: <? Super BoundingType>.
Agora vamos considerar o seguinte código:
Par <estudante> alunos = novo par <estudante> (Student1, Student2); par <? estende as pessoas> Wildchards = estudantes; Wildchards.setFirst (People1);
A terceira linha do código acima relatará um erro porque o WildChards é um par <?? Estende as pessoas> objeto, e seu método setfirst e o método GetFirst são os seguintes:
Void setFirst (? Extende as pessoas)? estende as pessoas getfirst ()
Para o método setFirst, o compilador não saberá que tipo de parâmetros formais são (conhecidos apenas por ser uma subclasse de pessoas). Quando tentamos passar em um objeto de pessoas, o compilador não pode determinar se as pessoas e os parâmetros formais são "IS-A"; portanto, chamar o método setfirst relatará um erro. É legal chamar o método Getfirst da Wildchards, porque sabemos que ele retornará uma subclasse de pessoas e a subclasse das pessoas "sempre é um povo". (Você sempre pode converter objetos de subclasse em objetos pais)
No caso da limitação do supertipo de curinga, chamar o método getter é ilegal, enquanto chamando o método do setter é legal.
Além das limitações do subtipo e das limitações do supertipo, também há um curinga chamado Infinite Wildcard, que se parece com o seguinte: <?>. Quando vamos usar isso? Considere este cenário. Quando chamamos um método, retornaremos um método getPairs, que retornará um conjunto de objetos de par <t, t>. Entre eles estão pares <estudante, aluno> e pares <professor, professor> objetos. (Não há relação de herança entre a aula de estudante e a aula de professores) Obviamente, neste caso, tanto a limitação do subtipo quanto a limitação do supertipo não podem ser usadas. Neste momento, podemos usar esta declaração para resolvê -la:
Par <?> [] Pares = getpairs (...);