Diferença entre Lista, Lista <?>, Lista <T>, Lista <E> e Lista <Object>

194

Quais são as diferenças entre List, List<?>, List<T>, List<E>, e List<Object>?

1. Lista

List: é um tipo bruto, portanto não typesafe. Isso gerará apenas um erro de tempo de execução quando a transmissão estiver ruim. Queremos um erro de tempo de compilação quando o elenco é ruim. Não recomendado para uso.

2. Lista <?>

List<?>é um curinga ilimitado. Mas não sei ao certo para que serve? Posso imprimir um List<?>sem problemas:

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

Por que não consigo adicionar itens a um List<?>?

public static void test(List<?> list){
    list.add(new Long(2));     // Error
    list.add("2");             // Error
    System.out.println(list);
}

3. Lista <T>

public static void test(List<T> list){   // T cannot be resolved
    System.out.println(list);
}

Eu não entendo essa sintaxe. Eu vi algo assim, e funciona:

public <T> T[] toArray(T[] a){
    return a;   
}

Às vezes, eu vejo <T>, ou <E>, ou <U>, <T,E>. Eles são todos iguais ou representam algo diferente?

4. Liste <Objeto>

Isso fornece o erro "O método test(List<Object>)não é aplicável ao argumento List<String>":

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

Se eu tentar isso, obtive "Não é possível transmitir de List<String>para List<Object>":

test((List<Object>) names);

Estou confuso. Stringé uma subclasse de Object, então por que não é List<String>uma subclasse de List<Object>?

Thang Pham
fonte

Respostas:

77

1) Correto

2) Você pode pensar nisso como uma lista "somente leitura", onde você não se importa com o tipo de itens. Pode, por exemplo, ser usado por um método que está retornando o comprimento da lista.

3) T, E e U são os mesmos, mas as pessoas tendem a usar, por exemplo, T para o tipo, E para o elemento, V para o valor e K para a chave. O método que compila diz que ele pegou uma matriz de um determinado tipo e retorna uma matriz do mesmo tipo.

4) Você não pode misturar laranjas e maçãs. Você seria capaz de adicionar um objeto à sua lista de strings se pudesse passar uma lista de strings para um método que espera listas de objetos. (E nem todos os objetos são cadeias)

Kaj
fonte
2
+1 para a lista somente leitura ativada 2. Eu escrevo alguns códigos para demonstrar isso 2. tyvm
Thang Pham
Por que as pessoas usariam List<Object>?
precisa
3
É uma maneira de criar uma lista que aceita qualquer tipo de item e você raramente a usa.
Kaj
Na verdade, eu não acho que alguém usaria, considerando que você não pode ter um identificador agora.
if_zero_equals_one
1
@if_zero_equals_one Sim, mas você receberá um aviso do compilador (ele avisaria e diria que está usando tipos brutos) e nunca deseja compilar seu código com avisos.
Kaj
26

Para a última parte: Embora String seja um subconjunto de Objeto, mas Lista <> não é herdada da Lista <Objeto>.

Farshid Zaker
fonte
11
Ponto muito bom; muitos assumem que, porque a classe C herda da classe P, essa Lista <C> também herda da Lista <P>. Como você apontou, esse não é o caso. O motivo foi que, se pudéssemos converter da Lista <> para a Lista <Objeto>, poderíamos colocar Objetos nessa lista, violando o contrato original da Lista <> ao tentar recuperar um elemento.
Peter
2
+1. Bom ponto também. Então, por que as pessoas usariam List<Object>?
precisa
9
A lista <Object> pode ser usada para armazenar uma lista de objetos de diferentes classes.
Farshid Zaker
20

A notação List<?>significa "uma lista de algo (mas não estou dizendo o que)". Como o código testfunciona para qualquer tipo de objeto na lista, isso funciona como um parâmetro formal do método.

O uso de um parâmetro de tipo (como no seu ponto 3) exige que o parâmetro de tipo seja declarado. A sintaxe Java para isso é colocar <T>na frente da função. Isso é exatamente análogo a declarar nomes de parâmetros formais para um método antes de usar os nomes no corpo do método.

Quanto a List<Object>não aceitar a List<String>, isso faz sentido porque a Stringnão é Object; é uma subclasse de Object. A correção é declarar public static void test(List<? extends Object> set) .... Mas então o extends Objecté redundante, porque toda classe se estende direta ou indiretamente Object.

Ted Hopp
fonte
Por que as pessoas usariam List<Object>?
precisa
10
Eu acho que "uma lista de algo" é um significado melhor, List<?>pois a lista é de algum tipo específico, mas desconhecido. List<Object>seria realmente "uma lista de qualquer coisa", pois pode realmente conter qualquer coisa.
colind
1
@ CololinD - eu quis dizer "qualquer coisa" no sentido de "qualquer coisa". Mas você está certo; significa "uma lista de algo, mas não vou lhe dizer o que".
Ted Hopp
@ColinD, ele quis dizer por que você repetiu suas palavras? sim foi escrito com um pouco de palavras diferentes, mas o significado é o mesmo ...
USER25
14

A razão que você não pode lançar List<String>a List<Object>é que ele iria permitir que você violar as restrições do List<String>.

Pense no seguinte cenário: Se eu tiver um List<String>, ele deve conter apenas objetos do tipo String. (Que é uma finalclasse)

Se eu puder converter isso para a List<Object>, isso me permitirá adicionar Objecta essa lista, violando o contrato original de List<String>.

Portanto, em geral, se a classe Cherda de classe P, você não pode dizer que GenericType<C>também herda de GenericType<P>.

NB: Eu já comentei isso em uma resposta anterior, mas queria expandi-lo.

Peter
fonte
tyvm, carrego seu comentário e sua resposta, pois é uma explicação muito boa. Agora, onde e por que as pessoas usariam List<Object>?
Thang Pham
3
Geralmente você não deve usar, List<Object>pois isso meio que derrota o propósito dos genéricos. No entanto, há casos em que o código antigo pode ter Listtipos diferentes aceitos; portanto, você pode modificar o código para usar a parametrização de tipo apenas para evitar avisos do compilador para tipos brutos. (Mas a funcionalidade é inalterada) #
Peter Peter
5

Eu aconselharia a leitura de quebra-cabeças Java. Explica muito bem herança, genéricos, abstrações e curingas em declarações. http://www.javapuzzlers.com/

if_zero_equals_one
fonte
5

Vamos falar sobre eles no contexto da história do Java;

  1. List:

Lista significa que pode incluir qualquer objeto. A lista estava no release anterior ao Java 5.0; O Java 5.0 introduziu a Lista, para compatibilidade com versões anteriores.

List list=new  ArrayList();
list.add(anyObject);
  1. List<?>:

?significa Objeto desconhecido, nenhum Objeto; a ?introdução do curinga é para resolver o problema criado pelo tipo genérico; veja curingas ; mas isso também causa outro problema:

Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error
  1. List< T> List< E>

Significa Declaração genérica na premissa de nenhum tipo T ou E em seu projeto Lib.

  1. List< Object> significa parametrização genérica.
phil
fonte
5

No terceiro ponto, "T" não pode ser resolvido porque não é declarado; geralmente, quando você declara uma classe genérica, pode usar "T" como o nome do parâmetro do tipo acoplado , muitos exemplos online, incluindo os tutoriais da Oracle, usam "T" como o nome do parâmetro type, digamos, por exemplo, que você declara uma classe como:

public class FooHandler<T>
{
   public void operateOnFoo(T foo) { /*some foo handling code here*/}

}

você está dizendo que o FooHandler's operateOnFoométodo espera uma variável do tipo "T" declarada na própria declaração de classe. Com isso em mente, você poderá adicionar outro método posteriormente, como

public void operateOnFoos(List<T> foos)

em todos os casos, T, E ou U, todos os identificadores do parâmetro type, você pode até ter mais de um parâmetro type que usa a sintaxe

public class MyClass<Atype,AnotherType> {}

em seu quarto ponto de vista, embora Sting seja efetivamente um subtipo de objeto, nas classes genéricas não existe tal relação, List<String>não é um subtipo, List<Object>pois são dois tipos diferentes do ponto de vista do compilador, isso é melhor explicado nesta entrada do blog

Harima555
fonte
5

Teoria

String[] pode ser lançado para Object[]

mas

List<String> não pode ser convertido para List<Object> .

Prática

Para listas, é mais sutil que isso, porque em tempo de compilação o tipo de um parâmetro List passado para um método não é verificado. A definição do método também pode dizerList<?> - do ponto de vista do compilador, é equivalente. É por isso que o exemplo 2 do OP fornece erros de tempo de execução, não erros de compilação.

Se você manipular um List<Object>parâmetro passado para um método com cuidado para não forçar uma verificação de tipo em nenhum elemento da lista, poderá definir seu método usando, List<Object>mas de fato aceitar um List<String>parâmetro do código de chamada.

A. Portanto, esse código não fornecerá erros de compilação ou tempo de execução e funcionará (e talvez surpreendentemente?):

public static void main(String[] args) {
    List argsList = new ArrayList<String>();
    argsList.addAll(Arrays.asList(args));
    test(argsList);  // The object passed here is a List<String>
}

public static void test(List<Object> set) {
    List<Object> params = new ArrayList<>();  // This is a List<Object>
    params.addAll(set);       // Each String in set can be added to List<Object>
    params.add(new Long(2));  // A Long can be added to List<Object>
    System.out.println(params);
}

B. Este código dará um erro de tempo de execução:

public static void main(String[] args) {
    List argsList = new ArrayList<String>();
    argsList.addAll(Arrays.asList(args));
    test1(argsList);
    test2(argsList);
}

public static void test1(List<Object> set) {
    List<Object> params = set;  // Surprise!  Runtime error
}

public static void test2(List<Object> set) {
    set.add(new Long(2));       // Also a runtime error
}

C. Este código dará um erro de tempo de execução ( java.lang.ArrayStoreException: java.util.Collections$UnmodifiableRandomAccessList Object[]):

public static void main(String[] args) {
    test(args);
}

public static void test(Object[] set) {
    Object[] params = set;    // This is OK even at runtime
    params[0] = new Long(2);  // Surprise!  Runtime error
}

Em B, o parâmetro setnão é digitado Listem tempo de compilação: o compilador vê como List<?>. Há um erro de tempo de execução porque, em tempo de execução, settorna-se o objeto real transmitido main()e esse é um List<String>. A List<String>não pode ser lançado para List<Object>.

Em C, o parâmetro setrequer um Object[]. Não há erro de compilação nem erro de tempo de execução quando é chamado com um String[]objeto como parâmetro. Isso porque String[]lança para Object[]. Mas o objeto real recebido por test()permanece a String[], não mudou. Portanto, o paramsobjeto também se torna a String[]. E o elemento 0 de a String[]não pode ser atribuído a a Long!

(Espero ter tudo aqui, se meu raciocínio estiver errado, tenho certeza de que a comunidade vai me dizer. ATUALIZADO: Atualizei o código no exemplo A para que ele realmente compile, enquanto ainda mostra o argumento.)

radfast
fonte
Eu tentei o seu exemplo A que isso não funciona: List<Object> cannot be applied to List<String>. Você não pode passar ArrayList<String>para um método que espera ArrayList<Object>.
Parsecer 14/05/19
Obrigado, bastante tarde, na data em que alterei o exemplo A, para que agora funcione. A principal mudança foi definir argsList genericamente em main ().
Radfast 29/10/19
4

O problema 2 está OK, porque "System.out.println (set);" significa "System.out.println (set.toString ());" set é uma instância de List, portanto, o complier chamará List.toString ();

public static void test(List<?> set){
set.add(new Long(2)); //--> Error  
set.add("2");    //--> Error
System.out.println(set);
} 
Element ? will not promise Long and String, so complier will  not accept Long and String Object

public static void test(List<String> set){
set.add(new Long(2)); //--> Error
set.add("2");    //--> Work
System.out.println(set);
}
Element String promise it a String, so complier will accept String Object

Problema 3: esses símbolos são os mesmos, mas você pode fornecer especificações diferentes. Por exemplo:

public <T extends Integer,E extends String> void p(T t, E e) {}

Problema 4: a coleção não permite covariância de parâmetro de tipo. Mas a matriz permite covariância.

yoso
fonte
0

Você está certo: String é um subconjunto de Objeto. Como String é mais "precisa" que Object, você deve convertê-la para usá-la como argumento para System.out.println ().

patapizza
fonte