La forma más eficiente de enviar List <SubClass> a List <BaseClass>

134

Tengo un List<SubClass>que quiero tratar como a List<BaseClass>. Parece que no debería ser un problema ya que lanzar un SubClassa a BaseClasses muy fácil, pero mi compilador se queja de que el reparto es imposible.

Entonces, ¿cuál es la mejor manera de obtener una referencia a los mismos objetos que a List<BaseClass>?

En este momento solo estoy haciendo una nueva lista y copiando la lista anterior:

List<BaseClass> convertedList = new ArrayList<BaseClass>(listOfSubClass)

Pero, según tengo entendido, eso tiene que crear una lista completamente nueva. ¡Me gustaría una referencia a la lista original, si es posible!

Riley Lark
fuente
3
la respuesta que tienes aquí: stackoverflow.com/questions/662508/…
lukastymo

Respuestas:

175

La sintaxis para este tipo de asignación utiliza un comodín:

List<SubClass> subs = ...;
List<? extends BaseClass> bases = subs;

Es importante darse cuenta de que a noList<SubClass> es intercambiable con a . El código que retiene una referencia al esperará que cada elemento de la lista sea a . Si otra parte del código se refiere a la lista como a , el compilador no se quejará cuando se inserte un o . Pero esto causará un primer fragmento de código, que supone que todo en la lista es un .List<BaseClass>List<SubClass>SubClassList<BaseClass>BaseClassAnotherSubClassClassCastExceptionSubClass

Las colecciones genéricas no se comportan igual que las matrices en Java. Las matrices son covariantes; es decir, está permitido hacer esto:

SubClass[] subs = ...;
BaseClass[] bases = subs;

Esto está permitido, porque la matriz "conoce" el tipo de sus elementos. Si alguien intenta almacenar algo que no es una instancia de SubClassla matriz (a través de la basesreferencia), se generará una excepción de tiempo de ejecución.

Las colecciones genéricas no "conocen" su tipo de componente; esta información se "borra" en el momento de la compilación. Por lo tanto, no pueden generar una excepción de tiempo de ejecución cuando se produce una tienda no válida. En su lugar, se ClassCastExceptiongenerará un punto de código distante y difícil de asociar cuando se lea un valor de la colección. Si presta atención a las advertencias del compilador sobre la seguridad de tipos, evitará estos errores de tipo en tiempo de ejecución.

erickson
fuente
Debe tener en cuenta que con las matrices, en lugar de ClassCastException al recuperar un objeto que no es del tipo Subclase (o derivado), obtendrá una ArrayStoreException al insertar.
Axel
Gracias especialmente por la explicación de por qué las dos listas no pueden considerarse iguales.
Riley Lark
El sistema de tipos Java finge que las matrices son covariantes, pero no son realmente sustituibles, como lo demuestra la excepción ArrayStoreException.
Paŭlo Ebermann
38

Erickson ya explicó por qué no puede hacer esto, pero aquí hay algunas soluciones:

Si solo desea eliminar elementos de su lista base, en principio su método de recepción debe declararse como tomando un List<? extends BaseClass>.

Pero si no lo es y no puede cambiarlo, puede ajustar la lista Collections.unmodifiableList(...), lo que permite devolver una Lista de un supertipo del parámetro del argumento. (Evita el problema de la seguridad de tipos al lanzar UnsupportedOperationException en los intentos de inserción).

Paŭlo Ebermann
fuente
¡Excelente! No lo sabía.
keuleJ
¡Muchas gracias!
Woland
14

Como explicó @erickson, si realmente desea una referencia a la lista original, asegúrese de que ningún código inserte nada en esa lista si alguna vez quiere usarla nuevamente bajo su declaración original. La forma más sencilla de obtenerlo es lanzarlo a una lista simple y poco genérica:

List<BaseClass> baseList = (List)new ArrayList<SubClass>();

No recomendaría esto si no sabe lo que le sucede a la Lista y le sugeriría que cambie cualquier código que necesite la Lista para aceptar la Lista que tiene.

hd42
fuente
3

A continuación se muestra un fragmento útil que funciona. Construye una nueva lista de matrices, pero la creación de objetos JVM por encima es insignificante.

Vi que otras respuestas son innecesariamente complicadas.

List<BaseClass> baselist = new ArrayList<>(sublist);
sigirisetti
fuente
1
Gracias por este fragmento de código, que puede proporcionar ayuda inmediata. Una explicación adecuada mejoraría enormemente su valor educativo al mostrar por qué esta es una buena solución al problema, y ​​la haría más útil para futuros lectores con preguntas similares, pero no idénticas. Por favor, editar su respuesta para agregar explicación y dar una indicación de lo que se aplican limitaciones y supuestos. En particular, ¿cómo difiere esto del código en la pregunta que hace una nueva lista?
Toby Speight
La sobrecarga no es insignificante, ya que también tiene que copiar (superficialmente) cada referencia. Como tal, se escala con el tamaño de la lista, por lo que es una operación O (n).
john16384
2

Perdí la respuesta en la que acabas de emitir la lista original, usando un doble elenco. Entonces aquí está para completar:

List<BaseClass> baseList = (List<BaseClass>)(List<?>)subList;

No se copia nada y la operación es rápida. Sin embargo, está engañando al compilador aquí, por lo que debe asegurarse absolutamente de no modificar la lista de tal manera que subListcomience a contener elementos de un subtipo diferente. Cuando se trata de listas inmutables, esto generalmente no es un problema.

john16384
fuente
1

List<BaseClass> convertedList = Collections.checkedList(listOfSubClass, BaseClass.class)

jtahlborn
fuente
55
Esto no debería funcionar, ya que para estos métodos todos los argumentos y el resultado toman el mismo parámetro de tipo.
Paŭlo Ebermann
extraño, tienes razón. Por alguna razón, tenía la impresión de que este método era útil para convertir una colección genérica. aún podría usarse de esta manera y proporcionará una colección "segura" si desea utilizar advertencias de supresión.
jtahlborn
1

Lo que está tratando de hacer es muy útil y creo que necesito hacerlo muy a menudo en el código que escribo. Un ejemplo de caso de uso:

Digamos que tenemos una interfaz Fooy tenemos un zorkingpaquete ZorkingFooManagerque crea y administra instancias de paquetes privados ZorkingFoo implements Foo. (Un escenario muy común).

Entonces, ZorkingFooManagernecesita contener a private Collection<ZorkingFoo> zorkingFoospero necesita exponer a public Collection<Foo> getAllFoos().

La mayoría de los programadores de Java no pensarían dos veces antes de implementar getAllFoos()como asignar un nuevo ArrayList<Foo>, rellenarlo con todos los elementos zorkingFoosy devolverlo. Me gusta entretener la idea de que aproximadamente el 30% de todos los ciclos de reloj consumidos por el código Java que se ejecuta en millones de máquinas en todo el planeta no hace más que crear copias inútiles de ArrayLists que son microsegundos recolectados de basura después de su creación.

La solución a este problema es, por supuesto, reducir la colección. Aquí está la mejor manera de hacerlo:

static <T,U extends T> List<T> downCastList( List<U> list )
{
    return castList( list );
}

Lo que nos lleva a la castList()función:

static <T,E> List<T> castList( List<E> list )
{
    @SuppressWarnings( "unchecked" )
    List<T> result = (List<T>)list;
    return result;
}

La resultvariable intermedia es necesaria debido a una perversión del lenguaje java:

  • return (List<T>)list;produce una excepción de "lanzamiento no verificado"; Hasta aquí todo bien; pero entonces:

  • @SuppressWarnings( "unchecked" ) return (List<T>)list; es un uso ilegal de la anotación suprimir advertencias.

Entonces, aunque no es kosher usarlo @SuppressWarningsen una returndeclaración, aparentemente está bien usarlo en una tarea, por lo que la variable adicional "resultado" resuelve este problema. (De todos modos, debe ser optimizado por el compilador o por el JIT).

Mike Nakis
fuente
0

Algo como esto también debería funcionar:

public static <T> List<T> convertListWithExtendableClasses(
    final List< ? extends T> originalList,
    final Class<T> clazz )
{
    final List<T> newList = new ArrayList<>();
    for ( final T item : originalList )
    {
        newList.add( item );
    }// for
    return newList;
}

Realmente no sé por qué se necesita clazz en Eclipse.

olivervbk
fuente
0

¿Qué hay de lanzar todos los elementos? Creará una nueva lista, pero hará referencia a los objetos originales de la lista anterior.

List<BaseClass> convertedList = listOfSubClass.map(x -> (BaseClass)x).collect(Collectors.toList());
drordk
fuente
Este es el código completo que funciona para transmitir la lista de subclases a la superclase.
kanaparthikiran
0

Este es el código de trabajo completo que utiliza Generics, para enviar la lista de subclases a superclase.

Método de llamada que pasa el tipo de subclase

List<SubClass> subClassParam = new ArrayList<>();    
getElementDefinitionStatuses(subClassParam);

Método Callee que acepta cualquier subtipo de la clase base

private static List<String> getElementDefinitionStatuses(List<? extends 
    BaseClass> baseClassVariableName) {
     return allElementStatuses;
    }
}
kanaparthikiran
fuente