1. Нововведения в Java 8: Функциональное программирование

Вместе с выходом Java 8 в ней появилась мощная поддержка функционального программирования. Можно даже сказать, долгожданная поддержка функционального программирования. Код стал писаться быстрее, хотя читать его стало сложнее 🙂

Перед изучением функционального программирования в Java, рекомендуем хорошо разобраться в трех вещах:

  1. ООП, наследование и интерфейсы (1-2 уровни квеста Java Core).
  2. Дефолтная реализация методов в интерфейсе.
  3. Внутренние и анонимные классы.

Хорошая новость заключается в том, что без знания всего этого можно пользоваться многими возможностями функционального программирования в Java. Плохая новость — понять, как именно все устроено и как все работает, без тех же внутренних анонимных классов уже сложно.

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

Чтобы разобраться во всех нюансах функционального программирования в Java, нужны месяцы. Читать же такой код можно научиться за несколько часов. Поэтому предлагаем начать с малого. Да хоть с тех же потоков ввода-вывода.


2. Потоки ввода-вывода: цепочки потоков

Помните, когда-то вы изучали потоки ввода-вывода: InputStream, OutputStream, Reader, Writer и т.п.?

Были классы-потоки, которые читали данные из источников данных, такие как FileInputSteam, а были и промежуточные потоки данных, которые читали данные из других потоков, такие как InputStreamReader и BufferedReader.

Эти потоки можно было организовывать в цепочки обработки данных. Например, так:

FileInputStream input = new FileInputStream("c:\\readme.txt");
InputStreamReader reader = new InputStreamReader(input);
BufferedReader buff = new BufferedReader(reader);

String text = buff.readLine();

Важно отметить, что в первых нескольких строках кода мы просто конструируем цепочку из Stream-объектов, но реальные данные по этой цепочке потоков еще не передаются.

И только когда мы вызовем метод buff.readLine(), произойдет следующее:

  1. Объект BufferedReader вызовет метод read() у объекта InputStreamReader
  2. Объект InputStreamReader вызовет метод read() у объекта FileInputStream
  3. Объект FileInputStream начнет читать данные из файла

Т.е. никакого движения данных по цепочке потоков нет, пока мы не начали вызывать методы типа read() или readLine(). Само конструирование цепочки потоков данные по ним не гоняет. Потоки сами данные не хранят, а только читают из других.

Коллекции и потоки

Начиная с Java 8, появилась возможность получить поток для чтения данных у коллекций (и не только у них). Но и это еще не самое интересное. На самом деле появилась возможность легко и просто конструировать сложные цепочки потоков данных, при этом тот код, который раньше требовал 5-10 строк, теперь можно было записать в 1-2 строки.

Пример — находим строку максимальной длины в списке строк:

Поиск строки максимальной длины
ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list, "Привет", "как", "дела?");
String max = list.stream().max((s1, s2)-> s1.length()-s2.length()).get();
ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list, "Привет", "как", "дела?");
Stream<String> stream = list.stream();
Optional<String> optional = stream.max((s1, s2)-> s1.length()-s2.length());
String max = optional.get();

3. Интерфейс Stream

Расширенная поддержка потоков в Java 8 реализована с помощью интерфейса Stream<T>. Где T — это тип-параметр, обозначающий тип данных, которые передаются в потоке. Другими словами, поток полностью независим от типа данных, которые он передает.

Чтобы получить объект-поток у коллекции, достаточно вызвать у нее метод stream(). Выглядит этот код примерно так:

Stream<Тип> имя = коллекция.stream();
Получение потока из коллекции

При этом коллекция будет считаться источником данных потока, а объект типа Stream<Тип> – инструментом по получению данных из коллекции именно в виде потока данных.

ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list, "Привет", "как", "дела?");
Stream<String> stream = list.stream();

Кстати, можно получить поток не только у коллекции, но и у массива. Для этого нужно воспользоваться методом Arrays.stream(); Пример:

Stream<Тип> имя = Arrays.stream(массив);
Получение потока из массива

При этом массив будет считаться источником данных для потока имя.

Integer[] array = {1, 2, 3};
Stream<Integer> stream = Arrays.stream(array);

После создания объекта Stream<Тип> никакого движения данных не происходит. Мы просто получили объект-поток для того, чтобы начать строить цепочку из потоков-данных.