В этом посте представлено применение JDK 8 — представленных потоков с коллекциями для более краткого выполнения обычно желаемой функциональности, связанной с коллекциями . Попутно будет продемонстрировано и кратко объяснено несколько ключевых аспектов использования потоков Java . Обратите внимание, что хотя потоки JDK 8 обеспечивают потенциальные преимущества в производительности благодаря поддержке распараллеливания , это не тема этого поста.
Образец коллекции и записи коллекции
Для целей этого поста экземпляры Movie
будут храниться в коллекции. Следующий фрагмент кода предназначен для простого класса Movie
используемого в этих примерах.
Movie.java
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
package dustin.examples.jdk8.streams; import java.util.Objects; /** * Basic characteristics of a motion picture. * * @author Dustin */ public class Movie { /** Title of movie. */ private String title; /** Year of movie's release. */ private int yearReleased; /** Movie genre. */ private Genre genre; /** MPAA Rating. */ private MpaaRating mpaaRating; /** imdb.com Rating. */ private int imdbTopRating; public Movie( final String newTitle, final int newYearReleased, final Genre newGenre, final MpaaRating newMpaaRating, final int newImdbTopRating) { this .title = newTitle; this .yearReleased = newYearReleased; this .genre = newGenre; this .mpaaRating = newMpaaRating; this .imdbTopRating = newImdbTopRating; } public String getTitle() { return this .title; } public int getYearReleased() { return this .yearReleased; } public Genre getGenre() { return this .genre; } public MpaaRating getMpaaRating() { return this .mpaaRating; } public int getImdbTopRating() { return this .imdbTopRating; } @Override public boolean equals(Object other) { if (!(other instanceof Movie)) { return false ; } final Movie otherMovie = (Movie) other; return Objects.equals( this .title, otherMovie.title) && Objects.equals( this .yearReleased, otherMovie.yearReleased) && Objects.equals( this .genre, otherMovie.genre) && Objects.equals( this .mpaaRating, otherMovie.mpaaRating) && Objects.equals( this .imdbTopRating, otherMovie.imdbTopRating); } @Override public int hashCode() { return Objects.hash( this .title, this .yearReleased, this .genre, this .mpaaRating, this .imdbTopRating); } @Override public String toString() { return "Movie: " + this .title + " (" + this .yearReleased + "), " + this .genre + ", " + this .mpaaRating + ", " + this .imdbTopRating; } } |
Несколько экземпляров Movie
помещаются в набор Java. Код, который делает это, показан ниже, потому что он также показывает значения, установленные в этих случаях. Этот код объявляет «movies» как статическое поле класса, а затем использует статический блок инициализации, чтобы заполнить это поле пятью экземплярами Movie
.
Заполнение фильмов с помощью экземпляров класса Movie
01
02
03
04
05
06
07
08
09
10
11
12
|
private static final Set<Movie> movies; static { final Set<Movie> tempMovies = new HashSet<>(); tempMovies.add( new Movie( "Raiders of the Lost Ark" , 1981 , Genre.ACTION, MpaaRating.PG, 31 )); tempMovies.add( new Movie( "Star Wars: Episode V - The Empire Strikes Back" , 1980 , Genre.SCIENCE_FICTION, MpaaRating.PG, 12 )); tempMovies.add( new Movie( "Inception" , 2010 , Genre.SCIENCE_FICTION, MpaaRating.PG13, 13 )); tempMovies.add( new Movie( "Back to the Future" , 1985 , Genre.SCIENCE_FICTION, MpaaRating.PG, 49 )); tempMovies.add( new Movie( "The Shawshank Redemption" , 1994 , Genre.DRAMA, MpaaRating.R, 1 )); movies = Collections.unmodifiableSet(tempMovies); } |
Первый взгляд на потоки JDK 8 с фильтрацией
Одним из типов функциональности, обычно выполняемой в коллекциях, является фильтрация. В следующем листинге кода показано, как отфильтровать «фильмы», Set
для всех фильмов с рейтингом PG. Я выделю некоторые наблюдения, которые можно сделать из этого кода после листинга.
Фильтрация фильмов с рейтингом PG
01
02
03
04
05
06
07
08
09
10
11
12
|
/** * Demonstrate using .filter() on Movies stream to filter by PG ratings * and collect() as a Set. */ private void demonstrateFilteringByRating() { printHeader( "Filter PG Movies" ); final Set<Movie> pgMovies = movies.stream().filter(movie > movie.getMpaaRating() == MpaaRating.PG) .collect(Collectors.toSet()); out.println(pgMovies); } |
Одна вещь, которую этот первый пример включает в себя и во всех примерах в этом посте, — это вызов метода stream () в коллекции. Этот метод возвращает объект, реализующий интерфейс java.util.Stream . Каждый из этих возвращаемых потоков использует коллекцию, для которой вызывается метод stream()
качестве источника данных. Все операции на этом этапе выполняются в Stream
а не в коллекции, которая является источником данных для Stream
.
В приведенном выше листинге кода метод filter ( Predicate ) вызывается в Stream
основе Set
«movies». В этом случае Predicate
задается лямбда-выражением movie -> movie.getMpaaRating() == MpaaRating.PG
. Это довольно читаемое представление говорит нам, что предикатом является каждый фильм в базовых данных, который имеет рейтинг MPAA PG.
Метод Stream.filter (Predicate) является промежуточной операцией , что означает, что он возвращает экземпляр Stream
который может быть использован другими операциями. В этом случае есть другая операция collect (Collector) , которая вызывается для Stream
возвращаемого Stream.filter(Predicate)
. Класс Collectors содержит множество статических методов, каждый из которых предоставляет реализацию Collector, которая может быть предоставлена этому методу collect(Collector)
. В этом случае Collectors.toSet () используется для получения Collector
который будет указывать, что результаты потока должны быть расположены в Set
. Метод Stream.collect(Collector)
является терминальной операцией. Это означает, что это конец строки, и он НЕ возвращает экземпляр Stream
поэтому после выполнения этого сбора больше операций Stream
может быть выполнено.
Когда приведенный выше код выполняется, он генерирует вывод, подобный следующему:
1
2
3
4
|
=========================================================== = Filter PG Movies =========================================================== [Movie: Raiders of the Lost Ark ( 1981 ), ACTION, PG, 31 , Movie: Back to the Future ( 1985 ), SCIENCE_FICTION, PG, 49 , Movie: Star Wars: Episode V - The Empire Strikes Back ( 1980 ), SCIENCE_FICTION, PG, 12 ] |
Фильтрация для одного (первого) результата
01
02
03
04
05
06
07
08
09
10
11
|
/** * Demonstrate using .filter() on Movies stream to filter by #1 imdb.com * rating and using .findFirst() to get first (presumably only) match. */ private void demonstrateSingleResultImdbRating() { printHeader( "Display One and Only #1 IMDB Movie" ); final Optional<Movie> topMovie = movies.stream().filter(movie -> movie.getImdbTopRating() == 1 ).findFirst(); out.println(topMovie.isPresent() ? topMovie.get() : "none" ); } |
Этот пример имеет много общего с предыдущим примером. Как и в предыдущем листинге кода, этот листинг показывает использование Stream.filter(Predicate)
, но на этот раз предикатом является лямбда-выражение movie -> movie.getImdbTopRating() == 1)
. Другими словами, Stream
полученный из этого фильтра, должен содержать только экземпляры Movie
с методом getImdbTopRating()
возвращающим число 1. Затем завершается операция Stream.findFirst () для Stream
возвращаемого Stream.filter(Predicate)
, Это возвращает первую запись, обнаруженную в потоке, и, поскольку в наших базовых экземплярах Set
of Movie
только один экземпляр с рейтингом IMDb Top 250 , равным 1, это будет первая и единственная запись, доступная в потоке в результате фильтра.
Когда этот листинг кода выполняется, его вывод выглядит так, как показано ниже:
1
2
3
4
|
=========================================================== = Display One and Only # 1 IMDB Movie =========================================================== Movie: The Shawshank Redemption ( 1994 ), DRAMA, R, 1 |
Следующий листинг кода иллюстрирует использование Stream.map (Function) .
01
02
03
04
05
06
07
08
09
10
|
/** * Demonstrate using .map to get only specified attribute from each * element of collection. */ private void demonstrateMapOnGetTitleFunction() { printHeader( "Just the Movie Titles, Please" ); final List<String> titles = movies.stream().map(Movie::getTitle).collect(Collectors.toList()); out.println(titles.size() + " titles (in " + titles.getClass() + "): " + titles); } |
Метод Stream.map(Function)
воздействует на Stream
которого он вызывается (в нашем случае это Stream
на основе базового Set
объектов Movie
), и применяет предоставленную функцию к этому Steam
чтобы вернуть новый Stream
, полученный в результате применение этой Function
к исходному Stream
. В этом случае, Function
представлена Movie::getTitle
, которая является примером ссылки на метод, введенный в JDK 8. Я мог бы использовать лямбда-выражение movie -> movie.getTitle()
вместо ссылки на метод Movie::getTitle
для тех же результатов. Документация « Ссылки на метод» объясняет, что это именно та ситуация, на которую предназначена ссылка на метод:
Вы используете лямбда-выражения для создания анонимных методов. Иногда, однако, лямбда-выражение делает только вызов существующего метода. В этих случаях часто яснее ссылаться на существующий метод по имени. Ссылки на методы позволяют вам сделать это; они являются компактными, легко читаемыми лямбда-выражениями для методов, которые уже имеют имя.
Как вы можете догадаться из его использования в приведенном выше коде, Stream.map(Function)
является промежуточной операцией. Этот листинг кода применяет завершающую операцию Stream.collect(Collector)
же, как это делали предыдущие два примера, но в этом случае это Collectors.toList (), который передается ему, и поэтому результирующая структура данных представляет собой List, а не Set
,
Когда приведенный выше листинг кода выполняется, его вывод выглядит так:
1
2
3
4
|
=========================================================== = Just the Movie Titles, Please =========================================================== 5 titles (in class java.util.ArrayList): [Inception, The Shawshank Redemption, Raiders of the Lost Ark, Back to the Future, Star Wars: Episode V - The Empire Strikes Back] |
Операции сокращения (до одного логического) anyMatch и allMatch
В следующем примере не используются Stream.filter(Predicate)
, Stream.map(Function)
или даже завершающая операция Stream.collect(Collector)
, которые использовались в большинстве предыдущих примеров. В этом примере операции сокращения и завершения Stream.allMatch (Predicate) и Stream.anyMatch (Predicate) применяются непосредственно к Stream
на основе нашего Set
объектов Movie
.
01
02
03
04
05
06
07
08
09
10
11
|
/** * Demonstrate .anyMatch and .allMatch on stream. */ private void demonstrateAnyMatchAndAllMatchReductions() { printHeader( "anyMatch and allMatch" ); out.println( "All movies in IMDB Top 250? " + movies.stream().allMatch(movie -> movie.getImdbTopRating() < 250 )); out.println( "All movies rated PG? " + movies.stream().allMatch(movie -> movie.getMpaaRating() == MpaaRating.PG)); out.println( "Any movies rated PG? " + movies.stream().anyMatch(movie -> movie.getMpaaRating() == MpaaRating.PG)); out.println( "Any movies not rated? " + movies.stream().anyMatch(movie -> movie.getMpaaRating() == MpaaRating.NA)); } |
Листинг кода демонстрирует, что Stream.anyMatch(Predicate)
и Stream.allMatch(Predicate)
каждый возвращает логическое значение, указывающее, поскольку их имена, соответственно, подразумевают, имеет ли Stream
хотя бы одну запись, соответствующую предикату, или все записи, соответствующие предикату. В этом случае все фильмы взяты из Топ 250 imdb.com, так что «allMatch» вернет true
. Однако не все фильмы имеют рейтинг PG, поэтому «allMatch» возвращает false
. Поскольку как минимум одному фильму присвоен рейтинг PG, «anyMatch» для предиката рейтинга PG возвращает значение « true
, а «anyMatch» для предиката оценки «Н / Д» возвращает значение « false
поскольку ни один фильм в базовом Set
имел рейтинга MpaaRating.NA
. Результат выполнения этого кода показан ниже.
1
2
3
4
5
6
7
|
=========================================================== = anyMatch and allMatch =========================================================== All movies in IMDB Top 250 ? true All movies rated PG? false Any movies rated PG? true Any movies not rated? false |
Легкая идентификация минимума и максимума
Последний пример применения возможностей Stream
для манипулирования коллекциями в этом посте демонстрирует использование Stream.reduce (BinaryOperator) с двумя различными экземплярами BinaryOperator : Integer :: min и Integer :: max .
01
02
03
04
05
06
07
08
09
10
|
private void demonstrateMinMaxReductions() { printHeader( "Oldest and Youngest via reduce" ); // Specifying both Predicate for .map and BinaryOperator for .reduce with lambda expressions final Optional<Integer> oldestMovie = movies.stream().map(movie -> movie.getYearReleased()).reduce((a,b) -> Integer.min(a,b)); out.println( "Oldest movie was released in " + (oldestMovie.isPresent() ? oldestMovie.get() : "Unknown" )); // Specifying both Predicate for .map and BinaryOperator for .reduce with method references final Optional<Integer> youngestMovie = movies.stream().map(Movie::getYearReleased).reduce(Integer::max); out.println( "Youngest movie was released in " + (youngestMovie.isPresent() ? youngestMovie.get() : "Unknown" )); } |
Этот Integer.min(int,int)
пример иллюстрирует использование Integer.min(int,int)
для поиска самого старого фильма в базовом Set
и использование Integer.max(int,int)
для поиска самого нового фильма в Set
. Для этого сначала Stream.map
использовать Stream.map
чтобы получить новый Stream
Stream.map
предоставленный годом выпуска каждого Movie
в исходном Stream
. Этот Stream
Integer
значений затем выполняет Stream.reduce(BinaryOperation)
со статическими методами Integer
используемыми в качестве BinaryOperation
.
Для этого листинга кода я намеренно использовал лямбда-выражения для Predicate
и BinaryOperation
при вычислении самого старого фильма ( Integer.min(int,int)
) и использовал ссылки на методы вместо лямбда-выражений для Predicate
и BinaryOperation
используемых при вычислении самого нового фильма ( Integer.max(int,int)
). Это доказывает, что лямбда-выражения или ссылки на методы могут использоваться во многих случаях.
Результат выполнения вышеуказанного кода показан ниже:
1
2
3
4
5
|
=========================================================== = Oldest and Youngest via reduce =========================================================== Oldest movie was released in 1980 Youngest movie was released in 2010 |
Вывод
JDK 8 Streams представляет мощный механизм для работы с коллекциями. Этот пост был посвящен удобочитаемости и краткости, которые дает работа с Streams по сравнению с работой с коллекциями напрямую, но Streams также предлагает потенциальные преимущества в производительности. В этом посте предпринята попытка использовать общие коллекции, обрабатывающие идиомы, в качестве примеров краткости, которую Streams привносит в Java. Попутно обсуждались также некоторые ключевые концепции, связанные с использованием потоков JDK. Наиболее сложными аспектами использования потоков JDK 8 являются привыкание к новым концепциям и новому синтаксису (например, к лямбда-выражениям и ссылкам на методы), но они быстро изучаются после игры с несколькими примерами. Разработчик Java, обладающий даже небольшим опытом работы с понятиями и синтаксисом, может исследовать методы Stream API для гораздо более длинного списка операций, которые могут выполняться над потоками (и, следовательно, с коллекциями, лежащими в основе этих потоков), чем показано в этом посте.
Дополнительные ресурсы
Цель этого поста состояла в том, чтобы дать легкий первый взгляд на потоки JDK 8 на основе простых, но довольно распространенных примеров манипуляции коллекциями. Чтобы глубже погрузиться в потоки JDK 8 и больше идей о том, как потоки JDK 8 облегчают манипулирование коллекциями, см. Следующие статьи:
- Обработка данных с помощью потоков Java SE 8, часть 1
- Часть 2. Обработка данных с помощью потоков Java SE 8
- Учебник Бенджамина Винтерберга по Java 8 Stream
- Введение Дэвида Хартвелда в Stream API
- Начало работы с потоками Java 8
- Сборник учебников по Java по операциям агрегирования потоков
- Учебник по учебникам по Java, посвященный сокращению потоков
- Сборник учебников по Java о параллельности потоков
- Синтаксис лямбда-выражений
- Ссылки на метод
Ссылка: | Функциональность потоковых коллекций в JDK 8 от нашего партнера JCG Дастина Маркса в блоге Inspired by Actual Events . |