First create a class to handle this datas. There's two important points to note. The equals and hashcode method are only based with the isbn and palletNumber values and there is a merge method that returns a new instance of PackingListRow with the quantities between this and an other instance you give as parameter.
class PackingListRow {
private final String isbn;
private final int palletNumber;
private final int quantity;
public PackingListRow(String isbn, int palletNumber, int quantity) {
this.isbn = isbn;
this.palletNumber = palletNumber;
this.quantity = quantity;
}
public String getIsbn() {
return isbn;
}
public int getPalletNumber() {
return palletNumber;
}
public int getQuantity() {
return quantity;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PackingListRow that = (PackingListRow) o;
return Objects.equals(palletNumber, that.palletNumber) &&
Objects.equals(isbn, that.isbn);
}
@Override
public int hashCode() {
return Objects.hash(isbn, palletNumber);
}
@Override
public String toString() {
return "PackingListRow{" +
"isbn='" + isbn + '\'' +
", palletNumber=" + palletNumber +
", quantity=" + quantity +
'}';
}
public PackingListRow merge(PackingListRow other) {
assert(this.equals(other));
return new PackingListRow(this.isbn, this.palletNumber, this.quantity + other.quantity);
}
}
Once you have that, you just need to create another new list that is initially empty. It will contains the merged values. For each instance in the initial list, you check whether it is already in the merged list. If yes, you modify the existing instance by calling merge, otherwise you just append it to the list. We end up with the following algorithm:
List<PackingListRow> list =
Arrays.asList(new PackingListRow("1234", 1, 10), new PackingListRow("1234", 2, 5), new PackingListRow("1234", 1, 15));
List<PackingListRow> mergedList = new ArrayList<>();
for(PackingListRow p : list) {
int index = mergedList.indexOf(p);
if(index != -1) {
mergedList.set(index, mergedList.get(index).merge(p));
} else {
mergedList.add(p);
}
}
System.out.println(mergedList);
Which outputs:
[PackingListRow{isbn='1234', palletNumber=1, quantity=25}, PackingListRow{isbn='1234', palletNumber=2, quantity=5}]
With Java 8, I would maybe use a different strategy (at least you show there are multiple ways to solve the problem). I would create a static class that does the grouping for me:
class PackingListRow {
private final String isbn;
private final int palletNumber;
private final int quantity;
static class GroupPacking {
private final String isbn;
private final int palletNumber;
public GroupPacking(PackingListRow p) {
this.isbn = p.isbn;
this.palletNumber = p.palletNumber;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GroupPacking that = (GroupPacking) o;
return Objects.equals(palletNumber, that.palletNumber) &&
Objects.equals(isbn, that.isbn);
}
@Override
public int hashCode() {
return Objects.hash(isbn, palletNumber);
}
}
....
public PackingListRow merge(PackingListRow other) {
assert (new GroupPacking(other).equals(new GroupPacking(this)));
return new PackingListRow(this.isbn, this.palletNumber, this.quantity + other.quantity);
}
}
Then you can use the Stream API. Given the original list, you get a Stream<PackingListRow> from which you collect the elements into a Map according by their GroupPacking instances (the keys). The value is simply the current PackingListRow instance. If you have two instances with the same GroupPacking value (according to equals/hashcode), you merge them. You finally get the values() of the map.
List<PackingListRow> mergedList =
new ArrayList<>(list.stream().collect(toMap(PackingListRow.GroupPacking::new, p -> p, PackingListRow::merge)).values());
void merge(MyData other). Then have aList<MyData>. Looping is an option, combined withMap<String, MyData>eventually. But really, it would be simpler do to the merging with a class.