Veio, vi, generalizei: imerso em Java Generics

O Java Generics é uma das mudanças mais significativas na história da linguagem Java. Os genéricos disponíveis no Java 5 tornaram o uso do Java Collection Framework mais fácil, conveniente e seguro. Erros associados ao uso incorreto de tipos agora são detectados no estágio de compilação. Sim, e a própria linguagem Java se tornou ainda mais segura. Apesar da aparente simplicidade dos tipos genéricos, muitos desenvolvedores têm dificuldade em usá-los. Neste post, falarei sobre os recursos do trabalho com Java Generics, para que você tenha menos dessas dificuldades. Útil se você não é um guru genérico e ajudará a evitar muitas dificuldades ao mergulhar no tópico.



Trabalhar com coleções


Suponha que um banco precise calcular a quantidade de economia nas contas dos clientes. Antes do advento dos “genéricos”, o método de calcular a soma era assim:

public long getSum(List accounts) {   long sum = 0;   for (int i = 0, n = accounts.size(); i < n; i++) {       Object account = accounts.get(i);       if (account instanceof Account) {           sum += ((Account) account).getAmount();       }   }   return sum; } 

Nós iteramos, examinamos a lista de contas e verificamos se o elemento dessa lista é realmente uma instância da classe Account - ou seja, a conta do usuário. O tipo de nosso objeto da classe Account e o método getAmount foram getAmount , retornando o valor nesta conta. Depois, resumiram tudo e devolveram o valor total. Foram necessárias duas etapas:
 if (account instanceof Account) { // (1) 

 sum += ((Account) account).getAmount(); // (2) 

Se você não marcar ( instanceof ) para pertencer à classe Account , em seguida, o segundo estágio pode ClassCastException - isto é, uma finalização anormal do programa. Portanto, essa verificação era obrigatória.

Com o advento dos genéricos, a necessidade de verificação e conversão de tipos desapareceu:
 public long getSum2(List<Account> accounts) {  long sum = 0;  for (Account account : accounts) {      sum += account.getAmount();  }  return sum; } 

Agora método
 getSum2(List<Account> accounts) 
aceita como argumentos apenas uma lista de objetos da classe Account . Essa restrição é indicada no próprio método, em sua assinatura, o programador simplesmente não pode transferir nenhuma outra lista - apenas a lista de contas de clientes.

Não precisamos verificar o tipo de elementos desta lista: isso está implícito na descrição do tipo do parâmetro do método
 List<Account> accounts 
(pode ser lido como uma Account ). E o compilador lançará um erro se algo der errado - isto é, se alguém tentar passar uma lista de objetos diferentes da classe Account para esse método.

Na segunda linha do cheque, a necessidade também desapareceu. Se necessário, a casting será feita na fase de compilação.

Princípio da substituição


O princípio de substituição de Barbara Liskov é uma definição específica de um subtipo na programação orientada a objetos. A ideia de Liskov de um "subtipo" define o conceito de substituição: se S é um subtipo de T , os objetos do tipo T em um programa podem ser substituídos por objetos do tipo S sem nenhuma alteração nas propriedades desejadas deste programa.

Tipo
Subtipo
Número
Inteiro
Lista <E>
ArrayList <E>
Coleção <E>
Lista <E>
Iterable <E>
Coleção <E>

Exemplos de relacionamento de tipo / subtipo

Aqui está um exemplo de uso do princípio de substituição em Java:
 Number n = Integer.valueOf(42); List<Number> aList = new ArrayList<>(); Collection<Number> aCollection = aList; Iterable<Number> iterable = aCollection; 

Integer é um subtipo de Number , portanto, a variável n tipo Number pode receber o valor que o Integer.valueOf(42) retorna.

Covariância, contravariância e invariância


Primeiro, um pouco de teoria. A covariância é a preservação da hierarquia de herança de tipos de origem em tipos derivados na mesma ordem. Por exemplo, se o gato é um subtipo de animais , o conjunto de <gatos> é um subtipo do conjunto de <animais> . Portanto, levando em consideração o princípio da substituição, é possível executar a seguinte atribuição:

Muitos <Animais> = Muitos <Gatos>

Contravariância é a reversão da hierarquia de tipos de origem em tipos derivados. Por exemplo, se o gato é um subtipo de , o conjunto <animais> é um subtipo do conjunto de <gatos> . Portanto, levando em consideração o princípio da substituição, é possível executar a seguinte atribuição:

Muitos <Gatos> = Muitos <Animais>

Invariância - falta de herança entre tipos derivados. Se o gato é um subtipo de animais , o conjunto de <gatos> não é um subtipo do conjunto de <animais> e o conjunto de <animais> não é um subtipo do conjunto de <gatos> .

Matrizes em Java são covariantes . Tipo S[] é um subtipo de T[] se S é um subtipo de T Exemplo de atribuição:
 String[] strings = new String[] {"a", "b", "c"}; Object[] arr = strings; 

Atribuímos um link a uma matriz de strings para a variável arr , cujo tipo é « » . Se matrizes não fossem covariantes, não poderíamos fazer isso. Java permite que você faça isso, o programa compila e executa sem erros.

 arr[0] = 42; // ArrayStoreException.       

Mas se tentarmos alterar o conteúdo da matriz através da variável arr e escrever o número 42, obteremos uma ArrayStoreException no estágio de execução do programa, pois 42 não é uma sequência, mas um número. Esta é a desvantagem da covariância de matrizes Java: não podemos executar verificações no estágio de compilação, e algo pode quebrar já em tempo de execução.

"Genéricos" são invariantes. Aqui está um exemplo:
 List<Integer> ints = Arrays.asList(1,2,3); List<Number> nums = ints; // compile-time error.      nums.set(2, 3.14); assert ints.toString().equals("[1, 2, 3.14]"); 

Se você tomar uma lista de números inteiros, então não vai ser de qualquer subtipo do Number , nem qualquer outro subtipo. Ele é apenas um subtipo de si mesmo. Ou seja, a List <Integer> é uma List<Integer> e nada mais. O compilador garantirá que a variável ints declarada como uma lista de objetos da classe Integer contenha apenas objetos da classe Integer e nada mais. No estágio de compilação, uma verificação é realizada e nada cairá em nosso tempo de execução.

Curingas


Os genéricos são sempre invariantes? Não. Vou dar exemplos:
 List<Integer> ints = new ArrayList<Integer>(); List<? extends Number> nums = ints; 

Isso é covariância. List<Integer> - subtipo de List<? extends Number> List<? extends Number>

 List<Number> nums = new ArrayList<Number>(); List<? super Integer> ints = nums; 

Isso é contravariância. List<Number> é um subtipo de List<? super Integer> List<? super Integer> .

Um registro como "? extends ..." ou "? super ..." é chamado de curinga ou curinga, com um limite superior ( extends ) ou um limite inferior ( super ). List<? extends Number> List<? extends Number> pode conter objetos cuja classe é Number ou herda de Number . List<? super Number> List<? super Number> pode conter objetos cuja classe é Number ou cujo Number é um herdeiro (supertipo de Number ).


estende B - curinga com limite superior
super B - curinga com um limite inferior
onde B - representa a borda

Um registro do formato T2 <= T1 significa que o conjunto de tipos descrito por T2 é um subconjunto do conjunto de tipos descrito por T1

isto é
Número <=? estende o objeto
? estende o número <=? estende o objeto
e
? super Objeto <=? super número


Interpretação mais matemática do tópico

Um par de tarefas para testar o conhecimento:

1. Por que o erro em tempo de compilação está no exemplo abaixo? Qual valor posso adicionar à lista de nums ?
 List<Integer> ints = new ArrayList<Integer>(); ints.add(1); ints.add(2); List<? extends Number> nums = ints; nums.add(3.14); // compile-time error 

A resposta
O contêiner deve ser declarado com curinga ? extends ? extends , você pode apenas ler os valores. Nada pode ser adicionado à lista, exceto null . Para adicionar um objeto à lista, precisamos de outro tipo de curinga - ? super ? super


2. Por que não consigo obter um item da lista abaixo?
 public static <T> T getFirst(List<? super T> list) {  return list.get(0); // compile-time error } 

A resposta
Não consegue ler um item de um contêiner com curinga ? super ? super , exceto um objeto da classe Object

 public static <T> Object getFirst(List<? super T> list) {  return list.get(0); } 



O princípio Get and Put ou PECS (produtor estende o consumidor super)


O recurso curinga com limites superior e inferior fornece recursos adicionais relacionados ao uso seguro de tipos. Você pode ler apenas de um tipo de variável, gravar apenas em outro (a exceção é a capacidade de escrever null para extends e ler Object para super ). Para facilitar a lembrança de quando usar qual curinga, existe o princípio PECS - Producer Extends Consumer Super.

  • Se declaramos um curinga com extends , esse é o produtor . Ele apenas "produz", fornece um elemento do contêiner e não aceita nada.
  • Se anunciamos um curinga com super , então isso é consumidor . Ele apenas aceita, mas não pode fornecer nada.

Considere usar o Wildcard e o princípio PECS usando o método copy na classe java.util.Collections como exemplo.

 public static <T> void copy(List<? super T> dest, List<? extends T> src) { … } 

Método transporta até itens da lista original src na lista dest . src - declarado com curinga ? extends ? extends e é o produtor, e dest é declarado com curinga ? super ? super e é um consumidor. Dada a covariância e a contravariância do curinga, você pode copiar elementos da lista de ints para a lista de nums :
 List<Number> nums = Arrays.<Number>asList(4.1F, 0.2F); List<Integer> ints = Arrays.asList(1,2); Collections.copy(nums, ints); 


Se confundimos os parâmetros do método copy por engano e tentamos copiar da lista nums para a lista ints , o compilador não nos permitirá fazer isso:
 Collections.copy(ints, nums); // Compile-time error 


<?> e tipos brutos


Abaixo está um curinga com um curinga ilimitado. Colocamos apenas <?> , Sem as palavras-chave super ou extends :
 static void printCollection(Collection<?> c) {  // a wildcard collection  for (Object o : c) {      System.out.println(o);  } } 


De fato, esse curinga "ilimitado" ainda é limitado, de cima. Collection<?> Também é um curinga, como " ? extends Object ". Um registro do formulário Collection<?> equivalente a Collection<? extends Object> Collection<? extends Object> , o que significa que a coleção pode conter objetos de qualquer classe, uma vez que todas as classes em Java são herdadas de Object - portanto, a substituição é chamada ilimitada.

Se omitirmos a indicação de tipo, por exemplo, como aqui:
 ArrayList arrayList = new ArrayList(); 

eles dizem que ArrayList é o tipo Raw do ArrayList parametrizado <T> . Usando os tipos Raw, retornamos à era dos genéricos e abandonamos conscientemente todos os recursos inerentes aos tipos parametrizados.

Se tentarmos chamar um método parametrizado no tipo Raw, o compilador nos dará um aviso "Chamada não verificada". Se tentarmos atribuir uma referência a um tipo Raw parametrizado para um tipo, o compilador emitirá um aviso "Atribuição não verificada". Ignorar esses avisos, como veremos mais adiante, pode levar a erros durante a execução de nosso aplicativo.
 ArrayList<String> strings = new ArrayList<>(); ArrayList arrayList = new ArrayList(); arrayList = strings; // Ok strings = arrayList; // Unchecked assignment arrayList.add(1); //unchecked call 


Captura de curinga


Agora vamos tentar implementar um método que permita os elementos de uma lista na ordem inversa.

 public static void reverse(List<?> list); // ! public static void reverse(List<?> list) { List<Object> tmp = new ArrayList<Object>(list); for (int i = 0; i < list.size(); i++) {   list.set(i, tmp.get(list.size()-i-1)); // compile-time error } } 

Ocorreu um erro de compilação porque o método reverse leva uma lista com um caractere curinga ilimitado <?> Como argumento.
<?> significa o mesmo que <? extends Object> <? extends Object> . Portanto, de acordo com o princípio PECS, a list é producer . E o producer produz apenas elementos. E no loop for chamamos o método set() , ou seja, tentando escrever na list . E, portanto, apoiamos a proteção Java, que não nos permite definir algum valor por índice.

O que fazer O padrão de Wildcard Capture nos ajudará. Aqui criamos um método de rev genérico. É declarado com uma variável do tipo T Este método aceita uma lista de tipos T e podemos fazer um conjunto.
 public static void reverse(List<?> list) { rev(list); } private static <T> void rev(List<T> list) { List<T> tmp = new ArrayList<T>(list); for (int i = 0; i < list.size(); i++) {   list.set(i, tmp.get(list.size()-i-1)); } } 

Agora tudo irá compilar conosco. A captura de curinga foi capturada aqui. Quando o método reverse(List<?> list) é chamado reverse(List<?> list) , uma lista de alguns objetos (por exemplo, seqüências de caracteres ou números inteiros) é passada como argumento. Se pudermos capturar o tipo desses objetos e atribuí-lo a uma variável do tipo X , podemos concluir que T é X

Você pode ler mais sobre o Wildcard Capture aqui e aqui .

Conclusão


Se você precisar ler a partir do contêiner, use um curinga com a borda superior " ? extends ". Se você precisar gravar no contêiner, use um curinga com uma borda inferior de " ? super ". Não use curinga se precisar gravar e ler.

Não use tipos Raw ! Se o argumento de tipo não estiver definido, use o caractere curinga <?> .

Variáveis ​​de tipo


Quando anotamos o identificador entre colchetes angulares, por exemplo, <T> ou <E> ao declarar uma classe ou método, criamos uma variável de tipo . Uma variável de tipo é um identificador não qualificado que pode ser usado como um tipo no corpo de uma classe ou método. Uma variável de tipo pode ser delimitada acima.
 public static <T extends Comparable<T>> T max(Collection<T> coll) { T candidate = coll.iterator().next(); for (T elt : coll) {   if (candidate.compareTo(elt) < 0) candidate = elt; } return candidate; } 

Neste exemplo, a expressão T extends Comparable<T> define T (uma variável de tipo) delimitada acima pelo tipo Comparable<T> . Diferentemente do curinga, as variáveis ​​de tipo podem ser limitadas apenas na parte superior (somente extends ). Não pode escrever super . Além disso, neste exemplo, T depende de si mesmo, é chamado de recursive bound - uma borda recursiva.

Aqui está outro exemplo da classe Enum:
 public abstract class Enum<E extends Enum<E>>implements Comparable<E>, Serializable 

Aqui, a classe Enum é parametrizada pelo tipo E, que é um subtipo de Enum<E> .

Vários limites


Multiple Bounds - várias restrições. É escrito através do caractere " & ", ou seja, dizemos que o tipo representado por uma variável do tipo T deve ser delimitado de cima pela classe Object e pela interface Comparable .

 <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) 

Object & Comparable<? super T> registro Object & Comparable<? super T> Object & Comparable<? super T> forma o tipo de interseção Multiple Bounds . A primeira limitação - neste caso, Object - é usada para erasure , o processo de substituição de tipos. É realizado pelo compilador no estágio de compilação.

Conclusão


Uma variável de tipo pode ser limitada apenas a um ou mais tipos. No caso de várias restrições, a borda esquerda (a primeira restrição) é usada no processo de substituição (Eliminação de tipo).

Tipo apagamento


Eliminação de tipo é um mapeamento de tipos (possivelmente incluindo tipos parametrizados e variáveis ​​de tipo) para tipos que nunca são tipos parametrizados ou tipos de variáveis. Escrevemos o tipo T esmagando como |T| .

A exibição de esmagamento é definida da seguinte maneira:
  • A trituração do tipo parametrizado G < T1 , ..., Tn > é | G
  • Masashing um TC do tipo aninhado é | T |. C
  • A combinação do tipo de matriz T [] é | T | []
  • A combinação de uma variável de tipo está esmagando sua borda esquerda
  • Masash de qualquer outro tipo é esse próprio tipo


Durante a execução do Type Erasure (type mashing), o compilador executa as seguintes ações:
  • adiciona conversão de tipo para fornecer segurança de tipo, se necessário
  • gera métodos Bridge para manter o polimorfismo


T (Tipo)
| T | (Tipo de purê)
Lista <Integer>, Lista <>> Lista <Lista <String >>
Lista
Lista <Integer> []
Lista []
Lista
Lista
int
int
Inteiro
Inteiro
<T estende Comparável <T>>
Comparável
<T estende Objeto e Comparável <? super T >>
Object
LinkedCollection <E> .Node
LinkedCollection.Node

Esta tabela mostra em que os diferentes tipos se transformam durante o processo de esmagamento, Tipo Apagamento.

Na captura de tela abaixo, há dois exemplos do programa:


A diferença entre os dois é que ocorre um erro em tempo de compilação à esquerda e à direita tudo é compilado sem erros. Porque

A resposta
Em Java, dois métodos diferentes não podem ter a mesma assinatura. No processo Type Erasure, o compilador adicionará o método de ponte public int compareTo(Object o) . Mas a classe já contém um método com essa assinatura que causará um erro durante a compilação.

Compile a classe Name removendo compareTo(Object o) método compareTo(Object o) e observe o bytecode resultante usando javap:
 # javap Name.class Compiled from "Name.java" public class ru.sberbank.training.generics.Name implements java.lang.Comparable<ru.sberbank.training.generics.Name> { public ru.sberbank.training.generics.Name(java.lang.String); public java.lang.String toString(); public int compareTo(ru.sberbank.training.generics.Name); public int compareTo(java.lang.Object); } 

Vimos que a classe contém um método int compareTo(java.lang.Object) , embora o tenhamos removido do código-fonte. Este é o método de ponte que o compilador adicionou.


Tipos renováveis


Em Java, dizemos que um tipo é reifiable se suas informações estiverem totalmente acessíveis em tempo de execução. Os tipos renováveis ​​incluem:
  • Tipos primitivos ( int , long , boolean )
  • Tipos não paramétricos (não genéricos) ( String , Inteiro )
  • Tipos parametrizados cujos parâmetros são representados como curinga ilimitada (caracteres curinga ilimitados) ( Lista <?> , Coleção <?> )
  • Tipos brutos (não formados) ( List , ArrayList )
  • Matrizes cujos componentes são tipos renováveis ​​( int [] , Número [] , Lista <?> [] , Lista [ )


Por que as informações sobre alguns tipos estão disponíveis, mas não sobre outros? O fato é que, devido ao processo de substituição de tipos pelo compilador, informações sobre alguns tipos podem ser perdidas. Se for perdido, esse tipo não será mais reificável. Ou seja, não está disponível em tempo de execução. Se disponível - respectivamente, reificável.

A decisão de não disponibilizar todos os tipos genéricos em tempo de execução é uma das decisões de design mais importantes e conflitantes no sistema de tipos Java. Isso é feito, em primeiro lugar, para compatibilidade com o código existente. Eu tive que pagar pela compatibilidade da migração - a acessibilidade total de um sistema de tipos genéricos em tempo de execução não é possível.

Quais tipos não são reificáveis:
  • Variável de tipo ( T )
  • Tipo parametrizado com o tipo de parâmetro especificado ( List <Number> ArrayList <String> , List <List <String>> )
  • Um tipo parametrizado com o limite superior ou inferior especificado ( Lista <? Estende Número>, Comparável <? Super String> ). Mas aqui está uma reserva: List <? estende o Objeto> - não é passível de refazer, mas Lista <?> - reifiável


E mais uma tarefa. Por que no exemplo abaixo não é possível criar uma exceção parametrizada?

 class MyException<T> extends Exception {  T t; } 

A resposta
Cada expressão de captura no try-catch verifica o tipo da exceção recebida durante a execução do programa (que é equivalente à instanceof), respectivamente, o tipo deve ser Reifiable. Portanto, Throwable e seus subtipos não podem ser parametrizados.

 class MyException<T> extends Exception {// Generic class may not extend 'java.lang.Throwable'  T t; } 



Avisos não verificados


A compilação de nosso aplicativo pode produzir o chamado Unchecked Warning - um aviso de que o compilador não pôde determinar corretamente o nível de segurança do uso de nossos tipos. Isso não é um erro, mas um aviso, para que você possa ignorá-lo. Mas é aconselhável consertar tudo para evitar problemas no futuro.

Poluição por pilha


Como mencionamos anteriormente, atribuir uma referência a um tipo Raw a uma variável de um tipo parametrizado leva ao aviso "Atribuição não verificada". Se a ignorarmos, é possível uma situação chamada “ Heap Pollution ” (poluição por pilha). Aqui está um exemplo:
 static List<String> t() {  List l = new ArrayList<Number>();  l.add(1);  List<String> ls = l; // (1)  ls.add("");  return ls; } 

Na linha (1), o compilador avisa sobre "Atribuição não verificada".

Precisamos dar outro exemplo de "poluição de pilha" - quando usamos objetos parametrizados. O trecho de código abaixo mostra claramente que não é permitido usar tipos parametrizados como argumentos para um método usando Varargs . Nesse caso, o parâmetro do método m é List<String>… , ou seja, de fato, uma matriz de elementos do tipo List<String> . Dada a regra de exibir tipos durante o mashing, o tipo stringLists transforma em uma matriz de listas brutas ( List[] ), ou seja, a atribuição pode ser feita Object[] array = stringLists; e, em seguida, escreva para uma array um objeto que não seja a lista de cadeias (1), que ClassCastException na cadeia (2).

 static void m(List<String>... stringLists) {  Object[] array = stringLists;  List<Integer> tmpList = Arrays.asList(42);  array[0] = tmpList; // (1)  String s = stringLists[0].get(0); // (2) } 


Considere outro exemplo:
 ArrayList<String> strings = new ArrayList<>(); ArrayList arrayList = new ArrayList(); arrayList = strings; // (1) Ok arrayList.add(1); // (2) unchecked call 

Java permite a atribuição na linha (1). Isso é necessário para compatibilidade com versões anteriores. Mas se tentarmos executar o método add na linha (2), obteremos um aviso de Unchecked call - o compilador nos avisa sobre um possível erro. De fato, estamos tentando adicionar um número inteiro à lista de strings.

Reflexão


Embora, durante a compilação, tipos parametrizados sejam submetidos a um procedimento de apagamento de tipo, podemos obter algumas informações usando o Reflection.

  • Todos os reificáveis ​​estão disponíveis através do mecanismo de reflexão.
  • Informações sobre o tipo de campos de classe, parâmetros de método e os valores retornados por eles estão disponíveis no Reflection.

Se queremos obter informações sobre o tipo de um objeto através do Reflection e esse tipo não é Reifiable, não teremos sucesso. Mas, se, por exemplo, esse objeto nos foi retornado por algum método, podemos obter o tipo do valor retornado por este método:
 java.lang.reflect.Method.getGenericReturnType() 

Com o advento dos genéricos, a classe tornou- java.lang.Classse parametrizada. Considere este código:
 List<Integer> ints = new ArrayList<Integer>(); Class<? extends List> k = ints.getClass(); assert k == ArrayList.class; 


A variável intsé do tipo List<Integer>e contém uma referência ao objeto de tipo ArrayList< Integer>. Em seguida, ele ints.getClass()retornará um objeto do tipo Class<ArrayLis>, uma vez que é List<Integer>sobrescrito List. Class<ArrayList>Um objeto de tipo pode ser atribuído a uma variável de ktipo de Class<? extends List>acordo com a covariância de caracteres curinga? extends. A ArrayList.classretorna um objeto do tipo Class<ArrayList>.

Conclusão


, Reifiable. Reifiable : , , , Raw , reifiable.

Unchecked Warnings « » .

Reflection , Reifiable. Reflection , .

Type Inference


« ». () . :
 List<Integer> list = new ArrayList<Integer>(); 

- Java 7 ArrayList :
 List<Integer> list = new ArrayList<>(); 

ArrayListList<Integer> . type inference .

Java 8 JEP 101.
Type Inference. :
  • (reduction)
  • (incorporation)
  • (resolution)

: , , — .
, . JEP 101 .

, :
 class List<E> {  static <Z> List<Z> nil() { ... };  static <Z> List<Z> cons(Z head, List<Z> tail) { ... };  E head() { ... } } 

List.nil() :
 List<String> ls = List.nil(); 

, List.nil() String — JDK 7, .

, , , :
 List.cons(42, List.nil()); //error: expected List<Integer>, found List<Object> 

JDK 7 compile-time error. JDK 8 . JEP-101, — . JDK 8 — :
 List.cons(42, List.<Integer>nil()); 


JEP-101 , , :
 String s = List.nil().head(); //error: expected String, found Object 

, . , JDK , :
 String s = List.<String>nil().head(); 


JEP 101 StackOverflow . , , 7- , 8- – ? :
 class Test {  static void m(Object o) {      System.out.println("one");  }  static void m(String[] o) {      System.out.println("two");  }  static <T> T g() {      return null;  }  public static void main(String[] args) {      m(g());  } } 


- JDK1.8:
   public static void main(java.lang.String[]);   descriptor: ([Ljava/lang/String;)V   flags: ACC_PUBLIC, ACC_STATIC   Code:     stack=1, locals=1, args_size=1        0: invokestatic  #6   // Method g:()Ljava/lang/Object;        3: checkcast     #7   // class "[Ljava/lang/String;"        6: invokestatic  #8   // Method m:([Ljava/lang/String;)V        9: return     LineNumberTable:       line 15: 0       line 16: 9 


0 g:()Ljava/lang/Object; java.lang.Object . , 3 («») , java.lang.String , 6 m:([Ljava/lang/String;) , «two».

- JDK1.7 – Java 7:
   public static void main(java.lang.String[]);   flags: ACC_PUBLIC, ACC_STATIC   Code:     stack=1, locals=1, args_size=1        0: invokestatic  #6   // Method g:()Ljava/lang/Object;        3: invokestatic  #7   // Method m:(Ljava/lang/Object;)V        6: return            LineNumberTable:       line 15: 0       line 16: 6 


, checkcast , Java 8, m:(Ljava/lang/Object;) , «one». Checkcast – , Java 8.

, Oracle JDK1.7 JDK 1.8 , Java, , .

, Java 8 , Java 7, :

 public static void main(String[] args) { m((Object)g()); } 


Conclusão


Java Generics . , :


  • Bloch, Joshua. Effective Java. Third Edition. Addison-Wesley. ISBN-13: 978-0-13-468599-1

, Java Generics.

Source: https://habr.com/ru/post/pt416413/


All Articles