Занятие № 5
Функциональное программирование в Java
Петров Андрей
Обо мне
Программирую 8 лет, в СИС – 5 лет.
Руководитель группы разработки.
#angular #typescript #sping #java #postgresql
Увлечения:
- Программирование
- Настольные игры
- Лазертаг
Roadmap
- Основы ФП
- Функции высшего порядка
- Лямбда-функции
- StreamAPI
- Optional
Откуда пошло ФП
Алан Тьюринг
Машина Тьюринга
Алонзо Чёрч
Лямбда-исчисление
Знакомьтесь: функция
// f(x, y) = x + y * 2
int f(int x, int y) {
return x + y * 2;
}
Как это нам поможет?
Отфильтруем список товаров по типу
List<Product> filterByType(
List<Product> products, ProductType type
) {
List<Product> result = new ArrayList<>();
for (int i = 0; i < products.size(); i++) {
Product product = products.get(i);
if (product.getType() == type) {
result.add(product);
}
}
return result;
}
Недостатки приведенного кода
- Много кода
- Слишком сложно
List<Product> filterByType(
List<Product> products, ProductType type
) {
List<Product> result = new ArrayList<>();
for (int i = 0; i < products.size(); i++) {
Product product = products.get(i);
if (product.getType() == type) {
result.add(product);
}
}
return result;
}
List<Product> filterByPrice(
List<Product> products, int threshold
) {
List<Product> result = new ArrayList<>();
for (int i = 0; i < products.size(); i++) {
Product product = products.get(i);
if (product.getPrice() > threshold) {
result.add(product);
}
}
return result;
}
List<Product> filterByProducer(
List<Product> products, String producer
) {
List<Product> result = new ArrayList<>();
for (int i = 0; i < products.size(); i++) {
Product product = products.get(i);
if (product.getProducer().equals(producer)) {
result.add(product);
}
}
return result;
}
Как можно улучшить?
Вариант цикла с foreach
List<Product> filterByType(
List<Product> products, ProductType type
) {
List<Product> result = new ArrayList<>();
for (Product product : products) {
if (product.getType() == type) {
result.add(product);
}
}
return result;
}
Класс-предикат
public interface Predicate {
boolean match(Product value);
}
public class ProductTypePredicate implements Predicate {
private ProductType type;
ProductTypePredicate(ProductType type) {
this.type = type;
}
@Override
boolean match(Product product) {
return product.getType() == this.type;
}
}
Фильтрация с предикатом
// Метод фильтрации товаров
List<Product> filter(List<Product> products, Predicate predicate) {
List<Product> result = new ArrayList<>();
for (Product product : products) {
// Меняем частную бизнес-логику на вызов предиката
if (predicate.match(product)) {
result.add(product);
}
}
return result;
}
Функция высшего порядка
Пример фильтрации с предикатом
List<Product> filterProductsByType(
List<Product> products, ProductType type
) {
Predicate predicate = new ProductTypePredicate(type);
return filter(products, predicate);
}
Убираем все лишнее
Вспомним предикат
public class ProductTypePredicate implements Predicate {
private ProductType type;
ProductTypePredicate(ProductType type) {
this.type = type;
}
@Override
boolean match(Product product) {
return product.getType() == this.type;
}
}
Идеально
List<Product> filterProductsByType(
List<Product> products, ProductType type
) {
return filter(products, item -> item.getType() == type);
}
Лямбда-функция
Predicate predicate = product -> item.getType() == type;
- Аргументы
- Разделитель
- Тело
Однострочная лямбда
Лямбда с блоком
Predicate<Product> predicate = (Product item) -> {
if (item.getPrice() > 100) {
return item.getType() == type;
}
return false;
};
Было
List<Product> filterByType(
List<Product> products, ProductType type
) {
List<Product> result = new ArrayList<>();
for (int i = 0; i < products.size(); i++) {
Product product = products.get(i);
if (product.getType() == type) {
result.add(product);
}
}
return result;
}
List<Product> filterByPrice(
List<Product> products, int threshold
) {
List<Product> result = new ArrayList<>();
for (int i = 0; i < products.size(); i++) {
Product product = products.get(i);
if (product.getPrice() > threshold) {
result.add(product);
}
}
return result;
}
List<Product> filterByProducer(
List<Product> products, String producer
) {
List<Product> result = new ArrayList<>();
for (int i = 0; i < products.size(); i++) {
Product product = products.get(i);
if (product.getProducer().equals(producer)) {
result.add(product);
}
}
return result;
}
Стало
List<Product> filterByType(
List<Product> products, ProductType type
) {
return filter(products, product -> product.getType() == type);
}
List<Product> filterByPrice(List<Product> products, int threshold) {
return filter(products, product -> product.getPrice() > threshold);
}
List<Product> filterByProducer(List<Product> products, String producer) {
return filter(products, product -> product.getProducer().equals(producer));
}
Постоянно писать функции
высшего порядка?
Ответ – StreamAPI!
Не те Stream, а эти Stream
Что такое Stream?
Поток, выполняющий операции над элементами коллекции
Трансформация элементов
map: принимает на вход функцию трансформации элемента коллекции, применяет ее ко всем элементам
List<ProductType> getUniqueProductTypes(List<Product> products) {
return products.stream()
.map(product -> product.getType())
.distinct()
.collect(Collectors.toList());
}
Фильтрация элементов
List<Product> filterProductsByType(
List<Product> products, ProductType type
) {
return products.stream()
.filter(item -> item.getType() == type)
.collect(Collectors.toList());
}
filter: аналог нашего метода фильтрации, принимает предикат
Как получить результат работы потока?
Два типа методов Stream. Нетерминальные.
Добавляют новую операцию обработки потока, возвращают новый поток
Примеры:
- filter
- map
- flatMap
- skip
- limit
Два типа методов Stream. Терминальные.
Завершают работу потока, выполняют все операции, формируют и возвращают результат работы потока
Примеры:
- findFirst
- reduce
- collect
- forEach
- toArray
- allMatch
- count
Плюсы использования StreamAPI
- Декларативность: мы описываем, что мы хотим получить
- Ленивость: нет вычислений, пока нам не понадобится результат
- Лаконичность: пишем меньше кода, допускаем меньше низкоуровневых ошибок
Рассмотрим метод findFirst
Optional<T> findFirst();
Optional?
Optional – попытка решить проблему null
Напишем пару простых методов
Product findProductById(
List<Product> products, long id
) {
for (Product product : products) {
if (product.getId() == id)
return product;
}
return null;
}
double getPriceWithNDS(Product product) {
double price = product.getPrice();
return price * 1.2;
}
Используем их вместе
double getProductPriceWithNDS(
List<Product> products, long productId
) {
Product product = findProductById(products, productId);
return getPriceWithNDS(product);
}
NullPointerException в рантайме, если товар с таким id не найден
Перепишем поиск на использование Stream
double getProductPriceWithNDS(
List<Product> products, long productId
) {
Optional<Product> product = products
.stream()
.filter(p -> p.getId() == productId)
.findFirst();
return getPriceWithNDS(product);
}
Ошибка компиляции
Optional – коробочка со значением
Как получить значение
double getProductPriceWithNDS(
List<Product> products, long productId
) {
Optional<Product> product = ...;
if (product.isPresent()) {
return getPriceWithNDS(product.get());
} else {
throw new RuntimeException("Не найден такой товар");
}
}
Метод map
double getProductPriceWithNDS(
List<Product> products, long productId
) {
Optional<Double> price = products
...
.findFirst()
.map(this::getPriceWithNDS);
if (price.isPresent()) {
return price.get();
} else {
throw new RuntimeException("Не найден такой товар");
}
}
Ссылка на метод: то же, что и product -> this.getPriceWithNDS(product)
Избавляемся от isPresent()
double getProductPriceWithNDS(
List<Product> products, long productId
) {
return products
.stream()
.filter(p -> p.getId() == productId)
.findFirst()
.map(this::getPriceWithNDS)
.orElseThrow(
() -> new RuntimeException("Не найден такой товар")
);
}
А если без исключения?
double getProductPriceWithNDS(
List<Product> products, long productId
) {
return products
.stream()
.filter(p -> p.getId() == productId)
.findFirst()
.map(this::getPriceWithNDS)
.orElse(0.0);
}
Что почитать
Домашняя работа
Предметная область: грузовые перевозки
Есть несколько видов транспорта, у каждого транспорта своя стоимость доставки груза и вместимость.
Есть груз, который нужно доставить, груз имеет список доступных путей (ж/д, морской, воздушный и т.д.)
Задача: для заданного груза рассчитать оптимальный по затратам способ доставки
JavaSIS #2.19 Занятие 5
By walkingdev
JavaSIS #2.19 Занятие 5
Функциональное программирование в Java
- 411