pburgov
pburgov

Reputation: 159

How to merge two list of POJOS using stream JAVA 8 modifying some values when necessary

Hope you can help me. I'll try to make myself clear about the title of the question. I have one POJO like this

public class Factor {
    private int id;
    private String item;
    private int unitId;
    private Double factor;

    // constructor getters and setters
}

I have two List of this class with data from two different tables in two different DB.

The equalsmethod is defined this way

import com.google.common.base.Objects;
//....
@Override
public boolean equals( Object o ) {
    if ( this == o )
        return true;
    if ( o == null || getClass() != o.getClass() )
        return false;
    Factor that = (Factor) o;
    return getUnitId() == that.getUnitId() &&
               Objects.equal(getItem(), that.getItem());
}

so two objects are considered equals when they have the same item and unitId attributes.

Let's say I have these two list:

List<Factor> listA = new ArrayList<>();
listA.add(new Factor(1, "Item1", 1, 0.5));
listA.add(new Factor(2, "Item1", 2, 0.6));
listA.add(new Factor(3, "Item2", 1, 1.0));
listA.add(new Factor(4, "Item3", 1, 2.0));

List<Factor> listB = new ArrayList<>();
listB.add(new Factor(0, "Item1", 1, 0.8));
listB.add(new Factor(0, "Item1", 2, 0.9));
listB.add(new Factor(0, "Item4", 1, 1.0));
listB.add(new Factor(0, "Item5", 1, 2.0));

Taking the equalsmethod into account, the first two elements of the lists should be considered equals.

The reason why the ids in the B list are all 0, is because that table has not an id field.

What I'm trying to achieve is to create a new list by merging these two lists, but if the objects are equals, it should take the id from listA and the factor from listB.

In this way, the result list would have the following objects

(1, "Item1", 1, 0.8)
(2, "Item1", 2, 0.9)
(0, "Item4", 1, 1.0)
(0, "Item5", 1, 2.0)

I know how to compare both list with stream and filter which objects are equals or different but does anybody know how to get a new object combining the equals?

Upvotes: 3

Views: 2138

Answers (2)

Sweeper
Sweeper

Reputation: 270995

You are looking for a "zip" function. However, there isn't one in the Stream API (at least in Java 8, I don't know about newer versions).

You can find how to write a zip method yourself in this post.

Using the answer by siki, we get this method:

public static<A, B, C> Stream<C> zip(Stream<? extends A> a,
                                     Stream<? extends B> b,
                                     BiFunction<? super A, ? super B, ? extends C> zipper) {
    Objects.requireNonNull(zipper);
    Spliterator<? extends A> aSpliterator = Objects.requireNonNull(a).spliterator();
    Spliterator<? extends B> bSpliterator = Objects.requireNonNull(b).spliterator();

    // Zipping looses DISTINCT and SORTED characteristics
    int characteristics = aSpliterator.characteristics() & bSpliterator.characteristics() &
            ~(Spliterator.DISTINCT | Spliterator.SORTED);

    long zipSize = ((characteristics & Spliterator.SIZED) != 0)
            ? Math.min(aSpliterator.getExactSizeIfKnown(), bSpliterator.getExactSizeIfKnown())
            : -1;

    Iterator<A> aIterator = Spliterators.iterator(aSpliterator);
    Iterator<B> bIterator = Spliterators.iterator(bSpliterator);
    Iterator<C> cIterator = new Iterator<C>() {
        @Override
        public boolean hasNext() {
            return aIterator.hasNext() && bIterator.hasNext();
        }

        @Override
        public C next() {
            return zipper.apply(aIterator.next(), bIterator.next());
        }
    };

    Spliterator<C> split = Spliterators.spliterator(cIterator, zipSize, characteristics);
    return (a.isParallel() || b.isParallel())
            ? StreamSupport.stream(split, true)
            : StreamSupport.stream(split, false);
}

We can use it like this:

List<Factor> result = zip(list1.stream(), list2.stream(),
        (x, y) -> x.equals(y) ? new Factor(x.getId(), y.getItem(), y.getUnitId(), y.getFactor()) : y) // you might want to create a copy of y instead
        .collect(Collectors.toList());

The third argument is basically saying how we want to merge the items. Here the lambda says:

If the items of the two objects are equal, then we create a new item with the ID from the first item (from list1) and everything else from the second item (from list2). Otherwise, just use the second item.

This will result in some of the objects in result to be brand new ones, and some be existing ones. If you want them to all be brand new ones, replace y at the very end of that line with:

new Factor(y.getID(), y.getItem(), y.getUnitId(), y.getFactor())

Upvotes: 1

Adrian
Adrian

Reputation: 3134

assuming that you also have overridden hashCode method you can use toMap collector:

Collection<Factor> values = Stream.concat(listA.stream(), listB.stream())
            .collect(Collectors.toMap(Function.identity(), 
                                      Function.identity(), (a, b) -> {
        return new Factor(a.getId(), a.getItem(), a.getUnitId(), b.getFactor());
    })).values();

Upvotes: 1

Related Questions