Java 8: применение карты потока и фильтра за один раз

Я пишу парсер для файла в Java 8. Файл читается с использованием Files.lines и возвращает последовательный Stream<String>.

Каждая строка сопоставляется с объектом данных Result следующим образом:

Result parse(String _line) {
  // ... code here
  Result _result = new Result().
  if (/* line is not needed */) {
    return null;
  } else {
    /* parse line into result */
   return _result;
  }
}

Теперь мы можем сопоставить каждую строку в потоке с соответствующим результатом:

public Stream<Result> parseFile(Path _file) {
  Stream<String> _stream = Files.lines(_file);
  Stream<Result> _resultStream = _stream.map(this::parse);
}

Однако поток теперь содержит значения null, которые я хочу удалить:

parseFile(_file).filter(v -> v != null);

Как я могу совместить операцию карты/фильтра, как я уже знаю в parseLine/_stream.map, если нужен результат?


person user3001    schedule 06.11.2014    source источник
comment
Я не понимаю, что не так с return Files.line(_file).map(this::parse).filter(v -> v != null); ?   -  person user2336315    schedule 06.11.2014
comment
Ну, я предполагаю, что поток должен обрабатываться два раза, один раз для карты и один раз для фильтра. Я хочу отбросить ненужные элементы в операции карты, это в любом случае должно быть быстрее.   -  person user3001    schedule 06.11.2014
comment
Поток будет обработан за один прогон и только в том случае, если вы используете терминальную операцию, требующую полной итерации (например, forEach, collect, reduce).   -  person Lukasz Wiktor    schedule 06.11.2014
comment
@ user3001 См. stackoverflow. ком/вопросы/23696317/   -  person user2336315    schedule 06.11.2014
comment
См. также: stackoverflow.com/questions/21219667/stream-and-lazy -оценка   -  person assylias    schedule 06.11.2014
comment
Ваше предположение о множественных проходах неверно. Фильтрация и сопоставление выполняются за один проход. (Как правило, весь конвейер обрабатывается за один проход, если нет таких операций, как сортировка, которая должна просмотреть все данные, прежде чем выдавать какие-либо данные.)   -  person Brian Goetz    schedule 06.11.2014


Ответы (2)


Как уже отмечалось в комментариях, поток будет обрабатываться за один проход, так что на самом деле ничего менять не нужно. Для чего вы можете использовать flatMap и позволить parse вернуть поток:

Stream<Result> parse(String _line) {
  .. code here
  Result _result = new Result().
  if (/* line is not needed */) {
    return Stream.empty();
  } else {
    /** parse line into result */
   return Stream.of(_result);
  }
}  

public Stream<Result> parseFile(Path _file) {
  return Files.lines(_file)
              .flatMap(this::parse);
}

Таким образом, у вас не будет никаких значений null.

person a better oliver    schedule 06.11.2014

Обновление для Java 9:

Использование Stream<Result> кажется неправильным типом возвращаемого значения для функции parse(). Поток может содержать очень много значений, поэтому пользователь parse() должен либо предположить, что в потоке будет не более одного значения, либо использовать что-то вроде collect для извлечения и использования результатов операции parse(). Если функция и ее использование разделены всего несколькими строками кода, это может быть нормально, но если расстояние увеличивается, например, в совершенно другом файле для тестирования JUnit, контракт интерфейса не ясен из возвращаемого значения.

Вместо того, чтобы возвращать Stream, было бы лучше, если бы контракт интерфейса возвращал пустой Optional, когда строка не нужна.

Optional<Result> parse(String _line) {
   ... code here
   Result _result = null;
   if (/* line needed */) {
      /** parse line into result */
   }
   return Optional.ofNullable(_result);
}

К сожалению, теперь _stream.map(this::parse) возвращает поток значений Optional, поэтому с Java 8 вам снова нужно отфильтровать и сопоставить это с .filter(Optional::isPresent).map(Optional::get), и вопрос заключался в поиске решения, которое могло бы сделать это «за один раз».

Этот вопрос был опубликован 3 года назад. В Java 9 теперь у нас есть возможность (каламбур) использовать Optional::stream, поэтому вместо этого мы можем написать:

public Stream<Result> parseFile(Path _file) {
  return Files.lines(_file)
      .map(this::parse)
      .flatMap(Optional::stream)
}

для преобразования потока значений Optional в поток значений Result без каких-либо пустых опций.

person AJNeufeld    schedule 28.01.2018
comment
В этом случае вы заменяете map&filter на map&flatMap. Нет большой разницы между использованием null напрямую и последующей фильтрацией. - person Alowaniak; 20.05.2021