Java Generics: lista, lista , lista

Alguém pode explicar, o mais detalhado possível, as diferenças entre os seguintes tipos?

List List List 

Deixe-me tornar isso mais específico. Quando eu iria querer usar

 // 1 public void CanYouGiveMeAnAnswer(List l) { } // 2 public void CanYouGiveMeAnAnswer(List l) { } // 3 public void CanYouGiveMeAnAnswer(List l) { } 

Como os outros posts notaram, você está perguntando sobre um recurso Java chamado genéricos. Em C ++, isso é chamado de modelos. Os bichos de Java são muito mais fáceis de lidar.

Deixe-me responder às suas perguntas funcionalmente (se não for uma palavra malcriada para as discussões OO).

Antes dos genéricos, você tinha boas classs antigas de concreto, como Vector.

 Vector V = new Vector(); 

Vetores retêm qualquer object antigo que você lhes der.

 V.add("This is an element"); V.add(new Integer(2)); v.add(new Hashtable()); 

No entanto, eles fazem isso lançando tudo o que você atribui a um Objeto (a raiz de todas as classs Java). Tudo bem até você tentar recuperar os valores armazenados no seu Vector. Quando você fizer isso, você precisa converter o valor de volta para a class original (se você quiser fazer algo significativo com ele).

 String s = (String) v.get(0); Integer i = (Integer) v.get(1); Hashtable h = (Hashtable) v.get(2); 

Casting fica muito velho rápido. Mais do que isso, o compilador reclama de você sobre lançamentos não verificados. Para um exemplo vívido disso, use a biblioteca XML-RPC do Apache (versão 2 de qualquer maneira). O problema mais importante é que os consumidores do seu Vector precisam saber a class exata de seus valores em tempo de compilation para serem convertidos corretamente. Nos casos em que o produtor do Vector e o consumidor estão completamente isolados uns dos outros, isso pode ser um problema fatal.

Digite genéricos. Os genéricos tentam criar classs fortemente tipadas para fazer operações genéricas.

 ArrayList aList = new ArrayList(); aList.add("One"); String element = aList.get(0); // no cast needed System.out.println("Got one: " + element); 

Agora, se você der uma olhada na gangue infame do livro Design Patterns de quatro, você notará que há alguma sabedoria nas variables ​​de divórcio de sua class de implementação. É melhor pensar em contratos do que em implementação. Portanto, você pode dizer que todos os objects List fazem as mesmas coisas: add() , get() , size() , etc. No entanto, existem muitas implementações de operações List que podem optar por obedecer ao contrato de várias maneiras (por exemplo, ArrayList ). No entanto, o tipo de dados com os quais esses objects lidam é deixado como uma consideração de tempo de execução para você, o usuário da class genérica. Coloque tudo junto e você verá a seguinte linha de código com muita frequência:

 List L = new ArrayList(); 

Você deve ler isso como “L é um tipo de lista que lida com objects String”. Quando você começa a lidar com classs Factory, é essencial lidar com contratos em vez de implementações específicas. As fábricas produzem objects de vários tipos em tempo de execução.

Usando genéricos é muito fácil (na maioria das vezes). No entanto, um dia terrível, você pode decidir que deseja implementar uma class genérica. Talvez você tenha pensado em uma ótima nova implementação de lista. Quando você define essa class, você usa como um espaço reservado para o tipo de object que será manipulado pelos methods. Se você estiver confuso, use as classs genéricas para List até ficar confortável. Então, você pode mergulhar na implementação com um pouco mais de confiança. Ou você pode olhar o código-fonte para as várias classs de Lista que acompanham o JRE. O código aberto é ótimo assim.

Dê uma olhada nos documentos da Oracle / Sun sobre genéricos . Felicidades.

Em meus próprios termos simples:

Lista

Declararia uma coleção comum, poderia conter qualquer tipo e sempre retornaria Object.

Listar

Vai criar uma lista que pode conter qualquer tipo de object, mas só pode ser atribuído a uma outra lista

Por exemplo, isso não funciona;

 List l = new ArrayList(); 

Claro que você pode adicionar qualquer coisa, mas só pode puxar o object.

 List l = new ArrayList(); l.add( new Employee() ); l.add( new String() ); Object o = l.get( 0 ); Object o2 = l.get( 1 ); 

Finalmente

Listar < ?>

Permitirá que você atribua qualquer tipo, incluindo

 List < ?> l = new ArrayList(); List < ?> l2 = new ArrayList(); 

Isso seria chamado de coleção de desconhecido e como o denominador comum do desconhecido é Objeto, você será capaz de buscar Objetos (uma coincidência).

A importância do desconhecido vem quando é usado com subsorting:

 List< ? extends Collection> l = new ArrayList(); // compiles List< ? extends Collection> l = new ArrayList(); // doesn't, // because String is not part of *Collection* inheritance tree. 

Espero usar o Collection, pois o tipo não cria confusão, essa foi a única tree que veio à minha mente.

A diferença aqui é que l é uma coleção de unknow que pertence à hierarquia Collection .

Eu o encaminho para o excelente tutorial Java Generics e o tutorial “avançado” Generics , ambos disponíveis na Sun Microsystems. Outro grande recurso é o livro Java Generics and Collections .

Para adicionar as já boas respostas aqui:

Argumentos do método:

List< ? extends Foo>

boa escolha se você não pretende alterar a lista, e apenas se importa que tudo na lista seja atribuível ao tipo ‘Foo’. Desta forma, o chamador pode passar em uma lista e seu método funciona. Geralmente a melhor escolha.

List

boa escolha se você pretende adicionar objects Foo à lista em seu método. O chamador não pode passar em uma lista , como você pretende adicionar um Foo à lista.

List< ? super Foo>

boa escolha se você pretende adicionar objects Foo à lista, e não é importante o que mais está na lista (ou seja, você está recebendo um List que contém um ‘Cachorro’ que não tem nada a ver com o Foo).

Valores de retorno do método

apenas como argumentos de método, mas com os benefícios invertidos.

List< ? extends Foo>

Garante que tudo na lista retornada tenha o tipo ‘Foo’. Pode ser List embora. O chamador não pode adicionar à lista. Esta é a sua melhor opção e, de longe, o caso mais comum.

List

Apenas como List < ? estende Foo> mas também permite que o chamador adicione à Lista. Menos comum.

List< ? super Foo>

permite ao chamador adicionar objects Foo à Lista, mas não garante o que será retornado de list.get (0) … pode ser qualquer coisa de Foo para Objeto. A única garantia é que esta não será uma lista de ‘Dog’ ou alguma outra opção que impeça o list.add (foo) de ser legal. Caso de uso muito raro.

Espero que isso ajude. Boa sorte!

ps. Resumindo … duas perguntas …

você precisa adicionar à lista? Você se importa com o que está na lista?

sim sim – use List .

sim não – use List < ? super foo>.

não sim – use < ? estende Foo> — mais comum.

não não – use < ?>.

Explicação mais simples que não é “RTFM”:

 List 

Gerará muitos avisos de compilador, mas é principalmente equivalente a:

 List 

Enquanto:

 List< ?> 

basicamente significa algo genérico, mas você não sabe qual é o tipo genérico. É ótimo para se livrar dos avisos do compilador quando você não pode modificar os tipos de retorno de outras coisas que apenas retornaram List. É muito mais útil na forma:

 List< ? extends SomeOtherThing> 

Vou tentar responder isso em detalhes. Antes dos genéricos, estávamos tendo apenas uma List (uma lista bruta) e ela pode conter praticamente qualquer coisa em que possamos pensar.

 List rawList = new ArrayList(); rawList.add("String Item"); rawList.add(new Car("VW")); rawList.add(new Runnable() { @Override public void run() { // do some work. } }); 

O principal problema com a lista bruta é quando queremos obter qualquer elemento fora dessa lista, só podemos garantir que seria Object e, por essa razão, precisamos usar a conversão como:

  Object item = rawList.get(0); // we get object without casting. String sameItem = (String) rawList.get(0); // we can use casting which may fail at runtime. 

Portanto, a conclusão é que uma List pode armazenar Object (quase tudo é Object em Java) e sempre retorna um Object.

Genéricos

Agora vamos falar sobre genéricos. Considere o seguinte exemplo:

 List stringsList = new ArrayList<>(); stringsList.add("Apple"); stringsList.add("Ball"); stringsList.add(new Car("Fiat")); //error String stringItem = stringsList.get(0); 

No caso acima, não podemos inserir nada diferente de String em stringsList pois o compilador Java aplica a verificação de tipo forte ao código genérico e emite erros se o código violar a segurança do tipo. E recebemos um erro quando tentamos inserir uma ocorrência Car nela. Também elimina o casting como você pode verificar quando invoke método get. Verifique este link para entender por que devemos usar genéricos .

List

Se você ler sobre o apagamento de tipos, entenderá que List, List, List etc. estarão com tipos estáticos diferentes no tempo de compilation, mas terão o mesmo tipo dynamic List em tempo de execução.

Se tivermos List , ele pode armazenar apenas Object nele e quase tudo é Object em Java. Então podemos ter:

  List objectList = new ArrayList(); objectList.add("String Item"); objectList.add(new Car("VW")); objectList.add(new Runnable() { @Override public void run() { } }); Object item = objectList.get(0); // we get object without casting as list contains Object String sameItem = (String) objectList.get(0); // we can use casting which may fail at runtime. 

Parece que List e List são iguais, mas na verdade não são. Considere o seguinte caso:

 List tempStringList = new ArrayList<>(); rawList = tempStringList; // Ok as we can assign any list to raw list. objectList = tempStringList; // error as List is not subtype of List becuase generics are not convariant. 

Você pode ver que podemos atribuir qualquer lista à lista bruta e a principal razão para isso é permitir a compatibilidade com versões anteriores. Também List será convertido para List em tempo de execução devido ao apagamento do tipo e a atribuição será boa de qualquer maneira.

Mas List significa que só pode se referir a uma lista de objects e também pode armazenar apenas objects. Mesmo que String seja um subtipo de Object , não podemos atribuir List a List porque os genéricos não são covariantes como matrizes. Eles são invariantes. Além disso, verifique este link para mais. Além disso, verifique a diferença entre List e List nesta questão .

List< ?>

Agora ficamos com List< ?> Que basicamente significa lista de tipo desconhecido e pode se referir a qualquer lista.

 List< ?> crazyList = new ArrayList(); List stringsList = new ArrayList<>(); stringsList.add("Apple"); stringsList.add("Ball"); crazyList = stringsList; // fine 

O personagem ? é conhecido como curinga e List< ?> é uma lista de curinga ilimitada. Há certos pontos para observar agora.

Não podemos instanciar esta lista, pois o código a seguir não será compilado:

 List< ?> crazyList = new ArrayList< ?>(); // any list. 

Podemos dizer que um tipo com parâmetro curinga é mais parecido com um tipo de interface, já que podemos usá-lo para se referir a um object de tipo compatível, mas não para si mesmo.

 List< ?> crazyList2 = new ArrayList(); 

Não podemos inserir nenhum item para ele, pois não sabemos o que realmente seria o tipo.

 crazyList2.add("Apple"); // error as you dont actually know what is that type. 

Agora pergunta surge Quando eu iria querer usar List< ?> ?

Você pode pensar nisso como uma lista somente de leitura, onde você não se importa com o tipo de itens. Você pode usá-lo para invocar methods como retornar o tamanho da lista, imprimi-lo etc.

  public static void print(List< ?> list){ System.out.println(list); } 

Você também pode verificar a diferença entre List, List< ?>, List, List, and List aqui .

A explicação mais curta possível é: O segundo item é uma lista que pode conter qualquer tipo e você pode adicionar objects a ele:

 List 

O primeiro item listado é tratado como essencialmente equivalente a este, exceto que você receberá avisos do compilador porque é um “tipo bruto”.

 List 

A terceira é uma lista que pode conter qualquer tipo, mas você não pode adicionar nada a ela:

 List< ?> 

Basicamente, você usa o segundo formulário ( List ) quando você realmente tem uma lista que pode conter qualquer object e você quer poder adicionar elementos à lista. Você usa o terceiro formulário ( List< ?> ) Ao receber a lista como um valor de retorno do método e iterará sobre a lista, mas nunca adicionará nada a ela Nunca use o primeiro formulário ( List ) no novo código compilado em Java 5 ou mais tarde.

Eu colocaria desta forma: Enquanto List e List podem conter qualquer tipo de objects, List< ?> Contém elementos de um tipo desconhecido, mas uma vez que esse tipo é capturado, ele só pode conter elementos desse tipo. É por isso que é o único tipo de variante segura desses três e, portanto, geralmente preferível.

Para complementar os tutoriais mencionados por Rob, aqui está um wikibook explicando o tópico:
http://en.wikibooks.org/wiki/Java_Programming/Generics


Editar:

  1. Nenhuma restrição no tipo de itens na lista

  2. Itens na lista devem estender o object

  3. Curinga usado sozinho, então combina com qualquer coisa

Seria ingênuo da minha parte concluir que, neste ponto, quase não há diferença alguma?

Quando eu iria querer usar

 public void CanYouGiveMeAnAnswer( List l ){} 

Quando você não pode fazer todo o casting de si mesmo.

Quando eu iria querer usar

 public void CanYouGiveMeAnAnswer( List l ){} 

Quando você deseja restringir o tipo da lista. Por exemplo, isso seria um argumento inválido.

  new ArrayList(); 

Quando eu iria querer usar

 public void CanYouGiveMeAnAnswer( List l< ?> ){} 

Principalmente nunca.

List, List< ?>, and List< ? extends Object> List, List< ?>, and List< ? extends Object> são a mesma coisa. O segundo é mais explícito. Para uma lista desse tipo, você não pode saber quais tipos são legais para colocar nela, e você não sabe nada sobre os tipos que você pode obter, exceto que eles serão objects.

List significa especificamente que a lista contém qualquer tipo de object.

Digamos que façamos uma lista de Foo :

 List foos= new ArrayList(); 

Não é legal colocar um Bar em foos.

 foos.add(new Bar()); // NOT OK! 

É sempre legal colocar qualquer coisa em uma List .

 List objs = new ArrayList(); objs.add(new Foo()); objs.add(new Bar()); 

Mas você não deve ser autorizado a colocar uma Bar em uma List – esse é o ponto principal. Então isso significa que isso:

 List objs = foos; // NOT OK! 

não é legal.

Mas não há problema em dizer que foos é uma lista de algo, mas não sabemos especificamente o que é:

 List< ?> dontKnows = foos; 

Mas isso significa que deve ser proibido ir

 dontKnows.add(new Foo()); // NOT OK dontKnows.add(new Bar()); // NOT OK 

porque a variável dontKnows não sabe quais tipos são legais.

List destina-se a passar o parâmetro de tipo de input de um object. Enquanto Listar < ? > representa o tipo curinga. O curinga < ? > é do tipo de parâmetro desconhecido. O curinga não pode ser usado como um argumento de tipo para um método genérico e não pode ser usado para criar uma instância genérica de uma class. O curinga pode ser usado para estender uma class de subtipo, List < ? estende o número>. Para relaxar a restrição de um tipo de object e, neste caso, para relaxar o tipo de object “Número”.