1. Нововведення в Java 8: функціональне програмування
У версії Java 8 з'явилася потужна підтримка функціонального програмування. Правду кажучи, довгоочікувана підтримка функціонального програмування. Тепер код можна писати швидше, натомість читати — складніше 🙂
Перед вивченням функціонального програмування в Java рекомендуємо як слід розібратися в трьох речах:
- ООП, успадкування та інтерфейси.
- Дефолтна реалізація методів в інтерфейсі.
- Внутрішні й анонімні класи.
Добра новина — навіть не знаючи всього цього, можна користуватися багатьма можливостями функціонального програмування в 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()
, відбудеться таке:
- Об'єкт
BufferedReader
викличе методread()
для об'єктаInputStreamReader
- Об'єкт
InputStreamReader
викличе методread()
для об'єктаFileInputStream
- Об'єкт
FileInputStream
почне читати дані з файлу
Тобто ніякого руху даних у ланцюжку потоків не буде, доки ми не почнемо викликати методи типу read()
або readLine()
. Саме лише конструювання ланцюжка потоків дані цим ланцюжком не передає. Потоки не зберігають дані, а тільки читають їх з інших потоків.
Колекції й потоки
Починаючи з Java 8 з'явилася можливість отримати потік для читання даних із колекцій (і не тільки з них). Але й це ще не найцікавіше. Насправді з'явилася можливість легко й просто конструювати складні ланцюжки потоків даних; водночас код, який раніше потребував 5–10 рядків, тепер можна було записати 1-2 рядками.
Приклад — пошук рядка максимальної довжини у списку рядків:
Пошук рядка максимальної довжини |
---|
|
|
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<Тип>
ніякого руху даних не відбувається. Ми просто отримали об'єкт-потік для того, щоб почати будувати ланцюжок із потоків-даних.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ