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

309
例如,假设你有两个类:

For example, lets say you have two classes:

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

我有一个方法返回一个List<TestA>,我想将列表中的所有对象转换为TestB,以便最终得到一个List<TestB>

20个回答

4
当您转换对象引用时,您只是转换引用的类型,而不是对象的类型。转换不会改变对象的实际类型。
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));
}

相比于直接支持的语言,这种方式更冗长,但它确实有效,您不需要经常这样做。


2
你可以使用 selectInstances 方法在 Eclipse Collections 中。这将涉及创建一个新的集合,因此效率不如使用强制转换的已接受解决方案。
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);

我在示例中包含了StringBuffer,以展示selectInstances不仅可以向下转换类型,而且如果集合包含混合类型,还可以过滤。

注意:我是Eclipse Collections的提交者。


2
如果你有一个TestA类的对象,你不能将其转换为TestB。每个TestB都是一个TestA,但反过来不一定成立。
在下面的代码中:
TestA a = new TestA();
TestB b = (TestB) a;

第二行将会抛出一个ClassCastException异常。

只有当对象本身是TestB时,您才能强制转换为TestA引用。例如:

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

因此,您可能并不总是能将TestA的列表转换为TestB的列表。


1
我认为他在谈论的是这样一种情况,你会得到一个作为方法参数的'a',它被创建为- TestA a = new TestB(),然后你想要获取TestB对象,接着你需要进行强制类型转换- TestB b = (TestB) a; - samsamara

1
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更新: 在强类型 Java 中,实际上无法从超类型转换为子类型。但是您可以引入一个超类型实现的接口。
interface ITest {
}

class TestA implements ITest {
}

class TestB extends TestA {
}

public class Main {

  public static void main(String[] args) {
    List<ITest> testAList = Arrays.asList(new TestA(), new TestA());

    Class<ITest> clazz = ITest.class;
    List<ITest> testBList = testAList.stream()
        .map(clazz::cast)
        .collect(Collectors.toList());

    System.out.println(testBList.size());
    System.out.println(testAList);
  }
}

或者您可以从子类型转换为超类型。就像这样:

class TestA {
}

class TestB extends TestA {
}

public class Main {

  public static void main(String[] args) {
    var a = new TestA();
    var b = new TestB();
    System.out.println(((TestA) b));

    List<TestB> testBList = Arrays.asList(new TestB(), new TestB());

    Class<TestA> clazz = TestA.class;
    List<TestA> testAList = testBList.stream()
        .map(clazz::cast)
        .collect(Collectors.toList());

    System.out.println(testAList.size());
    System.out.println(testAList);
  }
}

提供信息,我的原始答案指出了使用泛型的可能性。实际上,我从我的Hibernate DAO代码中获取了它。


抱歉,但这非常粗糙。 - shinzou

1
问题在于,如果您的方法包含TestB,则不会返回TestA列表,那么如果类型正确呢?那么这个转换:
class TestA{};
class TestB extends TestA{};
List<? extends TestA> listA;
List<TestB> listB = (List<TestB>) listA;

这个工具的效果和你所期望的一样好(Eclipse会警告你有一个未检查的强制转换,而这正是你正在做的事情,所以没关系)。那么你能用它来解决你的问题吗?实际上是可以的,因为有了这个:

List<TestA> badlist = null; // Actually contains TestBs, as specified
List<? extends TestA> talist = badlist;  // Umm, works
List<TextB> tblist = (List<TestB>)talist; // TADA!

这正是你所要求的,对吧?或者更精确地说:

List<TestB> tblist = (List<TestB>)(List<? extends TestA>) badlist;

对我来说似乎编译得很好。


1
手动将列表转换仍未由某些实现类似以下内容的工具箱提供,这相当奇怪:
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T extends E, E> List<T> cast(List<E> list) {
    return (List) list;
}

当然,这不会逐个检查项目,但这正是我们想要避免的,因为我们很清楚我们的实现只提供了子类型。

1
这是由于类型擦除的原因,你会发现。
List<TestA> x = new ArrayList<TestA>();
List<TestB> y = new ArrayList<TestB>();
x.getClass().equals(y.getClass()); // true

内部两个列表都是List<Object>类型。出于这个原因,您无法将一个列表强制转换为另一个 - 没有任何要转换的内容。


“没有可转换的内容”和“你不能将一个对象转换为另一个对象”是有点矛盾的。 Integer j = 42; Integer i =(Integer)j; 运行良好,因为两个对象都是整数类型,它们都属于相同的类别,因此“没有可转换的内容”。 - Simon Forsberg

0

简单回答

你不能直接进行类型转换。

解决方法

 public <T> List<T> getSubItemList(List<IAdapterImage> superList, Class<T> clazz) {
        return superList.stream()
                .map(item -> clazz.isInstance(item) ? clazz.cast(item) : null)
                .collect(Collectors.toList());
    }

使用方法

private final List<IAdapterImage> myList = new ArrayList<>();

List<SubType> subTypeList = getSubItemList(myList,SubType.class);

这是我用来将具有超类型的列表转换为具有子类型的列表的简单解决方法。我们在这里使用Java 8引入的流API,对我们的超级列表使用map函数,只需检查传递的参数是否是我们的超类型的实例,然后返回该项。最后,我们将其收集到一个新的列表中。当然,在这里我们必须将结果放入一个新的列表中。

我添加了一个接近你的答案,但我认为创建一个带有null的列表并不理想,所以在使用isInstance输出类之前对其进行了过滤,并且对于有效值,我将map转换了。 - Lucas Souza

0
我必须在公司系统的一个方法中实现这个功能:
interface MyInterface{}
Class MyClass implements MyInterface{}

该方法接收一个接口列表,也就是说,我已经实例化了一个接口列表。
private get(List<MyInterface> interface) {
   List<MyClass> myClasses = interface.stream()
                 .filter(MyClass.class::isInstance)
                 .map(MyClass.class::cast)
                 .collect(Collectors.toList());
}

-1

这段代码应该可以工作

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

2
这会造成不必要的复制。 - defnull

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接