В этом посте представлено применение 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? trueAll movies rated PG? falseAny movies rated PG? trueAny 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 1980Youngest 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 . |