Stream API
Среди большинства новых средств, внедренных в версии JDK 8, особое место занимают лямбда-выражения и потоковый прикладной программный интерфейс API. Как станет ясно из материала этой главы, потоковый API разработан с учетом лямбда-выражений. Более того, потоковый API наглядно демонстрирует, насколько лямбда-выражения повышают эффективность Java.
Несмотря на впечатляющую совместимость потокового API с лямбда-выражениями, главной его особенностью является способность выполнять сложные операции поиска, фильтрации, преобразования и иного манипулирования данными. Используя потоковый API можно, например, сформировать последовательность действий, принципиально подобную на запросы баз данных, составляемых на языке SQL. Более того, подобные действия, как правило, выполняются параллельно и, следовательно, повышают производительность, особенно при обработке крупных массивов данных. Проще говоря, потоковый API предоставляет мощные средства для обработки данных эффективным, но простым способом.
Прежде всего, следует заметить, что в потоковом API применяются одни из самых развитых языковых средств Java. Поэтому для полного понимания и применения этого прикладного программного интерфейса потребуется твердое знание обобщений и лямбда-выражений, а также основных принципов параллельного выполнения и применения каркаса коллекций Collections Framework.
Основные положения о потоках данных
Прежде всего, само понятие "поток данных в потоковом API" определяется как канал передачи данных. Следовательно, поток данных представляет собой последовательность объектов. Поток данных оперирует источником данных, например, массивом или коллекцией. В самом потоке данные не хранятся, а только перемещаются, фильтруются, сортируются и обрабатываются иным образом. Но, как правило, действие самого потока данных не видоизменяет их источник. Например, сортировка данных в потоке не изменяет их упорядочение в источнике, а, скорее, приводит к созданию нового потока данных, дающего отсортированный результат.
Потоковые интерфейсы
В потоковом API определяется ряд потоковых интерфейсов, входящих в состав пакета java.util.stream. В основе лежит интерфейс BaseStream, в котором определяются основные функциональные возможности всех потоков данных. Интерфейс BaseStream является обобщенным и определяется следующим образом :
intefaceBаseStreaa<T , S extends BaseStreaa<T , S>>
где параметр Т обозначает тип элементов в потоке данных, а параметр S – тип потока данных, расширяющего интерфейс BaseStream.
В свою очередь, интерфейс BaseStream расширяет интерфейс AutoClosable, следовательно, потоком данных можно управлять в блоке оператора try с ресурсами. Но, как правило, закрывать приходится только те потоки данных, где источники данных требуют закрытия (например, те потоки, которые связаны с файлами). В то же время потоки, источниками данных для которых обычно служат коллекции, закрывать не нужно.
Обратите внимание на то, что в обеих приведенных выше таблицах методы обозначены как конечные или промежуточные операции. Их отличие состоит в том, что конечная операция потребляет поток данных и дает конечный результат, например, обнаруживает минимальное значение в потоке данных или выполняет некоторое действие, как это делает метод forEach ( ). Если поток данных потреблен, он не может быть использован повторно. А промежуточная операция производит поток данных и служит для создания конвейера для выполнения последовательности действий. Кроме того, промежуточные операции не выполняются немедленно. Напротив, указанное действие происходит в том случае, когда конечная операция выполняется в новом потоке данных, созданном промежуточной операцией. Такой механизм называется отложенным поведением, а промежуточные операции - отложенными.
Благодаря отложенному поведению потоковый API действует более эффективно. Еще одна особенность потоков данных состоит в том, что одни промежуточные операции выполняются без сохранения состояния, а другие – с сохранением состояния. В операции без сохранения состояния каждый элемент обрабатывается независимо от остальных. А в операции с сохранением состояния обработка элемента может зависеть от особенностей остальных элементов. Например, сохранение данных является операцией с сохранением состояния, поскольку упорядочение элемента зависит от значений других элементов. Следовательно, метод sorted( ) выполняется с сохранением состояния. Но фильтрация элементов на основании предиката, не имеющего состояния, выполняется без сохранения состояния, поскольку каждый элемент обрабатывается отдельно. Следовательно, метод filter( ) может (и должен) выполняться без сохранения состояния. Отличие операций с сохранением состояния от операций без сохранения состояния особенно важно для параллельной обработки потоков данных, поскольку операцию с сохранением состояния, возможно, придется выполнить не за один, а за несколько проходов. Интерфейс Stream оперирует ссылками на объекты, и поэтому он не может обращаться непосредственно к примитивным типам данных. Для обработки потоков примитивных типов данных в потоковом API определяются следующие интерфейсы: - -
• DoubleStream
• IntStream
• LongStream
Все эти интерфейсы расширяют интерфейс Base Stream и обладают теми же функциональными возможностями, что и интерфейс Stream, за исключением того, что они оперируют примитивными, а не ссылочными типами данных. Они предоставляют также служебные методы, например, boxed ( ), упрощающие их применение. В этой главе основное внимание уделяется интерфейсу Stream, поскольку потоки объектов употребляются чаще всего. Хотя аналогичным образом могут быть использованы и потоки примитивных типов данных.
Простой пример потока данных
Прежде чем продолжить дальше, рассмотрим пример, в котором применяются потоки данных. В приведенной ниже программе сначала создается списочный массив myList типа ArrаyList , содержащий коллекцию целочисленных значений, которые автоматически упаковываются в объекты ссылочного типа Integer . Затем в этой программе получается поток данных, использующий массив myList в качестве источника данных. А далее в ней демонстрируются различные потоковые операции.
import java.util.*;
import java.util.stream.*;
classStreamDemo {
public static void main(String[] args) {
}
}