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

Функциональные интерфейсы — это интерфейсы которые имеют только ОДИН абстрактный метод (default и статические создавать можно)
Для них даже есть специальная аннотация @FunctionalInterface, которая блокирует создание нескольких абстрактных методов на уровне компилятора.
Например, всем известный Runnable тоже является функциональным, т.к. имеет только один метод run.

Самые известные функциональные интерфейсы, которые появились в Java 8:

1)Predicate — принимает на вход T, возвращает boolean.

public static Predicate<Integer> isEvenNumber = x -> {
		System.out.println("x = " + x);
		return x % 2 == 0;
	};

Вызов:

boolean b = isEvenNumber.test(20);
System.out.println(b);

2)UnaryOperator — принимает Т и возвращает Т.

UnaryOperator<Integer> square = x -> x * x;
System.out.println(square.apply(5));

3)BinaryOperator — принимает на вход два объекта типа Т и возвращает Т.

BinaryOperator<Integer> summ = (x, y) -> x + y;
System.out.println(summ.apply(-5, 15)); // 10
System.out.println(summ.apply(52, 8)); // 60

4)Function — принимает на вход тип Т, возвращает тип R.

Function<Integer, String> convertRub = x -> {
	return String.valueOf(x) + " рублей.";
};
System.out.println(convertRub.apply(9));

5)Consumer — принимает тип Т, ничего не возвращает (void).

Consumer<Integer> print = x -> {
	System.out.println("Consumer start...");
	System.out.println(x + " рублей");
	System.out.println("Consumer end.");
};	
print.accept(50);

6)Supplier — ничего не принимает, но возвращает T.

Supplier<String> supplier = ()->{
    return "abc";
};
         
System.out.println(supplier.get());
Реклама

Static factory methods

Мы часто, чтобы создать экземпляр класса используем обычный public конструктор.
Казалось бы все достаточно просто, но есть проблемы:
1. Множество конструкторов с разными ролями
2. Когда разные конструкторы принимают одни и те же аргументы
3. У всех конструкторов однинаковые имена и часто нельзя отличить, какой объект мы создаем.

Пример.
У нас есть обычныый класс Product. Предположим, что продукты могут быть двух видов — продовольственные и непродовольственные.
Если продукт непродовольственный, то нам не надо указывать калорийность.
Что мы здесь видим ?
Мы создали статические методы foodProduct и nonFoodProduct типа Product. Сами конструкторы делаем приватными. Нам не надо, например, для
foodProduct явно указывать что isFood = true, а для nonFoodProduct поле isFood = false

package composite;

public class Product {
	private int idProduct;
	private String brand;
	private String subBrand;
	private String type;
	private boolean isFood;
	private String energyValue;
	
	private Product(int idProduct, String brand, String subBrand, String type) {
		super();
		this.idProduct = idProduct;
		this.brand = brand;
		this.subBrand = subBrand;
		this.type = type;
		this.isFood = false;
	}
	
	private Product(int idProduct, String brand, String subBrand, String type, String energyValue) {
		this(idProduct, brand, subBrand, type);
		this.energyValue = energyValue;
		this.isFood = true;
	}
	
	
	public static final Product foodProduct(int idProduct, String brand, String subBrand, String type, String energyValue) {
		return new Product(idProduct, brand, subBrand, type, energyValue);
	}
	
	public static final Product nonFoodProduct(int idProduct, String brand, String subBrand, String type) {
		return new Product(idProduct, brand, subBrand, type);
	}
	
	
	
	
	public int getIdProduct() {
		return idProduct;
	}
	public void setIdProduct(int idProduct) {
		this.idProduct = idProduct;
	}
	public String getBrand() {
		return brand;
	}
	public void setBrand(String brand) {
		this.brand = brand;
	}
	public String getSubBrand() {
		return subBrand;
	}
	public void setSubBrand(String subBrand) {
		this.subBrand = subBrand;
	}
	public String getType() {
		return type;
	}
	public void setType(String type) {
		this.type = type;
	}
	
	
	
	@Override
	public String toString() {
		return "Product [idProduct=" + idProduct + ", brand=" + brand + ", subBrand=" + subBrand + ", type=" + type
				+ ", isFood=" + isFood + ", energyValue=" + energyValue + "]";
	}

	public boolean isFood() {
		return isFood;
	}

	public void setFood(boolean isFood) {
		this.isFood = isFood;
	}

	public String getEnergyValue() {
		return energyValue;
	}
	public void setEnergyValue(String energyValue) {
		this.energyValue = energyValue;
	}
	
}

Вызов:

Product bread = Product.foodProduct(1, "brand", "subbrend", "bread type", "500");
System.out.println(bread);
Product pen = Product.nonFoodProduct(2, "pen brand", "pen subbrend", "pen type");
System.out.println(pen);

Вывод на консоль:
Product [idProduct=1, brand=brand, subBrand=subbrend, type=bread type, isFood=true, energyValue=500]
Product [idProduct=2, brand=pen brand, subBrand=pen subbrend, type=pen type, isFood=false, energyValue=null]

Минусы:
1. Если мы захотим создать класс Milk и унаследовать его от Product, то мы его не сможем создать. Т.к. нам будет нужен public конструктор в Product
2. Возможна путаница, если будет в классе Product другие статические методы.

Java 8. Мой опыт использования Stream API

Stream API я в своих проектах использую достаточно давно. Многие вещи решать значительно проще с помощью Stream API, а многие нет.

Все примеры на простеньком классе User

public class User {
	private String name;
	private int age;
	
	
	public User(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	
	@Override
	public String toString() {
		return "User [name=" + name + ", 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;
	}
	

}

Создадим коллекцию объектов типо User:

List<User> users = new ArrayList<>(); 
users.add(new User("Ivanov", 22));
users.add(new User("Petrov", 25));
users.add(new User("Sidorov", 50));
users.add(new User("Zabavski", 32));
users.add(new User("Melnikov", 14));

1) Вывести пользователя с максимальным возрастом:
Типичное решение со стримами:

User oldestUserStrem = users.stream().max(Comparator.comparing(User::getAge)).get();
System.out.println("oldestUserStrem is " + oldestUserStrem);

Но можно значительно проще:

User oldestUser = Collections.max(users, Comparator.comparing(User::getAge));		
System.out.println("oldestUser is " + oldestUser);

Если сделаем статические импорты, то вид вообще примитивен:

User oldestUser = max(users, comparing(User::getAge));

Вообще странно, что метод max класса Collections редко используют.

2)Перебрать все элементы коллекции и, например, если возраст 22 года изменить на 55 лет.

 users.stream().forEach((user) -> {
    
      if (user.getAge() == 22) {
	      	user.setAge(55);
       }
});

Зачем здесь стрим ? у нас нет никаких промежуточных операций. Намного проще так:

users.forEach((user) -> {
		       
        if (user.getAge() == 22) {
        	user.setAge(55);
        }
});

3)Подсчитать количество элементов в коллекции.

users.stream().count()

Не понятно, зачем так писат если у нас нет промежуточных операций. Можно просто написать:

users.size()

4)Проверить, что есть пользователи, старше 30 лет

if (users.stream().filter(p->p.getAge()>30).findFirst().isPresent()) {
	System.out.println("Есть пользователи старше 30 лет");
}

Зачем так писат, если можно намного проще с anyMatch:
if (users.stream().anyMatch(p->p.getAge()>30)) {
	System.out.println("Есть пользователи старше 30 лет");
}

Java 8. Stream API. Пример с reduce

Стала задача, сделать метод, который будет показывать, какое значение по дефолту м.б. в поле:

0 — если нет кол-во знаков после запятой
0.00 — если кол-во знаков после запятой два
0.000 — если кол-во знаков после запятой три и т.п.

Для этого весьма неплохо подходит метод reduce из Java 8

Есть несколько reduce методов:
Первый принимает только Accumulator, который сворачиват все данные в одно
Второй (в примере он) принимает Identity и Accumulator. Identity — это начальное значение
Есть еще третий reduce метод, но его рассматривать не будем.

Теперь вернемся к методу, код очень легкий:

public static String geDefaultKursValue(int digitAfterComma) {				
	if (digitAfterComma == 0) {
		return "0";
	} else {   
		return Stream.generate(() -> "0").limit(digitAfterComma).reduce("0.", (a,b)->a + b);				
	}		
}

Если кол-во знаков после запятой нет, мы возвращает 0.
Если же знаки после запятой есть, мы генерим бесконечный стрим, берем limit, а потом используем второй reduce метод

Java Порядок инициализации полей и блоков при наследовании

Порядок инициализации полей и блоков при наследовании

Есть суперкласс А, от данного суперкласса наследуется подкласс B:

public class A {
	 
    {
        System.out.println("A class. No static block 1");
    }
 
    {
        System.out.println("A class. No static block 2");
    }
 
    {
        System.out.println("A class. No static block 3");
    }
 
    static {
        System.out.println("A class. Static block 1");
 
    }
 
    static {
        System.out.println("A class. Static block 2");
 
    }
    public static String a1 = psv("a1");
     
    public String a11 = pnsv("a11");
 
    static {
        System.out.println("A class. Static block 3");
 
    }
    public static String a2 = psv("a2");
 
    private static String psv(String a) {
        System.out.println("A class. Static perem " + a);
        return a;
 
    }
 
    private String pnsv(String a) {
        System.out.println("A class. No static perem " + a);
        return a;
 
    }
     
    public A() {
    	System.out.println("A class. Constructor");
    }
 
 
    public String a22 = pnsv("a22");
 
    public static String a3 = psv("a3");
     
    {
        System.out.println("A class. No static block 4");
    }
     
    public String a33 = pnsv("a33");
 
}

И класс B, который наследуется от класса A:

public class B extends A{
	private int b = bb(20);

	public B(int b) {	
		this.b = b;
	}
	
	private int bb(int x) {
		System.out.println("B class. No static perem " + x);
		return 0;
	}

	public B() {
		System.out.println("B class. Constructor");
	}
	
	static {
		System.out.println("B class. Static block 1");
	}
	
	
}

Вызов конструктора у класса B:

public class Test {	
	public static void main(String[] args) {
		new B();
	
	}
}

Вывод на консоль:

A class. Static block 1
A class. Static block 2
A class. Static perem a1
A class. Static block 3
A class. Static perem a2
A class. Static perem a3
B class. Static block 1
A class. No static block 1
A class. No static block 2
A class. No static block 3
A class. No static perem a11
A class. No static perem a22
A class. No static block 4
A class. No static perem a33
A class. Constructor
B class. No static perem 20
B class. Constructor

Т.е. порядок такой:
1. Статические блоки и статические поля в порядке объявления суперкласса(А)
2. Статические блоки и статические поля в порядке объявления подкласса(В)
3. Не статические блоки и не статические поля в порядке объявления суперкласса(А)
4. Конструктор суперкласса(А)
5. Не статические блоки и не статические поля в порядке объявления подкласса(B)
6. Конструктор подкласса(B)

Java.Порядок инициализации полей и блоков БЕЗ наследования.

Рассмотрим простенький пример при инициализации полей и блоков (без наследования)
Есть класс:

package inizialization;

public class A {

	{
		System.out.println("No static block 1");
	}

	{
		System.out.println("No static block 2");
	}

	{
		System.out.println("No static block 3");
	}

	static {
		System.out.println("Static block 1");

	}

	static {
		System.out.println("Static block 2");

	}
	public static String a1 = psv("a1");
	
	public String a11 = pnsv("a11");

	static {
		System.out.println("Static block 3");

	}
	public static String a2 = psv("a2");

	private static String psv(String a) {
		System.out.println("static perem " + a);
		return a;

	}

	private String pnsv(String a) {
		System.out.println("no static perem " + a);
		return a;

	}
	
	public A() {
		System.out.println("Вызов конструктора");
	}


	public String a22 = pnsv("a22");

	public static String a3 = psv("a3");
	
	{
		System.out.println("No static block 4");
	}
	
	public String a33 = pnsv("a33");

}

Вызовем конструктор:

package inizialization;

public class Main {
	
	public static void main(String[] args) {
		new A();		
	}
}

На консоль будет выведено:

Static block 1
Static block 2
static perem a1
Static block 3
static perem a2
static perem a3
No static block 1
No static block 2
No static block 3
no static perem a11
no static perem a22
No static block 4
no static perem a33
Вызов конструктора

Т.е. при вызове конструктора сначала инициализируются
1. Статические поля и статические блоки(в порядке объявления в классе)
2. Нестатические поле и нестатичиские блоки (в порядке объявления в классе)
3. Конструтор

Вызовем два раза конструктор класса A:

package inizialization;

public class Main {
	
	public static void main(String[] args) {
		new A();
		new A();		
	}
}

На консоль будет выведено:

Static block 1
Static block 2
static perem a1
Static block 3
static perem a2
static perem a3
No static block 1
No static block 2
No static block 3
no static perem a11
no static perem a22
No static block 4
no static perem a33
Вызов конструктора
No static block 1
No static block 2
No static block 3
no static perem a11
no static perem a22
No static block 4
no static perem a33
Вызов конструктора

Т.е. при вызове двух конструкторов сначала один раз (именно один раз) инициализируются
статичские поля и статические блоки, потом инициализируются нестатические поля и нестатические блоки, потом конструктор,
потом опять нестатические поля и нестатические блоки, потом конструктор.
Важно, что статические поля и статичекские блоки инициализируются ровно один раз.

Теперь рассмотрим пример, где мы просто вызываем статичекий метод (т.е. через ИМЯ_КЛАССА.ИМЯ_СТАТИЧЕСКОГО_МЕТОДА):

package inizialization;

public class Main {
	
	public static void main(String[] args) {
		String test =  A.a1;
	}
}

Вывод на консоль:

Static block 1
Static block 2
static perem a1
Static block 3
static perem a2
static perem a3

Т.е. при вызове статического метода инициализируются ТОЛЬКО статические поля и блоки и только 1 раз, т.е.даже если мы напишем:

String test =  A.a1;
String test2 =  A.a1;

то вывод на консоль не поменяется

Java 8 Добавить дни к дате, исключая выходные.

Часто бывает задача, что нам надо к дате прибавить несколько дней. Но мы должны игнорировать выходные(Суббота, Воскресенье).
Использую новое API для работы с датами, это делается примерно так:

private static final String FORMAT_DATE = "dd.MM.yyyy";
	
public static DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(FORMAT_DATE);
	    
	    
public static String addDaysWithoutWeekends(String date, int countDays) {
    if (countDays <= 0) {
        return date;
    }

    LocalDate resultDate = LocalDate.parse(date, dateFormatter);
    int addedDays = 0;
    while (addedDays < countDays) {
    	resultDate = resultDate.plusDays(1);
        if (!(resultDate.getDayOfWeek() == DayOfWeek.SATURDAY ||
       		resultDate.getDayOfWeek() == DayOfWeek.SUNDAY)) {
                addedDays++;	           
        }
     }

    return dateFormatter.format(resultDate);
}

Если выполнить код:

System.out.println(addDaysWithoutWeekends("31.01.2018", 4));

То вывод на консоль будет:
06.02.2018

Java8. Класс Files метод walk

Иногда есть простенькие задачи, зайти в некий каталог и перебрать там все файлы, оставив только xml.
В Java8 добавили метод walk для перебора файлов

List<Path> paths =  Files.walk(Paths.get(PATH_TO_FILES)).filter(Files::isRegularFile).filter(p->p.toString().endsWith(".xml")).collect(Collectors.toList());

Можно чуть усложнить задачу.
Например нам нао получить список xml файлов в папке, которые содержат какую-то строку.

public static List<File> listFiles(String path, String fileEndsWith, String fileContains) throws IOException {
		return Files.walk(Paths.get(path)).filter(Files::isRegularFile).filter(p->p.toString().endsWith(fileEndsWith)).
				filter(p->{					
					try {
						String content = new String(Files.readAllBytes(p));
						if (content.contains(fileContains)) {
							return true;
						}
					} catch (Exception e) {						
						e.printStackTrace();						
					}
					return false;			
			}).map(Path::toFile).collect(Collectors.toList());
	}

Java8. StreamAPI. Метод peek

В стримах есть классный метод peek
Он служит для двух целей:
а)отладка
б)изменение элементов стрима(т.е. peek возвращает тоже стрим, но применяет функцию к
каждому элементу в стриме)

Например, есть некая коллекция:

public static List<Product> productList = Arrays.asList(new Product("Lipton Yellow Label", 131),
			new Product("English Breakfast", 170), new Product("Lipton Green Label", 180),
			new Product("Gold tea", 1000), new Product("Green tea", 199));

Выполним следующий код:

System.out.println(productList);
		System.out.println("==================");
		productList.stream()
				.peek(p->p.setPrice((int) (p.getPrice()*0.8)))
				.collect(Collectors.toList());
		System.out.println( productList);

Здесь мы все цены умножили на 0.8

Получаем вывод на консоль:

[Product [productName=Lipton Yellow Label, price=131], Product [productName=English Breakfast, price=170], Product [productName=Lipton Green Label, price=180], Product [productName=Gold tea, price=1000], Product [productName=Green tea, price=199]]
==================
[Product [productName=Lipton Yellow Label, price=104], Product [productName=English Breakfast, price=136], Product [productName=Lipton Green Label, price=144], Product [productName=Gold tea, price=800], Product [productName=Green tea, price=159]]

Как видим цена уменьшилась на 20%

Java8. Метод partitioningBy

У класса Collectors есть метод partitioningBy. Он разбивает коллекцию на две части.
В первую часть попадают элементы, которые удовлетворяет условию, а во вторую — которые не удовлетворяют

Есть простой POJO класс Product:

package org.example.sreams;

public class Product {
	
	private String productName;
	
	private int price;
	
	public Product(String productName, int price) {
		super();
		this.productName = productName;
		this.price = price;
	}
	public String getProductName() {
		return productName;
	}
	public void setProductName(String productName) {
		this.productName = productName;
	}
	public int getPrice() {
		return price;
	}
	public void setPrice(int price) {
		this.price = price;
	}
	@Override
	public String toString() {
		return "Product [productName=" + productName + ", price=" + price + "]";
	}
	
	
}

Теперь главный класс:

package org.example.sreams;

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

public class Main {

	private final static int elitePrice = 1000;

	public static List<Product> productList = Arrays.asList(new Product("Lipton Yellow Label", 131),
			new Product("English Breakfast", 170), new Product("Lipton Green Label", 180),
			new Product("Gold tea", 1000), new Product("Green tea", 200));

	public static void main(String args[]) {
		System.out.println("==================");
		Map<Boolean, List<Product>> priceOverMax = productList.stream()
				.collect(Collectors.partitioningBy(tea -> tea.getPrice() >= elitePrice));
		System.out.println(priceOverMax);

	}
}

Вывод на консоль:

==================
{false=[Product [productName=Lipton Yellow Label, price=131], Product [productName=English Breakfast, price=170], Product [productName=Lipton Green Label, price=180], Product [productName=Green tea, price=200]], true=[Product [productName=Gold tea, price=1000]]}

Здесь мы разбили коллекцию на 2 части:
В первую часть(т.е. где true) попали данные где цена больше или равна elitePrice(т.е. 1000) — подходит только один товар,
во вторую часть (т.е. где false) попали данные где цена меньше 1000.

Чтобы взять данные где условие выполнилось надо написать:
priceOverMax.get(true)
соответственно, где условие не выполнилось :
priceOverMax.get(false)