Функціональне програмування на Java
Функціональне програмування в Java
[ред.]Ця стаття про:
- Про застосування функціонального стилю програмування в мові Java.
- Про деякі базові патерни для роботи з колекціями даних з функціонального програмування в прикладах на Java.
- Про бібліотеку Google Collections.
Якщо ви програмуєте на мовах Java, C #, C + +, PHP, або будь-якому іншому ГО мовою, хотіли б познайомитися з функціональним програмуванням, але не має можливості / бажання вивчати Haskell / Scala / Lisp / Python, - ця стаття спеціально для вас.
Тим, хто знайомий з функціональним програмуванням, але ніколи не застосовував його в Java, думаю, це буде теж цікаво.
Конструкція «функція» в мові Java
[ред.]Що таке функціональне програмування? Якщо в двох словах, то функціональне програмування - це програмування, в якому функції є об'єктами, і їх можна присвоювати змінним, передавати в якості аргументів інших функцій, повертати як результат від функцій і т. п. Переваги, які розкриває така можливість, будуть зрозумілі трохи пізніше. Поки нам треба розібратися, як в Java можна використовувати саму конструкцію «функція».
Як відомо, в Java немає функцій, там є тільки класи, методи та об'єкти класів. Зате в Java є анонімні класи, тобто класи без імені, які можна оголошувати прямо в коді будь-якого методу. Цим ми і скористаємося. Для початку оголосимо такий інтерфейс:
public final interface Function<F, T>
{
T apply(F from);
}
Тепер в коді якогось методу ми можемо оголосити анонімну реалізацію цього інтерфейсу:
public static void main() {
// Оголошуємо "функцію", присвоюємо її змінну intToString.
Function<Integer, String> intToString = new Function<Integer, String>() {
@Override public String apply(Integer from) {
return from.toString();
}
};
intToString.apply(9000); // Викликаємо нашу функцію. Отримуємо рядок "9000".
}
Таку реалізацію ми й будемо називати «анонімної функцією». З точки зору функціонального програмування з нею можна робити все те ж саме, що і з функцією з функціональних мов: присвоювати змінним, передавати в якості аргументу інших функцій (і методам класів), одержувати в якості результату від функцій (і методів класів).
Тепер можна перейти до викладу деяких базових патернів функціонального програмування.
Робота з колекціями у функціональному стилі
[ред.]Припустимо, у нас є якась колекція цілих чисел. Ми хочемо їх вивести на екран у вигляді рядка, і кожне число в рядку буде розділено через кому. Нефункціональне рішення виглядало б приблизно так:
public String joinNumbers(Collection<? extends Integer> numbers) {
StringBuilder result = new StringBuilder();
boolean first = true;
for (Integer number : numbers) {
if (first)
first = false;
else
result.append(", ");
result.append(number);
}
return result.toString();
}
Для реалізації функціонального рішення нам буде потрібно спершу підготувати кілька функцій і методів. Будемо оголошувати їх як статичні поля класу:
public static final Function<Integer, String> INT_TO_STRING = ... // Вже реалізували вище
// Бере поелементно значення з колекції from, перетворює їх за допомогою функції<br /> transformer
// і повертає список результатів перетворення в тому ж порядку.
public static <F, T> List<T> map(Collection<F> from, Function<? super F,? extends T> <br /> transformer)
{
ArrayList<T> result = new ArrayList<T>();
for (F element : from)
result.add(transformer.apply(element));
return result;
}
// Бере колекцію довільних елементів і конкатенірует їх в рядок
public static <T> String join(Collection<T> from, String separator) {
StringBuilder result = new StringBuilder();
boolean first = true;
for (T element : from) {
if (first)
first = false;
else
result.append(separator);
result.append(element);
}
return result.toString();
}
Тепер наш метод joinNumbers буде виглядати наступний чином:
public String joinNumbers(Collection<? extends Integer> numbers) {
return join(map(numbers, INT_TO_STRING), ", ");
}
Метод реалізований рівно в один простий рядок.
public String joinNumbers(Collection<? extends Integer> numbers) {
return join(map(numbers, INT_TO_STRING), ", ");
}
Робота з колекціями за допомогою Google Collections
[ред.]Google якраз створили зручну бібліотеку з утілітнимі класами, що дозволяє працювати з колекціями в Java у функціональному стилі. Ось деякі з можливостей, які вона надає:
- Interface Function <F, T>. Інтерфейс, аналогічний наведений вище.
- Iterables.filter. Бере колекцію і функцію-предикат (функцію, яка повертає булево значення).
У відповідь повертає колекцію, яка містить усі елементи вихідної, на які зазначена функція
повернула true. Зручно, наприклад, якщо ми хочемо відсіяти з колекції всі парні числа:
Iterables.filter (numbers, IS_ODD); - Iterables.transform. Робить те ж саме, що функція map в прикладі вище.
- Functions.compose. Бере дві функції. Повертає нову функцію - їх композицію, тобто функцію,
яка отримує елемент, подає його в другу функцію, результат подає в першу функцію, і отриманий з
першої функції результат повертає користувачу. Композицію можна використовувати, наприклад, так:
Iterables.transform (numbers, Functions.compose (INT_TO_STRING, MULTIPLY_X_2));
У Google Collections звичайно є ще багато інших корисних речей як для функціонального програмування, так і для роботи з колекціями в імперативному стилі.