JavaRush /Java блог /Random /Непростые простые потоки
Oleg Savenko
41 уровень
Одесса

Непростые простые потоки

Статья из группы Random
С каждой новой версией Java становится все богаче и богаче. Хотя зачастую основа богатств заложена в более ранних версиях, но язык продолжал совершенствоваться как в методологии, так и в реализации. Коллекции в Java не являются исключением. Основной каркас коллекций появился в версии J2SE 1.2 и продолжал развиваться, претерпевая изменения, которые больше радовали, нежели огорчали. С выходом JDK 5 коллекции стали удобней, быстрее, а работать с ними стало легче. Это привело к тому, что программисты начали более интенсивней их эксплуатировать. Выработались некие паттерны по работе с ними, которые без всякого сомнения эффективны. Но с появлением JDK 8 коллекции снова стали лучше, а лучше они стали благодаря потокам. Очевидно, благодаря тому, что коллекцию можно представить в виде потоков, будет меняться и методика работы с ними. Поэтому я хочу показать, как привычные и понятные решения c коллекциями становятся еще проще.

Пример 1. Проще не бывает

Конечно начнем с самого простого, пробежимся по всем элементам коллекции и выведем все элементы.

// создадим и заполним список
   List<Integer> list = new ArrayList<>();
   Collections.addAll(list, 1, 5, 6, 11, 3, 15, 7, 8);
   // а теперь
   // быстрый for по всем элементам, только для коллекций
   for (Integer i:list){
       System.out.println(i);
   }
   //но мы уже живем в JDK 8
   //а значит нужно так
   list.stream().forEach(System.out::println);
Как вы заметили, есть новый синтаксис, который, на мой взгляд, новичка в Java, гораздо проще. Итак, что видно в новом синтаксисе:

берем_список(list).превращаем_в_поток(stream).перебираем_все_элементы(forEach)(тут_интуитивно_понятно)
System.out::println — ссылка на статический метод, которая выводит строку на консоль Вместо ссылки на статический метод можно использовать немного другую, пока менее понятную запись:

list.stream().forEach(i -> System.out.println(i));
в этой записи используется лямбда-выражения. И да, чтобы научится работать с потоками, нужно будет выучить лямбда-выражения – они прекрасны. Далее я не буду показывать как работать с коллекциями используя только потоки, рассчитывая на то, что с традиционными способами вы познакомились в ходе курса.

Пример 2. Найдем четные значения в списке и выведем их в консоль


list.stream().filter(i -> i%2==0).forEach(System.out::println);
Вся задача решена в одну строчку. Надеюсь вам нравится работать в одну строчку. С помощью метода filter мы профильтровали поток и то что осталось вывели на консоль. Фильтр очень мощная штука, которая способна помочь в самых неожиданных случаях. Давайте поизвращаемся и изменим условия задачи. Например, нам нужно посчитать сколько четных чисел в списке:

long count = list.stream().filter(i -> i%2==0).count();
И снова в одну строчку. Как-то кажется все просто. В фильтре мы использовали лямбда-выражение, тем самым поместив в новый поток только четные числа, а потом к новому потоку применили count, который и посчитал сколько элементов в новом потоке.

Пример 3. Посчитаем сколько слов в списке имеет длину 5 символов

Поиграли с целыми числами, теперь поиграем со словами.

List<String> list = new ArrayList<>();
Collections.addAll(list, "разые", "слова", "интересные", "и", "не", "очень");

System.out.println(list.stream().filter(w -> w.length() == 5).count());
Мы снова воспользовались фильтром. В фильтре, с помощью лямбда-выражения вывели новый поток, а далее понятный count посчитал, сколько в новом потоке элементов.

Пример 4. Вывести уникальные слова

Знакомая задача, когда мы прочитали в коллекцию из файла много разных слов, а теперь нам нужны только уникальные.

List<String> list = new ArrayList<>();
Collections.addAll(list, "Вася", "Таня", "Оля", "Вася", "Оля", "Сергей");

list.stream().distinct().forEach(System.out::println);
Основное действие было сделано над потоком с помощью distinct. Далее я предлагаю взглянуть на некоторые наши задачи из курса с помощью потоков

Пример 5. Длинные слова

На 9-м уровне Java Core, в 11 лекции есть интересная задача, в ней нужно записать через запятую в Файл2 слова, длина которых строго больше 6. Какой бы огород не городили, я предлагаю такой ход решения:
  1. Из файла источника читаем все слова в список.

  2. Потом выполняем следующую строку

    
    Optional<String> rezult = list.stream()
    				.filter(w->w.length()>6)
    				.reduce((w1,w2)->w1+", "+w2);
    
  3. result.get() записываем в файл.

Вот такое интересное решение с помощью потоков. Фильтр профильтровал, а с помощью reduce и регулярного выражения сформировали необходимую строку.

Пример 6. Слова с цифрами

Записать через пробел в Файл2 все слова, которые содержат цифры, например, а1 или abc3d. Это тоже условие из нашего задачника, как вы догадались, решение простое.

Optional<String> rezult = list.stream()
				.filter(w->w.matches(".*?\\d+.*?"))
				.reduce((w1,w2)->w1+" "+w2);
Профильтровали поток с помощью регулярного выражения, а потом reduce и лямбда-выражение. Как много общего с предыдущей задачей.

Пример 7. Выделяем числа

Вся задача звучит так:
  • Считать с консоли 2 имени файла.
  • Вывести во второй файл все числа, которые есть в первом файле.
  • Числа выводить через пробел.
  • Закрыть потоки.
Для основной задачи, а это запись чисел в строку через пробел, для дальнейшей записи в файл, я предлагаю применить поток, выглядить это будет так:

Optional<String> rezult = list.stream().filter(w->w.matches("\\d+"))
				.reduce((w1,w2)->w1+" "+w2);
System.out.println(rezult.get());
При помощи потоков задача решается очень простым способом. Но, о чудо, сравните это решение с двумя предыдущими самостоятельно. Завершая статью, я хочу Вам признаться: ребята, я жульничал, когда подбирал примеры. Дело в том, что я выбрал наиболее простые примеры, с целью показать Вам на знакомых задачах незнакомую тему. Конечно же, потоки используются как для простых, так и для более сложных задач. Но как подчеркнул в своей книге Хорстман «Потоки данных действуют по принципу «что, а не как делать.», а значит многие, ранее сложные задачи, могут стать более простыми. Не знаю как Вам, но мне потоки понравились, надеюсь, я не отбил у Вас охоту их поучить. И еще кое-что поясню:
  1. Я не ставил себе целью научить читателя использовать потоки в коллекциях, не настолько я опытный наставник. Я хотел показать Вам, что потоки это просто и очень интересно! Вообще это необходимость.
  2. Чем больше я понимаю потоки, тем больше я вижу задач, где они решают все проблемы из задачника курса, а значит, потоки придумали не напрасно.
  3. С потоками не только просто работать, но они еще имеют важное преимущество — при работе с большими массивами данных потоки чаще производительней.
PS. Про ВСЕ ПРОБЛЕМЫ я пошутил. Рекомендую: Java библиотека профессионала. Том 2 Расширенные средства программирования. Кей Хорстманн. У меня десятое издание.
Комментарии (4)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Евгений Шевцов Уровень 17
9 октября 2019
"//а значит нужно так list.stream().forEach(System.out::println);" а можно и так list.forEach(System.out::println);
Сергеев Виктор Уровень 40 Master
28 апреля 2019
есть хорошая практика, писать стримы в столбик. стрим .действиеСтрима(что сделать) .действиеСтрима(что сделать) т.е. в вашем случае :

Optional<String> rezult = list.stream()
                                 .filter(w->w.matches("\\d+"))
				                 .reduce((w1,w2)->w1+" "+w2);