java - que - ¿Cómo convertir una lista de supertipos en una lista de subtipos?
pasar de una lista a otra lista en java netbeans (16)
Con Java 8, realmente puedes
List<TestB> variable = collectionOfListA
.stream()
.map(e -> (TestB) e)
.collect(Collectors.toList());
Por ejemplo, digamos que tienes dos clases:
public class TestA {}
public class TestB extends TestA{}
Tengo un método que devuelve una List<TestA>
y me gustaría convertir todos los objetos en esa lista a TestB
para que termine con una List<TestB>
.
Cuando emite una referencia de objeto, simplemente está emitiendo el tipo de referencia, no el tipo de objeto. casting no cambiará el tipo real del objeto.
Java no tiene reglas implícitas para convertir tipos de objetos. (A diferencia de los primitivos)
En su lugar, debe proporcionar cómo convertir un tipo a otro y llamarlo de forma manual.
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 idioma con soporte directo, pero funciona y no debería tener que hacerlo muy a menudo.
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 se podría esperar (Eclipse le advierte de un elenco no seleccionado que es exactamente lo que está haciendo, así que meh). Entonces, ¿puedes usar esto para resolver tu problema? En realidad se puede 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 exactos:
List<TestB> tblist = (List<TestB>)(List<? extends TestA>) badlist;
Parece compilar bien para mí.
Esto debería funcionar
List<TestA> testAList = new ArrayList<>();
List<TestB> testBList = new ArrayList<>()
testAList.addAll(new ArrayList<>(testBList));
Esto es posible debido al borrado de tipo. Encontraras eso
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 a otro, no hay nada que lanzar.
La única forma que conozco es copiando:
List<TestB> list = new ArrayList<TestB> (
Arrays.asList (
testAList.toArray(new TestB[0])
)
);
La mejor manera segura es implementar un AbstractList
y lanzar elementos en la implementación. ListUtil
clase de ayuda ListUtil
:
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 método de convert
para lanzar objetos a ciegas en la lista y convert
método para realizar una conversión segura. 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
}
No puede convertir la List<TestB>
en la List<TestA>
como Steve Kuo menciona, PERO puede volcar el contenido de la List<TestA>
en la 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, así que probablemente haya errores, pero la idea es que se debe recorrer el objeto de datos y agregar los elementos (objetos TestB) a la Lista. Espero que eso funcione para usted.
Puede utilizar el método selectInstances
en las colecciones de Eclipse . Sin embargo, esto implicará la creación de una nueva colección, por lo que no será tan eficiente como la solución aceptada que utiliza el casting.
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);
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 comendador de Eclipse Collections.
Realmente no puedes *:
Ejemplo se toma 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é sucede con la List<A>
y la List<B>
?
Resulta que la List<B>
no es una subclase de la 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 tanto para la List<A>
como para la 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.
Si tiene un objeto de la clase TestA
, no puede convertirlo en TestB
. Cada 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 lanzaría una ClassCastException
.
solo puede emitir una referencia de TestA
si el objeto en sí es TestB
. por ejemplo:
TestA a = new TestB();
TestB b = (TestB) a;
por lo tanto, no siempre puede emitir una lista de TestA
a una lista de TestB
.
Simplemente lanzar a la List<TestB>
casi funciona; pero no funciona porque no puede convertir un tipo genérico de un parámetro a otro. Sin embargo, puede pasar a través de un tipo de comodín intermedio y se permitirá (ya que puede enviar desde y hacia tipos de comodines, solo con una advertencia sin marcar):
List<TestB> variable = (List<TestB>)(List<?>) collectionOfListA;
Sin embargo, creo que estás lanzando en la dirección equivocada ... si el método devuelve una lista de objetos TestA
, entonces no es seguro TestB
a TestB
.
Básicamente, le está pidiendo al compilador que le permita realizar operaciones de TestB
en un tipo TestA
que no los admite.
no es posible realizar el lanzamiento de genéricos, pero si define la lista de otra manera, es posible almacenar TestB en ella:
List<? extends TestA> myList = new ArrayList<TestA>();
Aún tiene que realizar una comprobación de tipos cuando está utilizando los objetos en la lista.
Como esta es una pregunta ampliamente referenciada, y las respuestas actuales explican principalmente por qué no funciona (o proponen soluciones intrépidas 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 figuran en la lista original. Cuando hay objetos en la lista cuyo tipo no es del tipo TestB
, sino de una subclase diferente de TestA
, la TestA
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 ...
- ... emitir la lista completa o
- ... emitir 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, entonces 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 introdujo el elemento incorrecto 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, lo que dificulta aún más la depuración.
El problema de depurar estas ClassCastExceptions
falsas de ClassCastExceptions
se puede aliviar con la familia de métodos Collections#checkedCollection
.
Filtrado de la lista en función del tipo.
Una forma más segura de convertir una List<Supertype>
en 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 null
entradas null
), 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 usar para filtrar listas arbitrarias (no solo con una relación dada de subtipo-supertipo 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]
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 convertir la List<Object>
a la List<MyClass>
. Pero debe prestar atención a esa lista de objectList
que debe contener instancias del mismo tipo que MyClass
. Y este ejemplo puede considerarse cuando se utiliza la List<T>
. Para este propósito, obtenga el campo Class<T> clazz
en el constructor y utilícelo en lugar de MyClass.class
.