Первая часть перевода статьи Java 8 Features – The ULTIMATE Guide. Вторая часть тут (ссылка может поменяться).
От редакции: статья опубликована в то время как Java 8 была доступна общественности и все указывает на то, что это действительно major-версия.
Здесь мы в изобилии предоставили руководства Java Code Geeks, такие как Играем с Java 8 – Лямбды и Параллелизм, Java 8 руководство по API даты и времени: LocalDateTime и Абстрактный Класс против Интерфейса в эру Java 8.
Мы также ссылаемся на 15 необходимых к прочтению руководств по Java 8 из других источников. Конечно мы рассматриваем некоторые из недостатков, например, Темная сторона Java 8.
Итак, пришло время собрать все основные особенности Java 8 в одном месте для вашего удобства. Наслаждайтесь!
1. Введение
Без сомнений релиз Java 8 величайшее событие со времен Java 5 (выпущена довольно давно, в 2004-м). Он принес множество новых особенностей в Java как в язык, так и в компилятор, библиотеки, инструменты и JVM (виртуальная машина Java). В этом руководстве мы собираемся взглянуть на эти изменения и продемонстрировать различные сценарии использования на реальных примерах. Руководство состоит из нескольких частей каждая из которой затрагивает конкретную сторону платформы:- Язык
- Компилятор
- Библиотеки
- Инструменты
- Среда выполнения (JVM)
2. Новые особенности в языке Java 8
В любом случае Java 8 – это крупный релиз. Можно сказать, что это заняло так много времени из-за реализации возможностей, которые искал каждый Java-разработчик. В этом разделе мы собираемся охватить большинство из них.2.1. Лямбды и Функциональные интерфейсы
Лямбды (также известные как закрытые или анонимные методы) наиболее большое и наиболее ожидаемое изменение языка во всем релизе Java 8. Они позволяют нам задавать функциональность как аргумент метода (объявляя функцию вокруг), или задавать код как данные: понятия с которыми знаком каждый разработчик функционального программирования. Много языков на платформе JVM (Groovy, Scala, …) имели лямбды с первого дня, но у разработчиков Java не было выбора кроме как представлять лямбды через анонимные классы. Обсуждение дизайна лямбд заняли много времени и усилий общественности. Но в конце концов компромиссы были найдены, что привело к появлению новых кратких конструкций. В своей простейшей форме лямбда может быть представлена в виде разделенных запятыми списка параметров, символа –> и тела. Например:
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) )
Обратите внимание, что тип аргумента e определен компилятором. Кроме того вы можете явно указать тип параметра обернув параметр в скобки. Например:
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );
В случае, если тело лямбды более сложное, оно может быть обернуто в фигурные скобки подобно определению обычной функции в Java. Например:
Arrays.asList( "a", "b", "d" ).forEach( e -< {
System.out.print( e );
System.out.print( e );
} );
Лямбда может ссылаться на члены класса и локальные переменные (неявно делает обращение эффективным независимо от того обращается к final
полю или нет). Например, эти 2 фрагмента эквиваленты:
String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
( String e ) -> System.out.print( e + separator ) );
И:
final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
( String e ) -> System.out.print( e + separator ) );
Лямбды могут возвращать значение. Тип возвращаемого значения будет определен компилятором. Объявление return
не требуется, если тело лямбды состоит из одной строки. Два фрагмента кода ниже эквивалентны:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
И:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
int result = e1.compareTo( e2 );
return result;
} );
Разработчики языка долго думали как сделать уже существующие функции лямбда-дружелюбными. В результате появилось понятие функционального интерфейса. Функциональный интерфейс – это интерфейс с только одним методом. В результате он может быть неявно преобразован в лямбда-выражение. java.lang.Runnable
и java.util.concurrent.Callable
два замечательных примера функциональных интерфейсов. На практике функциональные интерфейсы очень хрупки: если кто-то добавит хотя бы один другой метод в определение интерфейса, он не будет больше функциональным и процесс компиляции не завершится. Чтобы избежать этой хрупкости и явно определить намерения интерфейса как функционального в Java 8 была добавлена специальная аннотация @FunctionalInterface
(все существующие интерфейсы в библиотеке Java получили аннотацию @FunctionalInterface). Давайте посмотрим на это простое определение функционального интерфейса:
@FunctionalInterface
public interface Functional {
void method();
}
Есть одна вещь, которую нужно иметь в виду: методы по умолчанию и статические методы не нарушают принцип функционального интерфейса и могут быть объявлены:
@FunctionalInterface
public interface FunctionalDefaultMethods {
void method();
default void defaultMethod() {
}
}
Лямбды наиболее популярный пункт Java 8. Они имеют весь потенциал для привлечения большего количества разработчиков к этой прекрасной платформе и обеспечить умелую поддержку функциональным особенностям в чистой Java. Для более детальной информации обратитесь к официальной документации.
2.2. Интерфейсы по умолчанию и статические методы
В Java 8 было расширено определение интерфейсов двумя новыми концепциями: метод по умолчанию и статический метод. Методы по умолчанию делают интерфейсы несколько похожими на трейты, но служат немного другой цели. Они позволяют добавлять новые методы к существующим интерфейсам не нарушая обратную совместимость для ранее написанных версий этих интерфейсов. Разница между методами по умолчанию и абстрактными методами в том, что абстрактные методы должны быть реализованы, а методы по умолчанию нет. Вместо этого каждый интерфейс должен предоставить так называемую реализацию по умолчанию, и все наследники будут получать ее по умолчанию (с возможностью переопределить эту реализацию по умолчанию при необходимости). Давайте посмотрим на пример ниже.
private interface Defaulable {
// Интерфейсы теперь разрешают методы по умолчанию,
// клиент может реализовывать (переопределять)
// или не реализовывать его
default String notRequired() {
return "Default implementation";
}
}
private static class DefaultableImpl implements Defaulable {
}
private static class OverridableImpl implements Defaulable {
@Override
public String notRequired() {
return "Overridden implementation";
}
}
Интерфейс Defaulable
объявил метод по умолчанию notRequired()
используя ключевое слово default
как часть определения метода. Один из классов, DefaultableImpl
, реализует этот интерфейс оставляя метод по умолчанию как есть. Другой класс, OverridableImpl
, переопределяет реализацию по умолчанию и предоставляет свою собственную.
Другая интересная особенность, представленная в Java 8 – это то, что интерфейсы могут объявить (и предложить реализацию) статических методов. Вот пример:
private interface DefaulableFactory {
// Interfaces now allow static methods
static Defaulable create( Supplier<Defaulable> supplier ) {
return supplier.get();
}
}
Небольшой фрагмент кода объединяет метод по умолчанию и статический метод из примера выше:
public static void main( String[] args ) {
Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
System.out.println( defaulable.notRequired() );
defaulable = DefaulableFactory.create( OverridableImpl::new );
System.out.println( defaulable.notRequired() );
}
Вывод на консоль этой программы выглядит так:
Default implementation
Overridden implementation
Реализация методов по умолчанию в JVM является очень эффективной и вызов метода поддерживается инструкциями байт-кода. Методы по умолчанию позволили существующим Java интерфейсам развиваться не нарушая процесс компиляции. Хорошие примеры – множество добавленных методов в интерфейс java.util.Collection
: stream()
, parallelStream()
, forEach()
, removeIf()
, …
Хотя будучи мощными, методы по умолчанию следует использовать с осторожностью: прежде, чем объявить метод по умолчанию стоит подумать дважды действительно ли это необходимо так как это может привести к неоднозначности компиляции и ошибках в сложных иерархиях. Для получения более детальной информации обращайтесь к документации.
2.3. Ссылочные методы
Ссылочные методы внедряют полезный синтаксис, чтобы ссылаться на существующие методы или конструкторы Java-классов или объектов (экземпляров). Совместно с лямбда-выражениями, ссылочные методы делают языковые конструкции компактными и лаконичными, делая его шаблонным. Ниже представлен классCar
как пример различных определений методов, давайте выделим четыре поддерживаемых типа ссылочных методов:
public static class Car {
public static Car create( final Supplier<Car> supplier ) {
return supplier.get();
}
public static void collide( final Car car ) {
System.out.println( "Collided " + car.toString() );
}
public void follow( final Car another ) {
System.out.println( "Following the " + another.toString() );
}
public void repair() {
System.out.println( "Repaired " + this.toString() );
}
}
Первый ссылочный метод – ссылка на конструктор с синтаксисом Class::new
или альтернативный для дженериков (generics) Class< T >::new
. Обратите внимание, что конструктор не имеет аргументов.
final Car car = Car.create( Car::new );
final List<Car> cars = Arrays.asList( car );
Второй вариант это ссылка на статический метод с синтаксисом Class::static_method
. Обратите внимание, что метод принимает ровно один параметр типа Car
.
cars.forEach( Car::collide );
Третий тип – ссылка на метод экземпляра произвольного объекта определенного типа с синтаксисом Class::method
. Обратите внимание, что никакие аргументы не принимаются методом.
cars.forEach( Car::repair );
И последний, четвертый тип – ссылка на метод экземпляра определенного класса с синтаксисом instance::method
. Обратите внимание, что метод принимает только один параметр типа Car
.
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
Запуск всех этих примеров как Java-программы производит следующий вывод на консоль (ссылка на класс Car
может отличаться):
Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Для более детальной информации и деталей ссылочных методов обращайтесь к официальной документации.
2.4. Повторяющиеся аннотации
С тех пор как в Java 5 была введена поддержка аннотаций, эта возможность стала очень популярной и очень широко используемой. Тем не менее, одним из ограничений использования аннотаций был тот факт, что одна и та же аннотация не может быть объявлена более одного раза в одном месте. Java 8 нарушает это правило и представляет повторяющиеся аннотации. Это позволяет тем же аннотациям повторяться несколько раз в том месте где они объявлены. Повторяющиеся аннотации следует аннотировать себя с использованием аннотации@Repeatable
. На самом деле, это не сколько изменение языка сколько трюк компилятора, в то время как техника остается той же. Давайте посмотрим на простой пример:
package com.javacodegeeks.java8.repeatable.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class RepeatingAnnotations {
@Target( ElementType.TYPE )
@Retention( RetentionPolicy.RUNTIME )
public @interface Filters {
Filter[] value();
}
@Target( ElementType.TYPE )
@Retention( RetentionPolicy.RUNTIME )
@Repeatable( Filters.class )
public @interface Filter {
String value();
};
@Filter( "filter1" )
@Filter( "filter2" )
public interface Filterable {
}
public static void main(String[] args) {
for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {
System.out.println( filter.value() );
}
}
}
Как мы можем видеть, класс Filter
аннотирован с помощью @Repeatable( Filters.class
). Filters
просто владелец аннотаций Filter
, но компилятор Java старается скрыть их присутствие от разработчиков. Таким образом, интерфейс Filterable
содержит аннотации Filter
, которые объявлены дважды (без упоминания Filters
).
Также Reflection API предоставляет новый метод getAnnotationsByType()
для возвращения повторяющихся аннотаций некоторого типа (помните, что Filterable.class
.getAnnotation( Filters.class
) вернет экземпляр Filters
введенный компилятором).
Вывод программы будет выглядеть следующим образом:
filter1
filter2
За более детальной информацией обратитесь к официальной документации.
2.5. Улучшенное выведение типов
Компилятор Java 8 получил много улучшений выведения типов. Во многих случаях явные параметры типов могут быть определены компилятором, тем самым делая код чище. Давайте взглянем на один из примеров:
package com.javacodegeeks.java8.type.inference;
public class Value<T> {
public static<T> T defaultValue() {
return null;
}
public T getOrDefault( T value, T defaultValue ) {
return ( value != null ) ? value : defaultValue;
}
}
А вот использование с типом Value<String>
:
package com.javacodegeeks.java8.type.inference;
public class TypeInference {
public static void main(String[] args) {
final Value<String> value = new Value<>();
value.getOrDefault( "22", Value.defaultValue() );
}
}
Параметр типа Value.defaultValue()
определеляется автоматически и не должен быть представлен явно. В Java 7 тот же пример не будет компилироваться и должен быть переписан в виде <NOBR>Value.<String>defaultValue()</NOBR>.
2.6. Расширенная поддержка аннотаций
Java 8 расширяет контекст где могут быть использованы аннотации. Сейчас аннотацию может иметь почти все: локальные переменные, обобщенные типы, суперклассы и реализуемые интерфейсы, даже исключения методов. Несколько примеров представлено ниже:
package com.javacodegeeks.java8.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;
public class Annotations {
@Retention( RetentionPolicy.RUNTIME )
@Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
public @interface NonEmpty {
}
public static class Holder<@NonEmpty T> extends @NonEmpty Object {
public void method() throws @NonEmpty Exception {
}
}
@SuppressWarnings( "unused" )
public static void main(String[] args) {
final Holder<String> holder = new @NonEmpty Holder<String>();
@NonEmpty Collection<@NonEmpty String> strings = new ArrayList<>();
}
}
ElementType.TYPE_USE
и ElementType.TYPE_PARAMETER
два новых типа элементов, чтобы описать соответствующий контекст аннотаций. Annotation Processing API
также потерпело незначительные изменения, чтобы распознать новые типы аннотаций в Java.
3. Новые возможности в компиляторе Java
3.1. Имена параметров
На протяжении всего времени разработчики Java изобретали разнообразные способы сохранения имен параметров метода в Java байт-коде, чтобы сделать их доступными во время выполнения (например, библиотека Paranamer). И наконец, Java 8 создает эту нелегкую функцию в языке (используя Reflection API и методParameter.getName()
) и байт-коде (с помощью нового аргумента компилятора javac
: –parameters
).
package com.javacodegeeks.java8.parameter.names;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class ParameterNames {
public static void main(String[] args) throws Exception {
Method method = ParameterNames.class.getMethod( "main", String[].class );
for( final Parameter parameter: method.getParameters() ) {
System.out.println( "Parameter: " + parameter.getName() );
}
}
}
Если вы скомпилируете этот класс без использования аргумента –parameters
и затем запустите программу, вы увидите что-то типа этого:
Parameter: arg0
С параметром –parameters
переданным компилятору, вывод программы будет отличаться (будет показано фактическое имя параметра):
Parameter: args
Для опытных пользователей Maven аргумент –parameters может быть добавлен в компиляцию используя секцию maven-compiler-plugin
:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<compilerArgument>-parameters</compilerArgument>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
Для проверки доступности имен параметров есть удобный isNamePresent()
метод предоставленный классом Parameter
.
4. Новые инструменты Java
Java 8 поставляется с новым набором инструментов командной строки. В этом разделе мы рассмотрим самые интересные из них.4.1. Движок Nashorn: jjs
jjs – автономный движок Nashorn, который основан на командной строке. Он принимает список JavaScript файлов исходного кода и запускает их. Например, давайте создадим файл func.js следующего содержания:
function f() {
return 1;
};
print( f() + 1 );
Чтобы запустить этот файл давайте передадим его как аргумент в jjs:
jjs func.js
Вывод на консоль будет таким:
2
Для более подробной информации смотрите документацию.
4.2. Анализатор зависимостей класса: jdeps
jdeps действительно отличный инструмент командной строки. Он показывает зависимости на уровне пакета или класса для Java классов. Он принимает .class файл, папку или JAR-файл на вход. По умолчанию jdeps выводит зависимости в стандартную систему вывода (консоль). В качестве примера давайте посмотрим на отчет зависимостей популярной библиотеки Spring Framework. Чтоб сделать пример коротким давайте посмотрим зависимости только для JAR-файлаorg.springframework.core-3.0.5.RELEASE.jar
.
jdeps org.springframework.core-3.0.5.RELEASE.jar
Эта команда выводит довольно много, поэтому мы будем анализировать только часть вывода. Зависимости сгруппированы по пакетам. Если зависимостей нет – будет выведено not found.
org.springframework.core-3.0.5.RELEASE.jar -> C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar
org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)
-> java.io
-> java.lang
-> java.lang.annotation
-> java.lang.ref
-> java.lang.reflect
-> java.util
-> java.util.concurrent
-> org.apache.commons.logging not found
-> org.springframework.asm not found
-> org.springframework.asm.commons not found
org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)
-> java.lang
-> java.lang.annotation
-> java.lang.reflect
-> java.util
Для более детальной информации обращайтесь к официальной документации.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ