1. Propor o conceito de genéricos (por que os genéricos são necessários)?
Primeiro, vejamos o seguinte código curto:
public class Generictest {public static void main (string [] args) {list list = new ArrayList (); list.add ("qqyumidi"); list.add ("milho"); list.add (100); for (int i = 0; i <list.size (); i ++) {string name = (string) list.get (i); // 1 system.out.println ("nome:" + nome); }}}Defina uma coleção do tipo de lista, primeiro adicione dois valores de tipo de string e, em seguida, adicione um valor de tipo inteiro. Isso é completamente permitido porque o tipo padrão de lista é objeto no momento. No loop subsequente, é fácil ter erros semelhantes a // 1 porque esqueci de adicionar valores do tipo inteiro ou outros motivos de codificação na lista antes. Como o estágio de compilação é normal, a exceção "java.lang.classcastException" aparecerá no tempo de execução. Portanto, esses erros são difíceis de detectar durante o processo de codificação.
Durante o processo de codificação como acima, descobrimos que existem dois problemas principais:
1. Quando colocamos um objeto na coleção, a coleção não se lembrará do tipo desse objeto. Quando esse objeto é retirado da coleção novamente, o tipo de compilação do objeto alterado se torna o tipo de objeto, mas seu tipo de tempo de execução ainda é seu próprio tipo.
2. Portanto, ao retirar o elemento de coleta em // 1, os tipos forçados artificialmente precisam ser convertidos para o tipo de destino específico e é fácil ver a exceção "java.lang.classCastException".
Então, existe alguma maneira de fazer com que a coleção se lembre de vários tipos de elementos da coleção e, desde que não haja problema durante a compilação, não haverá exceção "java.lang.classCastException" durante o tempo de execução? A resposta é usar genéricos.
2. O que é genérico?
Genéricos, ou seja, "tipo parametrizado". Quando se trata de parâmetros, o mais familiar é ter parâmetros concretos ao definir um método e depois passar os parâmetros reais ao chamar esse método. Então, como você entende o tipo de parametrização? Como o nome sugere, significa parametrizar o tipo do tipo específico original, semelhante aos parâmetros variáveis no método. No momento, o tipo também é definido como um formulário de parâmetro (pode ser chamado de parâmetro formal do tipo) e, em seguida, o tipo específico (tipo de parâmetro real) é passado quando usado/invocado.
Parece um pouco complicado. Primeiro, vamos dar uma olhada no exemplo acima usando a escrita genérica.
classe pública generictest {public static void main (string [] args) { /* list list = new ArrayList (); list.add ("qqyumidi"); list.add ("milho"); list.add (100); */ List <String> list = new ArrayList <String> (); list.add ("qqyumidi"); list.add ("milho"); //list.add(100); // 1 solicita um erro de compilação para (int i = 0; i <list.size (); i ++) {string name = list.get (i); // 2 system.out.println ("nome:" + nome); }}}Após usar a escrita genérica, ocorre um erro de compilação quando você deseja adicionar um objeto do tipo inteiro em // 1. Através da Lista <String>, é diretamente limitado que apenas os elementos do tipo de string possam estar contidos na coleção de listas, portanto, não há necessidade de lançar o tipo em // 2, porque neste momento a coleção pode lembrar o tipo de informação do elemento e o compilador pode confirmar que é o tipo de string.
Combinando a definição genérica acima, sabemos que na lista <String>, a string é um parâmetro de tipo, ou seja, a interface da lista correspondente deve conter parâmetros formais do tipo. Além disso, o resultado de retorno do método get () é diretamente esse tipo de parâmetro formal (ou seja, o parâmetro de tipo de entrada correspondente). Vamos dar uma olhada na definição específica da interface da lista:
Lista de interface pública <E> estende a coleção <E> {int size (); booleano isEmpty (); Booleano contém (objeto O); Iterator <e> iterator (); Objeto [] ToArray (); <t> t [] ToArray (t [] a); add boolean (e e); Remover booleano (objeto O); Boolean containsall (coleção <?> c); Boolean Addall (coleção <? Extende e> c); Boolean addall (índice int, coleção <? Extende e> c); Removeall booleano (coleção <?> c); Boolean retainall (coleção <?> c); void clear (); boolean é igual (objeto o); int hashcode (); E Get (Int Index); E definido (índice int, e elemento); void add (índice int, e elemento); E remover (índice int); int indexOf (objeto o); int lastIndexOf (objeto o); ListIterator <e> listIterator (); ListIterator <e> listIterator (int index); Lista <E> sublist (int deindex, int toIndex);}Podemos ver que, após a adoção da definição genérica na interface da lista, e em <e> representa um parâmetro formal do tipo, que pode receber parâmetros de tipo específico. Nesta definição de interface, onde E aparece, significa que os mesmos parâmetros de tipo aceitos de fora são aceitos.
Naturalmente, a ArrayList é uma classe de implementação para a interface da lista, e seu formulário de definição é:
A partir disso, entendemos da perspectiva do código -fonte por que o objeto do tipo inteiro é compilado incorretamente em // 1, e o tipo obtido em // 2 é diretamente o tipo de string.
public class ArrayList <e> estende a lista de implementos abstrataList <E> <E>, RandomAccess, clonável, java.io.Serializable {public boolean add (e e) {securecapacityIn (tamanho + 1); // incrementos modCount !! ElementData [size ++] = e; retornar true; } public e get (int index) {rangecheck (index); checkForComodification (); return ArrayList.This.ElementData (deslocamento + índice); } //...Omit outros processos de definição específicos}3. Personalize interfaces genéricas, classes genéricas e métodos genéricos
A partir do conteúdo acima, todos entenderam o processo de operação específico de genéricos. Sabe -se também que interfaces, classes e métodos também podem ser definidos usando genéricos e usados de acordo. Sim, quando usado especificamente, ele pode ser dividido em interfaces genéricas, classes genéricas e métodos genéricos.
Interfaces genéricas personalizadas, classes genéricas e métodos genéricos são semelhantes à lista e ao Arraylist no código -fonte Java acima. Da seguinte maneira, analisamos a definição de classe e método mais simples de classe e método:
public class Generictest {public static void main (string [] args) {box <string> name = new Box <String> ("milho"); System.out.println ("nome:" + name.getData ()); }} classe Caixa <T> {private t Data; public box () {} public box (t data) {this.data = data; } public t getData () {retornar dados; }}No processo de definição de interfaces genéricas, classes genéricas e métodos genéricos, nossos parâmetros comuns como T, E, K, V, etc. são frequentemente usados para representar parâmetros formais genéricos porque recebem parâmetros de tipo passados do uso externo. Portanto, para diferentes tipos de parâmetros recebidos, os tipos de instâncias de objeto correspondentes são geradas iguais?
public class Generictest {public static void main (string [] args) {box <string> name = new Box <String> ("milho"); Caixa <TEGER> Age = New Box <Integer> (712); System.out.println ("Classe de nome:" + name.getClass ()); // com.qqyumidi.box System.out.println ("Idade classe:" + Age.getClass ()); // com.qqyumidi.box System.out.println (name.getClass () == Age.getClass ()); // verdadeiro }}A partir disso, descobrimos que, ao usar classes genéricas, embora diferentes argumentos genéricos sejam transmitidos, diferentes tipos não são gerados no sentido verdadeiro. Existe apenas uma classe genérica que passa em diferentes argumentos genéricos na memória, ou seja, ainda é o tipo mais básico original (caixa neste exemplo). Obviamente, logicamente, podemos entendê -lo como vários tipos genéricos diferentes.
O motivo é que o objetivo do conceito de genéricos em Java é que ele funciona apenas no estágio de compilação de código. Durante o processo de compilação, após verificar corretamente os resultados genéricos, as informações relevantes dos genéricos serão apagadas. Ou seja, o arquivo de classe compilado com sucesso não contém nenhuma informação genérica. Informações genéricas não entrarão na fase de tempo de execução.
Isso está resumido em uma frase: os tipos genéricos são logicamente considerados como vários tipos diferentes e são realmente os mesmos tipos básicos.
Quatro. Tipo Wildcard
Seguindo a conclusão acima, sabemos que a caixa <número> e a caixa <TEGER> são na verdade os dois tipos de caixa. Agora precisamos continuar explorando uma pergunta. Então, logicamente, a BOX <Number> e a caixa <Integer> podem ser considerados tipos genéricos com relacionamentos entre pais e filhos?
Para esclarecer esse problema, vamos continuar analisando o exemplo a seguir:
classe pública Generictest {public static void main (string [] args) {box <número> name = new Box <número> (99); Caixa <TEGER> Age = New Box <Integer> (712); getData (nome); // o método getData (caixa <número>) no tipo mais generativo é // não aplicável aos argumentos (caixa <Integer>) getData (idade); // 1} public static void getData (caixa <número> data) {System.out.println ("Data:" + data.getData ()); }}Descobrimos que uma mensagem de erro apareceu no código // 1: o método getData (caixa <número>) no T YPE Generict mais não é aplicável aos argumentos (caixa <Integer>). Obviamente, ao solicitar informações, sabemos que a caixa <Mum> não pode ser considerada logicamente como a classe pai da caixa <Integer>. Então, qual é o motivo?
classe pública Generictest {public static void main (string [] args) {box <Teger> a = new Box <Teger> (712); Caixa <número> b = a; // 1 caixa <Float> f = nova caixa <Float> (3.14f); B.SetData (f); // 2} public static void getData (caixa <número> data) {System.out.println ("Data:" + data.getData ()); }} classe Caixa <T> {private t Data; public box () {} public box (t data) {setData (dados); } public t getData () {retornar dados; } public void setData (t data) {this.data = data; }}Neste exemplo, haverá uma mensagem de erro em // 1 e // 2. Aqui, podemos usar o método contra-provoque para explicá-lo.
Supondo que a caixa <Mum> possa ser considerada logicamente como a classe pai da caixa <Integer>, não haverá prompts de erro em // 1 e // 2. Então o problema surge. Que tipo é quando buscando dados através do método getData ()? Inteiro? Flutuador? ou número? Além disso, devido à ordem incontrolável no processo de programação, o julgamento do tipo deve ser feito quando necessário e a conversão do tipo é realizada. Obviamente, isso contradiz a idéia de genéricos; portanto, logicamente, a caixa <Mum> não pode ser considerada como a classe pai da caixa <Integer>.
OK, então vamos olhar para o primeiro exemplo em "Type WildCards", sabemos o motivo mais profundo de seus prompts de erro específicos. Então, como resolvê -lo? A sede pode definir uma nova função. Isso é obviamente contrário ao conceito de polimorfismo em Java; portanto, precisamos de um tipo de referência que possa ser usado logicamente para representar a classe pai da caixa <Terger> e da caixa <Mumber> e, portanto, o tipo de curinga surgiu.
Os tipos de tipos são geralmente usados em vez de argumentos de tipo específico. Observe que este é um parâmetro de tipo, não um parâmetro de tipo! E a caixa <?> É logicamente a classe pai de toda a caixa <Integer>, caixa <número> ... etc. Portanto, ainda podemos definir métodos genéricos para atender a esses requisitos.
public class Generictest {public static void main (string [] args) {box <string> name = new Box <String> ("milho"); Caixa <TEGER> Age = New Box <Integer> (712); Caixa <número> número = nova caixa <número> (314); getData (nome); getdata (idade); getData (número); } public static void getData (caixa <?> data) {System.out.println ("Data:" + data.getData ()); }}Às vezes, também podemos ouvir sobre os tipos superior e inferior de curingas. Como exatamente é?
No exemplo acima, se você precisar definir um método que funcione semelhante ao getData (), mas há outras restrições nos argumentos do tipo: pode ser apenas a classe numérica e suas subclasses. Neste momento, o limite superior do tipo é necessário.
public class Generictest {public static void main (string [] args) {box <string> name = new Box <String> ("milho"); Caixa <TEGER> Age = New Box <Integer> (712); Caixa <número> número = nova caixa <número> (314); getData (nome); getdata (idade); getData (número); // getupPernumberData (nome); // 1 getupPernumberData (idade); // 2 getupPernumberData (número); // 3} public static void getData (caixa <?> Data) {System.out.println ("Data:" + data.getData ()); } public static void getupPernumberData (caixa <? Extende o número> data) {System.out.println ("Data:" + data.getData ()); }}Nesse ponto, obviamente, a chamada no código // 1 aparecerá uma mensagem de erro, enquanto a chamada em // 2 // 3 será normal.
O limite superior do tipo curingas é definido pela forma de caixa <?? estende o número>. Correspondentemente, o limite inferior do tipo curingas é a forma de caixa <?? Super Número>, e seu significado é exatamente o oposto do limite superior dos curingas do tipo. Não vou explicar muito aqui.
5. Capítulo extra
Os exemplos deste artigo são citados principalmente para ilustrar algumas idéias em genéricos e não têm necessariamente usabilidade prática. Além disso, quando se trata de genéricos, acredito que o que você mais usa está na coleção. De fato, no processo de programação real, você pode usar genéricos para simplificar o desenvolvimento e garantir bem a qualidade do código. E uma coisa a observar é que não há a chamada matriz genérica em Java.
Para os genéricos, o mais importante é entender as idéias e propósitos por trás delas.
O exposto acima é uma compilação de conhecimento e informações sobre os genéricos do Java. Continuaremos a adicionar informações relevantes no futuro. Obrigado pelo seu apoio a este site!