Diferencia entre Lista, Lista <?>, Lista <T>, Lista <E> y Lista <Objeto>

194

¿Cuáles son las diferencias entre List, List<?>, List<T>, List<E>, y List<Object>?

1. Lista

List: es un tipo sin formato, por lo tanto no typesafe. Solo generará un error de tiempo de ejecución cuando el casting sea malo. Queremos un error de tiempo de compilación cuando el reparto es malo. No se recomienda su uso.

2. Lista <?>

List<?>es un comodín ilimitado. ¿Pero no estoy seguro de para qué sirve? Puedo imprimir un List<?>sin problema:

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

¿Por qué no puedo agregar elementos a un 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);
}

No entiendo esta sintaxis. Vi algo como esto, y funciona:

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

A veces, veo <T>, o <E>, o <U>, <T,E>. ¿Son todos iguales o representan algo diferente?

4. Lista <Objeto>

Esto da el error "El método test(List<Object>)no es aplicable para el argumento List<String>":

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

Si intento esto, obtengo "No se puede transmitir desde List<String>a List<Object>":

test((List<Object>) names);

Estoy confundido. Stringes una subclase de Object, entonces ¿por qué no es List<String>una subclase de List<Object>?

Thang Pham
fuente

Respuestas:

77

1) correcto

2) Puede pensar en eso como una lista de "solo lectura", en la que no le importa el tipo de elementos. Por ejemplo, podría utilizarse por un método que devuelve la longitud de la lista.

3) T, E y U son iguales, pero las personas tienden a usar, por ejemplo, T para el tipo, E para el elemento, V para el valor y K para la clave. El método que compila dice que tomó una matriz de cierto tipo y devuelve una matriz del mismo tipo.

4) No puedes mezclar naranjas y manzanas. Podría agregar un objeto a su lista de cadenas si pudiera pasar una lista de cadenas a un método que espera listas de objetos. (Y no todos los objetos son cadenas)

Kaj
fuente
2
+1 para la lista de solo lectura en 2. Escribo algunos códigos para demostrar esto en 2. tyvm
Thang Pham
¿Por qué la gente usaría List<Object>?
Thang Pham
3
Es una manera de crear una lista que acepte cualquier tipo de elementos, rara vez lo usa.
Kaj
En realidad, no creo que nadie lo use teniendo en cuenta que ahora no puede tener un identificador.
if_zero_equals_one
1
@if_zero_equals_one Sí, pero luego recibiría una advertencia del compilador (advertiría y diría que está utilizando tipos sin formato), y nunca querrá compilar su código con advertencias.
Kaj
26

Para la última parte: Aunque String es un subconjunto de Object, List <String> no se hereda de List <Object>.

Farshid Zaker
fuente
11
Muy buen punto; muchos suponen que debido a que la clase C hereda de la clase P, esa Lista <C> también hereda de la Lista <P>. Como señaló, este no es el caso. La razón es que si pudiéramos pasar de List <String> a List <Object>, entonces podríamos poner Objetos en esa lista, violando así el contrato original de List <String> cuando intentamos recuperar un elemento.
Peter
2
+1. Buen punto también. Entonces, ¿por qué la gente usaría List<Object>?
Thang Pham
9
List <Object> se puede usar para almacenar una lista de objetos de diferentes clases.
Farshid Zaker
20

La notación List<?>significa "una lista de algo (pero no digo qué)". Dado que el código en testfunciona para cualquier tipo de objeto en la lista, esto funciona como un parámetro de método formal.

El uso de un parámetro de tipo (como en su punto 3) requiere que se declare el parámetro de tipo. La sintaxis de Java para eso es poner <T>delante de la función. Esto es exactamente análogo a declarar nombres de parámetros formales a un método antes de usar los nombres en el cuerpo del método.

En cuanto a List<Object>no aceptar a List<String>, eso tiene sentido porque a Stringno lo es Object; que es una subclase de Object. La solución es declarar public static void test(List<? extends Object> set) .... Pero entonces el extends Objectes redundante, porque cada clase se extiende directa o indirectamente Object.

Ted Hopp
fuente
¿Por qué la gente usaría List<Object>?
Thang Pham
10
Creo que "una lista de algo" es un mejor significado List<?>ya que la lista es de algún tipo específico pero desconocido. List<Object>realmente sería "una lista de cualquier cosa", ya que de hecho puede contener cualquier cosa.
ColinD
1
@ColinD: quise decir "cualquier cosa" en el sentido de "cualquier cosa". Pero estás en lo correcto; significa "una lista de algo, pero no voy a decirte qué".
Ted Hopp
@ColinD se refería a por qué repetiste sus palabras. sí, fue escrito con palabras un poco diferentes, pero el significado es el mismo ...
user25
14

La razón por la que no se puede echar List<String>a List<Object>es que permitiría a violar las restricciones del List<String>.

Piense en el siguiente escenario: si tengo un List<String>, se supone que solo contiene objetos de tipo String. (Que es una finalclase)

Si puedo convertir eso en a List<Object>, entonces eso me permite agregar Objecta esa lista, violando así el contrato original de List<String>.

Por lo tanto, en general, si la clase Chereda de la clase P, no se puede decir que GenericType<C>también hereda de GenericType<P>.

Nota: ya he comentado sobre esto en una respuesta anterior, pero quería ampliarlo.

Peter
fuente
tyvm, subo tanto tu comentario como tu respuesta, ya que es una muy buena explicación. Ahora, ¿dónde y por qué usaría la gente List<Object>?
Thang Pham
3
En general, no debe usarlo, List<Object>ya que de alguna manera derrota el propósito de los genéricos. Sin embargo, hay casos en los que el código antiguo puede Listaceptar diferentes tipos, por lo que es posible que desee actualizar el código para usar la parametrización de tipos solo para evitar advertencias del compilador para los tipos sin formato. (Pero la funcionalidad no ha cambiado)
Peter
5

Yo recomendaría leer los rompecabezas de Java. Explica bastante bien la herencia, los genéricos, las abstracciones y los comodines en las declaraciones. http://www.javapuzzlers.com/

if_zero_equals_one
fuente
5

Hablemos de ellos en el contexto de la historia de Java;

  1. List:

Lista significa que puede incluir cualquier objeto. La lista estaba en la versión anterior a Java 5.0; Lista de Java 5.0 introducida, por compatibilidad con versiones anteriores.

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

?significa Objeto desconocido, no cualquier Objeto; la ?introducción de comodines es para resolver el problema creado por Generic Type; ver comodines ; pero esto también causa otro problema:

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

Significa declaración genérica en la premisa de ningún tipo T o E en su proyecto Lib.

  1. List< Object> significa parametrización genérica.
phil
fuente
5

En su tercer punto, "T" no se puede resolver porque no se declara, generalmente cuando declara una clase genérica puede usar "T" como el nombre del parámetro de tipo enlazado , muchos ejemplos en línea, incluidos los tutoriales de Oracle, usan "T" como nombre del parámetro de tipo, por ejemplo, declara una clase como:

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

}

Estás diciendo que el FooHandler's operateOnFoométodo espera una variable de tipo "T" que se declara en la declaración de la clase, con esto en mente, luego puedes agregar otro método como

public void operateOnFoos(List<T> foos)

en todos los casos, T, E o U, todos los identificadores del parámetro de tipo, incluso puede tener más de un parámetro de tipo que utiliza la sintaxis

public class MyClass<Atype,AnotherType> {}

en su cuarto punto, aunque efectivamente Sting es un subtipo de Objeto, en las clases de genéricos no existe tal relación, List<String>no es un subtipo de List<Object>que son dos tipos diferentes desde el punto de vista del compilador, esto se explica mejor en esta entrada del blog

Harima555
fuente
5

Teoría

String[] puede ser lanzado a Object[]

pero

List<String>No se puede lanzar a List<Object>.

Práctica

Para las listas es más sutil que eso, porque en el momento de la compilación no se verifica el tipo de un parámetro de Lista pasado a un método. La definición del método también podría decir List<?>: desde el punto de vista del compilador es equivalente. Es por eso que el ejemplo # 2 del OP da errores de tiempo de ejecución y no errores de compilación.

Si maneja un List<Object>parámetro pasado a un método con cuidado para no forzar una verificación de tipo en ningún elemento de la lista, entonces puede definir su método usando List<Object>pero de hecho aceptar un List<String>parámetro del código de llamada.

R. Entonces, este código no dará errores de compilación o tiempo de ejecución y realmente funcionará (¿y quizás sorprendentemente?):

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á un error de tiempo de ejecución:

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á un error de tiempo de ejecución ( 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
}

En B, el parámetro setno se escribe Listen tiempo de compilación: el compilador lo ve como List<?>. Hay un error de tiempo de ejecución porque, en tiempo de ejecución, se setconvierte en el objeto real pasado main(), y eso es a List<String>. A List<String>no puede ser lanzado a List<Object>.

En C, el parámetro setrequiere un Object[]. No hay error de compilación ni error de tiempo de ejecución cuando se llama con un String[]objeto como parámetro. Eso es porque String[]echa a Object[]. Pero el objeto real recibido por test()sigue siendo un String[], no cambió. Entonces el paramsobjeto también se convierte en a String[]. Y el elemento 0 de a String[]no se puede asignar a a Long!

(Espero tener todo aquí, si mi razonamiento es incorrecto, estoy seguro de que la comunidad me lo dirá. ACTUALIZADO: He actualizado el código en el ejemplo A para que realmente se compile, sin dejar de mostrar el punto hecho).

Radfast
fuente
He intentado el ejemplo A lo que no funciona: List<Object> cannot be applied to List<String>. No puede pasar ArrayList<String>a un método que espera ArrayList<Object>.
analista
Gracias, bastante tarde en la fecha en que modifiqué el ejemplo A para que ahora funcione. El cambio principal fue definir argsList genéricamente en main ().
Radfast
4

El problema 2 está bien porque "System.out.println (set);" significa "System.out.println (set.toString ());" set es una instancia de List, por lo que el cumplidor llamará a 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: estos símbolos son los mismos, pero puede darles especificaciones diferentes. Por ejemplo:

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

Problema 4: la colección no permite la covarianza de parámetros de tipo. Pero la matriz permite la covarianza.

yoso
fuente
0

Tienes razón: String es un subconjunto de Object. Como String es más "preciso" que Object, debe convertirlo para usarlo como argumento para System.out.println ().

patapizza
fuente