Показаны сообщения с ярлыком java. Показать все сообщения
Показаны сообщения с ярлыком java. Показать все сообщения

среда, 22 ноября 2017 г.

Mongo Java driver

Рассмотрим простой пример приложения, которое обращается к Mongo. Содержимое оставим из предыдущих частей.
В зависимостях будет лишь драйвер для Mongo:
    <dependencies>
        <dependency>
            <groupId>org.mongodb</groupId>
            <artifactId>mongo-java-driver</artifactId>
            <version>3.5.0</version>
        </dependency>
    </dependencies>
Необходимо заметить, что я использую драйвер 3.х.х. В долшинстве примеров, что я нашел, использовался драйвер версии 2.х.х, в 3 версии большинство используемых в примерах объектов отмечены ка устаревшие (Deprecated).

Для начала посчитаем количество записей в коллекции и выведем ее:

MongoClient mongo = new MongoClient( "localhost" , 27017 );

MongoDatabase database = mongo.getDatabase("test");
FindIterable<Document> unicorns = database.getCollection("employees").find();
long employeesCount = database.getCollection("employees").count();

System.out.println("count = " + employeesCount);
printD(id);

Функция для вывода коллекции:
private static void printD(Iterable<Document> objects){
 objects.forEach(document -> document.forEach((key, value) -> System.out.println(key + " = " + value)));
}
Создадим  новую запись:
        Document document = new Document();
        document.put("name", "Martin");
        document.put("hired", new Date());
        document.put("skills", "[smart, responsible]");
        document.put("age", 22);
        document.put("gender", "m");
        document.put("salary", 2468);
        database.getCollection("employees").insertOne(document);

        employeesCount = database.getCollection("employees").count();
        System.out.println("count = " + employeesCount);
Количество записей увеличилось на 1.  Попробуем обновить, созданную запись:
 Document find = new Document();
        find.put("name", "Martin");
        FindIterable<Document> beforeUpdate = database.getCollection("employees").find(find);
        printD(beforeUpdate);

        System.out.println("update:");

        Document update = new Document();
        Document set = new Document();
        update.put("salary", 2479);
        set.put("$set", update);
        database.getCollection("employees").updateMany(find, set);
        FindIterable<Document> updated = database.getCollection("employees").find(find);
        printD(updated);
Поиск записи по ИД. Внимание, я использую свой ИД, необходимо использовать именно ваш, сегенерированный ид:
 System.out.println("find by id");
        Document findById = new Document();
        findById.put("_id", new ObjectId("5a1525ea0f6ce550de6fc392"));
        FindIterable<Document> id = database.getCollection("employees").find(findById);
        printD(id);
Удалим созданный объект:
        database.getCollection("employees").deleteMany(find);
        employeesCount = database.getCollection("employees").count();
        System.out.println("count = " + employeesCount);

Количество записей уменьшилось на 1.

Полностью код приведен ниже:
package com;

import com.mongodb.MongoClient;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import org.bson.types.ObjectId;

import java.util.Date;
public class Application {

    public static void main(String[] args) {

        MongoClient mongo = new MongoClient( "localhost" , 27017 );

        MongoDatabase database = mongo.getDatabase("test");
        FindIterable<Document> unicorns = database.getCollection("employees").find();
        long employeesCount = database.getCollection("employees").count();
        System.out.println("count = " + employeesCount);
        printD(unicorns);

        Document document = new Document();
        document.put("name", "Martin");
        document.put("hired", new Date());
        document.put("skills", "[smart, responsible]");
        document.put("age", 22);
        document.put("gender", "m");
        document.put("salary", 2468);
        database.getCollection("employees").insertOne(document);

        employeesCount = database.getCollection("employees").count();
        System.out.println("count = " + employeesCount);


        Document find = new Document();
        find.put("name", "Martin");
        FindIterable<Document> beforeUpdate = database.getCollection("employees").find(find);
        printD(beforeUpdate);

        System.out.println("update:");

        Document update = new Document();
        Document set = new Document();
        update.put("salary", 2479);
        set.put("$set", update);
        database.getCollection("employees").updateMany(find, set);
        FindIterable<Document> updated = database.getCollection("employees").find(find);
        printD(updated);


        System.out.println("find by id");
        Document findById = new Document();
        findById.put("_id", new ObjectId("5a1525ea0f6ce550de6fc392"));
        FindIterable<Document> id = database.getCollection("employees").find(findById);
        printD(id);

        database.getCollection("employees").deleteMany(find);
        employeesCount = database.getCollection("employees").count();
        System.out.println("count = " + employeesCount);
      
    }

    private static void printD(Iterable<Document> objects){
        objects.forEach(document -> document.forEach((key, value) -> System.out.println(key + " = " + value)));
    }
}

суббота, 12 августа 2017 г.

Синглтон: глубокое погружение

Сингтон - паттерн проектирования, который используется, чтобы контролировать число создаваемых объектов, слодовательно он относится семейству паттернов Создатели.
Была тенденция создавать максимум один объект, но в некоторых ситуациях необходимо создать фиксированное число объектов; и именно в этом нам поможет данный паттерн. Обычно, конструктор создается приватным, чтобы убедиться, что внешний мир не может создать объекты и предоставляем один статический метод, который возвращает объект. Создание объекта происходит только если он не был создан до этого.
Со временем люди осознали несколькоп роблем с обычной реализацией синглтона и он был улучшен, чтобы решить эти проблемы. Запомните, что синглтон не хороший или плохой, он просто может подходить для решения вышей проблемы.
В этой статье мы рассмотрим различные реализации синглтонов.
Взглянем на диаграмму класса:

Применение синглтона

Есть несколько ситуаций, когда целесобразно использовать паттерн синглтон. На пример: логирование, кэширование, балансировка нагрузки, конфигурирование, коммуникации и пулл-коннекшинов в БД. Пример использования синглтона в Java API - класс Runtime.

Реализация синглтона

Приведем пример простой реализации синглтона. Инициализация произойдет при запуске приожения, так же данный способ является потокобезопасный.

public class MySingleton {
     private static final MySingleton mySingleton = new MySingleton();
     private MySingleton(){}     
     public static MySingleton getInstance(){
        return mySingleton;
     }
}
А вот реализация использующая инициализацию при первом обращении. В многопоточном приложении данная реализация будет плогих решением:

class MySingleton {
     private static MySingleton mySingleton;
     private MySingleton(){}
     public static MySingleton getInstance(){
           if(null == mySingleton) {
                 mySingleton = new MySingleton();
           }
           return mySingleton;
     }
}
Решение для многопоточной среды должо избежать гонки состояний  и убедиться, что не будет нарушения философии синглтона. Но в примере ниже, делать весь метод synchronized - не лучший подход, т.к нам необходимо добавить ограничение только на создание объекта.

class MySingleton {
     private static MySingleton mySingleton;
     private MySingleton(){}
     public synchronized static MySingleton getInstance(){      
        if(null == mySingleton) {
           mySingleton = new MySingleton();
        }
        return mySingleton;
     }
}
Пример ниже делает все то же, но так же пользуется двойной проверкой, используя блокировку по объекту. Это реализация гарантирует потокобезопасность. Но дополнительный объект, который нужен только для блокировки - не лушчая практика.

class MySingleton {
  private static MySingleton mySingleton;
  private static final Object lock = new Object();
  private MySingleton(){}
  public static MySingleton getInstance(){
     if(null == mySingleton) {
         synchronized(lock) {
            if(null == mySingleton) {
               mySingleton = new MySingleton();
            }
         }   
     }
     return mySingleton;
  }
}
Другая многопоточная реализация,  пользуется двойной проверкой, но с блокировкой на уровне класса. Отметив объект MySingletone, как volatile, мы уверены, что изменения , сделанные в одном потоке, будут видны другим потокам. Данная реализация гарантирует потокобезопасность.

class MySingleton {
    private volatile static MySingleton mySingleton;
    private MySingleton() {}
    public static MySingleton getInstance() {
        if (null == mySingleton) {
            synchronized(MySingleton.class) {
                if (null == mySingleton) {
                    mySingleton = new MySingleton();
                }
            }
        }
        return mySingleton;
    }
}
Данная реализация предоставляет конструктор, который помешает нарушить контракт синглтона, используя рефлексию.

class MySingleton {
    private volatile static MySingleton mySingleton;
    //Reflection может получить доступ к приватному конструктору
    private MySingleton() throws Exception {
        if (null == mySingleton) {
            mySingleton = new MySingleton();
        } else {
            throw new Exception("Это singleton; не ожидается создание более одного экземпляра");
        }
    }
    public static MySingleton getInstance() throws Exception {
        if (null == mySingleton) {
            synchronized(MySingleton.class) {
                if (null == mySingleton) {
                    mySingleton = new MySingleton();
                }
            }
        }
        return mySingleton;
    }
}
Очень популярная реализация, с использованием статического класса, которая предоставляет ленивую инициализацию (при первом обращении) и потокобезопасность.

public class MySingleton {
    private MySingleton() {}
    private static class SingletonUisngInner {
        private static MySingleton mySingleton = new MySingleton();
    }
    public static MySingleton getInstance() {
        return SingletonUisngInner.mySingleton;
    }
}
В некоторых обстоятельствах, если ваш синглтон наследуется от Cloneable - вашему классу необходимы дополнительные проверки, чтобы избежать нарушения контракта синглтона. Вам необходимо переопределить методо clone  и явно выкинуть ошибку CloneNotSupportedException.

class ClonedClass implements Cloneable {
    //Некоторая логика
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class MySingleton extends ClonedClass {
    private MySingleton() {}
    private static class SingletonUisngInner {
        private static MySingleton mySingleton = new MySingleton();
    }
    public static MySingleton getInstance() {
        return SingletonUisngInner.mySingleton;
    }
    public Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }
}
Следующий и наш финальный, очень популярный и умный способ реализации синглтона - использовать Enum, который возьмет все заботы на себя.

public enum  EnumSingleton{
    INSTANCE;
}
Иногда, люди говорят о синглтонах в нескольких JVM - разберемся с этим. Синглтон означет, что есть только один объект, и мы хоршо знаем, что жизненным циклом объекта управляет JVM, так что один разделяемый объект, среди нескольких JVM невозможен.
Но если вам это необходимо, вы можете создать объект в одной JVM и передавать его через сериализацию, в другие JVM (но помните, что вы десириализуете его, так что, все что отмечено static или transient не передается, и где то контракт синглтона может быть нарушен). Вы так же можете использовать RMI объекты, как синглтоны, чтобы удовлетворить вашим потребностям.
Веселого обучения!
PS это мой перевод данной статьи

пятница, 11 августа 2017 г.

Что такого плохого в Синглтонах

Все мы сталкивались со словом 'Синглтон', и большинство из нас даже знает его значение. Со слов Википедии: Синглтоны - классы, у которых есть ограничение: иметь только один экземпляр. Возможно несколько счастливчиков пользовались им, но поверьте мне, даже если вы использовали синглтон, вы можете не знать насколько большое это зло.

Чтобы было более понятно, этот паттерн содержит класс, который отвечает за создание объекта, а так же удостоверяется, что создается только один объект.

class Singleton {
    private static Singleton INSTANCE = null;
    private String criticalData;
    private Singleton() {
        criticalData = "Это должно быть уникальным, а состояние универсальным";
    }
    synchronized static Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
    public String getString() {
        return this.criticalData;
    }
    public void setCriticalData(String value) {
        criticalData = value;
    }
}
Теперь мы знаем, что из мебя представляе класс синглтон и как он  работает. Итак, в чем проблема? Подсказка: самая очевидная проблема находится в 7 строке.
Т.к. блок synchronized может быть доступен только одному потоку, это может создать узкое место при получении объекта. Но есть и другие менее известные проблемы.

Синглтоны и состояние

Сингтоны - враги unit-тестирования. Одно из основных требований для unit-тестирования, что каждый тест должен быть независим от других. Это может привезти к тому, что тест проходит, хотя это произошло, только потому, что они вызывались в определенном порядке.

Создается один объект

Разве это не полное нарушение принципа единичной ответственности, который утверждает: класс должен иметь одну и только одну причину для изменения. Обычно класс не должен знать, является ли он сингтоном или нет.  Если вы хотите ограничить возможность создания экземпляров используйте фаблику или паттерн строитель, который инкапсулируют создание. Здесь вы можете ограничить число объектов до одного или до любого числа.

Сингтоны предоставляют глобальное состояние

Именно поэтому у нас есть синглтоны. Да. Но какой ценой? (Глобальные переменные были плохими! Помните?) Это обеспечивает возможность для некоторого сервиса в вашем приложении, так что нам не нужно передавать ссылку на этот сервис. Желание создать что-то глобальное, чтобы избежать его распространение, - это запашок от вашего дизайна.

PS это мой перевод данной статьи

пятница, 28 июля 2017 г.

Код без NPE и без проверок на NULL

Первый: Ты действительно уверен, что эта переменная никогда не будет null
Второй: конечно!
NullPointerException 

Ведь так начинается Java код-ревью? NPE всегда является кошмаром для Java разработчиков. Рассмотрим не самый простой пример кода:

public interface Service {  
    public boolean switchOn(int timmer);
    public boolean switchOff(int timmer);
    //другие элементы
}
public class RefrigeratorService implements Service {
// ...
}
public class HomeServices {
    private static final int NOW = 0;
    private static HomeServices service;
    public static HomeServices get() {
        //проверка на Null #1
        if(service == null) {
            service = new HomeServices();
        }
        return service;
    }
    public Service getRefrigertorControl() {
        return new RefrigeratorService();
    }
    public static void main(String[] args) {
        /* получаем Home Services */
        HomeServices homeServices = HomeServices.get();
        //проверка на Null #2
        if(homeServices != null) {
            Service refrigertorControl = homeServices.getRefrigertorControl();
            //проверка на Null #3
            if (refrigertorControl != null) {
                refrigertorControl.switchOn(NOW);
            }
        }
    }
}
Как видно вышел, в коде несколько проверок на Null. Конечно все это сделано, чтобы код был более надежным во всех ситуация. Это необходимо, или ...

Есть путь лучше?

В общем - да! В Java 8 представили java.util.Optional<T>. Это контейнер, который может содержить или не содержить null значения. Java 8 предоставляет нам более безопасный способ  взаимодействовать с объектами, которые  в некоторых случаях могут быть null. Данное решение вхоновлено идеями Haskell  и Scala.
Если коротко - класс Optional включает методы, для корректной работы в двух случаях, когда значение существует и когда отсутствует. Преимущество, в сравнении с null проверкой, заключается в том, что класс Optional, заставляет обдумывать случаи, когда значение отсутствует. Т.о.  получится избежать непредвиденных NPE.
В примере выше у нас есть фабрика HomeService, которая обрабатывает несколько устройств, доступных в доме. Но эти устройства функционально могут быть досутпны или не доступны. Т.е. может случиться NPE. Вместо того, чтобы добавлять проверку на null, обернем объект в Optional.

Оборачивание в Optional<T>

Рассмотрим получение объекта из фабрики. Вместо возвращение инстанса элемента, обернем его в Optional. Это позволит пользователю API, понимать, что возвращаемый сервис и может быть функционально доступным или нет. Используем безопасно:

public Optional<Service> getRefrigertorControl() {
    Service s = new  RefrigeratorService();
    //...
    return Optional.ofNullable(s);
}
Как вы видите, Optional.ofNullable предоставляет простой путь  получить обернутый объект. Есть другие пути, чтобы получить оберный объект в Optional: Optiona.empty() или Optional.of(). Первый возвращает пустой объект, вместо null,  а второй - оборачивает не null значения объектов.

Как это помогает избежать null проверок?

Обернув объект в Optional единожды, получаем множество полезных методов для вызова функционала обернутого объекта, без NPE.

Optional ref = homeServices.getRefrigertorControl(); 
ref.ifPresent(HomeServices::switchItOn);
Optiona.ifPresent вызывает потребителя (Consumer) по ссылке, если значение не null, иначе ничего не делает. Это очень красиво и легко для понимания. В примере выше, HomeService.switchOn(Service) вызывается, только если Optional содержить не null значение.
Мы часто используем тернарный оператор, чтобы проверить на null и значение по умолчанию, если null. Optional предоставляет то же самое без проверок. Optional.orElse(defaultObj) возвращает defaultObj, если в Optional значение null. Используем это в нашем коде:

public static Optional<HomeServices> get() {
    service = Optional.of(service.orElse(new HomeServices()));
    return service;
}
Сейчас HomeServices.get() делает то же самое, но лучшим способов. Он проверяет, проинициализирован ли сервис. Optional<T>.orElse(T) помогает вернуть значение по умолчанию.
В итоге вот наш код без NPE и null проверок:

import java.util.Optional;
public class HomeServices {
    private static final int NOW = 0;
    private static Optional<HomeServices> service;
    public static Optional<HomeServices> get() {
        service = Optional.of(service.orElse(new HomeServices()));
        return service;
    }
    public Optional<Service> getRefrigertorControl() {
  if(ServiceDiscovery.isAvaiable("refrigetor")) {
   Service s = RefrigeratorService.get();
   return Optional.ofNullable(s);
  }
  return Optional.empty();
 }
    public static void main(String[] args) {
        Optional<HomeServices> homeServices = HomeServices.get();
        if(homeServices.isPresent()) {
            Optional<Service> refrigertorControl = homeServices.get().getRefrigertorControl();
            refrigertorControl.ifPresent(HomeServices::switchItOn);
        }
    }
    public static void switchItOn(Service s){
        //...
    }
}

PS: это мой перевод данной статьи

суббота, 1 июля 2017 г.

Spring boot. Spring Security

Рассмотрим возможности Spring Security. В качестве примера, воспользуемся веб-проектом из предыдущих статей. Первым делом, необходимо добавить зависимость Spring Security в наш pom-файл:
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
Собственно, этого остаточно, для самого простого примера. Если запустить наш пример и посмотреть на консоль вывода, можно заметить следующее сообщение:

Данный пароль можно использовать, если обратиться к localhost:8080:
Попробуем немного усложнить пример и создадим application.properties файл с таким содержимым:
security.user.name = user
security.user.password = password
Теперь, запустив наш проект, мы не увидим в консоли вывод пароль, в данном случае нам подойдут данные, которые мы указали в проперти файле.

Далее, рассмотрим In-memory security. Для этого необходимо создать следующий класс:
package com.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableGlobalAuthentication
public class InMemorySecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("user").password("password").
                roles("USER")
                .and().withUser("admin").password("password").
                roles("USER", "ADMIN");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/journal/**").authenticated()
                .and()
                .httpBasic();
    }
}

В данном классе:

  • Aннотация EnableGlobalAuthentication - говорит о том, что класс с данной аннотацией используется для конфигурирования AuthenticationManagerBuilder.
  • AuthenticationManagerBuilder инжектится благодаря аннотации Autowired
  • WebSecurityConfigurationAdapter - абстрактный класс, предоставляющий удобную базу для конфигурации инстанса WebSecurityConfigurer
  • configure - переопределенный метод, для настройки HttpSecurity. В данном случае, достп по адресу "/" предоставляется свободный, а для /journal/** - необходима авторизация



суббота, 24 июня 2017 г.

Spring boot. JDBC

Рассмотрим, как с помощью Spring Boot, можно обращаться к БД, используя шаблоны JDBC. Рассмотрим pom файл:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>spring-boot-jdbc-template</groupId>
    <artifactId>jdbc-template</artifactId>
    <version>1.0</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.3.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
Сущность для манипуляции:

package com.entity;

import java.text.SimpleDateFormat;
import java.util.Date;

public class Journal {
    private Long id;
    private String title;
    private Date created;
    private String summary;
    private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy");
    public Journal(Long id, String title, String summary, Date date){
        this.id = id;
        this.title = title;
        this.summary = summary;
        this.created = date;
    }
    Journal(){}

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public Date getCreated() {
        return created;
    }
    public void setCreated(Date created) {
        this.created = created;
    }
    public String getSummary() {
        return summary;
    }
    public void setSummary(String summary) {
        this.summary = summary;
    }
    public String getCreatedAsShort(){
        return format.format(created);
    }
    public String toString(){
        StringBuilder value = new StringBuilder("* JournalEntry(");
        value.append("Id: ");
        value.append(id);
        value.append(",Title: ");
        value.append(title);
        value.append(",Summary: ");
        value.append(summary);
        value.append(",Created: ");
        value.append(getCreatedAsShort());
        value.append(")");
        return value.toString();
    }
}

Далее создадим сервис. У него будет две функции: добавление новой записи в БД и получение всех записей.

package com.service;

import com.entity.Journal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Service
public class JournalService {
    private static final Logger log = LoggerFactory.getLogger(JournalService.class);

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void insertData(){
        log.info("> Table creation");
        jdbcTemplate.execute("DROP TABLE JOURNAL IF EXISTS");
        jdbcTemplate.execute("CREATE TABLE JOURNAL(id SERIAL, title VARCHAR(255), summary VARCHAR(255), created TIMESTAMP)");
        log.info("> Inserting data...");
        jdbcTemplate.execute("INSERT INTO JOURNAL(title,summary,created) VALUES('Get to know Spring Boot','Today I will learn Spring Boot'," +
                        "'2016-01-01 00:00:00.00')");
        jdbcTemplate.execute("INSERT INTO JOURNAL(title,summary,created) VALUES('Simple Spring Boot Project','I will do my first Spring Boot" +
                                "project','2016-01-02 00:00:00.00')");
        jdbcTemplate.execute("INSERT INTO JOURNAL(title,summary,created) VALUES('Spring Boot Reading','Read more about Spring Boot'," +
                        "'2016-02-01 00:00:00.00')");
        jdbcTemplate.execute("INSERT INTO JOURNAL(title,summary,created) VALUES('Spring Boot in the Cloud','Learn Spring Boot using Cloud" +
                                "Foundry','2016-01-01 00:00:00.00')");
        log.info("> Done.");
    }

    public List<Journal> findAll(){
        List<Journal> entries = new ArrayList<>();
        jdbcTemplate.query("SELECT * FROM JOURNAL",
                new Object[]{},
                (rs,row) -> new Journal(rs.getLong("id"),
                        rs.getString("title"), rs.getString("summary"),
                        new Date(rs.getTimestamp("created").getTime())))
                        .forEach(entries::add);
        return entries;
    }

}
  • JdbcTemplate - автосвязанный класс, который отвечает за выполнение запросов для БД.
  • insertDate - первым делом, метод пытается удалить таблицу JOURNAL, если она есть, далее создает таблицу занова и добавляет в нее записи
  • findAll - получает,с помощью RowMapper, все записи из таблицы JOURNAL.
И класс запуска приложения:
package com;

import com.service.JournalService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application implements CommandLineRunner {
    private static final Logger log = LoggerFactory.getLogger(Application.class);
    @Autowired
    private JournalService service;

    public static void main(String[] arg){
        SpringApplication.run(Application.class, arg);
    }

    @Override
    public void run(String... strings) throws Exception {
        log.info("@@ Inserting Data....");
        service.insertData();
        log.info("@@ findAll() call...");
        service.findAll().forEach(entry -> log.info(entry.toString()));
    }
}

Данный класс имплементирует интерфейс CommandLineRunner, для этого необходимо переопределить метод run, он выполнится после старта приложения.

воскресенье, 11 июня 2017 г.

Spring boot. Конфигурация XML

Предположим, у нас есть приложение написанное с помощью Spring Boot, а так же есть конфигурация на xml, рассмотрим, как использовать их в одном приложении:
Есть 2 сущности, кошки и пользователи:

package com.entity;

public class User {
    private String name;
    private String surname;

    public User() {
    }

    public User(String name, String surname) {
        this.name = name;
        this.surname = surname;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }
}
package com.entity;

public class Cat {
    private String name;
    private int age;

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Контроллер для пользователей и для котов, обратите внимание, они находятся в разных пакета:
package com.controller;

import com.entity.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

@RestController
public class UserController {

    private static List users = new ArrayList(){{
       add(new User("name", "surname"));
       add(new User("name2", "surname2"));
       add(new User("name3", "surname3"));
       add(new User("name4", "surname4"));
       add(new User("name5", "surname5"));
    }};

    @RequestMapping("/api/users/all")
    public List getAllUser(){
        return users;
    }
}

package ru.controller.web;

import com.entity.Cat;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

@RestController
public class CatController {
    private static List cats = new ArrayList(){{
        add(new Cat("name",1));
        add(new Cat("name2",2));
        add(new Cat("name3",3));
        add(new Cat("name4",4));
    }};

    @RequestMapping("/api/cats/all")
    public List getCats(){
        return cats;
    }
}

Конфиг xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       ">
    <context:component-scan base-package="ru.controller.*"/>


</beans>
И инициализатор для Spring Boot:
package com;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;

@SpringBootApplication(scanBasePackages = "com.*")
@ImportResource("classpath*:context.xml")
public class Application {
    public static void main(String[] arg){
        SpringApplication.run(Application.class, arg);
    }
}

Необходимо обратить внимание, на то, что сканирование просиходит из пакета com.*, т.е. контроллер для кошек - игнорирутся. Подключение xml конфигурации происходит с помощью аннотации ImportResources, в него передается массив с путями до xml конфигураций. Запустив приложение, видим, что оба пути /api/cats/all и /api/users/all - работают.

воскресенье, 28 мая 2017 г.

Spring boot. Часть 2 Настройки

Рассмотрим некоторые возможности конфигурации приложения.

Исключение определенных конфигураций.

Чтобы не использовать какую то автоконфигурацию достаточно просто исключить ее в аннотации SpringBootApplication:

@SpringBootApplication( exclude={ActiveMQAutoConfiguration.class} )
И данный класс не учавствует в настройках, достаточно удобно.

Настройка для SpringApplication

Для того, чтобы иметь доступ к дополнительным конфигурациям необходимо создать инстанс класса:

SpringApplication app = new SpringApplication(Application.class);
        app.run(arg);
Например, можно заменить баннер спринга, который печатется при запуске приложения:

SpringApplication app = new SpringApplication(Application.class);
        app.setBanner((environment, aClass, printStream) -> printStream.println("\n\n\nThis is my banner!\n\n"));
        app.run(arg);
Или вообще его отключить:
SpringApplication app = new SpringApplication(SpringBootSimpleApplication.class);
app.setBannerMode(Mode.OFF);
app.run(args);

Так же, используя билдер, можно добавить профили или слушателя на события приложения:

new SpringApplicationBuilder(Application.class)
                .listeners((ApplicationListener<ApplicationEvent>) event ->
                        log.info("#### > " + event.getClass().getCanonicalName())).profiles("prod","cloud")
                .banner((environment, aClass, printStream) -> {
                    printStream.println("\n\n\nMy new Banner!\n\n");
                })
                .run(arg);

Настройка через properties

Создадим файл application.properties, в нем добавим строку:
data.server=remoteserver:3030
К ней можно легко обратиться, рассмотрим пример из прошлого проекта. Контроллер  JournalController:
 @Value("${data.server}")
    private String server;
@RequestMapping("/server")
    public @ResponseBody String getServer(){
        return server;
    }


суббота, 27 мая 2017 г.

Spring boot Часть 1

Создадим первое приложение на Spring boot, пусть оно будеть чуть сложнее, чем HelloWorld! Начнем рассмотрение с pom файла:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
          http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>spring-boot-journal</groupId>
    <artifactId>spring-boot-journal</artifactId>
    <version>1.0</version>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.3.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
Далее создадим сущность:

package ru.domain;

import javax.persistence.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

@Entity
public class Journal {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String title;
    private Date create;
    private String summary;
    @Transient
    private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy");

    public Journal(){
    }

    public Journal(String title, String summary,String create) throws ParseException{
        this.title = title;
        this.create = format.parse(create);
        this.summary = summary;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Date getCreate() {
        return create;
    }

    public void setCreate(Date create) {
        this.create = create;
    }

    public String getSummary() {
        return summary;
    }

    public void setSummary(String summary) {
        this.summary = summary;
    }

    public String getCreatedAsShort(){
        return format.format(create);
    }

    public String toString(){
        return "JournalEntry(" + "Id: " +
                id +
                ",Title: " +
                title +
                ",Summary: " +
                summary +
                ",Created: " +
                getCreatedAsShort() +
                ")";
    }
}

Здесь ничего необычного, т.к. используем JPA - значит нам нужный аннотации Entity, ID и GeneratedValue. Используем 2 коструктора, с параметрами для нашего удобства и без парамтеров - для JPA. Аннотация Transient, говорит о том, что это поле не будет мапиться на таблицу.
Далее создадим репозиторий, используя SpringData

package ru.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import ru.domain.Journal;

public interface JournalRepository extends JpaRepository<Journal, Long> {
}

Данный интерфейс расширяет JpaRepository, что позволяет спрингу, с помощью proxy создавать не только просты CRUD действия, но и более сложные используя конвенцию имен, например, findById, findByTitle, findByTitleLike, findByTitleLikeIgnoreCase.
Теперь веб контроллер:

package ru.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import ru.domain.Journal;
import ru.repository.JournalRepository;

import javax.annotation.Resource;
import java.util.List;

@Controller
public class JournalController {
    @Resource
    private JournalRepository repository;

    @RequestMapping("/journal")
    public @ResponseBody List<Journal>getJournals(){
        return repository.findAll();
    }

    @RequestMapping("/")
    public String index(Model model){
        model.addAttribute("journal", repository.findAll());
        return "index";
    }
}
Ну и сама веб страничка:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" >
<head>
    <meta charset="UTF-8"/>
    <meta http-equiv="Content-Type" content="text/html"/>
    <title>Spring Boot Journal</title>
    <link rel="stylesheet" type="text/css" media="all" href="css/bootstrap.min.css"/>
    <link rel="stylesheet" type="text/css" media="all" href="css/bootstrap-glyphicons.css"/>
    <link rel="stylesheet" type="text/css" media="all" href="css/styles.css"/>
</head>
<body>
<div class="container">
    <h1>Spring Boot Journal</h1>
    <ul class="timeline">
        <div  th:each="entry,status : ${journal}" >
            <li  th:attr="class=${status.odd}?'timeline-inverted':''" >
                <div class="tl-circ"></div>
                <div class="timeline-panel">
                    <div>
                        <h4> <span th:text="${entry.title}">TITLE</span>
                        </h4>
                        <p><small class="text-muted">
                            <i class="glyphicon glyphicon-time"></i>
                            <span th:text="${entry.createdAsShort}">CREATED</span>
                        </small>
                        </p>
                    </div>

                    <div class="tl-body">
                        <p> <span th:text="${entry.summary}">SUMMARY</span> </p>
                    </div>
                </div>
            </li>
        </div>
    </ul>
</div>
</body>
</html>
Ну и сам запуск приложения:

package ru;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration;
import org.springframework.context.annotation.Bean;
import ru.domain.Journal;
import ru.repository.JournalRepository;

@SpringBootApplication( exclude={ActiveMQAutoConfiguration.class} )
public class Application {
    public static void main(String[] arg){
        SpringApplication.run(Application.class, arg);
    }

    @Bean
    InitializingBean saveData(JournalRepository repository){
        return () -> {
            repository.save(new Journal("Get to know Spring Boot","Today I will learn Spring Boot","01/01/2016"));
            repository.save(new Journal("Simple Spring Boot Project","I will do my first Spring Boot Project","01/02/2016"));
            repository.save(new Journal("Spring Boot Reading","Read more about Spring Boot","02/01/2016"));
            repository.save(new Journal("Spring Boot in the Cloud","Spring Boot using Cloud Foundry","03/01/2016"));
        };
    }
}

Запустив приложение, увидим:
Отлично, все работает! Но за счет чего? Ведь нет ни web.xml, ни xml или java конфигов для сприга. Если открыть аннотацию SpringBootApplication, то мы увидим следующее:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    @AliasFor(
        annotation = EnableAutoConfiguration.class,
        attribute = "exclude"
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class,
        attribute = "excludeName"
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
}

И после этого становится все понятнее. Основная аннтоция - EnableAutoConfiguration. С ее помощью спринг использует автоконфигуратор, опираясь на classpath, аннотация, которые используются в коде. Например, спринг заметит, что у нас используется зависимость spring-boot-starter-web, и поймет, что у нас веб приложение и будет конфигурировать наше приложение, как веб. Так же к приложению будет вшит Tomcat для запуска и после сборки проекта, через mvn package мы сможем запустить наше приложение не используя дополнительный сервер приложений.

пятница, 21 апреля 2017 г.

Функциональное программирование в Java 8 (Часть 4): Splitter

Сегодня, мы напишем несколько полезных примеров, используя предыдущие главы. Вы потренируюте парадигмы из прошлых частей и узнаете, как оптимизировать процесс выполнения вашей программы.

Пример: Разделение stream, используя специальный фильтр

Вы могли сталкиваться с такой проблемой раньше: у вас есть коллекция объектов, и вы хотите разделить ее с помощью фильтра. После вы хотите выполнить действия с элементами, которые подошли условию фильтра и другое действие с элементами, которые не подошли.

Обычныи (и медленный) подход

public <T> void splitAndPerform(Collection<T> items, Predicate<T> splitBy, Consumer<T> passed, Consumer<T> notPassed) {
    items.stream()
        .filter(splitBy)
        .forEach(passed);
    items.stream()
        .filter(splitBy.negate())
        .forEach(notPassed);
}
Решение работает, но достаточно медленно. Сложность  выполнения O(2n), т.к. мы проходим по коллекции два раза:  один проход, для тех элементов, которые удовлетворяют условиям, второй, для тех, которые не удовлетворяют.
Но что, если мы сами созданим разделитель. Он сортирует элементы коллекции, в зависимости от того, проходят ли они условия или нет, разделит их на 2 списка. Это позволит снизить сложность выполнения до О(n), т.к. проход по списку происходит один раз. Давайте сделаем это.

Более быстрый подход.

1. Разделение коллекции.
Как вы уже догадались, сначала нужно создать разделение.
В нашей функции splitBy(), мы хотим получить Predicate<T> в качестве парамета и вернуть новый объект Splitter, каоторый представляет из себя объект из двух списков. Один список содержит объекты, которые подошли фильру, второй - которые не подошли.
public class Splitter<T> {
    private List<T> passed;
    private List<T> notPassed;
    private Splitter(List<T> passed, List<T> notPassed) {
        this.passed = passed;
        this.notPassed = notPassed;
    }
    public static <T> Splitter<T> splitBy(Collection<T> items,Predicate<T> test) {
        List<T> passed = new LinkedList<T>();
        List<T> notPassed = new LinkedList<T>();
        items.stream()
                .forEach(item -> {
                    if(test.test(item)){
                        passed.add(item);
                        return;
                    }
                    notPassed.add(item);
                });
        return new Splitter<T>(passed, notPassed);
    }
}
Как вы заметили, мы использовали паттер Фабрика для создания Splitter. Теперь надо создать сам объект и наделить его функционалом.
2. Работа с разделенными списками.
Мы хотим работать со списками таким же способом, как со Stream. Но мы не хотим пересоздавать весь функционал, котоый есть у Stream, для наших списков. Здесь нам пригодится паттерн, я узнал о нем из этого видео и это очень клевый способ использовать лямбды. Фактически, мы создаем две функции, workOnPassedItems и workOnNotPassedItems. Они принимают Consumer<Stream<T>>. Следовательно, мы можем создавать лямды  и работать внутри нее с нормальным потоком. Этот метод будет применен к обоим спискам.

public class Splitter<T> {
    //...
    public Splitter<T> workWithPassed(Consumer<Stream<T>> func) {
        func.accept(passed.stream());
        return this;
    }
    public Splitter<T> workWithNotPassed(Consumer<Stream<T>> func) {
        func.accept(notPassed.stream());
        return this;
    }
}
Мы использовали шаблон Каскад, чтобы сделать использование различных методов более приятным. Вы увидите это в примере ниже.
В общем-то этои есть наш разделитель (Splitter). Рассмотрим несколько примеров использования.

Пример 1. Вывод числе и возведение в квадрат всех нечетных

В нашем первом примере мы хотим оперировать со списком чисел. Мы хотим печатать простые числа и возвести в квадрат все нечетные перед тем как их напечатать. Сначала мы раделяем числа не четные и нечетным. Далее работаем с каждым списком как описывалось.

public void workOnNumbers() {
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Splitter.splitBy(numbers, num -> num%2 == 0)
            .workWithPassed(passed ->
                    passed.forEach(even -> System.out.println("" + even + " -> " + even)))
            .workWithNotPassed(notPassed ->
                        notPassed.forEach(odd -> System.out.println(odd + " -> " + (odd * odd))
                    ));
}

Пример 2. Отправка всем победителям уведомление и проигравшим другое

У нас есть список кандидатов. У все есть метода hasWon(), который возвращает boolean. Мы хотим разделить список на победителей и проигравших. Далее мы хотим разослать всем уведомления о победе или поражении. 

public void sendEmails(List<Candidates> candidates) {
    Splitter.splitBy(candidates, Candidates::hasWon)
            .workWithPassed(winners ->
                    winners.forEach(winner -> Email.send(winner.getEmail(), "You won!"))
    )
    .workWithNotPassed(losers ->
            losers.forEach(loser -> Email.send(loser.getEmail(), "You lost, sorry!"))
    );
}

об использовании partitioningBy

Получая обратную связь на данную статью, выявилось, что  Stream.collect(Collectors.partitioningBy(Predicate<T> test)) подходит нашему случаю и я с этим полностью согласен.
Происходит разделение Stream, в зависимости от test. Т.о. для нас map будет выглядеть как то так {true: passed, false: notPassed}. Далее мы получаем два списка из map и продолжаем. Новый метод будет выглядеть так:

public static <T> Splitter<T> splitBy(Collection<T> items,Predicate<T> test) {
        Map<Boolean, List<T>> map = items.stream()
                .collect(Collectors.partitioningBy(test));
        return new Splitter<T>(map.get(true), map.get(false));
}
И я должен признать, что данный метод выглядит лучше.

Какие права на существования у Splitter

Цель Splitter - продемонстировать как вы можете работать в функцией, как с объектом. Его цель не в том, чтобы заменить методы в JDK. Это класс для изучения и экспериментов. Если вы хотите эксперементировать - делайте это. Пожалуйста, оставьте комментарий к тому, что вы узнали или где вы оптимизировали класс, чтобы другие могли учиться на этом. Так же мы рассмотрели несколько паттернов. Они помогают сделать синтекс более приятным.

Вывод

На сегодня это все.
Мы научились создавать наш первый полезный класс с помощью Stream. Мы также оптимизировали среду выполнения нашей программы. Так же мы закрепили наши знания о шаблонах проектирования, таких как фабрика и каскадный шаблон. Наконец, мы опробовали наш сплиттер с некоторыми примерами.

PS это мой перевод данной статьи

воскресенье, 9 апреля 2017 г.

Функциональное программирование в Java 8 (Часть 3): Stream

Сегодня мы будем разбирться со Stream, которые вы используете, как функциональную альтернативу работая с коллекциями. Некоторые методы мы уже видели, когда рассматривали Optionals.

Когда мы используем Stream

Вы можете спросить, чем плох текущий способ хранения набора объектов. Почему нельзя продалжить использовать List,Set и другие?
Я хотел бы пояснить: с ними все в порядке. Но когда вы хотите работать в функциональном стиле, вы должны рассматривать возможность работать с ними (со Stream). Стандартный способ работы - перевести структуру данных в Stream. Далее, вы работаете с ними в функциональном стиле и в конце переводите в структуру данных по своему выбору. По этому мы научимся переводить наиболее популярные структуры данных в Stream.

Почему мы используем Stream

Stream - новый прекрасный способ работать с коллекциями данных. Они были представлены в Java 8. Одна из многих причин, почему вы должны их использовать - паттерн каскад, который используется в Stream. Это значит, что почти каждый метод Stream снова возвращает Stream, так что вы можете продолжить работать с ним. В следющей секции вы увидите, как можно использовать Stream, и это сделает код красивее. 
Stream так же неизменяемы. Так что каждый раз, когда вы изменяете его, создается новый Stream. Еще одно из преимуществ использования Stream, что они уважают особенности ФП. Если вы переведете структуру данных в Stream и будуте работать с ней, в итоге исходные данные не изменятся. Так что никаких побочных эффектов.

Как перевести структуру данных в Stream

Перевод набор объектов в Stream
Если вы хотите перести набор в Stream - можно использовать метод Stream.of():
public void convertObjects() {
    Stream<String> objectStream = Stream.of("Hello", "World");
}
Перевод List, Set, Array и др.
К счастью в Оракл продумали реализацию Stream в Java8. В каждый класс, который реализует  java.util.Collection<T>, добавили метод stream(), который переводит коллекцию в Stream. Массивы, так же легко могут быть переведны в Stream, с помощью Arrays.stream(array). Все так просто. 
public void convertStuff() {
    String[] array = {"apple", "banana"};
    Set<String> emptySet = new HashSet<>();
    List<Integer> emptyList = new LinkedList<>();
    Stream<String> arrayStream = Arrays.stream(array);
    Stream<String> setStream = emptySet.stream();
    Stream<Integer> listStream = emptyList.stream();
}
Всеравно, обычно, вы не будете хранить Stream в объекте. Вы будете его использовать и после этого переводить в необходимую структуру.

Работа со Stream

Как я уже сказал, Stream - это способ работать со структурой данных в функциональном стиле. А сейчас мы рассмотрим самый часто встречаемые методы. 

Уже изученные методы

Map
Все довольно просто. Вместо того что работать с один элементом, мы работаем со всеми элементами в Stream. Если мы хотим возвести в квадрат каждое число, то мы можем использовать Map, вместо того, чтобы писать функцию для List:

public void showMap() {
    Stream.of(1, 2, 3)
        .map(num -> num * num)
        .forEach(System.out::println); // 1 4 9
}
flatMap
Мы используем flatMap, чтобы перейти от Stream<List<Integer>> к Stream<Integer>. В примере мы хотим сложить два List в один

public void showFlatMapLists() {
    List<Integer> numbers1 = Arrays.asList(1, 2, 3);
    List<Integer> numbers2 = Arrays.asList(4, 5, 6);
    Stream.of(numbers1, numbers2) //Stream<List<Integer>>
        .flatMap(List::stream)  //Stream<Integer>
        .forEach(System.out::println); // 1 2 3 4 5 6
}
Так же в примере используется foreEach, который я опишу ниже:
forEach
Метода forEach, как и ifPresent у Optional, у него есть побочный эффект. Данный метод можно использовать, чтобы вывести все элементы Stream. ForEach один из немногих методов, который не возвращает Stream (Примечание:это метод терминальный), так что он используется последним и только один раз. 
Вы должны быть осторожны при использовании forEach, потому что это вызывает побочные эффекты, которые мы не хотим получить. Поэтому подумайте дважды, если вы могли бы заменить его другим методом без побочных эффектов.(Примечание: возможно, автор намекает на то, что данный метод не подходит для использование в параллельном Stream)

public void showForEach() {
    Stream.of(0, 1, 2, 3)
        .forEach(System.out::println); // 0 1 2 3
}
Filter
Filter - это основной метод. Он принимате 'test' функцию, которая принимает значение и возвращает boolena. Т.о. она првоеряет каждый объект в Stream. Если он проходит тест - остается в Stream. Иначе - будет удален.
Тип 'test' функции - Function<T, Boolean>. В JavaDoc вы заметите, что тип test функции на самом деле - Predicate<T>. Но это короткая форма для всех функций, которые принимают один параметр и возвращают boolean. 

public void showFilter() {
    Stream.of(0, 1, 2, 3)
        .filter(num -> num < 2)
        .forEach(System.out::println); // 0 1
}
Функции могут сделать твою жизнь легче, особенно если использовать Predicate.negate() и Objects.nonNull()
Первый инверсирует результат test. Все объекты, которые не проходят оригинальный тест, проходят тест после инверсии и наоборот.
Второй метод используется как метод ссылка, чтобы избавиться от всех null элементов. Это поможет избежать NullPointerException, например, при применении функции map:
public void negateFilter() {
    Predicate<Integer> small = num -> num < 2;
    Stream.of(0, 1, 2, 3)
        .filter(small.negate()) // Все большие цифры проходят
        .forEach(System.out::println); // 2 3
}
public void filterNull() {
    Stream.of(0, 1, null, 3)
        .filter(Objects::nonNull)
        .map(num -> num * 2) // без фильтра получили бы NullPointerExeception
        .forEach(System.out::println); // 0 2 6
}
Collect
Как было сказано выше, вы можете перевести Stream в другую структура данных. Для этого и нужен Collect. Чаще всего вы будете приводить к List или Stream.

public void showCollect() {
    List<Integer> filtered = Stream.of(0, 1, 2, 3)
        .filter(num -> num < 2)
        .collect(Collectors.toList());
}
Но Collect можно использовать для большего. Например, чтобы собрать все в String. Так же в конце строки не будет раздражающего разделителя в конце строки.

public void showJoining() {
    String sentence = Stream.of("Who", "are", "you?")
        .collect(Collectors.joining(" "));
    System.out.println(sentence); // Who are you?
}
Шорткаты
Эти методы могут быть заменине комбинацией filter, map и collect, но на то они и шорткаты.
Reduce
Отличная функция! Она принимает начальный параметр T  и функцию типа BiFunction<T, T, T>. Если у вас BiFuction, у которой все  параметры типа Т, то шорткат для нее BinaryOperator<T>. Фактически она (функция Reduce) приводит все объекты Stream к одному. Вы можете сложить все строки в одну или просуммировать все числа и т.д. В данных примерах стартовым параметром будет пустая строка или 0. Данная функция поможет сделать ваш код более читаемым, если вы знаете, что она делает.

public void showReduceSum() {
    Integer sum = Stream.of(1, 2, 3)
        .reduce(0, Integer::sum);
    System.out.println(sum); // 6
}
Расмотрим, как reduce работает:

  • суммируем первое число...
  • со вторым ...
  • с третьим...
  • со входным параметром
Как вы заметили появляется длинная цепочка функций. В конце мы получим sum(1, sum 2,(sum 3,0))). Они будут вычеслены с права налево или  из нутри к наружу. По этой причине нам нужен начальный параметр.
Sorted
Вы можете использовать Stream, чтоб отсортировать. Объекту в Streamб необязательно даже реализовывать Comperable<T>, т.к. можно написать свой собственный  Comperator<T>. Это обычная BiFunction<T, T, int>, но Comperator это шорткат для всех BiFunctional, которые принимают 2 паарметра и возвращают int.И этот int как в compareTo(), говорит нам, что первый объект меньше, когда int < 0, равны, когда int == 0, и больше, когда int > 0. Функция сортировки будет оперировать этими int и отсортирует Stream.

public void showSort() {
    Stream.of(3, 2, 4, 0)
        .sorted((c1, c2) -> c1 - c2)
        .forEach(System.out::println); // 0 2 3 4
}
Другие виды Stream
Есть специальные виды Stream, которые содержать только цифры, у них есть свой набор методов. Расмотрим IntStream и Sum, но так же есть DoubleStream, LongStream  и д.р. Подробности в JavaDoc. Чтобы перевести обычный Stream в IntStream, необходимо использовать mapToInt. Она делает то же самое, что обычная map, но возвращает IntStream, конечно, можно передать mapToInt функцию, которая будет возвращать int. В данном примере будет рассмотрено, как суммровать числа без reduce:

public void sumWithIntStream() {
    Integer sum = Stream.of(0, 1, 2, 3)
        .mapToInt(num -> num)
        .sum();
}
Использование Stream в тестах
Рассмотрим методы anyMatch, но тоак же есть методы count, max и др. которые могут пригодиться при тестировании. anyMatch работает, как filter, но он сообщает, прошел ли какой-либо объект фильтрацию. Его можно использовать в assertTrue, чтобы проверить, есть ли у какого-нибудь обхект специфическое свойство. В следующем примере проверим, было ли определенное имя сохранено в БД.

@Test
public void testIfNameIsStored() {
    String testName = "Albert Einstein";
    Datebase names = new Datebase();
    names.drop();
    db.put(testName);
    assertTrue(db.getData()
        .stream()
        .anyMatch(name -> name.equals(testName)));
}
Большой пример
В этом примере мы хотим отправить сообщение каждому пользователю, у которого сегодня день рождения.
класс User
User определяется именем и датой рождения. День рождения будет в формате "день.месяц.год". В данном примере не будем производить никаких проверок.

public class User {
    private String username;
    private String birthday;
    public User(String username, String birthday) {
        this.username = username;
        this.birthday = birthday;
    }
    public String getUsername() {
        return username;
    }
    public String getBirthday() {
        return birthday;
    }
}
Чтобы хранить всех пользователей используем List. В настоящей программе List будет заменен БД.

public class MainClass {
    public static void main() {
        List<User> users = new LinkedList<>();
        User birthdayChild = new User("peter", "20.02.1990");
        User otherUser = new User("kid", "23.02.2008");
        User birthdayChild2 = new User("bruce", "20.02.1980");
        users.addAll(Arrays.asList(birthdayChild, otherUser, birthdayChild2));
        greetAllBirthdayChildren(users);
    }
    private static void greetAllBirthdayChildren(List<User> users) {
        // Next Section
    }
}
Поздравление
Теперь мы хотим поздравить именинников. Прежде всего необходимо отфильтровать всех пользователей, у которых сегодня день рождения. После этого мы должны сообщить об этом. Итак, давайте сделаем это. Я не буду реализовывть sendMessage(String message, User receiver), он просто должен отправлять поздравления

public static void greetAllBirthdayChildren(List<User> users) {
    String today = "20.02"; //Чтобы облегчить пример. В реальности необходимо использовать LocalDateTime.
    users.stream()
        .filter(user -> user.getBirthday().startsWith(today))
        .forEach(user -> sendMessage("Happy birthday, ".concat(user.getUsername()).concat("!"), user));
}
private static void sendMessage(String message, User receiver) {
    //...
}
Параллелизм
Stream могут выполняться параллельно! По умолчанию каждый Stream не параллельный. Чтобы его сделать таковым необходимо использовать parallelStream(). Это поможет выполняться вашим Stream быстрее, но необходимо быть аккуратнее с этим. Как рассказано здесь параллелизм, например, может испортить сортировку. Поэтому будьте готовы столкнуться с неприятными ошибками с parralelStream, хотя это может сделать вашу программу значительно быстрее.
Выводы
Вот и все на сегодня! Мы много узнали о Stream на Java. Мы узнали, как преобразовать структуру данных в поток, как работать со Stream и как преобразовать ваш поток обратно в структуру данных. Я представил наиболее распространенные методы и когда вы должны их использовать. В конце урока, мы проверили наши знания на более крупном примере, где мы поприветствовали всех детей в день рождения. В следующей части этой серии у нас будет большой пример, когда мы будем использовать Stream. Но я пока не буду рассказывать вам пример, так что надеюсь, вы будете удивлены.
PS это мой перевод данной статьи.

пятница, 7 апреля 2017 г.

Функциональное программирование в Java 8 (Часть 2): Optionals

Всем привет.

После того, как  в прошлой статье мы сделали наш первый большой шаг в ФП, в сегоднешней части поговорим об Optionals.

Зачем нужен Optional?

Если быть откровенным, вы так же думаете, что null раздражает? Для каждого объекта, если его значение может быть null, нужно проверять, null он или нет:

if(argument == null) {
    throw new NullPointerException();
}

Стремное решение. Этот шаблон раздумает код, так же про него можно легко забыть. Как это исправить?

Знакомство с Optionals

В Java 8, был представлен java.util.Optional<T>, чтобы обрабатыать объекты, которые не инициализированы. Это объект-контейнер, который может хранить другой объект. Дженерик T - тип объекта, который вы хотите хранить. 

Integer i = 5;
Optional<Integer> optinalI = Optional.of(i);

У класса Optional нет никаких публичных конструкторов. Чтобы создать optional, необходимо использовать Optional.of(object) или Optional.ofNullable(object).  Первый сособ используется, если объект никогда не будет null. Второй - если объект может быть null.

Как работает Optional

У Optional 2 сосотояния. Либо в нем хранится null, либо объект. Если хранится объект - optional называют существующим, если null - пустым.  Чтобы получить существующий объект можно использовать Optional.get(),  но нужно быть аккуратным, если вызвать к пустому объекту - будет исключение: NoSuchElementException. Чтобы проверить что объект существует нужно вызвать Optional.isPresent().

Примеры:

public void playingWithOptionals() {
    String s = "Hello World!";

    String nullString = null;

    Optional<String> optionalS1 = Optional.of(s); // Will work

    Optional<String> optionalS2 = Optional.ofNullable(s); // Will work too

    Optional<String> optionalNull1 = Optional.of(nullString); // -> NullPointerException

    Optional<String> optionalNull2 = Optional.ofNullable(nullString); // Will work

    System.out.println(optionalS1.get()); // prints "Hello World!"

    System.out.println(optionalNull2.get()); // -> NoSuchElementException

    if(!optionalNull2.isPresent()) {

        System.out.println("Is empty"); // Will be printed

    }

}

Частые проблемы при использовании Optional

1. Работа с Optional и null
public void workWithFirstStringInDB() {

    DBConnection dB = new DBConnection();

    Optional<String> first = dB.getFirstString();

    if(first != null) {

        String value = first.get(); 

        //... 

    }

}
Так использовать Optional нельзя! Когда вы получаете Optional (В примере из DB), не нужно проверять значение объекта на null. Если в DB нет строки, она вернет Optional.empty(), а не null. Если получите пустой элемент, как в примере, всеравно будет исключение NoSuchElementException.
2. Использование isPresented() и get().
2.1 Использование значение, когда объект представлен.
public void workWithFirstStringInDB() {

    DBConnection dB = new DBConnection();

    Optional<String> first = dB.getFirstString();

    if(first.isPresent()) {

        String value = first.get(); 

        //... 

    }

}
Как было уже сказано, необходимо быть на 100% уверенным, прежде, чем использовать Optional.get(). Вы не получите NoSuchElementException в обновленной функции. Но не нужно проверять isPresent() + get() вместе. Т.к. не нужно забывать про null,  сначала, нужно проверить first != null. И мы получаем тот же результат! И как же избавиться от этого надоедливого блока? Вот как:
public void workWithFirstStringInDB() {

    DBConnection dB = new DBConnection();

    Optional<String> first = dB.getFirstString();

    first.ifPresent(value -> /*...*/);

}
Метод Optional.ifPresent() наш новый лучший заменитель блока проверки. Он принимает Функцию, т.е. лямду или ссылку на метод, и выполняет ее, если объект существует. 
2.2 Возвращение исмененного объекта.
public Integer doubleValueOrZero(Optional<Integer> value) {

    if(value.isPresent()) {

       return value.get() * 2;

    }

    return 0;

}
В этом методе мы хотид удвоить объект, если он представлен. Иначе вернуть 0. Данный пример работает, но это не функциональный способ решения проблемы. У нас есть 2 функции, чтобы сделать этот метод красивее. Первая Optional.map(Function<T, R> mapper) и вторая Optional.orElse(T other). Мap берет функцию, применяет ее и возвращает результат опять обернутый в Optional, если объект пустой - будет возвращен пустой Optional. orElse вернет значение Optional, если оно есть или вернет взначение, котороек передано как параметр orElse(object). Учитывая это, функцию можно реализовать в одну строку.
public Integer doubleValueOrZero(Optional<Integer> value) {

    return value.map(i -> i * 2).orElse(0);

}

Когда использовать объект, который м.б. null, а когда Optional

Вы можете найти много книг, лекций и дискуссий о вопросе: использовать мне null или Optional в конкретной ситуации. Оба решения могут быть правильными. По ссылке можно найти хорошее правило, которое можно применять почти во всех случаях. Использвать Optional, когда существует явная необходимость показать, что результата нет или null может стать причиной ошибки.
Т.е. не нужно использовать Optional так:
public String defaultIfOptional(String string) {

    return Optional.ofNullable(string).orElse("default");

}
Т.к. проверка на null более читабельна:
public String defaultIfOptional(String string) {

    return (string != null) ? string : "default";

}
Вы должны использовать Optionals, как возвращаемое значение из функции. Не стоит создавать новые, чтобы создать классную цепочку методов, как в примере выше. В большинстве случаев null достаточно.

Вывод

Вот и все на сегодня! Мы разобрались с Optional. Это класс-контейнер  для других классов, который либо существует, либо пустой. Мы удалили некоторый общий кода, который поставлялся с Optionals, и  снова использовали функции как объекты. Мы также обсудили, когда следует использовать null и когда Optionals.

В следующей части мы будем использовать Streams как новый способ обработки коллекции объектов, таких как Iterables и Collections.

Спасибо за чтение и хорошего дня,

Никлас

ps это мой перевод данной статьи

четверг, 6 апреля 2017 г.

Java 8 forEach

Рассмотрим примеры forEach в java 8 для List и для Map

1. List.
Просто создадим список:
List<String> list = new ArrayList<String>()
        {{
            add("a");
            add("b");
            add("c");
            add("d");
            add("e");
        }};
1.1 До Java8:

for(String item : list){
 System.out.println(item);
}
1.2 Рассмотрим варианты вывода списк в Java 8:
list.forEach(l -> System.out.println(l));
Его можно упростить используя method reference
list.forEach(System.out::println);
Так же можно через stream:
list.stream().forEach(System.out::println);
2. Map Заполнение:
Map<String, Integer> map = new HashMap<String, Integer>()
            {{
                put("a", 1);
                put("b", 2);
                put("c", 3);
                put("d", 4);
                put("e", 5);
                put("f", 6);
            }};

2.1 до Java 8:
for (Map.Entry<String, Integer> entry : map.entrySet()) {
 System.out.println("key : " + entry.getKey() + " value : " +
                     entry.getValue());
}
2.2 Java 8
map.forEach((String, Integer) -> System.out.println(
             "key : " + String + " value : " + Integer));

вторник, 28 марта 2017 г.

Функциональное программирование в Java 8 (Часть 1): Функция как объект

После прочтения Части 0 вы поняли, что функциональное программирование - это круто, сегодня мы сделаем наш первый большой шаг в данной теме. С выходом Java 8 функции  стали функциями первого класса. А это значит, что вы можете передавать функцию как параметр другой функции, возвращать функцию и хранить функцию как объект.

Почему я должен хранить функцию как объект?

  1. Создавать "super private" функции. Как вы знаете, качество кода очень важно. По этому мы используем private функции, чтобы снизить число открытых методов класса. Мы не хотим показывать основной код пользователям, им достаточно работать только с открытыми методами объекта. Но что, если мы хотим создать функцию, которая доступа только одному методу во всем классе? Если хранить функцию первого класса ,как объект, который будет доступен только одному методу, мы сможем этого добиться.
  2. Улучшить паттерны проектирования. Если вы когда-либо работали над большим проектом, то знаете, каким запутанным он может быть. По-этому были изобретены паттерны проектирования. Один из самых прикольных - паттерн стратегия. Я напишу подробный пост об этом позже, но основная мысль заключается в том, что он переключает похожие алгоритмы, в зависимости от параметра. Для каждого алгоритма необходимо написать свой класс, который имплементирует интерфейс. Но когда вы можете хранить функцию как объект, то вам нужна только одна функция-объект для каждого алгоритма. Это позволяет создавать меньше кода и делает его понятнее.
  3. Создавать функции "Высшего порядка". Теперь самое прикольное. Вы можете использовать каждый объект в качестве параметра метода. Почему бы не вызвать метод с аргументом функции? Методы, которые принимают функцию как параметр или возвращают ее, называются функциями более высокого порядка. Прежде чем я смогу привести вам пример, мы должны научиться хранить функцию в объекте.

Хранение функции в объекте.

В Java 8 был представлю интерфейс Interface Function<T,R>. Он может хранить функцию, которая принимает один аргумент и возвращает объект. Generic T является типом аргумента, а R - типом объекта, который вы возвращаете.

Пример: Функция вычисления.

Это очень простой пример функции высшего порядка. Он принимает функцию и Integer и вычисляет данную функцию с помощью Integer.

public static Integer compute(Function<Integer, Integer> function, Integer value) {
    return function.apply(value);
}
А теперь мы хотим использовать эту функцию, чтобы инвертировать число.
public class AwesomeClass {
    private static Integer invert(Integer value) {
        return -value;
    }
    public static Integer invertTheNumber(){
        Integer toInvert = 5;
        Function<Integer, Integer> invertFunction = AwesomeClass::invert;
        return compute(invertFunction, toInvert);
    }
    
}
Здесь два интересных момента, первый из них:
return function.apply(value);
Вызов метода объекта-функции, просто используются аргументы и возвращается результат метода. Для нашего примера необходимо написать:
return invert(value);
Второй интересный момент:
Function<Integer, Integer> invertFunction = AwesomeClass::invert;
То, что мы здесь используем называется ссылкой на метод. Мы делаем из метода invert() объект-функцию, используя оператор ::. Это один из двух способов хранить функцию, как объект. Но этот код ничего не упощает. Его можно изменить:
public class AwesomeClass {
    private static Integer invert(Integer value) {
        return -value;
    }
    public static Integer invertTheNumber(){
        Integer toInvert = 5;
        return invert(toInvert);
    }    
}
Такое решение не нуждается в функции compute, да и в самом ФП тоже. Чтобы сделать ФП подходящим в данном примере, нужно представить второй способ хранения функции как объект. Он опирается на анонимные функции или так называемые лямбды.

Как работать с лямбдами

Чтобы работать с лямбдами в Java 8 необходимо разобраться с новым синтаксисом, чтоб работать с ними (Лямбдами) правильно.

Пример: сложение двух Integer

В старой-доброй Java7 вы могли написать метод, чтобы сложить два числа:
public Integer add(Integer a, Integer b) {
    return a + b;
}
А это Java 8 c лямбдами:
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
Достаточно просто, не так ли? BiFunction - это еще один интерфейс из java.util, чтобы представить функцию с двумя входными параметрами и одним выходным.В скобках указываются входные параметры. Вы не обязаны указывать их тип. Необходимо только сказать сколько их будет и как они называются. В Java 7 это эквивалентно:
(Integer a, Integer b)
Следующее -> стрелка. Она соответствует фигурным скобочкам и отделяет голову функции от тела. После стрелки можно работать с входными параметрами. Если у функции всего одно вычисление - слово return не нужно, будет возвращен результат этого вычисления. Конечно, тело функции можно сделать больше, добавив фигурные скобки:
BiFunction<Integer, Integer, Integer> add = (a,b) -> {
    Integer result = a + b;
    return result;
};

Но чаще всего, нужна только одна строка, по-этому фигурные скобки и ключевое слово return не нужны.

Улучшение функции вычисления

Учитывая выше сказанное, можно провести рефакторинг:
public class AwesomeClass {
    public static Integer invertTheNumber(){
        Integer toInvert = 5;
        return compute((a) -> -a, toInvert);
    }
}
Вот это уже красивый код! Мы можем реализовать нашу функцию инвертирования через лямбды. Это делает наш код красивее, чем старая ФП версия и версия с Java7. Нам не нужно создавать дополнительные методы для инвертирования Integer, мы просто используем небольшую лямбду, которая все реализует.

Заключение

На сегодня все. Мы сделали первые крупные шаги в направлении ФП на Java 8. Прежде всего, мы увидели много преимуществ ФП. После этого мы использовали нашу первую функцию в качестве параметра в другом методе, используя ссылки на методы и лямбду (анонимные функции). Во второй части мы расскажем о Optionals и о том, как с ними работать.
 Спасибо за чтение и хорошего дня,
 Никлас

PS это мой перевод данной статьи

вторник, 21 марта 2017 г.

Функциональное программирование в Java 8(Часть 0): Мотивация

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


Возможно, вы слышали о функциональном программировании (ФП) и как сильно оно снижает LOC (строк кода - Lines Of Code) и улучшает читабельность кода. Но что в действительности значит программировать функционально и какие отличия от ООП.
  1. Все переменные final

  2.  public String greet(List<String> names) {
        String greeting = "Welcome ";
        for(String name : names) {
            greeting += name + " "; // We don't care about the last space right now.
        }
        greeting += "!";
        return greeting;
    }
    
    

    Это полностью валидная функция для создания строки приветствия в Java. Но если вы используете ФП, то такой способ не сработает. Вы изменяете состояние приветствия (greeting), что не разрешено в ФП. Если вы попытаетесь сделать final greeting, то получите ошибку. Всякий раз, когда вы используете += с String, значение меняется. Обычно в ФП вы конкатенируете все имена в одну строку:
    public String greet(List<String> names) {
        final String reducedString = "Welcome " + names.get(0) + " " + names.get(1) + " " + ...
                + names.get(names.size()-1) + " ";
        return reducedString + "!";
    }
    
    

    Если это решение вам кажется ужасным - вы правы. Но ФП может все исправить:
    public String greet(List<string> names) {
        String greeting = names
                .stream()
                .map(name -&gt; name + " ")
                .reduce("Welcome ",
                        (acc, name) -> acc + name);
        return greeting + "!";
    }
    

    Преимущество final переменных, что их значение всегда одинаковое. Это делает тестирование и дебаггинг намного проще.
  3. Не используйте глобальные пременные.
    Я выбрал пример с глобальным временем. Вы пишете static функцию, которая возвращает текущее время строкой. ООП версия может выглядеть так:
    public class Utils {
        private static Time time;
        public static String currTime() {
            return time.now().toString();
        }
    }
    

    Если вызвать currTime() дважды - результат будет разный, Хотя у нас один и тот же входной параметр(которого нет), результат разный.
    Это не может произойти в ФП. Каждый метод зависит только от своих собственных параметров и все. Т.е. если мы хотим это реализовать, то объект time должен быть входным параметром:
    public class Utils {
        public static String currTime(Time time) {
            return time.now().toString();
        }
    }
    
    Это может выглядеть странно для ООП, но у такого решения есть преимущества. Во-первых, такой код легче читать. Если вы знаете, что метод зависит только от своих параметров, вам не нужно искать глобальные паременные, которые добавляют магии в ваш метод. Во-вторых, такой когд лече тестировать.Когда вам нужно протестировать данный метод, вы можете просто замокировать объект Time. Для решения в ООП это сделать достаточно сложно.
  4. Используйте функции, как параметр.
    У ФП функция может быть аргументом другой функции. Разве это не круто? Предположим, что нам необходимо дабавить каждому элементу List 1. Вот пример релизации в ООП:
    public List<integer> addOne(List<integer> numbers) {
        List<integer> plusOne = new LinkedList&lt;&gt;();
        for(Integer number : numbers) {
            plusOne.add(number + 1);
        }
        return plusOne;
    }
    
    
    В итоге необходимо оперировать двумя списками. Что может привести к ошибкам. Так же существует шанс внести изменения в значения входного параметра, что может привести к дальнейшим проблемам. В ФП вы можете применить функцию map к каждому элементу списка и сохранить значение в другой список:
    public List<Integer> addOne(List<Integer> numbers) {
        return numbers
                .stream()
                .map(number -&gt; number + 1)
                .collect(Collectors.toList());
    }
    
    
    Это уменьшает количество переменных и, следовательно, места, где вы можете допустить ошибки. Вы создаете новый список и не измените числа во входном списке.

Заключение.


Надеюсь, вы заметили преимущества ФП. Позже вы научитесь по-настоящему ценить их. Final переменные - удобнее с точки зрения многопоточности, отсутствие глобальных переменных повышает тестируемость и функции, поскольку параметры улучшают качество кода. И не беспокойтесь, вначале вы можете смешивать ООП и ФП в своем коде. В следующей части мы поговорим о функциях как параметрах. Так же я представлю анонимные функции (lambdas) и ссылки на методы. Надеюсь, вам понравилось это краткое введение в большую тему функционального программирования. Пожалуйста, напишите мне, если что-то непонятно. Или есть ли какая-нибудь причина ненавидеть функциональное программирование? Я хотел бы обсудить эти темы с вами.


PS это мой перевод данной статьи