Genéricos em Java

Da Wikipédia, a enciclopédia livre
Ir para a navegação Saltar para pesquisar

Genéricos são um recurso de programação genérica que foi adicionado à linguagem de programação Java em 2004 na versão J2SE 5.0. Eles foram projetados para estender o sistema de tipos do Java para permitir que "um tipo ou método opere em objetos de vários tipos enquanto fornece segurança de tipo em tempo de compilação". [1] O aspecto segurança do tipo em tempo de compilação não foi totalmente alcançado, pois foi demonstrado em 2016 que não é garantido em todos os casos. [2]

A estrutura de coleções Java suporta genéricos para especificar o tipo de objetos armazenados em uma instância de coleção.

Em 1998, Gilad Bracha , Martin Odersky , David Stoutamire e Philip Wadler criaram o Generic Java, uma extensão da linguagem Java para suportar tipos genéricos. [3] O Java genérico foi incorporado ao Java com a adição de curingas .

Hierarquia e classificação

De acordo com a especificação da linguagem Java : [4]

  • Uma variável de tipo é um identificador não qualificado. Variáveis ​​de tipo são introduzidas por declarações de classe genéricas, declarações de interface genéricas, declarações de métodos genéricos e por declarações de construtores genéricos.
  • Uma classe é genérica se declara uma ou mais variáveis ​​de tipo. Essas variáveis ​​de tipo são conhecidas como parâmetros de tipo da classe. Ele define uma ou mais variáveis ​​de tipo que atuam como parâmetros. Uma declaração de classe genérica define um conjunto de tipos parametrizados, um para cada chamada possível da seção de parâmetro de tipo. Todos esses tipos parametrizados compartilham a mesma classe em tempo de execução.
  • Uma interface é genérica se declara uma ou mais variáveis ​​de tipo. Essas variáveis ​​de tipo são conhecidas como parâmetros de tipo da interface. Ele define uma ou mais variáveis ​​de tipo que atuam como parâmetros. Uma declaração de interface genérica define um conjunto de tipos, um para cada chamada possível da seção de parâmetro de tipo. Todos os tipos parametrizados compartilham a mesma interface em tempo de execução.
  • Um método é genérico se declara uma ou mais variáveis ​​de tipo. Essas variáveis ​​de tipo são conhecidas como parâmetros de tipo formal do método. A forma da lista de parâmetros de tipo formal é idêntica a uma lista de parâmetros de tipo de uma classe ou interface.
  • Um construtor pode ser declarado como genérico, independentemente de a classe na qual o construtor é declarado ser genérica. Um construtor é genérico se declarar uma ou mais variáveis ​​de tipo. Essas variáveis ​​de tipo são conhecidas como parâmetros de tipo formal do construtor. A forma da lista de parâmetros de tipo formal é idêntica a uma lista de parâmetros de tipo de uma classe ou interface genérica.

Motivação

O bloco de código Java a seguir ilustra um problema que existe ao não usar genéricos. Primeiro, ele declara um ArrayListdo tipo Object. Em seguida, ele adiciona um Stringao ArrayList. Finalmente, ele tenta recuperar o adicionado Stringe convertê-lo em um Integererro de lógica, pois geralmente não é possível converter uma string arbitrária em um inteiro.

Lista  v  =  new  ArrayList (); 
v . add ( "teste" );  // Uma String que não pode ser convertida em um Integer 
Integer  i  =  ( Integer ) v . obter ( 0 );  // Erro de tempo de execução

Embora o código seja compilado sem erros, ele lança uma exceção de tempo de execução ( java.lang.ClassCastException) ao executar a terceira linha de código. Esse tipo de erro de lógica pode ser detectado durante o tempo de compilação usando genéricos e é a principal motivação para usá-los.

O fragmento de código acima pode ser reescrito usando genéricos da seguinte forma:

List < String >  v  =  new  ArrayList < String > (); 
v . add ( "teste" ); 
Inteiro  i  =  ( Inteiro ) v . obter ( 0 );  // (erro de tipo) erro em tempo de compilação

O parâmetro type Stringdentro dos colchetes angulares declara o ArrayListser constituído de String(um descendente dos constituintes ArrayListgenéricos de ' Object). Com os genéricos, não é mais necessário converter a terceira linha em nenhum tipo específico, porque o resultado de v.get(0)é definido Stringpelo código gerado pelo compilador.

A falha lógica na terceira linha deste fragmento será detectada como um erro de tempo de compilação (com J2SE 5.0 ou posterior) porque o compilador detectará que v.get(0)retorna Stringem vez de Integer. Para um exemplo mais elaborado, consulte a referência. [5]

Aqui está um pequeno trecho da definição das interfaces Liste Iteratorno pacote java.util:

public  interface  List < E >  {  
    void  add ( E  x ); 
    Iterador < E >  iterador (); 
}

 interface  pública Iterador < E >  {  
    E  próximo (); 
    boolean  hasNext (); 
}

Digite curingas

Um argumento de tipo para um tipo parametrizado não está limitado a uma classe ou interface concreta. Java permite o uso de curingas de tipo para servir como argumentos de tipo para tipos parametrizados. Curingas são argumentos de tipo no formato " <?>"; opcionalmente com um limite superior ou inferior . Dado que o tipo exato representado por um curinga é desconhecido, restrições são colocadas no tipo de métodos que podem ser chamados em um objeto que usa tipos parametrizados.

Aqui está um exemplo onde o tipo de elemento de a Collection<E>é parametrizado por um curinga:

Coleção <?>  c  =  new  ArrayList < String > (); 
c . adicionar ( novo  Objeto ());  // erro em tempo de compilação 
c . adicionar ( nulo );  // permitido

Como não sabemos o que o tipo de elemento crepresenta, não podemos adicionar objetos a ele. O add()método recebe argumentos do tipo E, o tipo de elemento da Collection<E>interface genérica. Quando o argumento de tipo real é ?, significa algum tipo desconhecido. Qualquer valor de argumento de método que passamos para o add()método teria que ser um subtipo desse tipo desconhecido. Como não sabemos que tipo é esse, não podemos passar nada. A única exceção é null ; que é um membro de cada tipo. [6]

Para especificar o limite superior de um curinga de tipo, a extendspalavra-chave é usada para indicar que o argumento de tipo é um subtipo da classe delimitadora. Então List<? extends Number>significa que a lista dada contém objetos de algum tipo desconhecido que estende a Numberclasse. Por exemplo, a lista pode ser List<Float>ou List<Number>. Ler um elemento da lista retornará um Number. A adição de elementos nulos também é permitida. [7]

O uso de curingas acima adiciona flexibilidade, pois não há nenhum relacionamento de herança entre dois tipos parametrizados com tipo concreto como argumento de tipo. Nem List<Number>nem List<Integer>é um subtipo do outro; mesmo que Integerseja um subtipo de Number. Portanto, qualquer método que tome List<Number>como parâmetro não aceita um argumento de List<Integer>. Se sim, seria possível inserir um Numberque não é um Integernele; que viola a segurança de tipo. Aqui está um exemplo que demonstra como a segurança de tipo seria violada se List<Integer>fosse um subtipo de List<Number>:

List < Integer >  ints  =  new  ArrayList < Integer > (); 
int . adicionar ( 2 ); 
List < Number >  nums  =  ints ;   // válido se List<Integer> for um subtipo de List<Number> de acordo com a regra de substituição. 
números . adicionar ( 3,14 );   
Inteiro  x  =  inteiros . obter ( 1 );  // agora 3.14 é atribuído a uma variável Integer!

A solução com curingas funciona porque não permite operações que violam a segurança de tipo:

Lista <?  estende  Number >  nums  =  ints ;   // OK 
números . adicionar ( 3,14 );  // erro em tempo de compilação 
nums . adicionar ( nulo );  // permitido

Para especificar a classe de limite inferior de um tipo curinga, a superpalavra-chave é usada. Essa palavra-chave indica que o argumento de tipo é um supertipo da classe delimitadora. Então, List<? super Number>poderia representar List<Number>ou List<Object>. A leitura de uma lista definida como List<? super Number>retorna elementos do tipo Object. Adicionar a essa lista requer elementos de type Number, qualquer subtipo de Numberou null (que é um membro de cada tipo).

O mnemônico PECS (Producer Extends, Consumer Super) do livro Effective Java de Joshua Bloch fornece uma maneira fácil de lembrar quando usar curingas (correspondentes a covariância e contravariância ) em Java.

Definições genéricas de classe

Aqui está um exemplo de uma classe Java genérica, que pode ser usada para representar entradas individuais (chave para mapeamentos de valor) em um mapa :

public  class  Entry < KeyType ,  ValueType >  {
  
     chave KeyType final  privada ; valor do tipo de valor final privado ; 
       

    public  Entry ( chave KeyType  , valor ValueType ) { this . chave = chave ; isso . valor = valor ; }     
          
          
    

    public  KeyType  getKey ()  { 
        return  key ; 
    }

    public  ValueType  getValue ()  { 
        return  value ; 
    }

    public  String  toString ()  {  
        return  "("  +  chave  +  ", "  +  valor  +  ")" ;   
    }

}

Essa classe genérica pode ser usada das seguintes maneiras, por exemplo:

Entry < String ,  String >  nota  =  new  Entry < String ,  String > ( "Mike" ,  "A" ); 
Entry < String ,  Integer >  marca  =  new  Entry < String ,  Integer > ( "Mike" ,  100 ); 
Sistema . fora . println ( "nota:"  +  nota ); 
Sistema .. println ( "marca:"  +  marca );

Entry < Integer ,  Boolean >  prime  =  new  Entry < Integer ,  Boolean > ( 13 ,  true ); 
if  ( prime . getValue ())  System . fora . println ( prime . getKey ()  +  "é primo." ); 
senão  Sistema . fora . println ( prime . getKey ()  + "não é primo." );

Ele produz:

grau: (Mike, A)
marca: (Mike, 100)
13 é primo.

Operador de diamante

Graças à inferência de tipo , Java SE 7 e superior permitem que o programador substitua um par vazio de colchetes angulares ( <>, chamado de operador diamante ) por um par de colchetes angulares contendo um ou mais parâmetros de tipo que um contexto suficientemente próximo implica . [8] Assim, o exemplo de código acima usando Entrypode ser reescrito como:

Entry < String ,  String >  grade  =  new  Entry <> ( "Mike" ,  "A" ); 
Entry < String ,  Integer >  marca  =  new  Entry <> ( "Mike" ,  100 ); 
Sistema . fora . println ( "nota:"  +  nota ); 
Sistema . fora . println ( "marca:"  +  marca );

Entry < Integer ,  Boolean >  prime  =  new  Entry <> ( 13 ,  true ); 
if  ( prime . getValue ())  System . fora . println ( prime . getKey ()  +  "é primo." ); 
senão  Sistema . fora . println ( prime . getKey ()  +  "não é primo." );

Definições de métodos genéricos

Aqui está um exemplo de um método genérico usando a classe genérica acima:

public  static  < Tipo >  Entrada < Tipo ,  Tipo >  duas vezes ( Tipo  valor )  { 
    return  new  Entrada < Tipo ,  Tipo > ( valor ,  valor ); 
}

Nota: Se removermos o primeiro <Type>no método acima, obteremos um erro de compilação (não é possível encontrar o símbolo 'Type'), pois representa a declaração do símbolo.

Em muitos casos o usuário do método não precisa indicar os parâmetros de tipo, pois eles podem ser inferidos:

Entrada < String ,  String >  par  =  Entrada . duas vezes ( "Olá" );

Os parâmetros podem ser adicionados explicitamente, se necessário:

Entrada < String ,  String >  par  =  Entrada . < String > duas vezes ( "Olá" );

O uso de tipos primitivos não é permitido, e as versões em caixa devem ser usadas em seu lugar:

Entrada < int ,  int >  par ;  // Falha na compilação. Use Integer em vez disso.

Há também a possibilidade de criar métodos genéricos com base em determinados parâmetros.

public  < Type >  Type []  toArray ( Type ...  elements )  { 
    return  elements ; 
}

Nesses casos, você também não pode usar tipos primitivos, por exemplo:

Integer []  array  =  toArray ( 1 ,  2 ,  3 ,  4 ,  5 ,  6 );

Genéricos na cláusula throws

Embora as próprias exceções não possam ser genéricas, parâmetros genéricos podem aparecer em uma cláusula throws:

public  < T  extends  Throwable >  void  throwMeConditional ( boolean  condicional ,  T  exceção )  throws  T  { 
    if  ( condicional )  { 
        throw  exceção ; 
    } 
}

Problemas com apagamento de tipo

Genéricos são verificados em tempo de compilação para correção de tipo. As informações de tipo genérico são removidas em um processo chamado eliminação de tipo . Por exemplo, List<Integer>será convertido para o tipo não genérico List, que normalmente contém objetos arbitrários. A verificação em tempo de compilação garante que o código resultante seja de tipo correto.

Devido ao apagamento de tipo, os parâmetros de tipo não podem ser determinados em tempo de execução. Por exemplo, quando an ArrayListé examinado em tempo de execução, não há uma maneira geral de determinar se, antes do apagamento de tipo, era an ArrayList<Integer>ou an ArrayList<Float>. Muitas pessoas estão insatisfeitas com essa restrição. [9] Existem abordagens parciais. Por exemplo, elementos individuais podem ser examinados para determinar a que tipo pertencem; por exemplo, se an ArrayListcontiver um Integer, esse ArrayList pode ter sido parametrizado com Integer(no entanto, pode ter sido parametrizado com qualquer pai de Integer, como Numberou Object).

Demonstrando esse ponto, o código a seguir gera "Equal":

ArrayList < Integer >  li  =  new  ArrayList < Integer > (); 
ArrayList < Float >  lf  =  new  ArrayList < Float > (); 
if  ( li . getClass ()  ==  lf . getClass ())  {  // avalia como true 
    System . fora . println ( "Igual" ); 
}

Outro efeito do apagamento de tipo é que uma classe genérica não pode estender a classe Throwable de forma alguma, direta ou indiretamente: [10]

 classe  pública GenericException < T >  estende  Exception

A razão pela qual isso não é suportado é devido ao apagamento de tipo:

try  { 
    throw  new  GenericException < Integer > (); 
} 
catch ( GenericException < Integer >  e )  { 
    System . erro . println ( "Inteiro" ); 
} 
catch ( GenericException < String >  e )  { 
    System . erro . println ( "String" ); 
}

Devido ao apagamento de tipo, o tempo de execução não saberá qual bloco catch executar, portanto, isso é proibido pelo compilador.

Os genéricos Java diferem dos modelos C++ . Os genéricos Java geram apenas uma versão compilada de uma classe ou função genérica, independentemente do número de tipos de parametrização usados. Além disso, o ambiente de tempo de execução Java não precisa saber qual tipo parametrizado é usado porque as informações de tipo são validadas em tempo de compilação e não são incluídas no código compilado. Conseqüentemente, instanciar uma classe Java de um tipo parametrizado é impossível porque a instanciação requer uma chamada para um construtor, que fica indisponível se o tipo for desconhecido.

Por exemplo, o código a seguir não pode ser compilado:

< T >  T  instantiateElementType ( List < T >  arg )  { 
     return  new  T ();  //causa um erro de compilação 
}

Como há apenas uma cópia por classe genérica em tempo de execução, as variáveis ​​estáticas são compartilhadas entre todas as instâncias da classe, independentemente de seu parâmetro de tipo. Conseqüentemente, o parâmetro type não pode ser usado na declaração de variáveis ​​estáticas ou em métodos estáticos.

Projeto sobre genéricos

O Projeto Valhalla é um projeto experimental para incubar genéricos e recursos de linguagem Java aprimorados, para versões futuras potencialmente do Java 10 em diante. Os aprimoramentos potenciais incluem: [11]

Veja também

Referências

  1. ^ Linguagem de programação Java
  2. ^ Uma ClassCastException pode ser lançada mesmo na ausência de conversões ou nulos. "Sistemas de tipo de Java e Scala são infundados" (PDF) .
  3. ^ GJ: Java genérico
  4. ^ Java Language Specification, Terceira Edição por James Gosling, Bill Joy, Guy Steele, Gilad Bracha – Prentice Hall PTR 2005
  5. ^ Gilad Bracha (5 de julho de 2004). "Genéricos na linguagem de programação Java" (PDF) . www.oracle.com .
  6. ^ Gilad Bracha (5 de julho de 2004). "Genéricos na linguagem de programação Java" (PDF) . www.oracle.com . pág. 5.
  7. ^ Bracha, Gilad . "Coringas > Bônus > Genéricos" . Os Tutoriais Java™ . Oráculo. ...A única exceção é null, que é um membro de todos os tipos...
  8. ^ "Type Inference for Generic Instance Creation" .
  9. ^ Gafter, Neal (2006-11-05). "Genéricos Reificados para Java" . Recuperado 2010-04-20 .
  10. ^ "Especificação de linguagem Java, Seção 8.1.2" . Oráculo . Recuperado em 24 de outubro de 2015 .
  11. ^ Goetz, Brian. "Bem-vindo a Valhalla!" . Arquivo de correio OpenJDK . OpenJDK . Recuperado em 12 de agosto de 2014 .