среда, 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)));
    }
}

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

MongoDb основные операции. Часть 3

Выбор полей

Команда find(), так же может принимать второй необязательный парметр, в котором перечисляются поля, которые будут в выборке. Так же необходимо заметить, что поле _id выбирается по умолчанию, если мы хотим его исключить - нужно это указать. Попробуем выбрать все имена:

db.employees.find({},{name:1, _id:0});
db.employees.find({},{name:1});
Второй вариант вернется так же поле _id.

Сортировка

Чтобы отсортировать результат к курсору find() добавляется sort, с очень похожими входными параметрами, необходимо указать поле и направление сортировки 1 - по возрастанию, -1 - по убыванию:

db.employees.find({},{name:1, _id:0}).sort({name:1});
Необходимо упомянуть, что если попытатьс отсортировать большой объем данных без индекса - мы получим ошибку.

Разбиение на страницы

Разбиение на страницы выполняется с помощью limit и skip, так же их можно использовать, чтобы избежать проблем при сортировке по полям, не содержащим индексы. Получим 4 и 5 имена:
db.employees.find({}, {name:1, _id:0}).sort({name:1}).limit(2).skip(3);

Подсчет записей

Консоль mongoDb позволяет выполнять count над коллекцией, некоторые драйвера не позволяют делать этого, в таком случае, count необходимо выполнить после find():

db.employees.find({}, {name:1, _id:0}.count();

воскресенье, 5 ноября 2017 г.

MongoDb основные операции. Часть 2

Обновление данных

Чтобы обновить данные воспользуемся, командой update, например добавил з/п для Бориса:
db.employees.update({name:'Boris'}, {salary:7000});
После обновления попробуем получить запись:
db.employees.find({name:'Boris'});
Запись не найдена, т.к. обычный update полностью заменяет документ. Чтобы обновлять только нужные поля необходимо воспользоваться модификатором $set:

db.employees.update({salary:7000}, {$set:{name: 'Boris', hired: new Date(2002,2,13,7,47), skills: ['smart','happy'], age: 60, gender: 'm'}});
Кроме модификатора $set есть другие, например, $inc увеличивает на заданную величину число или $push добавлет элемент в массив:

db.employees.update({name:'Boris'}, {$inc:{salary:200}, $push:{skills:'responsible'}});

Вставка/Обновление

Чтобы выполнить обновление или вствку записи при ее отсутствии необходимо передать третий параметр-флаг, true, например, на склад завезли канцелярские товары, а именно ручки:
db.storage.update({stationery:'pen'}, {$inc:{count: 200}},true)
При первом выполнении произойдет создание документы, а при втором - увеличение счетчика на 200 единиц.

Множественное обновление

Команда update обновляет только первую найденную запись, чтобы обновить все записи, удовлетворяющие заданному условию, необходимо установить четвертый параметр в true. Так же необходимо заметить, что множественное обновление работает только с модификатором, чтобы не произошло замены документов, только обновление полей:

db.employees.update({}, {$set:{isIll:false}}, false, true);

пятница, 3 ноября 2017 г.

MongoDb основные операции

1. Скачивание и запуск

Прежде чем рассматривать основные операции в MongoDb - скачаем ее с офф сайта. Разархивируем скачанный файл и перейдем в папку bin. Больше всего нас интересуют файлы: mongod - сервер и mondo - клиентская консоль. В папке bin, создадим конфигурационный файл mongo.config. В него добавим: dbpath=путь_куда_создать_базу_данных. Теперь, запустим mongod, в консоли выполним: mongod.exe --config mongo.config. Для проверки запустим  файл mongo и выполним db.version(). В результате получим версию своей MongoDb.

2. Выбор БД и ее наполнение

Запустим консоль -  mongo.  Она работает на JavaScript. Существует несколько глобальных команд, например exit или help. Большинство команд выполняются применительно к каому-либо объекту, например, выполним db.stats(), получим информацию о текущей базе данных:
Текущая база - test, сменим ее, выполнив: use learn:
При создании первой коллекции произойдет и создание БД. Создим несколько коллекций:
db.employees.insert({name: 'Boris', hired: new Date(2002,2,13,7,47), skills: ['smart','happy'], age: 60, gender: 'm', salary: 6300}); 
db.employees.insert({name: 'Aurora', hired: new Date(2011, 0, 24, 13, 0), skills: ['smart', 'responsible'], age: 45, gender: 'f', salary: 4300}); 
db.employees.insert({name: 'Tom', hired: new Date(2003, 1, 9, 22, 10), skills: ['energetic', 'enthusiast'], age: 34, gender: 'm', salary: 18200}); 
db.employees.insert({name: 'Rod', hired: new Date(2009, 7, 18, 18, 44), skills: ['serious'], age: 57, gender: 'm', salary: 9900}); 
db.employees.insert({name: 'Solnara', hired: new Date(2005, 6, 4, 2, 1), skills:['serious', 'smart', 'serious'], age:55, gender:'f', salary:8000}); 
db.employees.insert({name:'Kenny', hired: new Date(2007, 6, 1, 10, 42), skills: ['responsible', 'independent'], age: 39, gender: 'm', salary: 3900}); 
db.employees.insert({name: 'Ralf', hired: new Date(2005, 4, 3, 0, 57), skills: ['serious', 'independent'], age: 21, gender: 'm', salary: 200}); 
db.employees.insert({name: 'Leia', hired: new Date(2001, 9, 8, 14, 53), skills: ['serious', 'easily trained'], age: 40, gender: 'f', salary: 3300}); 
db.employees.insert({name: 'Bill', hired: new Date(2007, 2, 1, 5, 3), skills: ['serious', 'easily trained'], age: 35, gender: 'm', salary: 5400}); 
db.employees.insert({name: 'Nimue', hired: new Date(2009, 11, 20, 16, 15), skills: ['responsible', 'smart'], age: 54, gender: 'f'}); 
db.employees.insert({name: 'Max', hired: new Date(2006, 6, 18, 18, 18), skills: ['responsible', 'easily trained'], age: 44, gender: 'm', salary: 16500}); 
 Каждая команда вставляет в коллекцию employees перевадаваемый аргумент, который представляет из себя обычный JSON. Выполним команду поиска: db.employees.find(). В результате получим все введенные данные. У них повилось поле _id. У каждой записи должно быть уникальное поле, можно его генерировать самому или позволить это делать MongoDb. Поле _id, по умолчанию индексируется.
Когда у нас есть данные, можно начать осваивать выборки. Поиск происходит, так же как и вставка, ипользуя связку: {поле:значение}, так де есть операторы: $lt, $lte, $gt, $gte и $ne, обозначающие "меньше", "меньше или равно", "больше", "больше или равно", и "не равно". Попробуем выбрать всех людей, кто младше 30:
db.employees.find({"age":{$lte:30}})
Теперь попробуем выбрать тех, что младже 30 или у кого з/п выше 10000:
db.employees.find({$or:[{"age":{$lte:30}},{"salary":{$gte:10000}}]})
Если хотим выбрать только серьезных работников:
db.employees.find({"skills":'serious'})

вторник, 31 октября 2017 г.

MongoDb NoSql Общие сведения

1. Введение

MongoDb - документоорентированная NoSql база данных, используемая для хранения больших объемов информации, предоставляющая высокую производительность, высокую доступность и автоматическое масштабирование.

2. Что такое MongoDb?

  • MongoDb - документная база данных, каждая база данных содержит коллекции, которые в свою очередь содержат документы. Каждый документа может сосостоять из различного числа полей. Размер и содержимое каждого документа может быть различен и хранится в JSON подобном формате.
  • Строкам (или документам, если называть в стиле MongoDb), не нужно заранее созданной схемы. Вместо этого, поля можно создавать на лету.
  • Модель данных, доступная в MongoDb, позволяет разработчикам легче представлять иерархические отношения, хранить массивы и другие более сложные структуры

3. Что такое NoSql

БД NoSql обозначают не-SQL БД или без реляционный подход к БД.
  • Предоставляет механизм хранения и извлечения данных, отличных от табличной реляционной модели, используемой в реляционныз БД
  • Предоставляет большую гибкость, т.к. все записи не ограничены именами столбцов и типами, определенными во всей таблице.
  • Не использует стандартный язык SQL для запроса данных и не предусматривает строгую схему.
  • С NoSql, ACID ( Атомарность,Консистенция, Изоляция, Долговечность) свойства транзакций БД не гарантируются.
  • Предлагает масштабируемость и высокую производительность для обработки огромных объемов данных.

4. Почему использовать MongoDb

Ниже приведены несколько причин, почему, необходимо начать использовать MongoDb
  • Документо-ориентированность: т.к. MongoDb - NoSql БД, то вместо того, чтобы хранить данные в релационном формате - они хранятся в виде документа. Это делает MongoDb более гибкой.
  • Специальные запросы: MongoDb поддерживает поиск по полю,  диапазону и регулярным выражениям. Так же есть возможность вернуть поле документа.
  • Индексация: Индексы создаются, чтобы увеличить производительность поиска.
  • Автоматическая балансировка нагрузки: MongoDb использует концепцию Шардирования для горизонтального масштабирования , разбивая данные на нескольких экземплярах MongoDb.
  • Репликация: MongoDb может обеспечить высокую доступность набора реплик.

5. Различия с РСУБД

Ниже рассмотрены основные различия между NoSql и РСУБД.

  • В РСУБД используется термин Таблица, в MongoDb - Коллекция.  В РСУБД  таблица состоит из строк и столбцов, которые используются для хранения данныйх, в MongoDb, коллекции состоят документов, которые состоят из полей, которые состоят пары ключ-значение.
  • Строка  в РСУБД, Документ в MongoDb.
  • Столбец в РСУБД, поле в MongoDb.
  • Join в РСУБД, встроенные документы в MongoDb. В РСУБД часто данные хранятся в различных таблицах, чтобы показать полный набор данных - необходимо произвести Join таблиц. В MongoDb данные обычно хранятся в одной коллекции, но разделены встроенными документами, т.о Join, как концепция - отсутствует.
  • В РСУБД - Первичный ключ, в MongoDb - _id, который создается автоматически.

четверг, 26 октября 2017 г.

Java 9: Фабричный метод для создания неизменяемых коллекций

В данной статье рассмотрим новую фичу JDK 9 - создание неизменяемых коллекций. До Java9, если мы хотели получить неизменяемые коллекции - необходимо было вызвать unmodifiableXXX();  методы у java.util.Collections. Например, чтобы создать неизменяемый список, необходимо выполнить:

List<Strong> immutablelist = Collections.unmodifiableList(new ArrayList<String>(){{
            add("Smart");
            add("Techie");
}});

immutablelist.add("Smart_1");
При попытки добавлении новой записи "Smart_1" - получаем ошибку:

java.lang.UnsupportedOperationException thrown:
        at Collections$UnmodifiableCollection.add (Collections.java:1056)

Код выше - слегка многословен, чтобы создать немодифицированный список. Т.к. Java адоптирует функциональный стиль в Java 9 разработаны удобные, более компактные фабричные методы для создания немодифицируемых коллекций, как указано в JEP 269. Рассмотрим как это работает:

//Пустой список
List immutableList = List.of();
// Не пустой список
immutableList = List.of("Smart","Techie");
// Не пустая Map
Map immutableMap = Map.of(1,"Smart",2,"Techie");

 Если вы посмотрите на вышеупомянутый фабричный метод Java 9, код представляет собой простой однострочный набор для создания неизменяемых коллекций

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

среда, 11 октября 2017 г.

Инициализация базы данных в Docker

Если нам необходима готовая база данных при старте посгрешки в докере, это возможно достичь, добавив следующие строки в dockerfile:

ENV INIT_SQL init.sql
ADD init.sql /docker-entrypoint-initdb.d/

Они копируют init.sql в докер контейнер. Выполнение данного скрипта произойдет только один раз, при мервом запуске.

воскресенье, 24 сентября 2017 г.

Запуск Postgres в Docker

Конфигурация docker файла:
FROM library/postgres
ENV POSTGRES_USER postgres
ENV POSTGRES_PASSWORD postgres
ENV POSTGRES_DB postgres

Для того, чтобы собрать контейнер, в консоли выполняем
sudo docker build  -t postgres .

В результате в консоль будет выводится процесс сборки контейнера, а в конце будет указан его идентификатор:
Этим идентификатором и воспользуемся для запуска:
sudo docker run 1f66d03cb1e7 & 
& используется для запуска в бэкграунде.  Выполнив:
sudo docker ps -a

Мы увидим, что наш контейнер запущен:

Чтобы понять, какой адрес у запущенной базы данных воспользуемся командой:
sudo docker inspect 0ba3681d3a3d
В консоль будет выведена вся информация о контейнере, в ней будет указан ip адрес, по нему и можно обращаться к базе:

понедельник, 18 сентября 2017 г.

Установка Docker на Ubuntu

Рассмотрим процесс установки docker, в Ubuntu, я использовал версию 17.04.
Первым делом обновим базу пакетов:
sudo apt-get update

Далее добавим ключ официального репозитория Docker в систему:
sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D

Добавим репозиторий Docker в список источников, для утилиты apt:
sudo apt-add-repository 'deb https://apt.dockerproject.org/repo ubuntu-xenial main'

Обновим информацию о пакетах из добавленного репозитория:
sudo apt-get update

Убедимся, что собираемся установить Docker из репозитория Docker, а не из репозитория по умолчанию Ubuntu:
apt-cache policy docker-engine

Результат должен быть похож на этот:
Установим Docker:
sudo apt-get install -y docker-engine

Проверим, запущен ли, демон:
sudo systemctl status docker

В результате получим:
Ну и запустил hello world:
sudo docker run hello-world

Т.к. это первый наш запуск, в начале произойдет загрузка имейджа:

суббота, 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. Тестирование web приложения

Рассмотрим процесс тестировани веб приложения. Для начала создадим тестовое веб приложение, очень похожее на пример из 1 части, только без БД.
Сущность:
package com.entity;

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

public class JournalEntry {
    private String title;
    private Date created;
    private String summary;
    private final SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy");
    public JournalEntry(String title, String summary, String date) throws
            ParseException{
        this.title = title;
        this.summary = summary;
        this.created = format.parse(date);
    }
    JournalEntry(){}
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public Date getCreated() {
        return created;
    }
    public void setCreated(String date) throws ParseException {
        Long _date = null;
        try{
            _date = Long.parseLong(date);
            this.created = new Date(_date);
            return;
        }catch(Exception ignored){}
        this.created = format.parse(date);
    }
    public String getSummary() {
        return summary;
    }

    public void setSummary(String summary) {
        this.summary = summary;
    }
    public String toString(){
        return "* JournalEntry(" + "Title: " +
                title +
                ",Summary: " +
                summary +
                ",Created: " +
                format.format(created) +
                ")";
    }
}
Контроллер:
package com.controller;

import com.entity.JournalEntry;
import org.springframework.web.bind.annotation.*;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@RestController
public class JournalController {
    private static List<JournalEntry> entries = new ArrayList<JournalEntry>();
    static {
        try {
            entries.add(new JournalEntry("Get to know Spring Boot", "Today I will learn Spring Boot", "01/01/2016"));
            entries.add(new JournalEntry("Simple Spring Boot Project", "I will do my first Spring Boot Project", "01/02/2016"));
            entries.add(new JournalEntry("Spring Boot Reading", "Read more about Spring Boot", "02/01/2016"));
            entries.add(new JournalEntry("Spring Boot in the Cloud", "Spring Boot using Cloud Foundry", "03/01/2016"));
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }

    @RequestMapping("/journal/all")
    public List<JournalEntry> getAll() throws ParseException{
        return entries;
    }

    @RequestMapping("/journal/findBy/title/{title}")
    public List<JournalEntry> findByTitleContains(@PathVariable String title) throws
            ParseException{
        return entries
                .stream()
                .filter(entry -> entry.getTitle().toLowerCase().contains(title.toLowerCase()))
                .collect(Collectors.toList());
    }

    @RequestMapping(value="/journal",method = RequestMethod.POST )
    public JournalEntry add(@RequestBody JournalEntry entry){
        entries.add(entry);
        return entry;
    }
}
Рассмотрим класс для тестирования:
package com;

import com.entity.JournalEntry;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.http.MockHttpOutputMessage;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.context.WebApplicationContext;

import javax.annotation.Resource;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.iterableWithSize;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Arrays;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
@WebAppConfiguration
public class SpringBootApplicationTest {
    private final String SPRING_BOOT_MATCH = "Spring Boot";
    private final String CLOUD_MATCH = "Cloud";
    private HttpMessageConverter mappingJackson2HttpMessageConverter;
    private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
            MediaType.APPLICATION_JSON.getSubtype(),
            Charset.forName("utf8"));
    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext webApplicationContext;
    @Autowired
    void setConverters(HttpMessageConverter<?>[] converters) {
        this.mappingJackson2HttpMessageConverter = Arrays.stream(converters).
                filter(
                        converter -> converter instanceof MappingJackson2HttpMessageConverter).
                findAny().get();
    }

    @Before
    public void setup() throws Exception {
        this.mockMvc = webAppContextSetup(webApplicationContext).build();
    }

    @Test
    public void getAll() throws Exception {
        mockMvc.perform(post("/journal")
                .content(this.toJsonString(new JournalEntry("Spring Boot Testing","Create Spring Boot Tests","05/09/2016")))
                .contentType(contentType)).andExpect(status().isOk());
        mockMvc.perform(get("/journal/all"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(contentType))
                .andExpect(jsonPath("$",iterableWithSize(5)))
                .andExpect(jsonPath("$[0]['title']",containsString(SPRING_BOOT_MATCH)));
    }

    @Test
    public void findByTitle() throws Exception {
        mockMvc.perform(get("/journal/findBy/title/" + CLOUD_MATCH))
                .andExpect(status().isOk())
                .andExpect(content().contentType(contentType))
                .andExpect(jsonPath("$",iterableWithSize(1)))
                .andExpect(jsonPath("$[0]['title']",containsString(CLOUD_MATCH)));
    }

    @Test
    public void add() throws Exception {
        mockMvc.perform(post("/journal")
                .content(this.toJsonString(new JournalEntry("Spring Boot Testing","Create Spring Boot Tests","05/09/2016")))
                        .contentType(contentType)).andExpect(status().isOk());
    }

    @SuppressWarnings("unchecked")
    protected String toJsonString(Object obj) throws IOException {
        MockHttpOutputMessage mockHttpOutputMessage = new MockHttpOutputMessage();
        this.mappingJackson2HttpMessageConverter.write(obj, MediaType.APPLICATION_JSON,
                mockHttpOutputMessage);
        return mockHttpOutputMessage.getBodyAsString();
    }
}
После этого можно запускать тесты отдельно или все вместе, запустив сам класс SpringBootApplicationTest. Если запускать весь класс - нет возможности влиять на порядок запускаемых тестов, для этого придумана аннотация FixMethodOrde, передав туда MethodSorters.DEFAULT - методты будут запускать по порядку расположения в классе, MethodSorters.NAME_ASCENDING - по имени

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 это мой перевод данной статьи.