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) {
    }
}

results matching ""

    No results matching ""