Статьи

Учебное пособие по функциям Java 8 — ULTIMATE Guide (PDF Download)

ПРИМЕЧАНИЕ ДЛЯ РЕДАКЦИИ: В этом посте мы представляем всеобъемлющее руководство по функциям Java 8. Прошло много времени с тех пор, как Java 8 стала достоянием общественности, и все указывает на тот факт, что это действительно важный релиз.

Мы предоставили множество учебников здесь, на Java Code Geeks, таких как Игра с Java 8 — Lambdas and Concurrency , Учебное пособие по API Java 8 Date Time: LocalDateTime и Абстрактный класс в сравнении с интерфейсом в эпоху JDK 8 .

Мы также ссылались на 15 обязательных руководств по Java 8 из других источников. Конечно, мы также рассмотрели некоторые недостатки, такие как Темная сторона Java 8 .

Теперь пришло время собрать все основные функции Java 8 в одном справочном посте для вашего удовольствия от чтения. Наслаждайтесь!

1. Введение

Без сомнения, выпуск Java 8 — величайшая вещь в мире Java со времен Java 5 (выпущенного довольно давно, еще в 2004 году). Он привносит в Java множество новых функций в качестве языка, его компилятора, библиотек, инструментов и самой JVM (виртуальной машины Java). В этом уроке мы рассмотрим все эти изменения и продемонстрируем различные сценарии использования на реальных примерах.

Учебник состоит из нескольких частей, каждая из которых касается определенной стороны платформы:

  • язык
  • компилятор
  • библиотеки
  • инструменты
  • время выполнения (JVM)

2. Новые возможности в языке Java

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

2.1. Лямбды и функциональные интерфейсы

Лямбды (также известные как замыкания) — самое большое и ожидаемое изменение языка во всей версии Java 8. Они позволяют нам рассматривать функциональность как аргумент метода (передавая функции) или обрабатывать код как данные: концепции, с которыми каждый функциональный разработчик хорошо знаком. Во многих языках на платформе JVM (Groovy, Scala , …) с первого дня были лямбды, но у разработчиков Java не было иного выбора, кроме как забивать лямбды стандартными анонимными классами.

Обсуждения дизайна Lambdas заняли много времени и усилий сообщества. Но, наконец, были найдены компромиссы, ведущие к новым кратким и компактным языковым конструкциям. В своей простейшей форме лямбда может быть представлена ​​в виде списка параметров, разделенных запятыми, символа -> и тела. Например:

1
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );

Обратите внимание, что тип аргумента e выводится компилятором. В качестве альтернативы вы можете явно указать тип параметра, заключив определение в скобки. Например:

1
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );

В случае, если тело лямбды является более сложным, оно может быть заключено в квадратные скобки, как обычное определение функции в Java. Например:

1
2
3
4
Arrays.asList( "a", "b", "d" ).forEach( e -> {
    System.out.print( e );
    System.out.print( e );
} );

Лямбды могут ссылаться на членов класса и локальные переменные (неявно делая их эффективными окончательными, если они не являются). Например, эти два фрагмента эквивалентны:

1
2
3
String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );

И:

1
2
3
final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );

Лямбда может возвращать значение. Тип возвращаемого значения будет выведен компилятором. Оператор return не требуется, если лямбда-тело состоит из одной строки. Два фрагмента кода ниже эквивалентны:

1
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );

И:

1
2
3
4
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). Давайте посмотрим на это простое определение функционального интерфейса:

1
2
3
4
@FunctionalInterface
public interface Functional {
    void method();
}

Следует помнить одну вещь: стандартные и статические методы не нарушают контракт функционального интерфейса и могут быть объявлены:

1
2
3
4
5
6
7
@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();
        
    default void defaultMethod() {           
    }       
}

Лямбды — это самая большая точка продажи Java 8. У нее есть все возможности привлечь все больше и больше разработчиков к этой великолепной платформе и обеспечить современную поддержку концепций функционального программирования на чистой Java. Для более подробной информации, пожалуйста, обратитесь к официальной документации .

2.2. Стандартные и статические методы интерфейса

Java 8 расширяет объявления интерфейса двумя новыми понятиями: стандартные и статические методы. Методы по умолчанию делают интерфейсы несколько похожими на черты, но служат немного другой цели. Они позволяют добавлять новые методы к существующим интерфейсам, не нарушая двоичную совместимость с кодом, написанным для более старых версий этих интерфейсов.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
private interface Defaulable {
    // Interfaces now allow default methods, the implementer may or
    // may not implement (override) them.
    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, заключается в том, что интерфейсы могут объявлять (и обеспечивать реализацию) статических методов. Вот пример.

1
2
3
4
5
6
private interface DefaulableFactory {
    // Interfaces now allow static methods
    static Defaulable create( Supplier< Defaulable > supplier ) {
        return supplier.get();
    }
}

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

1
2
3
4
5
6
7
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() );
}

Консольный вывод этой программы выглядит так:

1
2
Default implementation
Overridden implementation

Реализация методов по умолчанию в JVM очень эффективна и поддерживается инструкциями байтового кода для вызова метода. Методы по умолчанию позволили существующим Java-интерфейсам развиваться без прерывания процесса компиляции. Хорошие примеры — это множество методов, добавленных в интерфейс java.util.Collection : stream () , parallelStream () , forEach () , removeIf () ,…

Хотя методы по умолчанию являются мощными, их следует использовать с осторожностью: перед объявлением метода по умолчанию лучше дважды подумать, действительно ли он нужен, поскольку это может привести к неоднозначности и ошибкам компиляции в сложных иерархиях. Для более подробной информации, пожалуйста, обратитесь к официальной документации .

2,3. Ссылки на метод

Ссылки на методы предоставляют полезный синтаксис для прямой ссылки на существующие методы или конструкторы классов или объектов Java (экземпляры). В сочетании с выражениями Lambdas ссылки на методы делают языковые конструкции компактными и лаконичными, исключая шаблон.

Ниже, рассматривая класс Car как пример различных определений методов, выделим четыре поддерживаемых типа ссылок на методы.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
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 или, в качестве альтернативы, для обобщенных классов Class <T> :: new . Обратите внимание, что конструктор не имеет аргументов.

1
2
final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );

Второй тип — это ссылка на статический метод с синтаксисом Class :: static_method . Обратите внимание, что метод принимает ровно один параметр типа Car .

1
cars.forEach( Car::collide );

Третий тип — это ссылка на метод экземпляра произвольного объекта определенного типа с синтаксисом Class :: method . Пожалуйста, обратите внимание, что аргументы метода не принимаются.

1
cars.forEach( Car::repair );

И последний, четвертый тип — это ссылка на метод экземпляра конкретного экземпляра класса синтаксис instance :: method . Обратите внимание, что метод принимает ровно один параметр типа Car .

1
2
final Car police = Car.create( Car::new );
cars.forEach( police::follow );

Выполнение всех этих примеров в виде Java-программы приводит к следующему выводу на консоль (фактические экземпляры Car могут отличаться):

1
2
3
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. На самом деле, это не изменение языка, а скорее трюк с компилятором, так как метод остается неизменным. Давайте посмотрим на быстрый пример:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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 ). Фильтры являются всего лишь держателем аннотаций фильтров, но компилятор Java старается скрыть свое присутствие от разработчиков. Таким образом, интерфейс Filterable имеет аннотацию Filter, определенную дважды (без упоминания о Filters ).

Кроме того, API Reflection предоставляет новый метод getAnnotationsByType () для возврата повторяющихся аннотаций некоторого типа (обратите внимание, что Filterable. Class .getAnnotation (Filters. Class ) будет возвращать экземпляр Filters, введенный компилятором).

Вывод программы выглядит так:

1
2
filter1
filter2

Для более подробной информации, пожалуйста, обратитесь к официальной документации .

2.5. Лучший вывод типа

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

01
02
03
04
05
06
07
08
09
10
11
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> .

1
2
3
4
5
6
7
8
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() );
    }
}

Параметр типа Значение. defaultValue () выводится и не требуется указывать. В Java 7 тот же пример не будет компилироваться и должен быть переписан в Value. <String> defaultValue () .

2.6. Расширенная поддержка аннотаций

Java 8 расширяет контекст, в котором можно использовать аннотации. Теперь можно комментировать в основном все: локальные переменные, универсальные типы, суперклассы и реализующие интерфейсы, даже объявление исключений метода. Пара примеров показана ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 — это два новых типа элементов для описания применимого контекста аннотации. API обработки аннотаций также претерпел некоторые незначительные изменения, чтобы распознавать эти новые аннотации типов в языке программирования Java.

3. Новые возможности в компиляторе Java

3.1. Имена параметров

Буквально на века разработчики Java придумывают различные способы сохранения имен параметров методов в байт-коде Java и делают их доступными во время выполнения (например, библиотека Paranamer ). И, наконец, Java 8 встраивает эту требовательную функцию в язык (используя Reflection API и метод Parameter.getName () ) и байт-код (используя новый аргумент компилятора javac –parameters ).

01
02
03
04
05
06
07
08
09
10
11
12
13
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, а затем запустите эту программу, вы увидите что-то вроде этого:

1
Parameter: arg0

Если аргумент –parameters передан компилятору, вывод программы будет другим (будет показано фактическое имя параметра):

1
Parameter: args

Для опытных пользователей Maven аргумент –parameters может быть добавлен в компилятор, используя раздел конфигурации maven-compiler-plugin :

01
02
03
04
05
06
07
08
09
10
<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>

Последний выпуск Eclipse Kepler SR2 с Java 8 (пожалуйста, ознакомьтесь с этими инструкциями по загрузке ) предоставляет полезную опцию конфигурации для управления этим параметром компилятора, как показано на рисунке ниже.

Java 8 - настройка проектов Eclipse
Рисунок 1. Конфигурирование проектов Eclipse для поддержки нового аргумента компилятора Java 8 -parameters

Кроме того, для проверки доступности имен параметров существует удобный метод isNamePresent (), предоставляемый классом Parameter .

4. Новые возможности в библиотеках Java

Java 8 добавляет много новых классов и расширяет существующие, чтобы обеспечить лучшую поддержку современного параллелизма, функционального программирования, даты / времени и многих других.

4.1. По желанию

Знаменитая исключительная ситуация NullPointerException , безусловно, является самой популярной причиной сбоев Java-приложений. Давным-давно великий проект Google Guava представил Optional как решение для NullPointerException , препятствуя загрязнению кодовой базы нулевыми проверками и поощряя разработчиков писать более чистый код. Вдохновленный Google Guava , Optional теперь является частью библиотеки Java 8.

Необязательным является просто контейнер: он может содержать значение некоторого типа T или просто быть нулевым . Он предоставляет множество полезных методов, поэтому явные проверки на нуль больше не могут служить оправданием. Пожалуйста, обратитесь к официальной документации Java 8 для более подробной информации.

Мы рассмотрим два небольших примера необязательного использования: со значением NULL и со значением, которое не допускает нулевые значения s.

1
2
3
4
Optional< String > fullName = Optional.ofNullable( null );
System.out.println( "Full Name is set? " + fullName.isPresent() );       
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) );
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );

Метод isPresent () возвращает true, если этот экземпляр Optional имеет ненулевое значение, и false в противном случае. Метод orElseGet () предоставляет резервный механизм в случае, если Optional имеет значение NULL , принимая функцию для генерации функции по умолчанию. Метод map () преобразует текущее значение Optional и возвращает новый экземпляр Optional . Метод orElse () похож на orElseGet (), но вместо функции он принимает значение по умолчанию. Вот результат этой программы:

1
2
3
Full Name is set? false
Full Name: [none]
Hey Stranger!

Давайте кратко рассмотрим другой пример:

1
2
3
4
5
Optional< String > firstName = Optional.of( "Tom" );
System.out.println( "First Name is set? " + firstName.isPresent() );       
System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) );
System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
System.out.println();

И вот вывод:

1
2
3
First Name is set? true
First Name: Tom
Hey Tom!

Для более подробной информации, пожалуйста, обратитесь к официальной документации.

4.2. Streams

Недавно добавленный Stream API ( java.util.stream ) вводит в Java программирование в функциональном стиле. Это, безусловно, наиболее полное дополнение к библиотеке Java, предназначенное для того, чтобы сделать разработчиков Java значительно более производительными, позволяя им писать эффективный, чистый и лаконичный код.

Stream API значительно упрощает обработку коллекций (но не ограничивается только коллекциями Java, как мы увидим позже). Давайте начнем с простого класса Task.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Streams  {
    private enum Status {
        OPEN, CLOSED
    };
    
    private static final class Task {
        private final Status status;
        private final Integer points;
 
        Task( final Status status, final Integer points ) {
            this.status = status;
            this.points = points;
        }
        
        public Integer getPoints() {
            return points;
        }
        
        public Status getStatus() {
            return status;
        }
        
        @Override
        public String toString() {
            return String.format( "[%s, %d]", status, points );
        }
    }
}

Задача имеет некоторое представление о точках (или псевдосложность) и может быть либо ОТКРЫТА, либо ЗАКРЫТА . А затем давайте представим небольшой сборник заданий для игры.

1
2
3
4
5
final Collection< Task > tasks = Arrays.asList(
    new Task( Status.OPEN, 5 ),
    new Task( Status.OPEN, 13 ),
    new Task( Status.CLOSED, 8 )
);

Первый вопрос, на который мы собираемся ответить, — это сколько очков в сумме имеют все ОТКРЫТЫЕ задачи? До Java 8 обычным решением для этого была бы какая-то итерация foreach . Но в Java 8 ответы — это потоки: последовательность элементов, поддерживающих последовательные и параллельные агрегатные операции.

1
2
3
4
5
6
7
8
// Calculate total points of all active tasks using sum()
final long totalPointsOfOpenTasks = tasks
    .stream()
    .filter( task -> task.getStatus() == Status.OPEN )
    .mapToInt( Task::getPoints )
    .sum();
        
System.out.println( "Total points: " + totalPointsOfOpenTasks );

И вывод на консоль выглядит так:

1
Total points: 18

Здесь происходит несколько вещей. Во-первых, коллекция задач преобразуется в потоковое представление. Затем операция фильтрации в потоке отфильтровывает все ЗАКРЫТЫЕ задачи. На следующем шаге операция mapToInt преобразует поток задач s в поток целых чисел, используя метод Task :: getPoints каждого экземпляра задачи. И, наконец, все точки суммируются с использованием метода суммы , что дает конечный результат.

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

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

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

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

1
2
3
4
5
6
7
8
// Calculate total points of all tasks
final double totalPoints = tasks
   .stream()
   .parallel()
   .map( task -> task.getPoints() ) // or map( Task::getPoints )
   .reduce( 0, Integer::sum );
    
System.out.println( "Total points (all tasks): " + totalPoints );

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

Вот вывод консоли:

1
Total points (all tasks): 26.0

Часто возникает необходимость выполнить группировку элементов коллекции по некоторым критериям. Потоки могут помочь с этим, а также пример ниже демонстрирует.

1
2
3
4
5
// Group tasks by their status
final Map< Status, List< Task > > map = tasks
    .stream()
    .collect( Collectors.groupingBy( Task::getStatus ) );
System.out.println( map );

Вывод консоли в этом примере выглядит следующим образом:

1
{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}

Чтобы закончить с примером задач, давайте посчитаем общий процент (или вес) каждой задачи по всей коллекции, основываясь на ее точках.

01
02
03
04
05
06
07
08
09
10
11
12
// Calculate the weight of each tasks (as percent of total points)
final Collection< String > result = tasks
    .stream()                                        // Stream< String >
    .mapToInt( Task::getPoints )                     // IntStream
    .asLongStream()                                  // LongStream
    .mapToDouble( points -> points / totalPoints )   // DoubleStream
    .boxed()                                         // Stream< Double >
    .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
    .mapToObj( percentage -> percentage + "%" )      // Stream< String>
    .collect( Collectors.toList() );                 // List< String >
        
System.out.println( result );

Вывод консоли только здесь:

1
[19%, 50%, 30%]

И, наконец, как мы упоминали ранее, Stream API — это не только коллекции Java. Типичные операции ввода-вывода, такие как чтение текстового файла построчно, являются очень хорошим кандидатом для получения выгоды от потоковой обработки. Вот небольшой пример, чтобы подтвердить это.

1
2
3
4
final Path path = new File( filename ).toPath();
try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {
    lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );
}

Метод onClose, вызываемый в потоке, возвращает эквивалентный поток с дополнительным обработчиком закрытия. Обработчики закрытия запускаются при вызове метода close () в потоке.

Stream API вместе с Lambdas и Method References, созданными интерфейсом Default и Static Methods, является ответом Java 8 на современные парадигмы разработки программного обеспечения. Для более подробной информации, пожалуйста, обратитесь к официальной документации .

4,3. API даты / времени (JSR 310)

Java 8 делает еще один подход к управлению датой и временем, предоставляя новый API Date-Time (JSR 310) . Манипулирование датой и временем является одной из худших проблем для разработчиков Java. Стандартный java.util.Date, за которым следует java.util.Calendar, вообще не улучшил ситуацию (возможно, сделал ее еще более запутанной).

Так родился Joda-Time : отличный альтернативный API даты / времени для Java. Новый API Date-Time в Java 8 (JSR 310) находился под сильным влиянием Joda-Time и использовал лучшее из этого. Новый пакет java.time содержит все классы для даты, времени, даты / времени, часовых поясов, моментов времени, продолжительности и манипулирования часами . При разработке API неизменность была принята во внимание очень серьезно: изменения не допускаются (сложный урок, извлеченный из java.util.Calendar ). Если требуется изменение, будет возвращен новый экземпляр соответствующего класса.

Давайте посмотрим на ключевые классы и примеры их использования. Первый класс — Часы, который обеспечивает доступ к текущему моменту, дате и времени, используя часовой пояс. Часы можно использовать вместо System.currentTimeMillis () и TimeZone.getDefault () .

1
2
3
4
// Get the system clock as UTC offset
final Clock clock = Clock.systemUTC();
System.out.println( clock.instant() );
System.out.println( clock.millis() );

Пример вывода на консоль:

1
2
2014-04-12T15:19:29.282Z
1397315969360

Другие новые классы, которые мы рассмотрим, — LocaleDate и LocalTime . LocaleDate содержит только часть даты без часового пояса в календарной системе ISO-8601. Соответственно, LocaleTime содержит только часть времени без часового пояса в календарной системе ISO-8601. И LocaleDate, и LocaleTime могут быть созданы из часов .

01
02
03
04
05
06
07
08
09
10
11
12
13
// Get the local date and local time
final LocalDate date = LocalDate.now();
final LocalDate dateFromClock = LocalDate.now( clock );
        
System.out.println( date );
System.out.println( dateFromClock );
        
// Get the local date and local time
final LocalTime time = LocalTime.now();
final LocalTime timeFromClock = LocalTime.now( clock );
        
System.out.println( time );
System.out.println( timeFromClock );

Пример вывода на консоль:

1
2
3
4
2014-04-12
2014-04-12
11:25:54.568
15:25:54.568

LocalDateTime объединяет вместе LocaleDate и LocalTime и содержит дату со временем, но без часового пояса в календарной системе ISO-8601. Быстрый пример показан ниже.

1
2
3
4
5
6
// Get the local date/time
final LocalDateTime datetime = LocalDateTime.now();
final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );
        
System.out.println( datetime );
System.out.println( datetimeFromClock );

Пример вывода на консоль:

1
2
2014-04-12T11:37:52.309
2014-04-12T15:37:52.309

Если вам нужна дата / время для определенного часового пояса, ZonedDateTime здесь, чтобы помочь. Он содержит дату со временем и с часовым поясом в календарной системе ISO-8601. Вот пара примеров для разных часовых поясов.

1
2
3
4
5
6
7
8
// Get the zoned date/time
final ZonedDateTime zonedDatetime = ZonedDateTime.now();
final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );
        
System.out.println( zonedDatetime );
System.out.println( zonedDatetimeFromClock );
System.out.println( zonedDatetimeFromZone );

Пример вывода на консоль:

1
2
3
2014-04-12T11:47:01.017-04:00[America/New_York]
2014-04-12T15:47:01.017Z
2014-04-12T08:47:01.017-07:00[America/Los_Angeles]

И наконец, давайте посмотрим на класс Duration : количество времени в секундах и наносекундах. Это позволяет очень легко вычислить разницу между двумя датами. Давайте посмотрим на это.

1
2
3
4
5
6
7
// Get duration between two dates
final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );
final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );
 
final Duration duration = Duration.between( from, to );
System.out.println( "Duration in days: " + duration.toDays() );
System.out.println( "Duration in hours: " + duration.toHours() );

В приведенном выше примере вычисляется продолжительность (в днях и часах) между двумя датами, 16 апреля 2014 года и 16 апреля 2015 года . Вот пример вывода на консоль:

1
2
Duration in days: 365
Duration in hours: 8783

Общее впечатление от нового API даты / времени в Java 8 очень и очень положительное. Частично, из-за доказанного сражением фундамента, на котором он построен ( Joda-Time ), частично потому, что на этот раз он был окончательно решен, и голоса разработчиков были услышаны. Для более подробной информации, пожалуйста, обратитесь к официальной документации .

4.4. Нашорн JavaScript движок

Java 8 поставляется с новым движком Nashorn JavaScript, который позволяет разрабатывать и запускать определенные виды JavaScript-приложений на JVM. Движок Nashorn JavaScript — это просто еще одна реализация javax.script.ScriptEngine, которая следует тому же набору правил, обеспечивающих совместимость Java и JavaScript. Вот небольшой пример.

1
2
3
4
5
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName( "JavaScript" );
        
System.out.println( engine.getClass().getName() );
System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );

Пример вывода на консоль:

1
2
jdk.nashorn.api.scripting.NashornScriptEngine
Result: 2

Мы вернемся к Nashorn позже в разделе, посвященном новым инструментам Java .

4,5. Base64

Наконец, поддержка кодирования Base64 вошла в стандартную библиотеку Java с выпуском Java 8. Это очень легко использовать, как показано в следующем примере.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
package com.javacodegeeks.java8.base64;
 
import java.nio.charset.StandardCharsets;
import java.util.Base64;
 
public class Base64s {
    public static void main(String[] args) {
        final String text = "Base64 finally in Java 8!";
        
        final String encoded = Base64
            .getEncoder()
            .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
        System.out.println( encoded );
        
        final String decoded = new String(
            Base64.getDecoder().decode( encoded ),
            StandardCharsets.UTF_8 );
        System.out.println( decoded );
    }
}

Вывод консоли из прогона программы показывает как закодированный, так и декодированный текст:

1
2
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!

Существуют также кодер / декодер, дружественный к URL, и кодер / декодер, совместимый с MIME, предоставляемый классом Base64 ( Base64. GetUrlEncoder () / Base64. GetUrlDecoder () , Base64. GetMimeEncoder () / Base64. GetMimeDecoder () ).

4,6. Параллельные массивы

Выпуск Java 8 добавляет много новых методов, позволяющих обрабатывать параллельные массивы. Можно утверждать, что наиболее важным из них является parallelSort (), который может значительно ускорить сортировку на многоядерных машинах. Следующий небольшой пример демонстрирует это новое семейство методов ( parallelXxx ) в действии.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
package com.javacodegeeks.java8.parallel.arrays;
 
import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
 
public class ParallelArrays {
    public static void main( String[] args ) {
        long[] arrayOfLong = new long [ 20000 ];       
        
        Arrays.parallelSetAll( arrayOfLong,
            index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
            i -> System.out.print( i + " " ) );
        System.out.println();
        
        Arrays.parallelSort( arrayOfLong );    
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
            i -> System.out.print( i + " " ) );
        System.out.println();
    }
}

Этот небольшой фрагмент кода использует метод parallelSetAll () для заполнения массивов 20000 случайными значениями. После этого применяется параллельная сортировка () . Программа выводит первые 10 элементов до и после сортировки, чтобы гарантировать, что массив действительно упорядочен. Пример программы может выглядеть следующим образом (обратите внимание, что элементы массива генерируются случайным образом):

1
2
Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378
Sorted: 39 220 263 268 325 607 655 678 723 793

4,7. совпадение

Новые классы были добавлены в класс java.util.concurrent.ConcurrentHashMap для поддержки агрегатных операций, основанных на недавно добавленном объекте потоков и лямбда-выражениях. Кроме того, в класс java.util.concurrent.ForkJoinPool были добавлены новые методы для поддержки общего пула (см. Также наш бесплатный курс по параллелизму Java ).

Новый класс java.util.concurrent.locks.StampedLock был добавлен для обеспечения блокировки на основе возможностей с тремя режимами управления доступом для чтения / записи (его можно рассматривать как лучшую альтернативу для печально известного java.util.concurrent.locks.ReadWriteLock ).

Новые классы были добавлены в пакет java.util.concurrent.atomic :

  • DoubleAccumulator
  • DoubleAdder
  • LongAccumulator
  • LongAdder

5. Новые инструменты Java

Java 8 поставляется с новым набором инструментов командной строки. В этом разделе мы рассмотрим наиболее интересные из них.

5.1. Нашорн двигатель: JJS

jjs — это автономный движок Nashorn для командной строки. Он принимает список файлов исходного кода JavaScript в качестве аргументов и запускает их. Например, давайте создадим файл func.js со следующим содержимым:

1
2
3
4
5
function f() {
     return 1;
};
 
print( f() + 1 );

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

1
jjs func.js

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

1
2

Для более подробной информации, пожалуйста, обратитесь к официальной документации .

5.2. Анализатор зависимости класса: jdeps

jdeps — действительно отличный инструмент командной строки. Он показывает зависимости уровня классов или файлов классов Java. Он принимает файл .class , каталог или файл JAR в качестве входных данных. По умолчанию jdeps выводит зависимости на системный вывод (консоль).

В качестве примера рассмотрим отчет о зависимостях для популярной библиотеки Spring Framework . Чтобы сделать пример кратким, давайте проанализируем только один файл JAR: org.springframework.core-3.0.5.RELEASE.jar .

1
jdeps org.springframework.core-3.0.5.RELEASE.jar

Эта команда выводит довольно много, поэтому мы рассмотрим ее часть. Зависимости сгруппированы по пакетам. Если зависимость не доступна на пути к классам, она отображается как не найденная .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
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

Для более подробной информации, пожалуйста, обратитесь к официальной документации .

6. Новые функции в среде выполнения Java (JVM)

Пространство PermGen ушло и было заменено на Metaspace ( JEP 122 ). Параметры JVM -XX: PermSize и — XX: MaxPermSize были заменены на -XX: MetaSpaceSize и -XX: MaxMetaspaceSize соответственно.

7. Выводы

Будущее уже здесь: Java 8 продвигает эту великолепную платформу вперед, предоставляя функции, повышающие продуктивность разработчиков. Слишком рано переводить производственные системы на Java 8, но в ближайшие месяцы его внедрение должно постепенно начать расти. Тем не менее, настало время начать подготовку ваших кодовых баз для совместимости с Java 8 и быть готовыми включить коммутатор, как только Java 8 окажется безопасной и достаточно стабильной.

В качестве подтверждения признания Java 8 сообществом недавно Pivotal выпустила Spring Framework 4.0.3 с готовой поддержкой Java 8 .

Если вам понравилось, подпишитесь на нашу новостную рассылку, чтобы получать еженедельные обновления и бесплатные обзоры! Кроме того, ознакомьтесь с нашими курсами для повышения квалификации!

Вы можете поделиться своими комментариями о новых потрясающих функциях Java 8!

8. Ресурсы

Некоторые дополнительные ресурсы, которые подробно обсуждают различные аспекты функций Java 8:

Руководство по функциям Java 8 в последний раз обновлялось 3 октября 2016 г.