Как перебирать вложенные циклы for, относящиеся к родительским элементам, с использованием потоков Java 8?

Я хочу перебирать вложенные списки, используя java8 streams, и извлекать некоторые результаты из списков при первом совпадении. К сожалению, мне также нужно получить значения из родительского содержимого, если дочерний элемент соответствует фильтру.

Как я мог это сделать?

java7

Result result = new Result();

//find first match and pupulate the result object.
for (FirstNode first : response.getFirstNodes()) {
    for (SndNode snd : first.getSndNodes()) {
        if (snd.isValid()) {
            result.setKey(first.getKey());
            result.setContent(snd.getContent());
            return;
        }
    }
}

java8

 response.getFirstNodes().stream()
        .flatMap(first -> first.getSndNodes())
        .filter(snd -> snd.isValid())
        .findFirst()
        .ifPresent(???); //cannot access snd.getContent() here

person membersound    schedule 24.03.2015    source источник
comment
возможный дубликат Java 8 - вложенные потоки ForEach с другой коллекцией   -  person Bruno César    schedule 24.03.2015


Ответы (2)


Когда вам нужны оба значения и вы хотите использовать flatMap (что требуется, когда вы хотите выполнить операцию короткого замыкания, например findFirst), вы должны сопоставить объект, содержащий оба значения.

response.getFirstNodes().stream()
  .flatMap(first->first.getSndNodes().stream()
    .map(snd->new AbstractMap.SimpleImmutableEntry<>(first, snd)))
  .filter(e->e.getValue().isValid())
  .findFirst().ifPresent(e-> {
    result.setKey(e.getKey().getKey());
    result.setContent(e.getValue().getContent());
  });

Чтобы использовать только стандартные классы, я использую Map.Entry как тип Pair, тогда как реальный тип Pair может выглядеть более лаконичным.

В этом конкретном случае вы можете переместить операцию фильтрации во внутренний поток.

response.getFirstNodes().stream()
  .flatMap(first->first.getSndNodes().stream()
     .filter(snd->snd.isValid())
     .map(snd->new AbstractMap.SimpleImmutableEntry<>(first, snd)))
  .findFirst().ifPresent(e-> {
    result.setKey(e.getKey().getKey());
    result.setContent(e.getValue().getContent());
  });

который имеет аккуратный эффект, заключающийся в том, что только для одного соответствующего элемента будет создан экземпляр Map.Entry (ну, должен как текущая реализация не такая ленивая, как должна, но даже в этом случае она будет создавать меньше объектов, чем в первом варианте).

person Holger    schedule 24.03.2015
comment
Спасибо за объяснение. Глядя на ваш код, я чувствую, что все же лучше придерживаться стиля итерации в стиле java7, если необходимо получить доступ к нескольким/родительским элементам. - person membersound; 24.03.2015
comment
Да, старые добрые циклы for не устарели. Возможно, будущая версия Java с языковой поддержкой пар/кортежей откроет возможность для более удобного потокового решения… - person Holger; 24.03.2015

Это должно быть так:

Изменить: Спасибо Хольгеру за указание, что код не остановится на первом допустимом FirstNode.

response.getFirstNodes().stream()
  .filter(it -> {it.getSndNodes().stream().filter(SndNode::isValid).findFirst(); return true;})
  .findFirst()
  .ifPresent(first -> first.getSndNodes().stream().filter(SndNode::isValid).findFirst().ifPresent(snd -> {
    result.setKey(first.getKey());
    result.setContent(snd.getContent());
  }));

Тест можно найти здесь

person vsnyc    schedule 24.03.2015
comment
Это не делает ничего полезного. Исходный код вопроса назывался result.setKey(first.getKey()); result.setContent(snd.getContent()); и остановил обработку последующих элементов. Ваш код ничего подобного не делает. - person Holger; 24.03.2015
comment
Я показывал, как получить доступ к snd.getContent() внутри ifPresent(), теперь изменил код - person vsnyc; 24.03.2015
comment
@Holger, могу ли я получить -1, пожалуйста, я проверил, что мой код работает и делает то, что предполагалось оператором - person vsnyc; 24.03.2015
comment
Я не минусовал, поэтому ничего не могу с этим поделать. Кроме того, использование forEach по-прежнему не решает задачу вопроса, который заключается в остановке после первого совпадения. Конечно, при тестировании только с одним совпадающим элементом такого поведения не будет… - person Holger; 24.03.2015
comment
Нет проблем, спасибо, что указали на ошибки в моем коде. Теперь я исправил код, чтобы делать правильные вещи. Я оставлю ответ, потому что он правильный. Кто бы ни думал, что ответ заслуживает отрицательного голосования, было бы полезно, если бы мне сказали причину. - person vsnyc; 24.03.2015