Новое в JAVA 8

В этой статье мы рассмотрим грядущие нововведения Java 8. Следующая версия JDK запланирована на март 2014, и она предложит разработчикам следующее:
  • Default методы в интерфейсах и функциональные интерфейсы
  • Лямбда-выражения
  • java.util.function и java.util.stream
  • API для работы с датами и временем – java.time
  • Nashorn JavaScript Engine
Обо всем по порядку.

Default методы в интерфейсах.
В интерфейсах теперь можно определять статичные методы. Дело в том, что до Java 8 для некоего интерфейса, скажем, Employee, существует класс Employees, содержащий статичные методы для генерации объектов Employee и работы с ними. Удобство нововведения в том, что эти самые методы будут находиться в самом интерфейсе, а про класс Employees разработчику можно будет забыть.
В качестве примера посмотрим на метод naturalOrder, который был добавлен в java.util.Comparator
public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {   
    return (Comparator)   
        Comparators.NaturalOrderComparator.INSTANCE;   
}   
В интерфейсах можно будет объявлять default методы. Взглянем на forEach в java.lang.Iterable:
public default void forEach(Consumer action) {   
    Objects.requireNonNull(action);   
    for (T t : this) {   
        action.accept(t);   
    }   
} 
Default метод - это метод в интерфейсе с реализованной логикой, который не требуется обязательно определять в реализации этого интерфейса.
В предыдущих версиях JDK добавлять методы в интерфейсы стандартных библиотек не представлялось возможным, это разрушило бы весь пользовательский код, ведь для каждой реализации интерфейса пришлось бы дописывать реализацию недостающего нового метода. В Java 8 стандартные интерфейсы расширены большим количеством default методов.
Здесь стоит отметить, почему интерфейсы не реализуют default hashCode, equals и toString. Дело в том, что объекты, реализующие интерфейс, являются наследниками Object, в котором уже определены эти методы по умолчанию. Станет неясным, какой из default методов вызывать - тот, который определен в интерфейсе, или тот, что в родительском классе.

Функциональные интерфейсы.
Функциональный интерфейс - это тот интерфейс, который определяет только один абстрактный метод, как, например, java.lang.Runnable:
public abstract void run();  
Чтобы точно определить интерфейс как функциональный, добавлена аннотация @FunctionalInterface, работающая по принципу @Override. Она обозначит замысел и не даст определить второй абстрактный метод в интерфейсе.
Заметим, интерфейс может включать сколько угодно default методов и при этом оставаться функциональным, потому что default методы - не абстрактные.

Лямбда-выражения
Самое значимое нововведение в Java 8 - это лямбда-выражения. Если сказать вообщем, то под ними понимаются анонимные методы, которые при этом еще представляют из себя объект, который можно присваивать переменной и передавать как аргумент в другие методы. Пример:
Runnable r = () -> System.out.println("hello");   
Thread th = new Thread(r);   
th.start();  
Здесь благодаря такому лямбда-выражению мы значительно сокращаем код. Как бы это выглядело без лямбды:
Runnable r = new Runnable() {   
    public void run() {   
        System.out.println("hello");   
    }   
};   
Thread th = new Thread(r);   
th.start();  
В новой версии также введен особый синтаксис ссылок на методы, который по сути - сокращенная форма некоторых лямбда-выражений:

Ссылка и соответствующее лямбда-выражение
String::valueOf       x -> String.valueOf(x)
Object::toString      x -> x.toString()
x::toString              () -> x.toString()
ArrayList::new        () -> new ArrayList<>()

java.util.function
Еще одно нововведение JDK - множество функциональных интерфейсов, которые будут весьма полезны. Рассмотрим некоторые из них:
Function<T, R> - интерфейс, с помощью которого реализуется функция, получающая на ввод экземпляр класса T и возвращающая на выходе экземпляр класса R. Далее не настолько подробно.
Predicate<T> - на ввод - T, возвращает результат типа boolean.
Consumer<T> - на ввод - T, производит некое действие и ничего не возвращает.
Supplier<T> - ничего не принимает на ввод, возвращает T
BinaryOperator<T> - на ввод - два экземпляра T, возвращает один T

Пакет также снабжен примитивными реализациями данных интерфейсов для типов int, long и double.

java.util.stream
java.util.stream введен для поддержки распараллеливания вычислений в потоках. Теперь потоки делятся на последовательные и параллельные. Самая большая польза от этого - в работе с коллекциями:
Stream stream = collection.stream();  
Stream снабжен гибким API для преобразования данных и оперирования над ними. Вот так мы, например, быстро найдем суммарный вес по отдельности красных и синих блоков в коллекции:
int sumOfRedWeights  = blocks.stream().filter(b -> b.getColor() == RED)   
                                     .mapToInt(b -> b.getWeight())   
                                     .sum();   
int sumOfBlueWeights = blocks.stream().filter(b -> b.getColor() == BLUE)   
                                     .mapToInt(b -> b.getWeight())   
                                     .sum();  
Что происходит в коде: 
1) Из коллекции blocks мы получаем stream
2) Метод filter возвращает еще один stream, который уже содержит элементы только одного цвета. Это промежуточный (intermediate) метод класса Stream.
3) Метод mapToInt вызывается у stream'а, который получен в пункте 2), и возвращает stream, содержащий не сами элементы, а их свойства weight (то есть вес)
4) Метод sum вызывается у stream'а из пункта 3), он суммирует все weight и возвращает простой int. Это конечный (terminal) метод, он возвращает одиночный результат.
В целом, стиль очень похож на тот, что используется в функциональном программировании, и это хорошо, так как упростится решение широкого круга типовых задач.
Stream можно распараллелить, и тогда производительность операций над ним возрастет, ведь нагрузка распределится на все ядра процессора. Давайте сделаем подсчет элементов параллельным:
int sumOfWeights = blocks.parallelStream().filter(b -> b.getColor() == RED)   
                                        .mapToInt(b -> b.getWeight())   
                                        .sum();  
Единственное отличие в том, что теперь вызывается метод parallelStream, и каждый следующий промежуточный метод вернет параллельный stream. Таким образом, весь блок вычислений распределится на все ядра процессора.
Сделать stream обратно последовательным можно вызвав метод sequential(). Это пригодиться, когда нам, к примеру, нужно, чтобы отбор элементов по цвету был параллельным, а вычисление суммы weight - последовательным:
int sumOfWeights = blocks.parallelStream().filter(b -> b.getColor() == RED)   
.sequential()//переключаемся в последовательное выполнение   
.mapToInt(b -> b.getWeight())   
.sum();  
java.time
Если вы знакомы с Joda Time, то освоить новое API будет очень легко. Почти все в java.time является immutable. В отличие от старого API даты/времени, такие величины как месяцы и дни недели реализованы через enum. 
Основные классы java.time:
* LocalDateTime, LocalDate, LocalTime - представления даты и времени. Время хранится с точностью до наносекунды, так что в LocalTime можно хранить, например, величину "13:45.30.123456789". Есть множество удобных методов, таких как plusMinutes, plusHours, isAfter, toSecondOfDay и т.д.
* Year, Month, YearMonth, MonthDay, DayOfWeek - типы, связанные с датами, получили больший диапазон. Так, величина Year может быть от "-999,999,999" до "+999,999,999"
* Instant - почти тоже самое, что и java.util.Date, только основой исчисления служат величина long для количества секунд, прошедших с полуночи 1 января 1970, и int - для наносекунд в текущей секунде.
* Duration, Period - полезны, когда нужно выразить данные как "3 года, 2 месяца и 18 дней" или "3 часа, 45 минут и 8 секунд"

Nashorn JavaScript Engine
Nashorn - это движок JavaScript, разрабатываемый полностью на Java компанией Oracle. Он призван дать возможность встраивать код JavaScript в приложения Java. В сравнении с Rhino, который поддерживается Mozilla  Foundation, новый проект показывает более высокую производительность. Nashorn умеет компилировать код JavaScript и генерировать классы Java, которые загружаются специальным загрузчиком. Возможен вызов кода Java прямо из JavaScript. 

На этом новшества Java 8 не заканчиваются, а мы изучили самое ожидаемое в новой версии JDK. Все стандартные коллекции были расширены большим количеством удобных методов и получили возможность распараллеливания вычислений в них с java.util.stream. Вместе с java.util.function Java получила идиомы функционального программирования. Лямбда-выражения, безусловно, сократят объем кода, который разработчикам придется писать, а  новый API для работы с датами и временем пришел на замену порядком устаревшему старому.

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

Отправить комментарий