Reputation: 115
I have a stream of objects (a List) and want to create new objects from that stream, to be inserted into a Set. However, two or more objects in the incoming List may hash to the same Key in the Set, in which case I want to append a String from the nth List object to the one already in the Set instead of creating a new one.
Something like this, but in functional form:
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;
In functional form I though of creating something like this:
List classAList = new ArrayList<>();
classAList.stream()
.map(instanceA -> new ClassB(instanceA))
.collect(Collectors.toSet());
But then of course that ignores the hashmap and I don't get to combine fields my multiple instances of ClassA that would all resolve to the same ClassB. I'm not sure how to put that in there. Do I need ignore the map() call and create a custom collector instead to do this job? There seems to be more than one way to do this, but I'm new to Streams.
Upvotes: 1
Views: 6465
Reputation: 298519
It’s hard to understand what you actually want as your code example does not work at all. The problem is that a Set
does not work like a Map
, you can’t ask it for the contained equivalent object. Besides that, you are using different objects for your contains(…)
and get(…)
call. Also, it’s not clear what the difference between ClassB.key(instanceA)
and new ClassB(instanceA)
is.
Let’s try to redefine it:
Suppose we have a key type Key
and a method Key.key(instanceA)
to define the group candidates. Then we have a ClassB
which is the resulting type, created via new ClassB(instanceA)
for a single (or primary ClassA
instance), having an .appendFieldA(…)
method to receive a value of another ClassA
instance when merging two group members. Then, the original (pre Java 8) code will look as follows:
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));
}
}
Then, myMap.values()
provides you a collection of the ClassB
instances. If it has to be a Set
, you may create it via
Set<ClassB> result=new HashSet<>(myMap.values());
Note that this also works, when Key
and ClassB
are identical as it seems to be in your code, but you may ask youself, whether you really need both, the instance created via .key(instanceA)
and the one created via new ClassB(instanceA)
…
This can be simplified via the Java 8 API as:
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;
});
}
or, if you want it look even more function-stylish:
classAList.forEach(instanceA ->
myMap.compute(Key.key(instanceA), (k,b)-> {
if(b==null) b=new ClassB(instanceA);
else b.appendFieldA(instanceA.getFieldA());
return b;
})
);
For a stream solution, there is the problem, that a merge function will get two instances of the same type, here ClassB
, and can’t access the ClassA
instance via the surrounding context like we did with the compute
solution above. For a stream solution, we need a method in ClassB
which returns that first ClassA
instance, which we passed to its constructor, say getFirstInstanceA()
. Then we can use:
Map<Key, ClassB> myMap = classAList.stream()
.collect(Collectors.toMap(Key::key, ClassB::new, (b1,b2)->{
b1.appendFieldA(b2.getFirstInstanceA().getFieldA());
return b1;
}));
Upvotes: 3
Reputation: 225
You can group the entries into a map that maps the hashed key to the list of elements and then call map again to convert that map into the set you are after. Something like this:
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());
Upvotes: 1