I’m quite new into programming and got a tricky question. I got an object which has multiple parameters:
public class SampleObject { private String number; private String valueOne; private String valueTwo; private String valueThree; // getters, setters, all-args constructor }
Every object always has non-null number
attribute as well as one of three values-field. So for example, if valueOne
is not null
, the other two value fields valueTwo
and valueThree
would be null
.
So here’s my problem:
The SampleObject
is referenced in AnotherClass
which looks so:
public class AnotherClass { private UUID id; private List<SampleObject> sampleObjects; // getters, setters, all-args constructor }
I am receiving one object of AnotherClass
containing multiple entities of SampleClass
in a list.
What I want to do is merge all SampleObject
s which got the same number into one object and provide a map, where the number is the key and value are the value parameters. For example:
Sample1(number:"1", valueOne="1", valueTwo=null, valueThree=null) Sample2(number:"1", valueOne=null, valueTwo="2", valueThree=null) Sample3(number:"1", valueOne=null, valueTwo=null, valueThree="3") Sample4(number:"2", valueOne="5", valueTwo=null, valueThree=null)
Desired state:
Sample1Merged(number:"1", valueOne="1", valueTwo="2", valueThree="3") Sample4(number:"2", valueOne="5", valueTwo=null, valueThree=null)
What I have already done is the following:
final Map<String, SampleObject> mapOfMergedSamples = new LinkedHashMap<>(); anotherClass.getSampleObjects().stream() .sorted(Comparator.comparing(SampleObject::getNumber)) .forEach(s -> mapOfMergedSamples.put(s.getNumber(), new SampleObject(Stream.of(s.getValueOne(), s.getValueTwo()) .filter(Objects::nonNull) .collect(Collectors.joining()), s.getValueThree())) ); return mapOfMergedSamples;
The problem with my current try is that every number gets overwritten because they have the same key in the map (the number in the SampleObject
) does someone know how can I archive my desired state?
Advertisement
Answer
Based on your usage of Collector.joining()
I assume that you want to concatenate all non-null values without any delimiters (anyway it can be easily changed).
In order to combine SampleObject
instances having the same number
property, you can group them into an intermediate Map
where the number
would serve as Key and a custom accumulation type (having properties valueOne
, valueTwo
, valueThree
) would be a Value (note: if you don’t want to define a new type, you can put the accumulation right into the SampleObject
, but I’ll go with a separate class because this approach is more flexible).
Here’s it might look like (for convenience, I’ve implemented Consumer
interface):
public class SampleObjectAccumulator implements Consumer<SampleObject> { private StringBuilder valueOne = new StringBuilder(); private StringBuilder valueTwo = new StringBuilder(); private StringBuilder valueThree = new StringBuilder(); @Override public void accept(SampleObject sampleObject) { if (sampleObject.getValueOne() != null) valueOne.append(sampleObject.getValueOne()); if (sampleObject.getValueTwo() != null) valueTwo.append(sampleObject.getValueTwo()); if (sampleObject.getValueThree() != null) valueThree.append(sampleObject.getValueThree()); } public SampleObjectAccumulator merge(SampleObjectAccumulator other) { valueOne.append(other.valueOne); valueTwo.append(other.valueTwo); valueThree.append(other.valueThree); return this; } public SampleObject toSampleObject(String number) { return new SampleObject( number, valueOne.toString(), valueTwo.toString(), valueThree.toString() ); } // getters }
To create an intermediate Map we can use Collector groupingBy()
and as its downstream Collector, in order to leverage the custom accumulation type, we can provide a custom collector, which can instantiated using factory method Collector.of()
.
Then we need to create a stream over the entries of the intermediate map in order to transform the Value.
Note that sorting applied in only the second stream.
AnotherClass anotherClass = // initializing the AnotherClass instance final Map<String, SampleObject> mapOfMergedSamples = anotherClass.getSampleObjects().stream() .collect(Collectors.groupingBy( SampleObject::getNumber, Collector.of( SampleObjectAccumulator::new, SampleObjectAccumulator::accept, SampleObjectAccumulator::merge ) )) .entrySet().stream() .sorted(Map.Entry.comparingByKey()) .collect(Collectors.toMap( Map.Entry::getKey, e -> e.getValue().toSampleObject(e.getKey()), (left, right) -> { throw new AssertionError("All keys are expected to be unique"); }, LinkedHashMap::new ));