使用Java Stream条件映射到新对象

时间:2015-07-22 23:17:04

标签: java java-8 java-stream

我有一个对象流(List),想要从该流创建新对象,以便插入到Set中。但是,传入List中的两个或多个对象可能会散列到Set中的相同Key,在这种情况下,我想将第n个List对象中的String追加到Set中已经存在的String,而不是创建一个新的。

这样的东西,但是以功能形式:

HashSet<ClassB> mySet = new HashSet<>();
for (ClassA instanceA : classAList) {
    if (mySet.contains(ClassB.key(instanceA))) { //static method call to find the key
        mySet.get(instanceA).appendFieldA(instanceA.getFieldA());
    } else {
        mySet.add(new ClassB(instanceA));
    }
}
return mySet;

在功能形式上,我创造了这样的东西:

List classAList = new ArrayList<>();
classAList.stream()
.map(instanceA -> new ClassB(instanceA))
.collect(Collectors.toSet());

但是当然忽略了hashmap而且我不能将我的多个ClassA实例组合在一起,这些实例都将解析为同一个ClassB。我不知道怎么把它放在那里。我是否需要忽略map()调用并创建一个自定义收集器来代替这项工作?似乎有不止一种方法可以做到这一点,但我是Streams的新手。

2 个答案:

答案 0 :(得分:3)

很难理解你真正想要的是什么,因为你的代码示例根本不起作用。问题是Set不像Map那样工作,你不能要求它包含等效对象。除此之外,您还在为contains(…)get(…)电话使用不同的对象。此外,还不清楚ClassB.key(instanceA)new ClassB(instanceA)之间的区别是什么。

让我们尝试重新定义它:

假设我们有一个键类型Key和一个方法Key.key(instanceA)来定义候选组。然后我们有ClassB这是结果类型,通过new ClassB(instanceA)为单个(或主ClassA实例)创建,具有.appendFieldA(…)方法来接收另一个的值合并两个组成员时的ClassA实例。然后,原始(Java 8之前的)代码将如下所示:

HashMap<Key, ClassB> myMap = new HashMap<>();
for(ClassA instanceA: classAList) {
    Key key=Key.key(instanceA);
    if(myMap.containsKey(key)) {
        myMap.get(key).appendFieldA(instanceA.getFieldA());
    } else {
        myMap.put(key, new ClassB(instanceA));
    }
}

然后,myMap.values()为您提供了ClassB个实例的集合。如果必须是Set,您可以通过

创建它
Set<ClassB> result=new HashSet<>(myMap.values());

请注意,当KeyClassB与您的代码中的相同时,这也有效,但您可能会问自己,是否真的需要两者,通过{{创建的实例1}}和通过.key(instanceA) ...

创建的那个

这可以通过Java 8 API简化为:

new ClassB(instanceA)

或者,如果你想让它看起来更具功能性:

for(ClassA instanceA: classAList) {
    myMap.compute(Key.key(instanceA), (k,b)-> {
        if(b==null) b=new ClassB(instanceA);
        else b.appendFieldA(instanceA.getFieldA());
        return b;
    });
}

对于流解决方案,存在一个问题,即合并函数将获得两个相同类型的实例,此处为classAList.forEach(instanceA -> myMap.compute(Key.key(instanceA), (k,b)-> { if(b==null) b=new ClassB(instanceA); else b.appendFieldA(instanceA.getFieldA()); return b; }) ); ,并且无法通过周围的上下文访问ClassB实例我们使用上面的ClassA解决方案。对于流解决方案,我们需要compute中的一个方法,该方法返回第一个ClassB实例,我们将其传递给它的构造函数,例如ClassA。然后我们可以使用:

getFirstInstanceA()

答案 1 :(得分:1)

您可以将条目分组到将散列键映射到元素列表的地图中,然后再次调用map以将该地图转换为您所追求的集合。像这样:

List classAList = new ArrayList<>();
classAList.stream()
.collect(Collectors.groupingBy(instanceA -> ClassB.key(instanceB)))
.entrySet()
.map(entry -> entry.getValue().stream()
   .map(instanceA -> new ClassB(instanceA))
   .reduce(null, (a,b) -> a.appendFieldA(b)))
.collect(Collectors.toSet());