← Назад к вопросам

Что такое функциональное программирование?

1.8 Middle🔥 141 комментариев
#Stream API и функциональное программирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Функциональное программирование в Java

Функциональное программирование (FP) — это парадигма программирования, которая рассматривает вычисления как вычисление математических функций, избегая изменения состояния и изменяемых данных. FP подчёркивает использование чистых функций, неизменяемости и избежание побочных эффектов.

Основные принципы функционального программирования

1. Чистые функции (Pure Functions)

Чистая функция — это функция, которая:

  • Имеет один и только один результат для каждого входа
  • Не имеет побочных эффектов
  • Не зависит от внешнего состояния
public class PureFunctionsExample {
    // ХОРОШО: чистая функция
    public static int add(int a, int b) {
        return a + b;  // Всегда возвращает один результат для одних входов
    }
    
    // ПЛОХО: нечистая функция (побочный эффект - печать)
    public static void printAdd(int a, int b) {
        System.out.println(a + b);  // Побочный эффект
    }
    
    // ПЛОХО: нечистая функция (зависит от состояния)
    private static int counter = 0;
    public static int incrementCounter() {
        return ++counter;  // Зависит от внешнего состояния
    }
}

2. Неизменяемость (Immutability)

Данные не должны изменяться после создания:

// ПЛОХО: изменяемый класс
public class MutableUser {
    private String name;
    private int age;
    
    public void setName(String name) {
        this.name = name;  // Изменяемое состояние
    }
}

// ХОРОШО: неизменяемый класс
public record ImmutableUser(String name, int age) {
    // Данные задаются при создании и не могут быть изменены
}

public class ImmutableUserClass {
    private final String name;
    private final int age;
    
    public ImmutableUserClass(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return name;
    }
    
    public int getAge() {
        return age;
    }
    
    // Вместо setter создаём новый объект
    public ImmutableUserClass withAge(int newAge) {
        return new ImmutableUserClass(this.name, newAge);
    }
}

3. Функции высшего порядка (Higher-Order Functions)

Функции, которые принимают или возвращают другие функции:

import java.util.function.Function;
import java.util.function.Predicate;
import java.util.Arrays;
import java.util.List;

public class HigherOrderFunctions {
    public static void main(String[] args) {
        // Функция как параметр
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        
        // Передаём функцию в метод
        List<Integer> doubled = map(numbers, x -> x * 2);
        System.out.println(doubled);  // [2, 4, 6, 8, 10]
        
        // Функция, возвращающая функцию
        Function<Integer, Integer> multiplier = createMultiplier(3);
        System.out.println(multiplier.apply(5));  // 15
    }
    
    // Высшего порядка: принимает функцию как параметр
    private static <T, R> List<R> map(List<T> list, Function<T, R> function) {
        List<R> result = new java.util.ArrayList<>();
        for (T item : list) {
            result.add(function.apply(item));
        }
        return result;
    }
    
    // Высшего порядка: возвращает функцию
    private static Function<Integer, Integer> createMultiplier(int factor) {
        return x -> x * factor;
    }
}

4. Использование Lambda выражений

Лямбда-выражения позволяют писать функции как значения:

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class LambdaExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
        
        // Lambda выражение вместо анонимного класса
        List<Integer> evenNumbers = filter(numbers, n -> n % 2 == 0);
        System.out.println(evenNumbers);  // [2, 4, 6]
    }
    
    private static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
        List<T> result = new java.util.ArrayList<>();
        for (T item : list) {
            if (predicate.test(item)) {
                result.add(item);
            }
        }
        return result;
    }
}

5. Stream API

Functional API для обработки коллекций:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamAPIExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
        
        // Функциональный подход
        List<Integer> result = numbers.stream()
                .filter(n -> n % 2 == 0)           // Фильтрация (Predicate)
                .map(n -> n * n)                   // Преобразование (Function)
                .collect(Collectors.toList());     // Сбор результата
        
        System.out.println(result);  // [4, 16, 36]
        
        // Более сложный пример
        String output = numbers.stream()
                .filter(n -> n > 2)
                .map(Object::toString)
                .collect(Collectors.joining(", "));
        System.out.println(output);  // "3, 4, 5, 6"
    }
}

6. Рекурсия вместо циклов

Functional style предпочитает рекурсию:

public class RecursionExample {
    // Imperative (императивный стиль)
    public static int sumImperative(int[] numbers) {
        int sum = 0;
        for (int n : numbers) {
            sum += n;  // Изменяемое состояние
        }
        return sum;
    }
    
    // Functional (функциональный стиль)
    public static int sumFunctional(int[] numbers) {
        return sumHelper(numbers, 0, 0);
    }
    
    private static int sumHelper(int[] numbers, int index, int acc) {
        if (index >= numbers.length) {
            return acc;  // Базовый случай
        }
        return sumHelper(numbers, index + 1, acc + numbers[index]);  // Рекурсия
    }
    
    // Или с Stream API
    public static int sumStream(int[] numbers) {
        return java.util.Arrays.stream(numbers).sum();
    }
}

7. Избежание побочных эффектов

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class SideEffectsExample {
    // ПЛОХО: много побочных эффектов
    public static void processDataBad(List<Integer> numbers) {
        for (int i = 0; i < numbers.size(); i++) {
            numbers.set(i, numbers.get(i) * 2);  // Изменяет исходный список
        }
        System.out.println(numbers);  // Побочный эффект: печать
    }
    
    // ХОРОШО: чистая функция, возвращает новый список
    public static List<Integer> processDataGood(List<Integer> numbers) {
        return numbers.stream()
                .map(n -> n * 2)
                .collect(java.util.stream.Collectors.toList());
        // Не изменяет исходный список, возвращает новый
    }
    
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3));
        List<Integer> result = processDataGood(numbers);
        
        System.out.println("Original: " + numbers);  // [1, 2, 3]
        System.out.println("Result: " + result);     // [2, 4, 6]
    }
}

8. Compose и Pipe функции

Комбинирование функций:

import java.util.function.Function;

public class FunctionCompositionExample {
    public static void main(String[] args) {
        Function<Integer, Integer> double_fn = x -> x * 2;
        Function<Integer, Integer> add_three = x -> x + 3;
        Function<Integer, Integer> square = x -> x * x;
        
        // Compose: выполняет сначала double_fn, потом add_three
        Function<Integer, Integer> composed = double_fn.andThen(add_three);
        System.out.println(composed.apply(5));  // (5 * 2) + 3 = 13
        
        // Цепочка преобразований
        Function<Integer, Integer> pipeline = double_fn.andThen(add_three).andThen(square);
        System.out.println(pipeline.apply(5));  // ((5 * 2) + 3)^2 = 169
    }
}

Преимущества функционального программирования

  • Предсказуемость: чистые функции имеют предсказуемое поведение
  • Тестируемость: легче тестировать чистые функции
  • Параллелизм: неизменяемые данные можно безопасно обрабатывать параллельно
  • Отладка: меньше побочных эффектов — проще отлаживать
  • Модульность: функции можно легко комбинировать
  • Повторное использование: функции легче переиспользовать

Функциональные интерфейсы в Java

import java.util.function.*;

public class FunctionalInterfacesExample {
    public static void main(String[] args) {
        // Function<T, R>: применяет функцию
        Function<Integer, Integer> square = x -> x * x;
        System.out.println(square.apply(5));  // 25
        
        // Predicate<T>: проверка условия
        java.util.function.Predicate<Integer> isEven = n -> n % 2 == 0;
        System.out.println(isEven.test(4));  // true
        
        // Consumer<T>: потребляет значение (побочный эффект)
        java.util.function.Consumer<String> print = System.out::println;
        print.accept("Hello");  // Hello
        
        // Supplier<T>: поставляет значение
        java.util.function.Supplier<String> supplier = () -> "Generated";
        System.out.println(supplier.get());  // Generated
        
        // BiFunction<T, U, R>: функция с двумя параметрами
        java.util.function.BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
        System.out.println(add.apply(3, 5));  // 8
    }
}

Заключение

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

Что такое функциональное программирование? | PrepBro