如何将超类型列表转换为子类型列表?

bxgwgixi  于 2021-06-29  发布在  Java
关注(0)|答案(17)|浏览(313)

例如,假设您有两个类:

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

我有一个返回 List<TestA> 我想将列表中的所有对象 TestB 所以我最终得到了一个 List<TestB> .

lp0sw83n

lp0sw83n1#

非常奇怪的是,一些工具箱仍然没有提供手动转换列表的功能,例如:

@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T extends E, E> List<T> cast(List<E> list) {
    return (List) list;
}

当然,这不会逐个检查项,但这正是我们要避免的,如果我们知道我们的实现只提供子类型的话。

pengsaosao

pengsaosao2#

我觉得你选错了方向。。。如果方法返回 TestA 物体,那么把它们扔到 TestB .
基本上,您要求编译器允许您执行 TestB 对类型的操作 TestA 这不支持他们。

11dmarpk

11dmarpk3#

这是可能的,因为类型擦除。你会发现的

List<TestA> x = new ArrayList<TestA>();
List<TestB> y = new ArrayList<TestB>();
x.getClass().equals(y.getClass()); // true

在内部,两个列表都是 List<Object> . 因为这个原因,你不能把一个投给另一个-没有什么可以投的。

daupos2t

daupos2t4#

如果你有一个类的对象 TestA ,你不能把它投给 TestB . 每 TestB 是一个 TestA ,但不是相反。
在以下代码中:

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

第二行会抛出一个 ClassCastException .
你只能投一个 TestA 如果对象本身是 TestB . 例如:

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

所以,你不一定要列出 TestA 一份 TestB .

rseugnpd

rseugnpd5#

你真的不能:
示例取自本java教程
假设有两种类型 A 以及 B 以至于 B extends A . 则以下代码正确:

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

前面的代码有效,因为 B 是的一个子类 A . 现在,你怎么办 List<A> 以及 List<B> ?
事实证明 List<B> 不是的子类 List<A> 因此我们不能写作

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

而且,我们甚至不会写字

List<B> b = new ArrayList<>();
    List<A> a = (List<A>)b; // error, List<B> is not of type List<A>
  • :为了使铸造成为可能,我们需要一个共同的父母双方 List<A> 以及 List<B> : List<?> 例如。以下内容有效:
List<B> b = new ArrayList<>();
    List<?> t = (List<B>)b;
    List<A> a = (List<A>)t;

然而,你会得到一个警告。可以通过添加 @SuppressWarnings("unchecked") 你的方法。

l0oc07j2

l0oc07j26#

这应该管用

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

vuktfyat7#

转换泛型是不可能的,但是如果您以另一种方式定义列表,则可以在其中存储testb:

List<? extends TestA> myList = new ArrayList<TestA>();

在使用列表中的对象时,仍然需要进行类型检查。

jrcvhitl

jrcvhitl8#

简单地铸造 List<TestB> 几乎可以工作;但它不起作用,因为不能将一个参数的泛型类型强制转换为另一个参数。但是,您可以强制转换中间通配符类型,这是允许的(因为您可以在通配符类型之间进行强制转换,只需要一个未选中的警告):

List<TestB> variable = (List<TestB>)(List<?>) collectionOfListA;
gajydyqb

gajydyqb9#

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);
}

这个测试展示了如何铸造 List<Object>List<MyClass> . 但你要注意这一点 objectList 必须包含与相同类型的示例 MyClass . 这个例子可以在 List<T> 已使用。为此,获取字段 Class<T> clazz 在构造函数中,并使用它而不是 MyClass.class .

wlwcrazw

wlwcrazw10#

最好的安全方法是实施 AbstractList 并在实现中转换项。我创造了 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();
            }
        };
    }
}

你可以用 cast 一种在列表中盲目投射对象的方法 convert 安全铸造方法。例子:

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
}
jdg4fx2g

jdg4fx2g11#

你不能施法 List<TestB>List<TestA> 正如steve kuo提到的,但是你可以把 List<TestA> 进入 List<TestB> . 请尝试以下操作:

List<TestA> result = new List<TestA>();
List<TestB> data = new List<TestB>();
result.addAll(data);

我没有尝试过这段代码,所以可能会有错误,但我的想法是它应该遍历数据对象,将元素(testb对象)添加到列表中。我希望这对你有用。

gstyhher

gstyhher12#

当您强制转换对象引用时,您只是强制转换引用的类型,而不是对象的类型。强制转换不会更改对象的实际类型。
java没有用于转换对象类型的隐式规则(与基本体不同)
相反,您需要提供如何将一种类型转换为另一种类型并手动调用它。

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));
}

这比直接支持的语言更冗长,但它可以工作,您不需要经常这样做。

zpjtge22

zpjtge2213#

我知道的唯一方法就是复制:

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

1bqhqjot14#

使用Java8,您实际上可以

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

mzmfm0qo15#

由于这是一个被广泛引用的问题,并且当前的答案主要解释了为什么它不起作用(或者提出了我永远不希望在产品代码中看到的骇人的、危险的解决方案),所以我认为应该添加另一个答案,显示陷阱和可能的解决方案。
在其他答案中已经指出了这种转换通常不起作用的原因:转换是否真正有效取决于原始列表中包含的对象的类型。当列表中的对象的类型不是 TestB ,但属于 TestA ,则强制转换无效。
当然,演员阵容可能是有效的。您有时会得到编译器不可用的类型信息。在这些情况下,可以强制转换列表,但通常不建议:
一个人可以。。。
... 投下整个名单或
... 强制转换列表的所有元素
第一种方法(对应于目前公认的答案)的含义是微妙的。乍一看,它似乎工作正常。但是如果输入列表中有错误的类型,那么 ClassCastException 将被抛出,可能在代码中的一个完全不同的位置,并且可能很难对此进行调试并找出错误元素滑入列表的位置。最糟糕的问题是,有人甚至可能在列表被强制转换后添加无效元素,这使得调试更加困难。
调试这些伪代码的问题 ClassCastExceptions 可以缓解 Collections#checkedCollection 方法家族。

基于类型筛选列表

一种更安全的从 List<Supertype>List<Subtype> 实际上是过滤列表,并创建一个只包含具有特定类型的元素的新列表。这种方法的实施有一定的自由度(例如,关于 null 条目),但一种可能的实现可能如下所示:

/**
 * 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;
}

此方法可用于过滤任意列表(不仅具有与类型参数相关的给定子类型-超类型关系),如本例所示:

// 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]

相关问题