3

Scenario: There is a situation where I need to set some values to List of objects based on some field condition using the Java 8 streams API.

Below is the sample of object User.

public class User{
    private int id;
    private String name;
    private String text;
    private boolean isActive;
}

Here is the code I have worked out

List<User> users = userDao.getAllByCompanyId(companyId);
users.stream()
     .filter(Objects::nonNull)
     .filter(User::isActive)
     .peek(user -> user.setText('ABC'))
     .filter(user -> !user.isActive())
     .peek(user -> user.setText('XYZ')

I know that the way I have written to set the values to object based on condition is wrong.

This is just a try-out using stream, at the end I need to set values to users object.

Is there any way to handle if-else condition.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Newbie
  • 1,403
  • 2
  • 13
  • 22
  • Please provide an example, what do you want to have in the end? – C-Otto Jan 16 '18 at 14:43
  • 2
    Why don't you want to use `.forEach`? Because it's a terminal operation? – Zircon Jan 16 '18 at 14:44
  • Maybe provide loop-style code and we can try to change it into stream? – ByeBye Jan 16 '18 at 14:44
  • 3
    Firstly, you should arguably **not** modify your data source while streaming it. This means you shouldn't `setRole` to your users while you're streaming them. Also this defiles the scope of the `peek` invocation, which serves debugging or analytical purposes. Finally, it's not very clear what you want to do with those users, also considering you are probably retrieving object from a persisted source (hinted by the "DAO" reference), yet trying to modify them in-memory... – Mena Jan 16 '18 at 14:45
  • **If user is not present, set the user role to XYZ**. I don't understand the logic here. There is no user, so there's no role that can be set. – Michael Jan 16 '18 at 14:47
  • @Michael Updated the question :) . Is there any way to set values to objects based on condition while on stream. – Newbie Jan 16 '18 at 14:54

3 Answers3

10

Perhaps you are overcomplicating this.

    List<User> users = new ArrayList<>();
    users.stream()
            .filter(Objects::nonNull)
            .forEach(u -> u.setRole(u.isActive()?"ABC":"XYZ"));
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
6

I am guessing the desired behavior, please correct me if I'm wrong.

You can use a block inside lambda expressions:

List<User> users = userDao.getAllByCompanyId(companyId);
users.stream().filter(Objects::nonNull).forEach(user -> {
    if (user.isPresent()) {
        user.setRole("ABC");
    } else {
        user.setRole("XYZ");
    }
});
C-Otto
  • 5,615
  • 3
  • 29
  • 62
3

You can't do what you want by using another filter operation with the negated condition, as this will make you end up with an empty stream (i.e. if from a list of numbers you remove elements < 5 and then you remove elements >= 5, you'll end up with an empty list).

The most straightforward and clean way to achieve what you want is to use forEach as in the other answers, especially for a simple case such as this one (which consists of calling the same setter method with its argument being either ABC or XYZ, based on the condition).

But in case you need to apply different logic to each element, based on some condition, it might be worth partitioning your stream by this condition, and then act differently on each partition:

Map<Boolean, List<User>> partitioned = users.stream()
    .filter(Objects::nonNull)
    .collect(Collectors.partitioningBy(User::isActive));

Then, simply act on each partition:

// Apply logic to active users partition
partitioned.get(true).forEach(u -> u.setText("ABC"));

// Apply logic to inactive users partition
partitioned.get(false).forEach(u -> u.setText("XYZ"));
fps
  • 33,623
  • 8
  • 55
  • 110
  • 1
    Or `partitioned.forEach((b,l) -> l.forEach(u -> u.setText(b? "ABC": "XYZ")))` – Holger Jan 17 '18 at 10:23
  • Well, I thought about all these options to incorporate `partitioningBy` yesterday, then decided not to post any variant, as there is no benefit over the straight-forward `users.forEach(u -> u.setText(u.isActive()?"ABC":"XYZ"));` (I would question the necessity of the null check) and I feared that people might start actually using these baroque variants… – Holger Jan 17 '18 at 12:14
  • @Holger Interesting... I had the same thoughts yesterday, but decided to post anyways. The thing is that if you are calling the same method only changing the argument based on the condition, then yes, it's not worth using `partitioningBy`. But if you have to apply different logic based on the condition, then I think it's very useful. That was my idea behind using two separate statements in the first place. – fps Jan 17 '18 at 12:22