Пытаюсь представить вложенный цикл for в виде потока Java, но запутался

#java #java-stream

#java #java-stream

Вопрос:

Я пытаюсь лучше понять потоки в Java, и я просто пытаюсь представить вложенный цикл for в виде потока, но я изо всех сил пытаюсь понять это.

Я перепробовал несколько вещей, но, похоже, не могу этого понять.

Например, что-то вроде этого.

 for (Profile profile : profiles) {
    for (User user : users) {
        if (user.getUserId().equals(profile.getProfileId())) {
           profile.setField(false);
        }
    }
}
  

Как бы это сделать в виде потока на Java?

Комментарии:

1. В этом конкретном примере потоки вообще не приносят пользы и сводятся к простым вложенным вызовам forEach , которые выглядят почти точно так же, как оригинал.

2. Поскольку вы выполняете итерацию по двум несвязанным коллекциям, потоки здесь действительно не могут быть использованы

Ответ №1:

Я думаю, вы пытаетесь понять поток, но позвольте мне сказать, что for версия не хуже, а Stream версия не лучше: тест производительности и интуиция, вероятно, помогут вам в этом, а также в удобочитаемости.

Тем не менее, ваша проблема здесь в том, что для вашего второго вида требуется, чтобы первый работал:

 profiles.stream()
        .filter(profile -> users.stream() 
                                .anyMatch(user -> user.getUserId().equals(profile.getProfileId())))
        .forEach(profile -> profile.setField(false));
  
  

Это строго не то же самое, что ваш цикл for: вы вызываете setField несколько раз для каждого профиля, в то время как в потоковой версии это выполняется только в том случае, если есть один соответствующий пользователь. Я предположил, что setField это установщик, и, как таковой, поскольку его значение является постоянным, не должно иметь значения, вызывается ли оно один или несколько раз.

Я бы посоветовал вам не использовать первый поток в этом случае или ограничиться forEach :

 profiles.forEach(profile -> profile.setField(!users.stream() 
                                   .anyMatch(user -> user.getUserId().equals(profile.getProfileId())));
  

Который можно было бы упростить, сначала получив все идентификаторы пользователей и используя contains из сгенерированного набора:

    var userIds = users.stream().map(User::getUserId).collect(toSet());
   profiles.forEach(profile -> profile.setField(!userIds.contains(profile.getProfileId()));
  

Комментарии:

1. anyMatch не совпадает с последним совпадением, это может быть несовместимым изменением. С другой стороны, почему используется предположение о сравнении id с == рассматриваемым equals файлом? И ваш последний код также устанавливает field атрибут некоторых профилей на true , что опять же несовместимо с существующим поведением кода.

2. Это вопрос логики и лени: логика здесь заключается в чтении for*2 версии, которая вызывает profile.setField(false) , если есть соответствующий пользователь. Я ожидал, что метод будет простым установщиком: если бы он подсчитывал количество вызовов, это было бы действительно по-другому, но я бы предпочел выбрать для него лучшее название. Что касается user.id == profile.id , как уже было сказано , я упростил геттер из лени, так что да, так и должно быть user.getUserId().equals(profile.getProfileId()) .

3. Упрощение метода получения не означает, что вы не можете сравнивать user.id.equals(profile.id) . На самом деле я не имел в виду подсчет звонков. Просто протестируйте свой метод и тот, о котором идет речь, на примере данных, где некоторые профили не выбраны userIds , но им ранее было присвоено значение атрибута false for field .