Java 8 Killer Features. Часть 2

Продолжение первой части статьи, и разбор нововведений Java 8 на примерах.

Обратите внимание на первую часть Java 8 Killer Features. Часть 1.

 

Что мы узнаем нового в Java 8?

6. Встроенные функциональные интерфейсы

7. Потоки

8. Параллельные потоки

9. Map

10. Date API

11. Аннотации

 

6. Встроенные функциональные интерфейсы

Java 8 принесла с собой еще несколько давно известных нам, но в тот же момент новых вещей, а именно множество встроенных функциональных интерфейсов. Да вы и раньше их не раз использовали, на пример Comparator или Runnable.

Теперь все эти интерфейсы стали функциональными интерфейсами и если посмотреть в Исходники то можно увидеть что они проаннотированы как @FunctionalInterface.

Давайте рассмотрим новые встроенные функциональные интерфейсы Java 8. Я покажу лишь перевод, так как Встроенные функциональные интерфейсы требуют отдельного внимания.

Предикаты

Предикаты — это функции, принимающие один аргумент, и возвращающие значение типа boolean.

Интерфейс содержит различные методы по умолчанию, позволяющие строить сложные условия (and, or, negate).

Predicate<String> predicate = (s) -> s.length() > 0;

predicate.test("foo");              // true
predicate.negate().test("foo");     // false

Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;

Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();

 

Функции

Функции принимают один аргумент и возвращают некоторый результат. Методы по умолчанию могут использоваться для построения цепочек вызовов (compose, andThen).

Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);

backToString.apply("123");     // "123"

 

Поставщики

Suppliers — предоставляют результат заданного типа. В отличии от функций, поставщики не принимают аргументов.

Supplier<Person> personSupplier = Person::new;
personSupplier.get();   // new Person

 

Потребители

Consumers — представляют собой операции, которые производятся на одним входным аргументом.

Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));

 

Компараторы

Компараторы хорошо известны по предыдущим версиям Java. Java 8 добавляет в интерфейс различные методы по умолчанию.

Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);

Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");

comparator.compare(p1, p2);             // > 0
comparator.reversed().compare(p1, p2);  // < 0

 

Опциональные значения

Optionals  — это по контейнер для значения, которое может быть null.

Например, вам нужен метод, который возвращает какое-то значение, но иногда он должен возвращать пустое значение. Вместо того, чтобы возвращать null, в Java 8 вы можете вернуть опциональное значение.

Optionals не являются функциональными интерфейсами, однако являются удобным средством предотвращения всеми известным NullPointerException.

Optional<String> optional = Optional.of("bam");

optional.isPresent();           // true
optional.get();                 // "bam"
optional.orElse("fallback");    // "bam"

optional.ifPresent((s) -> System.out.println(s.charAt(0)));     // "b"

 

7. Потоки

java.util.Stream — предоставляет возможность обрабатывать последовательность элементов исполняя одну или несколько операций, которые могут выполняться либо последовательно либо паралельно. Такая возможность предоставленна в реализациях Collection. Рассмотрим на примерах. Для начало создадим коллекцию, которую будем обрабатывать. Map не поддерживается.

List<String> strings = new ArrayList<>();

strings.add("Hello");
strings.add("World");
strings.add("!");
strings.add("We love");
strings.add("Java 8");

 

Фильтруем данные

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

strings
        .stream()
        .filter(s -> s.startsWith("W"))
        .forEach(System.out::println);

Здесь, мы берем поток(последовательности), фильтруем и передаем в стандартный поток вывода.
В результате получим: (World We love).

 

Сортировка данных

В сортировке важно помнить:

1. Сортировка производится по стандартному, если не указать свой Comparator

2. Объекты внутри коллекции не сортируются. Просто возвращается отсортированная коллекция

strings
        .stream()
        .sorted()
        .filter(s -> s.startsWith("W"))
        .forEach(System.out::println);

В результате получим: (We love World).

 

Map

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

strings.add("1");
strings.add("2");
strings.add("3");
strings.add("4");
strings.add("5");

strings
        .stream()
        .sorted()
        .map(Integer::valueOf)
        .forEach(System.out::println);

Как по мне, очень удобно, быстро и без малейших затрат.  Идем дальше.

 

Match

Он поможет найти вхождения, соответствия обьекта шаблону. Для использования также используются предикаты. Есть 3 вида сравнения:

1. Все соответствуют шаблону  allMatch()

2. Хоть один соответствует шаблону anyMatch()

3. Ни один не соответствует noneMatch()

//false так как не все == 1
boolean allMatch = strings
        .stream()
        .allMatch(s -> s.startsWith("1"));

System.out.println(allMatch);

//true так как 1 элемент == 1
boolean anyMatch = strings
        .stream()
        .anyMatch(s -> s.startsWith("1"));

System.out.println(anyMatch);

//true так как 9 не содержиться в коллекции
boolean noneMatch = strings
        .stream()
        .noneMatch(s -> s.startsWith("9"));

System.out.println(noneMatch);

 

Count

Возвратит нам количество элементов . Допустим, просле проведения фильтрации, нужно узнать число элементов.

//Вернет 1, у нас одна 2-ка в коллекции
long count = strings
        .stream()
        .map(Long::valueOf)
        .filter(i -> i.equals(2L))
        .count();

System.out.println(count);

 

8. Параллельные потоки

До этого, операции в коллекции выполнялись в одном потоке. Здесь посмотрим, как выполнить комманду в нескольких потоках. Для этого действительно достаточно вызвать parallelStream(), и продолжить работу с ним как и прежде.

List<Integer> integers = new ArrayList<>();

for (int i = 0; i < 10; i++) {
    integers.add((int)(Math.random()*10));
}

integers
        .parallelStream()
        .filter(i -> i % 2 == 0)
        .sorted()
        .forEachOrdered(System.out::println);

В нашем примере, мы фильтруем данные, которые делятся на 2 без остатка, проводим сортировку и выводим результат. Обратите внимаение что для вывода используется forEachOrdered(), он гарантирует вывод вашей коллекции корректно.

 

9. Map

Хоть этот тип коллекции и не поддерживает потоки, но все же к нему было добавлено несколько важных методов.

getOrDefault() — возвращает значение, или значение по умолчание, если null

forEach() — выполнение действий с каждым элементом карты

replaceAll() — производит замену для всей коллекции по заданной функции

putIfAbsent() — добавление, если по ключу null, и возвращение значения

remove(Object key, Object value) — удаление, если ключ производит маппинг на конкретное значение

computeIfAbsent() — принимает функцию для вычисления null значения или еще не заданного

computeIfPresent() — устанавливаем значения для существуещего ключа, используя функцию

merge() — объединение наших значений

Это не все методы которые присутствуют в новой версии. Используем их на примере:

Map<Integer, String> map = new HashMap<>();

for (int i = 0; i < 10; i++) {
    map.putIfAbsent(i, "value_" + i);
}

//В коллекции в значении по ключу 2 будет "value_2Updated"
map.computeIfPresent(2, (k,v) -> v + "Updated");

//Никаких изминений в коллекции не будет, так как по ключу 100 у нас нету значений
map.computeIfPresent(100, (k,v) -> "New Value");

//Изминений мы также не увидим
map.computeIfAbsent(1, v -> "1");

//А вот так у нас добавиться новый элемент в map
map.computeIfAbsent(10, v -> "value_10");

//Удаляем элемент, с данным ключем и значением
map.remove(10, "value_10");

//Теперь мы получим сообщение о том, что значение было удалено
Object o = map.getOrDefault(10, "Value was deleted!");
System.out.println(o);

//Обьединение значений в итоге получим "value_0_1" вместо "value_0"
map.merge(0,"_1", (v,nv) -> v.concat(nv));

//Добавит ко всем значением подчеркивание вначале
map.replaceAll((k,v) -> v="_" + v);

map.forEach((id, val) -> System.out.println(val));

 

10. Date API

Много нововведений нас ждет также и в управлении временем и датой. Все они находятся в пакете java.time. Множество с классов внутри являются immutable.

Основные пакеты:

java.time — отвечает за работу со временем и датой. Классы есть потокобезопасными и не изменяемые.

java.time.chrono — для предоставления времени в другом стандарте чем стандартный(ISO-8601)

java.time.format — для форматирования и парсинга даты

java.time.temporal — расширенное API для фреймворков и разработчиков библиотек.

java.time.zone —  работа с временными зонами

Рассмотрим примеры работы и новые возможности:

 

LocalDate

LocalDate date = LocalDate.now();

/*Проводятся операции по смещению времени
в сторону увеличения*/
date = date
        .plusYears(1)
        .plusMonths(1)
        .plusWeeks(2)
        .plusDays(5);

/*Проводим смещение времени в сторону уменьшения*/
date = date
        .minusYears(1)
        .minusMonths(1)
        .minusWeeks(2)
        .minusDays(5);

/*Форматирование вывода*/
date
        .format(DateTimeFormatter.BASIC_ISO_DATE);

System.out.println(date);

/*Задаем дату используя строку*/
LocalDate dateFromString = LocalDate.parse("2014-04-16");

System.out.println(dateFromString);

 

Clock

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

Clock clock = Clock.systemDefaultZone();
Date date = Date.from(clock.instant());
long millis = clock.millis();

System.out.println(date);

 

Timezones

Доступ к временным зонам представлен через специальный идентификатор и фабрику по созданию.

ZoneId zoneId1 = ZoneId.of("Europe/Paris");
ZoneId zoneId2 = ZoneId.of("Brazil/East");

System.out.println(zoneId1.getRules());
System.out.println(zoneId2.getRules());

Результатом будет:

ZoneRules[currentStandardOffset=+01:00]
ZoneRules[currentStandardOffset=-03:00]

 

DateTimeFormatter

Бывает что дату мы получаем в определенном формате и нужно ее как-то обработать. Для этого есть очень удобный способ:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy - HH:mm");

LocalDateTime dateTime = LocalDateTime.parse("21.03.2014 - 12:00", formatter);

String formatted = formatter.format(dateTime);
System.out.println(formatted);

Результатом работы программы будет получение даты за заданным шаблоном, а потом преобразовании полученной даты в этот же формат:

21.03.2014 — 12:00

Более детально, о паттернах на офф. сайте.

 

11. Аннотации

Использование множества аннотаций одного типа. Допустим, У нас есть класс, который нужно аннотировать аннотацией Annotation дважды, просто с разными параметрами. Раньше мы б писали вот так:

public @interface Annotation {
    String value();
}

@interface Annotations{
    Annotation[] value();
}

//Аннотированный класс

@Annotations({@Annotation("1"),@Annotation("2")})
public class Book {
}

Теперь используя аннотацию Repeatable мы можем написать это в более читабельной форме:

@Repeatable(Annotations.class)
public @interface Annotation {
    String value();
}

@interface Annotations{
    Annotation[] value();
}

//и наш класс
@Annotation("1")
@Annotation("2")
public class Book {
}

Помимо этого есть еще одно обновление. Оно относится к новым типам target’ов в аннотациях.

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface Annotation {}

ElementType.TYPE_PARAMETER — параметризованный тип(MyClass<T>)

TYPE_USE — принимает любой тип параметра

 

На этом думаю пора бы и закончить.  Для написания данного урока использовали: winterbe.com, а также просторы документаций Oracle.

Урок создан: 21 апреля 2014 | Просмотров: 22144 | Автор: Олег Криль | Правила перепечатки


Добавить комментарий

Добавить комментарий

Ваш e-mail не будет опубликован.

Комментарии:

  • 25 июля 2014 в 04:17

    Денис

    Спасибо за пост.

  • 11 августа 2014 в 13:37

    Алекс

    В примере кода про предикаты ошибка:
    должно быть
    Predicate nonNull = Objects::nonNull;
    Predicate isNull = Objects::isNull;

    • 11 августа 2014 в 13:39

      Алекс

      Predicate\ nonNull = Objects::nonNull;
      Predicate\ isNull = Objects::isNull;

  • 08 декабря 2016 в 15:07

    Евгений

    Здравствуй Александр.

    9. Map
    Хоть этот тип коллекции и не поддерживает потоки …

    Карта — это не коллекция, они не состоят в родстве. Карта вообще стоит обособленно.
    А так статья познавательная, спасибо.

    • 22 февраля 2017 в 07:03

      Семен

      Он просто ошибочно указал map с большой буквы. Это новая функция в java 8 — mapping — преобразование одного типа в другой. Никаго отношения к семеству коллекций Map эти фунции не имеют (именно функции, так как их там несколько, например, mapToInt)