Функціональне програмування на 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 звичайно є ще багато інших корисних речей як для функціонального програмування, так і для роботи з колекціями в імперативному стилі.

Дивіться також

ред.