¿Cómo se puede lanzar una Lista de supertipos a una Lista de subtipos?

Por ejemplo, digamos que tienes dos clases:

public class TestA {} public class TestB extends TestA{} 

Tengo un método que devuelve una List y me gustaría convertir todos los objetos en esa lista a TestB para que termine con una List .

Simplemente List a List casi funciona; pero no funciona porque no puedes convertir un tipo genérico de un parámetro en otro. Sin embargo, puede transmitir a través de un tipo de comodín intermedio y estará permitido (ya que puede convertir desde y hacia tipos comodín, solo con una advertencia no marcada):

 List variable = (List)(List) collectionOfListA; 

no es posible la emisión de generics, pero si define la lista de otra forma, es posible almacenar TestB en ella:

 List myList = new ArrayList(); 

Aún tiene que realizar comprobaciones de tipo cuando usa los objetos en la lista.

Realmente no puedes *:

Se toma un ejemplo de este tutorial de Java

Supongamos que hay dos tipos A y B tales que B extends A Entonces el siguiente código es correcto:

  B b = new B(); A a = b; 

El código anterior es válido porque B es una subclase de A Ahora, ¿qué ocurre con List y List ?

Resulta que List no es una subclase de List por lo tanto, no podemos escribir

  List b = new ArrayList<>(); List a = b; // error, List is not of type List 

Además, ni siquiera podemos escribir

  List b = new ArrayList<>(); List a = (List)b; // error, List is not of type List 

*: Para que sea posible la conversión, necesitamos un elemento primario común para List y List : List Por ejemplo. Lo siguiente es válido:

  List b = new ArrayList<>(); List t = (List)b; List a = (List)t; 

Sin embargo, recibirá una advertencia. Puede suprimirlo agregando @SuppressWarnings("unchecked") a su método.

Con Java 8, en realidad puedes

 List variable = collectionOfListA .stream() .map(e -> (TestB) e) .collect(Collectors.toList()); 

Creo que estás lanzando en la dirección equivocada … si el método devuelve una lista de objetos TestA , entonces no es seguro enviarlos a TestB .

Básicamente le está pidiendo al comstackdor que le permita realizar operaciones TestB en un tipo TestA que no las admite.

Dado que esta es una pregunta ampliamente referenciada, y las respuestas actuales explican principalmente por qué no funciona (o proponen soluciones peligrosas y peligrosas que nunca me gustaría ver en el código de producción), creo que es apropiado agregar otra respuesta, mostrando las trampas, y una posible solución.


La razón por la que esto no funciona en general ya se ha señalado en otras respuestas: si la conversión es realmente válida o no depende de los tipos de objetos que están contenidos en la lista original. Cuando hay objetos en la lista cuyo tipo no es del tipo TestB , sino de una subclase diferente de TestA , entonces el molde no es válido.


Por supuesto, los moldes pueden ser válidos. A veces tiene información sobre los tipos que no están disponibles para el comstackdor. En estos casos, es posible emitir las listas, pero en general, no se recomienda :

Uno podría …

  • … lanzar la lista completa o
  • … lanzar todos los elementos de la lista

Las implicaciones del primer enfoque (que corresponde a la respuesta actualmente aceptada) son sutiles. Puede parecer que funciona correctamente a primera vista. Pero si hay tipos incorrectos en la lista de entrada, se lanzará una ClassCastException , tal vez en una ubicación completamente diferente en el código, y puede ser difícil depurar esto y descubrir dónde se deslizó el elemento equivocado en la lista. El peor problema es que alguien podría incluso agregar los elementos no válidos después de que la lista haya sido convertida, haciendo que la depuración sea aún más difícil.

El problema de depurar estas falsas ClassCastExceptions se puede aliviar con la familia de métodos Collections#checkedCollection .


Filtrar la lista según el tipo

Una forma más segura de conversión de un List a un List es filtrar la lista y crear una nueva lista que contenga solo elementos que tengan cierto tipo. Hay algunos grados de libertad para la implementación de dicho método (por ejemplo, en relación con el tratamiento de entradas null ), pero una posible implementación puede verse así:

 /** * Filter the given list, and create a new list that only contains * the elements that are (subtypes) of the class c * * @param listA The input list * @param c The class to filter for * @return The filtered list */ private static  List filter(List listA, Class c) { List listB = new ArrayList(); for (Object a : listA) { if (c.isInstance(a)) { listB.add(c.cast(a)); } } return listB; } 

Este método se puede usar para filtrar listas arbitrarias (no solo con una relación Subtipo-Supertipo dada con respecto a los parámetros de tipo), como en este ejemplo:

 // A list of type "List" that actually // contains Integer, Double and Float values List mixedNumbers = new ArrayList(Arrays.asList(12, 3.4, 5.6f, 78)); // Filter the list, and create a list that contains // only the Integer values: List integers = filter(mixedNumbers, Integer.class); System.out.println(integers); // Prints [12, 78] 

La mejor manera segura es implementar un AbstractList y moldear elementos en la implementación. ListUtil clase de ayuda de ListUtil :

 public class ListUtil { public static  List convert(final List list) { return new AbstractList() { @Override public TCastTo get(int i) { return list.get(i); } @Override public int size() { return list.size(); } }; } public static  List cast(final List list) { return new AbstractList() { @Override public TCastTo get(int i) { return (TCastTo)list.get(i); } @Override public int size() { return list.size(); } }; } } 

Puede usar el método de convert para lanzar objetos a ciegas en la lista y convert método para la conversión segura. Ejemplo:

 void test(List listA, List listB) { List castedB = ListUtil.cast(listA); // all items are blindly casted List convertedB = ListUtil.convert(listA); // wrong cause TestA does not extend TestB List convertedA = ListUtil.convert(listB); // OK all items are safely casted } 

No se puede convertir List en List como Steve Kuo menciona PERO se puede volcar el contenido de List en List . Pruebe lo siguiente:

 List result = new List(); List data = new List(); result.addAll(data); 

No he probado este código, por lo que probablemente haya errores, pero la idea es que se repita a través del objeto de datos que agrega los elementos (objetos TestB) en la Lista. Espero que eso funcione para usted.

Cuando echas una referencia a un objeto, solo estás emitiendo el tipo de referencia, no el tipo del objeto. el casting no cambiará el tipo real del objeto.

Java no tiene reglas implícitas para convertir tipos de objetos. (A diferencia de primitivos)

En su lugar, debe proporcionar cómo convertir un tipo a otro y llamarlo manualmente.

 public class TestA {} public class TestB extends TestA{ TestB(TestA testA) { // build a TestB from a TestA } } List result = .... List data = new List(); for(TestA testA : result) { data.add(new TestB(testA)); } 

Esto es más detallado que en un lenguaje con soporte directo, pero funciona y no debería necesitar hacer esto muy a menudo.

La única forma que conozco es copiando:

 List list = new ArrayList ( Arrays.asList ( testAList.toArray(new TestB[0]) ) ); 

Esto es posible debido al borrado de tipo. Encontrarás eso

 List x = new ArrayList(); List y = new ArrayList(); x.getClass().equals(y.getClass()); // true 

Internamente, ambas listas son del tipo List . Por esa razón, no puedes lanzar una contra la otra, no hay nada que transmitir.

si tiene un objeto de la clase TestA , no puede convertirlo en TestB . cada TestB es un TestA , pero no a la inversa.

en el siguiente código:

 TestA a = new TestA(); TestB b = (TestB) a; 

la segunda línea arrojaría una ClassCastException .

solo puede lanzar una referencia TestA si el objeto en sí es TestB . por ejemplo:

 TestA a = new TestB(); TestB b = (TestB) a; 

por lo tanto, no siempre puede TestA una lista de TestA a una lista de TestB .

El problema es que su método NO devuelve una lista de TestA si contiene un TestB, entonces, ¿qué sucede si se escribió correctamente? Entonces este elenco:

 class TestA{}; class TestB extends TestA{}; List listA; List listB = (List) listA; 

funciona tan bien como podrías desear (Eclipse te advierte de un reparto sin marcar, que es exactamente lo que estás haciendo, así que meh). Entonces, ¿puedes usar esto para resolver tu problema? En realidad puedes hacerlo por esto:

 List badlist = null; // Actually contains TestBs, as specified List talist = badlist; // Umm, works List tblist = (List)talist; // TADA! 

Exactamente lo que pediste, ¿verdad? o para ser realmente exacto:

 List tblist = (List)(List) badlist; 

parece comstackr muy bien para mí.

 class MyClass { String field; MyClass(String field) { this.field = field; } } @Test public void testTypeCast() { List objectList = Arrays.asList(new MyClass("1"), new MyClass("2")); Class clazz = MyClass.class; List myClassList = objectList.stream() .map(clazz::cast) .collect(Collectors.toList()); assertEquals(objectList.size(), myClassList.size()); assertEquals(objectList, myClassList); } 

Esta prueba muestra cómo lanzar List a List . Pero debe prestar atención a ese objectList debe contener instancias del mismo tipo que MyClass . Y este ejemplo se puede considerar cuando se usa List . Para este propósito, obtén el campo Class clazz en el constructor y Class clazz en lugar de MyClass.class .

Esto debería funcionar

 List testAList = new ArrayList<>(); List testBList = new ArrayList<>() testAList.addAll(new ArrayList<>(testBList)); 

Puede usar el método selectInstances en Eclipse Collections . Sin embargo, esto implicará crear una nueva colección, por lo que no será tan eficiente como la solución aceptada que utiliza el casting.

 List parent = Arrays.asList("1","2","3", new StringBuffer("4")); List strings = Lists.adapt(parent).selectInstancesOf(String.class); Assert.assertEquals(Arrays.asList("1","2","3"), strings); 

selectInstances StringBuffer en el ejemplo para mostrar que selectInstances no solo selectInstances el tipo, sino que también filtra si la colección contiene tipos mixtos.

Nota: soy un committer para las colecciones de Eclipse.

Intereting Posts