简体   繁体   English

带有通配符的子类上的 Java Stream.map() 不起作用

[英]Java Stream.map() on Subclasses with Wildcards Not Working

I have two classes ChildA and ChildB which are subclasses of Parent .我有两个类ChildAChildB ,它们是Parent的子类。

I am given a list of Parent and want to use Stream to map them to a Map<? extends Parent, List<? extends Parent>>我得到了一个Parent列表,并希望使用 Stream 将它们映射到Map<? extends Parent, List<? extends Parent>> Map<? extends Parent, List<? extends Parent>>

The issue is it seems the .map part is removing the subclass.问题是.map部分似乎正在删除子类。 Here is some sample code illustrating my problem:这是一些说明我的问题的示例代码:

items.stream()
        .map(item -> item.isA() ? item.getA() : item.getB()) //<--- issue here, returns List<Parent> instead of List<? extends Parent>
        .collect(Collectors.groupingBy(i -> i.getClass()));

With the above, the output is: Map<? extends Class, List<Parent>>有了上面,输出是: Map<? extends Class, List<Parent>> Map<? extends Class, List<Parent>> but I want Map<?extends Class, List<? extends Parent>> Map<? extends Class, List<Parent>>但我想要Map<?extends Class, List<? extends Parent>> Map<?extends Class, List<? extends Parent>> so I could then do something like: Map<?extends Class, List<? extends Parent>>所以我可以做类似的事情:

map.get(A.class) -> List<A> map.get(A.class) -> List<A>

So I can easily assign two variables:所以我可以很容易地分配两个变量:

List<A> a = map.get(a.class);
List<b> b = map.get(b.class);

Where as right now I would have to do something like: map.get(A.class).stream().map(A::cast).collect(Collectors.toList());现在我必须做类似的事情: map.get(A.class).stream().map(A::cast).collect(Collectors.toList());

Is there a way to get my desired result?有没有办法得到我想要的结果?

EDIT:编辑:

Here is a minimum reproducible example: https://replit.com/@rgurn/MinExample?v=1#Main.java这是一个最小的可重现示例: https ://replit.com/@rgurn/MinExample?v=1#Main.java

Note it was not my design choice to make the container class Item and I cannot change it.请注意,创建容器类Item不是我的设计选择,我无法更改它。

OK, here is what I came up with.好的,这就是我想出的。 Do it the way you were but you will need to rebuild the lists by casting.按照以前的方式进行操作,但您需要通过强制转换来重建列表。

List<Item> items = List.of(new Item("a", new A()),
                new Item("b", new B()));
    
Map<Class<?>, List<Parent>> map = items
                .stream()
                .map(item -> item.isA() ? item.getA() : item.getB())
                .collect(Collectors.groupingBy(p->p.getClass()));
        
List<A> list = new ArrayList<>();

for (Parent p :map.get(A.class)) {
     list.add(A.class.cast(p));
}

This doesn't scale well but you can always cast the contents of the List using the map Key.这不能很好地扩展,但您始终可以使用映射键投射列表的内容。 You should not get any class cast exceptions since you are separating them in the map according to class.你不应该得到任何类转换异常,因为你是根据类在地图中分离它们。

After the sad realization I had a misunderstanding of List and couldn't do what I initially tried, I ended up going with a Pair based approach like @M.在悲伤地意识到我对List有误解并且无法做我最初尝试的事情之后,我最终采用了像 @M 这样的基于Pair的方法。 Prokhorov suggested:普罗霍罗夫建议:

    Pair<ArrayList<A>, ArrayList<B>> collection = items.stream()
        .map(i -> i.isA() ? i.getA() : i.getB())
        .collect(() -> Pair.of(new ArrayList<A>(), new ArrayList<B>()), (p, u) -> {
          if (u instanceof A) {
            p.getFirst().add((A) u);
          } else {
            p.getSecond().add((B) u);
          }
        }, (p1, p2) -> {
          p1.getFirst().addAll(p2.getFirst());
          p1.getSecond().addAll(p2.getSecond());
        });

List<A> aList = collection.getFirst();
List<B> bList = collection.getSecond();

Overall I'm not too happy about it.总的来说,我对此不太满意。 Something just feels off, maybe I should just KISS.感觉有些不对劲,也许我应该亲吻一下。 If anyone has a better method of doing this I'd be happy to see it.如果有人有更好的方法来做到这一点,我会很高兴看到它。

Although I still don't think you need it, this a clean implementation of Item.class:尽管我仍然认为您不需要它,但这是 Item.class 的干净实现:

public class Item {
    private final Parent item;

    public Item(Parent item) {
        this.item = item;
    }

    public Parent get() {
        return item;
    }

    public boolean isA() {
        return item instanceof A;
    }

    public boolean isB() {
        return item instanceof B;
    }

    public A getA() {
        return (A) item;
    }

    public B getB() {
        return (B) item;
    }

    public String getType() {
        return getClass().getSimpleName();
    }
}

Your .map -call does nothing except for mapping an Item to a Parent (whether or not item is A, it just adds the attribute item back in).您的.map调用除了将Item映射到Parent之外什么都不做(无论 item 是否为 A,它只是将属性item添加回)。

But you cannot collect your Stream as the desired Map anyway ( there is no such Map in java ), so I made one for you:但是您无论如何都无法将您的Stream收集为所需的Mapjava 中没有这样的Map ),所以我为您制作了一个:

public class ClassMap<V> {
    private final HashMap<Class<? extends V>, List<? extends V>> map;

    public ClassMap() {
        this.map = new HashMap<>();
    }

    public ClassMap(@NonNull List<V> input) {
        this();
        for (V v : input) add(v);
    }

    public <W extends V> void add(@NonNull W item) {
        Class<W> clazz = (Class<W>) item.getClass();
        List<W> list = (List<W>) map.get(clazz);
        if (list == null) map.put(clazz, list = new ArrayList<>());
        list.add(item);
    }

    public <W extends V> List<W> get(@NonNull Class<W> clazz) {
        return (List<W>) map.get(clazz);
    }

    public int size() {
        return map.size();
    }
}

You can create one like this: ClassMap<Parent> map = new ClassMap<>(items.stream().map(Item::get).collect(Collectors.toList()));您可以像这样创建一个: ClassMap<Parent> map = new ClassMap<>(items.stream().map(Item::get).collect(Collectors.toList())); . .

Since Java 12, you can use a teeing collector and collect into a pair by using standard collector chains rather than your own function-based custom collector:从 Java 12 开始,您可以使用teeing 收集器并通过使用标准收集器链而不是您自己的基于函数的自定义收集器来收集成对:

import static java.util.stream.Collectors.filtering;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;

Pair<List<A>, List<B>> lists = items.stream().collect(
    Collectors.teeing(
        filtering(Parent::isA, mapping(Parent::getA, toList())),
        filtering(Parent::isB, mapping(Parent::getB, toList())),
        Pair::of
    )
);

List<A> as = lists.getFirst();
List<B> bs = lists.getSecond();

This, of course, assumes that Parent::isA , Parent::isB and other methods exist, but this can be rolled added easily in form of static utility functions.当然,这假设Parent::isA ::isA 、 Parent::isB和其他方法存在,但是可以很容易地以静态实用程序函数的形式滚动添加。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM