I have a question.
public class TestVO {
private String name;
private String id;
private int weight;
private int height;
public TestVO() {}
public TestVO(String name, String id, int weight, int height) {
this.name = name;
this.id = id;
this.height = height;
this.weight = weight;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Override
public String toString() {
return "Name: " + getName() + " ID: " + getId() + " Height: " + getHeight() + " Weight: " + getWeight();
}
}
List<TestVO> tmpList = new ArrayList<>();
tmpList.add(new TestVO("David", "id1", 10, 10));
tmpList.add(new TestVO("", "id2", 11, 11));
tmpList.add(new TestVO("Michael", "id3", 12, 12));
tmpList.add(new TestVO("Jack", "id4", 13, 13));
tmpList.add(new TestVO("Subodh", "id5", 14, 14));
tmpList.add(new TestVO("", "id6", 15, 15));
tmpList.add(new TestVO("Mrinal", "id7", 16, 16));
tmpList.add(new TestVO("", "id8", 17, 17));
tmpList.add(new TestVO("Eddie", "id9", 18, 18));
tmpList.add(new TestVO("Peter", "id10", 19, 19));
What I want to do is that I want to iterate through the tmpList and find the sum of height and weight and add "No-Name" when the name is empty.
// below into stream
int totalWeight = 0;
int totalHeight = 0;
for (TestVO testVO : tmpList) {
if( !"".equals(testVO.getName())){
totalWeight += totalWeight;
totalHeight += totalHeight;
}else {
testVO.setName("No-Name");
}
}
Map<Boolean, List<TestVO>>tmpMap = tmpList.stream()
.collect(Collectors.partitioningBy(test -> !"".equals(test.getName())));
int totalWeigt = tmpMap.get(true).stream()
.mapToInt(test -> test.getWeight())
.sum();
int totalHeight = tmpMap.get(true).stream()
.mapToInt(test -> test.getHeight())
.sum();
tmpMap.get(false).stream()
.forEach(test -> {
test.setName("No-Name");
});
// method 1
List<TestVO> tmpRet1 = Stream.of(tmpMap.get(true), tmpMap.get(false)).flatMap(Collection::stream).collect(Collectors.toList());
//method 2
List<TestVO> tmpRet2 = Stream.concat(tmpMap.get(true).stream(), tmpMap.get(false).stream()).collect(Collectors.toList());
I have worked so far and It does not seem to be right. I mean I have to iterate each cases.
Is there any way to combine them all together? or any suggestions to make it better?
First, the loop version seems to be more appropriate for this task which aggregates over two fields (sum by height
and weight
), and modifies the state of empty name
fields while traversing the input collection because it ensures only one pass over the entire input.
Therefore, "stateful" stream operation as forEach
should be used for this entire task but this does not make significant difference with usual for-each
loop. Generally, use of such side-effect operations is NOT recommended:
_Side-effects in behavioral parameters to stream operations are, in general, discouraged, as they can often lead to unwitting violations of the statelessness requirement, as well as other thread-safety hazards. _
So, if the task is split into two separate subtasks, resolving each task separately using Stream API would be more appropriate.
Example using Java 16+ record
and Stream::reduce(BinaryOperator<T> accumulator)
:
record SumHeightWeight(int weight, int height) {
SumHeightWeight sum(SumHeightWeight that) {
return new SumHeightWeight(
this.weight() + that.weight(),
this.height() + that.height()
);
}
}
Predicate<TestVO> nonEmptyName = t -> !"".equals(t.getName());
SumHeightWeight sum = tmpList.stream()
.filter(nonEmptyName)
.map(t -> new SumHeightWeight(t.getWeight(), t.getHeight()))
.reduce(SumHeightWeight::sum) // Optional<SumHeightWeight>
.orElseGet(()-> new SumHeightWeight(0, 0));
System.out.println(sum); // -> SumHeightWeight[weight=102, height=102]
tmpList.stream()
.filter(Predicate.not(nonEmptyName))
.forEach(t -> t.setName("No-Name"));