notAChance
notAChance

Reputation: 1430

filter and set() in one stream

So I've some sample code I'm playing with to try and figure out this logic below. It's just one large main method and two POJOs, but it'll run. I'm debugging to get values at the point of termination.

public class Main {

    public static void main(String[] args) {

        obj1 obj1 = new obj1();
        obj1 obj12 = new obj1();

        obj2 obj2 = new obj2();
        obj2 obj22 = new obj2();

        obj1.id = 123L;
        obj1.carId = 1234L;
        obj12.id = 123L;
        obj12.carId = 1234L;

        obj2.carId = 1234L;
        obj22.carId = 12345L;

        ArrayList<obj1> obj1Arr = new ArrayList<>();
        ArrayList<obj2> obj2Arr = new ArrayList<>();

        obj1Arr.add(obj1);
        obj1Arr.add(obj12);

        obj2Arr.add(obj2);
        obj2Arr.add(obj22);

        List<obj2> newCarList= obj2Arr.stream()
                .filter(anObjOf2 -> obj1Arr
                        .stream()
                        .anyMatch(carReg -> carReg.getCarId().equals(anObjOf2.getCarId())))
                .collect(Collectors.toList());
    }
}

POJO1:

public class obj1 {

    Long id = null;
    Long carId = null;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getCarId() {
        return carId;
    }

    public void setCarId(Long carId) {
        this.carId = carId;
    }
}

POJO2:

public class obj2 {

    Long id = null;
    Long carId = null;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getCarId() {
        return carId;
    }

    public void setCarId(Long carId) {
        this.carId = carId;
    }
}

As you can see, I'm streaming and filtering out any objs that don't match on carId. My next goal is to then set id (not carId) on the matched object.

Basically, if obj1.carId == obj2.carId THEN set obj1.id = obj2.id.

My problem is I don't know how to do this in the same stream. Is it possible? I can't help but think there is a need for an iterator at this point?

Upvotes: 5

Views: 2823

Answers (4)

tobias_k
tobias_k

Reputation: 82929

I'd suggest first creating a Map mapping carId to id for each element in the first list (I've modified your class a bit to make it more readable and removed duplicate IDs, which I assume were a mistake):

@Data @AllArgsConstructor
class Car {
    Long id = null;
    Long carId = null;
}
List<Car> obj1Arr = Arrays.asList(new Car(1L, 123L), new Car(2L, 1234L));
List<Car> obj2Arr = Arrays.asList(new Car(0L, 1234L), new Car(0L, 12345L));

Map<Long, Long> carIdToId = obj1Arr.stream()
        .collect(Collectors.toMap(Car::getCarId, Car::getId));
// [Car(id=2, carId=1234), Car(id=0, carId=12345)]

Then you can use this Map to filter for cars with the same carID without having to compare with each other car. Then, use forEach just set the Id in the original list.

obj2Arr.stream()
        .filter(anObjOf2 -> carIdToId.containsKey(anObjOf2.getCarId()))
        .forEach(anObjOf2 -> anObjOf2.setId(carIdToId.get(anObjOf2.getCarId())));
System.out.println(obj2Arr);
// [Car(id=2, carId=1234), Car(id=0, carId=12345)]

If you want only the items with equal carId, you can set the ID in peek and then collect. This will still modify the original instances, though:

obj2Arr = obj2Arr.stream()
        .filter(anObjOf2 -> carIdToId.containsKey(anObjOf2.getCarId()))
        .peek(anObjOf2 -> anObjOf2.setId(carIdToId.get(anObjOf2.getCarId())))
        .collect(Collectors.toList());
System.out.println(obj2Arr);
// [Car(id=2, carId=1234)]

Or create a list of entirely new instances:

obj2Arr = obj2Arr.stream()
        .map(Car::getCarId)
        .filter(carIdToId::containsKey)
        .map(carId -> new Car(carIdToId.get(carId), carId))
        .collect(Collectors.toList());
System.out.println(obj2Arr);
// [Car(id=2, carId=1234)]

Upvotes: 2

ETO
ETO

Reputation: 7279

Don't do these two operations in one stream. It's a really bad style.

Collect the data first. Then mutate the elements. Something like:

Map<obj2, Optional<obj1>> map = 
    obj2Arr.stream()
           .collect(toMap(identity(), o2 -> obj1Arr.stream()
                                                  .filter(o1 -> o1.getCarId().equals(o2.getCarId()))
                                                  .findAny()));

map.forEach((o1, optO2) -> optO2.ifPresent(o2 -> o1.setId(o2.id)));

Otherwise you can just use nested for-each loops.

Upvotes: 3

Ryuzaki L
Ryuzaki L

Reputation: 40078

You can do it in the Predicate anyMatch

List<obj2> newCarList= obj2Arr.stream()
            .filter(anObjOf2 -> obj1Arr
                    .stream()
                    .anyMatch(carReg ->  {
            if(carReg.getCarId().equals(anObjOf2.getCarId()))) {
                 carReg.setId(anObjOf2.getId());
                  return true;
               }
           return false;
     }).collect(Collectors.toList());

Second Approach

Filtering first

List<obj2> newCarList= obj2Arr.stream()
            .filter(anObjOf2 -> obj1Arr
                    .stream()
                    .anyMatch(carReg -> carReg.getCarId().equals(anObjOf2.getCarId())))
            .collect(Collectors.toList());

Now Set the id

newCarList.forEach(obj->{
           obj1Arr.forEach(o-> {
            if(o.getCarId().equals(obj.getCarId()) {
                  o.setId(obj.getId());
                }
           };
    });

Upvotes: 1

Naman
Naman

Reputation: 31978

If an O(m*n) solution works for you, then the simpler implementation would be using the for loop as:

// classes renamed for naming convention and relatiing to question at the same time 
List<ObjectTwo> newCarList = new ArrayList<>();
for(ObjectTwo objectTwo: objectTwoArr) {
    for (ObjectOne objectOne : objectOneArr) {
        if (objectTwo.getCarId().equals(objectOne.getCarId())) {
            objectOne.setId(objectTwo.getId());
            newCarList.add(objectTwo);
        }
    }
}

m and n being the respective sizes of the lists.

Upvotes: 1

Related Questions