JavaRush /Java блог /Random /Лямбды и стримы, только практика, теории не будет
Сергеев Виктор
40 уровень

Лямбды и стримы, только практика, теории не будет

Статья из группы Random
Всем привет. По случаю конкурса я решил написать тут не статью, но небольшой урок. Он будет про лямбды и стримы (stream) в Java. Если вы уже знакомы и используете их, то мотайте сразу к концу статьи, там будет небольшая подборка задач с JavaRush на которых можно потренироваться. Нужна java 8 и выше, закалка от JR, будет мало подробностей и много непонятного, сильное желание разобраться. Начнем с того, что я не буду объяснять историю появлений лямбд и стримов, просто сам её не знаю. Знаю только то, что пришли они из функционального стиля программирования, в наш ООП’шный. За мой короткий опыт обучения, просто показывал, как и что, некоторым людям сложновато понять идею, поэтому просто запоминайте, как пишется, поймете потом.

Лямбды

Лямбды и стримы, только практика, теории не будет - 1Лямбды и стримы, только практика, теории не будет - 2Если вы совсем не знаете что такое лямбды, то: Лямбда выглядит так:

(a, b) -> a.compareTo(b)
(переменные) -> действие
Этого пока достаточно. Почитать теорию можно тут: ссылка раз, ссылка два, но мне кажется практика гораздо веселее. Предлагаю вам решить такую задачку: Напишите калькулятор 1 методом. Метод должен принимать 2 цифровых значения и кое-что ещё. Код ваш будет выглядеть примерно так:

class Lambda{
    public static void main (String[] args) {
	}
	
    public static double calculate(){
       	return null;
    }
}
Вам надо вписать 3 параметра в сигнатуру метода calculate, дописать 1 команду в return и протестировать вызов этого метода в main. Что должен уметь этот метод
  • Складывать;
  • умножать;
  • делить;
  • вычитать;
  • вычислять корень;
  • возводить в степень;
  • возводить в степень сумму аргументов поделенную на первое число + 117;
  • и все любые другие операции, которые сможете придумать.
Что нельзя использовать:
  • if-else;
  • char как указатель операции;
  • switch-case;
  • и все остальное что вам придет в голову.
Что можно использовать:
  • Только лямбды, задание то на них.
— Что? И это все? — Да это все, потому что дописать надо буквально 3 строки, если я подскажу хоть одну, остальные будут написаны на автомате. А при желании вы сможете нагуглить примеры, попытаться понять. Проверять конечны вы будете себя сами и если схитрите никто не узнает, но тогда зачем? Решив такую простенькую задачку, все мои 1,5 ученика обрели примерное понимание, что такое лямбды и как ими пользоваться. Это будет очень нужно для стримов. Если есть желание похвастаться результатом и узнать правильно ли вы сделали, код кидайте в личку. В комментарии не надо, туда можете кинуть интересные подсказки (но так, чтобы не слишком облегчать задачу). Повторюсь, решив этот пример, вы должны уже примерно понять, как пользоваться лямбдами.
Лямбды и стримы, только практика, теории не будет - 3
Теперь перейдем к стримам (java streams). Это не те стримы о которых ты, читатель возможно подумал. Нет это не inputStream и не OutputStream. Это другое, это интереснее. Стримы пришли на замену циклам, не полностью, но все же. Подаются они с девизом «не объясняй, как делать, объясняй что делать». Небольшой пример стрима:

	List<String> myList = Arrays.asList("a1", "a2", "b1", "c2", "c1");

	myList.stream()
	    .filter(s -> s.startsWith("c"))
	    .map(String::toUpperCase)
	    .sorted()
	    .forEach(System.out::println);
Что тут происходит? Добавим комментарии:

	myList.stream() // получить поток
	    .filter(s -> s.startsWith("c")) //отфильтровать значения, оставить те, что начинаются с «с»
	    .map(String::toUpperCase)  // преобразовать все значения, перевести в верхний регистр
	    .sorted() // отсортировать по порядку (дефолтный порядо)
	    .forEach(System.out::println); // вывести каждый элемент на экран
Сравним с обычным циклом:

List<String> toSort = new ArrayList<>();
for(String s : myList){
     if(s.startsWith("c")){
         toSort.add(s.toUpperCase());
     }
}

Collections.sort(toSort);

for(String s : toSort){
     System.ouy.println(s);
}
Когда читаешь код, все выглядит страшно, а с комментариями проще так? Это нормально, я их тоже долго не понимал. Ключ к пониманию – практика. Поэтому начинаем читать сторонние статьи и искать ответы на свои вопросы, можете их так же задавать тут в комментарии, полноценный ответ не дам, но укажу направление. Список задач от JavaRush, которые, я считаю, отлично подходят для практики стримов:
  • 2208 — можно решить 1 стримом и 1 return, т.е. тело метода будет начинаться с return и дальше будет 1 целый стрим. Требование StringBuilder опустим.

  • 1908 — так же можно решить 1 стримом и 1 return. Начиная с чтения файла. Запись в файл через стримы как сделать я не знаю (если это возможно), пока делаем ручками. Т.е. Открываем только 2 потока (консоль и запись в файл). Чтение файла производим через методы, которые вернут нам либо лист, либо сразу стрим (google и javadoc).

  • 1907 — по идее тоже можно решить в один стрим. На входе стрима имя файла, на выходе количество слов world.

На этом все. Если смогу напишу ещё уже просто рассказ. На мой взгляд, читать про что-то крутое без возможности это попробовать, как-то скучно, что ли. А после калькулятора и 3 задачек думаю вы уже неплохо ладите с лямбдами и стримами, чтобы почитать уже обо всех возможностях, если ещё не прочитали. UPD:
  • 1016 — немного извращенным способом можно решить в 1 стрим и 1 return;

  • 1821 — очень легко и в 1 стрим и 1 return.

    Эти 2 задачи познакомят вас с ещё одним методом стримов и ещё одним коллектором.

  • 1925 — можно одним стримом получить строку со словами и потом записать её в файл (можно ли писать в файл из стрима я не знаю)

Комментарии (24)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Daniyar Уровень 1
10 января 2021
1925

        try (BufferedReader reader = new BufferedReader(new FileReader(Path.of("...").toFile()))) {

            BufferedWriter writer = new BufferedWriter(new FileWriter(Path.of("...").toFile()));

            writer.write(reader.lines()
                    .flatMap(p -> Arrays.stream(p.split("\\n")))
                    .flatMap(p -> Arrays.stream(p.split("\\s+")))
                    .filter(p -> p.length() > 6)
                    .map(p -> p.replaceAll("\\s+", ", "))
                    .collect(Collectors.joining(",\n")));

            writer.close();
        }
Sergey Anisimov Уровень 29
11 ноября 2019
Помню в питоне впервые узнал про list compression.

list1 = [1,2,3,4,"az", "av", "d"]
print([i for i in list1 if str(i).startswith("a")])
В одну строчку можно из списка убрать все ненужные элементы. Да что хочешь можно с ними сделать. Можно даже ссылки парсить или вызывать дьявола для каждого элемента. Главное чтобы было понятно что ты делаешь в данный момент. Вот и в джаве смотрю есть нечто похожее? Или это по сути тоже самое? Я только месяц-полтора в программировании, так что если что-то не так сказал прошу ткнуть носом и разъяснить.
Sergey Уровень 35
27 октября 2019
наводка по первому заданию. следуй за BiFunction<T,U,R>
Riccio Уровень 35 Master
21 октября 2019
UPD: Кому нужна подсказка с решением - можно посмотреть по ссылке. Буду рад, если предложите иной вариант решения.
Riccio Уровень 35 Master
21 октября 2019
Копирование файла с помощью Stream API:

String inputFile = reader.readLine();
String outputFile = reader.readLine();

//1. копирование файла
Files.newOutputStream(Paths.get(outputFile))
 .write(Files.newInputStream(Paths.get(inputFile)).readAllBytes());

//2. копирование файла
Files.newInputStream(Paths.get(inputFile))
 .transferTo(new FileOutputStream(outputFile));
Vitaly Khan Уровень 40 Master
3 июня 2019
не уверен, что точно понял, какое решение предполагает автор, но я решил с использованием BiFunction... соответственно пришлось дописать только одну новую строку - для тестирования в main, плюс изменения в сигнатуру и после оператора return. тем, кто только начинает изучение лямбда, крайне рекомендую это видео в нем с самого нуля доходчиво объясняется, что такое лямбда, ссылки на метод (::). зачем это и откуда взялось.
21 февраля 2018
Я думал, это как доп обучение... Ну "Напишите калькулятор 1 методом. " не в моих силах с даннми знаниями по лямдам. И даже на меков, где рыть...
mb00mer Уровень 35
20 февраля 2018
На самом деле многие задачи, в которых - так или иначе - надо "бегать" по коллекциям, фильтровать их и т.д. можно решить через стримы. Только вот не везде это оправдано. Из последнего вспомнил задачу 2913, которую тоже попробовал решить через StreamAPI

    public static String getAllNumbersBetween(int a, int b) {
        return IntStream
                .rangeClosed(a > b ? b : a, a > b ? a : b)
                .boxed()
                .sorted( (i1, i2)-> a > b ? i2.compareTo(i1) : i1.compareTo(i2) )
                .map(String::valueOf)
                .collect(Collectors.joining(" "));
    }
, а затем сравнил по скорости с обычными циклами. Циклы в этом случае быстрее, иногда на порядок. Было бы интересно почитать - в каких случаях стоит использовать StreamAPI, а в каких нет.