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) {
sum += ((Account) account).getAmount();
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 / subtipoAqui 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;
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;
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ópicoUm 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);
A respostaO 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);
A respostaNã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);
<?> 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) {
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;
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);
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 respostaEm 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 respostaCada 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 {
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;
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;
Considere outro exemplo:
ArrayList<String> strings = new ArrayList<>(); ArrayList arrayList = new ArrayList(); arrayList = strings;
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.Class
se 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 k
tipo de Class<? extends List>
acordo com a covariância de caracteres curinga? extends
. A ArrayList.class
retorna 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<>();
ArrayList
–
List<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());
JDK 7 compile-time error. JDK 8 . JEP-101, — . JDK 8 — :
List.cons(42, List.<Integer>nil());
JEP-101 , , :
String s = List.nil().head();
, . , 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
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
,
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.