Сортировка объектов в Java8. Comparator, comparing, reversed

Когда-то давно, была статья про сортировку объектов:
https://user12vv.wordpress.com/2015/04/06/java-сортировка-объектов/

Но в Java8 это можно делать намного проще:

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

public class Main {
	
	
	public static List<Worker> getWorkers() {
		 List<Worker> workers = new ArrayList<>();      
         
		 workers.add(new Worker("Alan", 85, "alan4@gmail.com"));
		 workers.add(new Worker("Petrov", 30, "petrov@gmail.com"));
		 workers.add(new Worker("Sidorov", 55, "sidorov@gmail.com"));
		 workers.add(new Worker("Ivanov", 22, "ivanov@gmail.com"));
		 workers.add(new Worker("Alan", 37, "alan1@gmail.com"));
		 return workers;	
	}
	
	
	 public static void main(String args[]) {
	           
		 	List<Worker> workers = getWorkers();
	        System.out.println("dont sort");
	        System.out.println(workers);
	         
	        System.out.println("sort by name");
	      //  workers.sort((w1, w2) -> w1.getName().compareTo(w2.getName()));
	        workers.sort(Comparator.comparing(Worker::getName));
	        System.out.println(workers);
	         
	        System.out.println("sort by age");
	     
	        workers.sort(Comparator.comparing(Worker::getAge));
	        System.out.println(workers);
	         
	        System.out.println("sort by name and age");
	        workers.sort(Comparator.comparing(Worker::getName).thenComparing(Worker::getAge));
	        System.out.println(workers);
	 
	     
	    }
}


Код становится намного более простым. А все потому, что в интрефейс List добавили очень крутой метод sort, который на вход принимает компаратор:

 default void sort(Comparator<? super E> c)

Мы можем делать такие вещи — сортировать по имени, а потом и по возрасту(используем thenComparing):

 workers.sort(Comparator.comparing(Worker::getName).thenComparing(Worker::getAge));

Можно спросить, а как сделать сортировку в обратном порядке. Т.е. я хочу сортировку по возрасту выполнить по убыванию.
Опять же все примитивно, т.к. есть метод reversed:

System.out.println("sort by age reversed");	     
workers.sort(Comparator.comparing(Worker::getAge).reversed());
System.out.println(workers);
Реклама

Фильтрация коллекций на null

Иногда надо удалить из коллекции все null-элементы.
Т.к. я использую Java8 и стримы, то делал так:

List<String> cities  = Arrays.asList("Minsk", "Moscow" , "Kiev",null, "Ljubljana", null, null);
cities = cities.stream().filter(x -> x!=null).collect(Collectors.toList());
System.out.println(cities);

Вывод на консоль:
[Minsk, Moscow, Kiev, Ljubljana]

Но можно также делать и по-другому (имхо более красиво):

List<String> cities  = Arrays.asList("Minsk", "Moscow" , "Kiev",null, "Ljubljana", null, null);
cities = cities.stream().filter(Objects::nonNull).collect(Collectors.toList());
System.out.println(cities);

Java8 Класс Random. Методы ints и doubles

В Java8 в класс Random добавили пару интересных методов. Например, нам надо получить коллекцию случайных чисел от 5 до 8. Раньше, надо было
писать метод для получения случайного числа, потом создать коллекцию и туда записывать элементы.

В Java8 можно сделать так, хотя спорно, что это читабельнее, зато без циклов :

public static List<Integer> getRandomIntList(int count, int start, int finish) {
	return Arrays.stream(new Random().ints(count, start, finish + 1).toArray()).boxed().collect(Collectors.toList());
}

Можно также получат double и long. Пример для double (два знака после запятой)

public static List<Double> getRandomDoubleList(int count, int start, int finish) {
		return Arrays.stream(new Random().doubles(count, start, finish + 1).map(d -> (double) Math.round(d*100)/100).
			toArray()).boxed().collect(Collectors.toList());
}

Класс String. Метод regionMatches

В классе String есть булевский метод regionMatches, который сравнивает
подстроку одной строки с подстрокой другой строки.
Метод используется достаточно редко(я на практике использовал только один раз), но
данный метод достаточно интересен.

Есть целых два таких метода.

Первый — зависит от регистра, второй — нет.

Зависит от регистра

 public boolean regionMatches(int toffset, String other, int ooffset, int len)

toffset — с этой позиции мы начинаем отсчет для строки 1.
other — другая строка
ooffset — позиция для другой строки
len — количество символов, которые мы сравниваем.

Пример:

String a = "Мама мыла раму";
String b = "А раму мыла мама";
		 	
System.out.println(a.regionMatches(10, b, 2, 4));

Есть две строки.
Для первой строки мы отсчитываем 10 позиций и берем 4 символа, для второй строки берем 4 символа со второй позиции.
Т.е. получается, что мы сравниваем слова «раму» с первой и второй строки — строки равны между собой, значит результат
true.

Метод

public boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len) 

Точно такой же, только здесь есть пареметр ignoreCase, который позволяет игнорировать регистр.

Java8 Перебор map

Когда-то давно была мини статья по перебору Map
https://user12vv.wordpress.com/2013/10/30/java-%d1%81%d0%bf%d0%be%d1%81%d0%be%d0%b1%d1%8b-%d0%bf%d0%b5%d1%80%d0%b5%d0%b1%d0%be%d1%80%d0%b0-%d0%ba%d0%be%d0%bb%d0%bb%d0%b5%d0%ba%d1%86%d0%b8%d0%b8-map/

В Java8 все гораздо проще и без всяких циклов:

Map<String, Integer> fruits = new HashMap<>();
 	
fruits.put("pineapple", 100);
fruits.put("banana", 15);
fruits.put("mango", 60);
fruits.put("papaya", 20);
fruits.put("orange", 25);
fruits.put("lemon", 7);

fruits.forEach((key, value) -> {
		System.out.println(key + " == " + value);
});

Метод, который возвращает stream

Для удобства работы с коллекциями используются стримы.
Можно сделать весьма неплохой метод который будет возвращать стрим

Обычный POJO-класс с конструктором, геттерами и сеттерами

public class Person {
	private String name;
    private Integer age;

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

    public String getName() {
		return name;
	}

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

	public Integer getAge() {
		return age;
	}

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

	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}

}

Класс для собирания данных в коллекцию.
Метод getAllPersons собирает данные в коллекцию List. Данные могут храниться где угодно: БД, Excel, в самом коде.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream;

public class PersonCollect {
	
	private List<Person> persons = new ArrayList<>();

	public Stream<Person> persons() {
		return getAllPersons().stream();
	}
		
	public List<Person> getAllPersons() {
		if (persons.isEmpty()) {
			persons = Arrays.asList(new Person("Andrew", 17), new Person("Igor", 23),
					new Person("Ira", 23), new Person("Maxim", 24));
		}
		return persons;
	}

}

Неплохая идея, сделать метод persons(), который будет возвращать стрим, т.е. если мы захотим как-то модифицировать коллекцию, то
будем писать меньше кода.

Соответственно, если надо вывести только людей старше 20 лет, то код будет таким:

		 PersonCollect p = new PersonCollect();
		 List<Person> older20 = p.persons().filter(x->x.getAge()>20).collect(Collectors.toList());
		 System.out.println(older20);

Java8 чтение данных из файла

У класса Files есть метод lines, который читает данные из файла.

public static Stream<String> lines(Path path) throws IOException {
        return lines(path, StandardCharsets.UTF_8);
    }

Что здорово, там по дефолу проставляется кодировка UTF_8, т.е. нам её не надо указывать явно.

Т.е. это стрим, то мы можем сделать метод, который данные из каждой строки файла складывает в коллекцию лист.
Тут можно сделать в filter например игнорирование комментариев(считаем, что # это комментарий), удалять дублирующиеся символы
distinct и еще много чего:

public static List<String> listLineFiles(String pathToFile) {
		
		try {
			return  Files.lines(Paths.get(pathToFile)).filter(x->!x.startsWith("#")).distinct().collect(Collectors.toList());
		} catch (IOException e) {
			return null;
		}
	}

Т.е. есть файл с данными
example.org
example2.org
example3.org
example3.org
#для презентаций новым клиентам
exampledemo.org

Если мы вызовем данный метод, передав туда путь к файлу, то в коллекции будут только:
[example.org, example2.org, example3.org, exampledemo.org]

Java8 Метод removeIf для коллекций

В Java8 появился метод removeIf
Т.е. у вас есть некая коллекция элементов и вам надо из этой коллекции удалить данные.
До Java8 использовались Iterator, что было достаточно громоздко и сложно.
Но сейчас же это можно сделать просто в одну строчку

List<String> cities  = new ArrayList<>(Arrays.asList("Minsk", "Moscow" , "Kiev", "Ljubljana"));
	
cities.removeIf(x -> x.length()>7 && x.startsWith("L"));
	
System.out.println(cities);

На консоль НЕ будут выведены те города, длина которых больше 7 символов и который начинаются с буквы L.
Т.е. в данном случае:

[Minsk, Moscow, Kiev]
Метод removeIf он на вход принимает Предикат, т.е.на вход м.б. любое значение, но возвращаться будет тип boolean

Стоит также отметить, что мы модифицируем существующий список, а не возвращаем новый.

Java8. Основы StreamAPI

Лямбды появились в Java8 уже давно, но активно их использовать начал только сейчас.
Задача, найти четные числа от 5 до 50:

List<Integer> l = Arrays.asList(1,4,2,-30, 6, 45, 50,50, 20);
l  = l.stream().filter(s->s%2 == 0 && s>5 && s<=50).collect(Collectors.toList());
System.out.println(l);

Вывод на консоль:
[6, 50, 50, 20]

Найти количество четных чисел от 5 до 50:

List<Integer> l = Arrays.asList(1,4,2,-30, 6, 45, 50,50, 20);
long count  = l.stream().filter(s->s%2 == 0 && s>5 && s<=50).count();
System.out.println(count);

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

Найти количество четных чисел от 5 до 50 исключая дубликаты(distinct):

List<Integer> l = Arrays.asList(1,4,2,-30, 6, 45, 50,50, 20);
long count  = l.stream().filter(s->s%2 == 0 && s>5 && s<=50).distinct().count();
System.out.println(count);

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

enum ChronoUnit. Метод between

У enum ChronoUnit есть метод between, который позволяет находить время между двумя временными промежутками.
Например, сколько минут между временем 15:30 и 16:25

LocalTime t1 = LocalTime.of(15, 30);
LocalTime t2 = LocalTime.of(16, 25);
		
long l = ChronoUnit.MINUTES.between(t1, t2);
System.out.println(l);

Вывод на консоль — 55

Аналогично, можно искать часы, дни и т.п