Por ejemplo, supongamos que tiene dos clases:
public class TestA {}
public class TestB extends TestA{}
Tengo un método que devuelve ay List<TestA>
me gustaría convertir todos los objetos en esa lista para TestB
que termine con a List<TestB>
.
Por ejemplo, supongamos que tiene dos clases:
public class TestA {}
public class TestB extends TestA{}
Tengo un método que devuelve ay List<TestA>
me gustaría convertir todos los objetos en esa lista para TestB
que termine con a List<TestB>
.
Respuestas:
Simplemente echando a List<TestB>
casi obras; pero no funciona porque no puede emitir un tipo genérico de un parámetro a otro. Sin embargo, puede emitir a través de un tipo de comodín intermedio y se permitirá (ya que puede emitir desde y hacia tipos de comodín, solo con una advertencia sin marcar):
List<TestB> variable = (List<TestB>)(List<?>) collectionOfListA;
La conversión de genéricos no es posible, pero si define la lista de otra manera, es posible almacenar TestB en ella:
List<? extends TestA> myList = new ArrayList<TestA>();
Todavía tiene que hacer una verificación de tipo cuando usa los objetos de la lista.
Realmente no puedes *:
Se toma un ejemplo de este tutorial de Java
Suponga que hay dos tipos A
y B
tal 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é pasa con List<A>
y List<B>
?
Resulta que List<B>
no es una subclase de, List<A>
por lo tanto, no podemos escribir
List<B> b = new ArrayList<>();
List<A> a = b; // error, List<B> is not of type List<A>
Además, ni siquiera podemos escribir
List<B> b = new ArrayList<>();
List<A> a = (List<A>)b; // error, List<B> is not of type List<A>
*: Para hacer posible el casting necesitamos un padre común para ambos List<A>
y List<B>
: List<?>
por ejemplo. Lo siguiente es válido:
List<B> b = new ArrayList<>();
List<?> t = (List<B>)b;
List<A> a = (List<A>)t;
Sin embargo, recibirá una advertencia. Puede suprimirlo agregando @SuppressWarnings("unchecked")
a su método.
Con Java 8, en realidad puedes
List<TestB> variable = collectionOfListA
.stream()
.map(e -> (TestB) e)
.collect(Collectors.toList());
new List<TestB>
, un bucle y un elenco?
Dado que esta es una pregunta ampliamente referenciada, y las respuestas actuales explican principalmente por qué no funciona (o proponen soluciones extravagantes 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 cual 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 de tipo TestB
, sino de una subclase diferente de TestA
, entonces la conversión no es válida.
Por supuesto, los moldes pueden ser válidos. A veces tiene información sobre los tipos que no está disponible para el compilador. En estos casos, es posible emitir las listas, pero en general, no se recomienda :
Uno podría ...
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 ClassCastException
arrojará un , 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 incorrecto en la lista. El peor problema es que alguien podría incluso agregar los elementos inválidos después de que la lista ha sido lanzada, lo que dificulta aún más la depuración.
El problema de depurar estos espurios ClassCastExceptions
puede aliviarse con la Collections#checkedCollection
familia de métodos.
Una forma más segura de convertir de una List<Supertype>
a una List<Subtype>
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, con respecto al tratamiento de las null
entradas), pero una posible implementación puede tener este aspecto:
/**
* 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 <T> List<T> filter(List<?> listA, Class<T> c)
{
List<T> listB = new ArrayList<T>();
for (Object a : listA)
{
if (c.isInstance(a))
{
listB.add(c.cast(a));
}
}
return listB;
}
Este método se puede utilizar 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<Number>" that actually
// contains Integer, Double and Float values
List<Number> mixedNumbers =
new ArrayList<Number>(Arrays.asList(12, 3.4, 5.6f, 78));
// Filter the list, and create a list that contains
// only the Integer values:
List<Integer> integers = filter(mixedNumbers, Integer.class);
System.out.println(integers); // Prints [12, 78]
No se puede echar List<TestB>
a List<TestA>
como lo menciona Steve Kuo pero usted puede volcar el contenido de List<TestA>
dentro List<TestB>
. Intenta lo siguiente:
List<TestA> result = new List<TestA>();
List<TestB> data = new List<TestB>();
result.addAll(data);
No he probado este código, por lo que probablemente haya errores, pero la idea es que debería iterar a través del objeto de datos agregando los elementos (objetos TestB) a la Lista. Espero que eso funcione para usted.
La mejor manera segura es implementar AbstractList
y lanzar elementos en la implementación. He creado ListUtil
clase de ayuda:
public class ListUtil
{
public static <TCastTo, TCastFrom extends TCastTo> List<TCastTo> convert(final List<TCastFrom> list)
{
return new AbstractList<TCastTo>() {
@Override
public TCastTo get(int i)
{
return list.get(i);
}
@Override
public int size()
{
return list.size();
}
};
}
public static <TCastTo, TCastFrom> List<TCastTo> cast(final List<TCastFrom> list)
{
return new AbstractList<TCastTo>() {
@Override
public TCastTo get(int i)
{
return (TCastTo)list.get(i);
}
@Override
public int size()
{
return list.size();
}
};
}
}
Puede usar el cast
método para lanzar objetos a ciegas en la lista y el convert
método para el lanzamiento seguro. Ejemplo:
void test(List<TestA> listA, List<TestB> listB)
{
List<TestB> castedB = ListUtil.cast(listA); // all items are blindly casted
List<TestB> convertedB = ListUtil.<TestB, TestA>convert(listA); // wrong cause TestA does not extend TestB
List<TestA> convertedA = ListUtil.<TestA, TestB>convert(listB); // OK all items are safely casted
}
La única forma que sé es copiando:
List<TestB> list = new ArrayList<TestB> (
Arrays.asList (
testAList.toArray(new TestB[0])
)
);
Cuando emite una referencia de objeto, solo está emitiendo el tipo de referencia, no el tipo de objeto. el lanzamiento no cambiará el tipo real del objeto.
Java no tiene reglas implícitas para convertir tipos de objetos. (A diferencia de las primitivas)
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<TestA> result = ....
List<TestB> data = new List<TestB>();
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 con mucha frecuencia.
Si tiene un objeto de la clase TestA
, no puede lanzarlo TestB
. todo TestB
es un TestA
, pero no al revés.
en el siguiente código:
TestA a = new TestA();
TestB b = (TestB) a;
la segunda línea arrojaría un ClassCastException
.
solo puede emitir una TestA
referencia si el objeto en sí lo es TestB
. por ejemplo:
TestA a = new TestB();
TestB b = (TestB) a;
por lo tanto, no siempre puede emitir una lista TestA
a una lista de TestB
.
Puede usar el selectInstances
método 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 la conversión.
List<CharSequence> parent =
Arrays.asList("1","2","3", new StringBuffer("4"));
List<String> strings =
Lists.adapt(parent).selectInstancesOf(String.class);
Assert.assertEquals(Arrays.asList("1","2","3"), strings);
Incluí StringBuffer
en el ejemplo para mostrar que selectInstances
no solo rechaza el tipo, sino que también filtra si la colección contiene tipos mixtos.
Nota: Soy un committer para Eclipse Collections.
Esto es posible debido al tipo de borrado. Encontrarás que
List<TestA> x = new ArrayList<TestA>();
List<TestB> y = new ArrayList<TestB>();
x.getClass().equals(y.getClass()); // true
Internamente ambas listas son de tipo List<Object>
. Por esa razón no puedes lanzar uno al otro, no hay nada que lanzar.
Integer j = 42; Integer i = (Integer) j;
funciona bien, ambos son enteros, ambos tienen la misma clase, por lo que "no hay nada que emitir".
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<? extends TestA> listA;
List<TestB> listB = (List<TestB>) listA;
funciona tan bien como podrías esperar (Eclipse te advierte de un elenco sin control que es exactamente lo que estás haciendo, así que meh). Entonces, ¿puedes usar esto para resolver tu problema? En realidad puedes debido a esto:
List<TestA> badlist = null; // Actually contains TestBs, as specified
List<? extends TestA> talist = badlist; // Umm, works
List<TextB> tblist = (List<TestB>)talist; // TADA!
Exactamente lo que pediste, ¿verdad? o para ser realmente exacto:
List<TestB> tblist = (List<TestB>)(List<? extends TestA>) badlist;
Parece compilar bien para mí.
class MyClass {
String field;
MyClass(String field) {
this.field = field;
}
}
@Test
public void testTypeCast() {
List<Object> objectList = Arrays.asList(new MyClass("1"), new MyClass("2"));
Class<MyClass> clazz = MyClass.class;
List<MyClass> myClassList = objectList.stream()
.map(clazz::cast)
.collect(Collectors.toList());
assertEquals(objectList.size(), myClassList.size());
assertEquals(objectList, myClassList);
}
Esta prueba muestra cómo lanzar List<Object>
a List<MyClass>
. Pero debe prestar atención a que objectList
debe contener instancias del mismo tipo que MyClass
. Y este ejemplo puede considerarse cuando List<T>
se usa. Para este propósito, obtenga el campo Class<T> clazz
en el constructor y úselo en lugar de MyClass.class
.
Esto debería funcionar
List<TestA> testAList = new ArrayList<>();
List<TestB> testBList = new ArrayList<>()
testAList.addAll(new ArrayList<>(testBList));
Es bastante extraño que la transmisión manual de una lista todavía no sea proporcionada por alguna caja de herramientas que implemente algo como:
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T extends E, E> List<T> cast(List<E> list) {
return (List) list;
}
Por supuesto, esto no verificará los elementos uno por uno, pero eso es precisamente lo que queremos evitar aquí, si sabemos bien que nuestra implementación solo proporciona el subtipo.