Друга частина перекладу статті Java 8 Features - The ULTIMATE Guide . Перша частина тут (посилання може змінитися).
5. Нові можливості у бібліотеках Java 8
У Java 8 додали багато нових класів і розширабо існуючі з метою більш ефективної підтримки сучасного паралелізму, функціонального програмування, дати/часу та багато іншого.5.1. Клас Optional
Знаменитий NullPointerException на сьогоднішній день є найпопулярнішою причиною збоїв додатків Java. Давним давно прекрасний проект Google Guava представивOptional
як рішення NullPointerException
, тим самим перешкоджаючи забруднення коду перевірками на null, як наслідок заохочуючи написання чистішого коду. Натхненний Google Guava клас Optional
зараз частина Java 8. Optional
всього лише контейнер: він може містити значення або деякий тип Т
або просто бути null. Він надає багато корисних методів, так що явні перевірки на null тепер не мають виправдань. Зверніться до офіційної документації для більш детальної інформації. Давайте подивимося на два невеликі приклади використанняOptional
: з використанням null та без нього.
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
містить не null значення і false інакше. Метод orElseGet()
містить запасний механізм результату, якщо Optional
містить null, приймаючи функції для генерації значення за замовчуванням. Метод map () перетворює поточне значення Optional
та повертає новий екземпляр Optional
. Метод orElse()
схожий на orElseGet()
, але замість функції він набуває значення за замовчуванням. Ось висновок цієї програми:
Full Name is set? false
Full Name: [none]
Hey Stranger!
Давайте коротко розглянемо інший приклад:
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();
Результат буде таким:
First Name is set? true
First Name: Tom
Hey Tom!
Для більш детальної інформації зверніться до офіційної документації .
5.2. Потоки
Нещодавно доданий Stream API (java.util.stream
) представляє реальне програмування у функціональному стилі Java. Це, безумовно, найбільш повне доповнення до бібліотеки Java, яке дозволяє розробникам Java бути значно ефективнішими, а також дає можливість створити ефективний, чистий і короткий код. Stream API значно полегшує обробку колекцій (але не обмежуючись тільки ними, як ми побачимо пізніше). Візьмемо для прикладу простий клас Task
.
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 );
}
}
}
Task має деяке уявлення про окуляри (або псевдо-складнощі) і може бути або OPEN або CLOSE . Давайте введемо невелику колекцію завдань, щоб грати з ними.
final Collection<Task> tasks = Arrays.asList(
new Task( Status.OPEN, 5 ),
new Task( Status.OPEN, 13 ),
new Task( Status.CLOSED, 8 )
);
Перше питання, яке ми маємо намір з'ясувати, скільки очок містять завдання зі статусом OPEN зараз? До Java 8 звичайним рішенням для цього було б використання ітератора foreach
. Але в Java 8 відповідь у потоках: послідовність елементів, які підтримують послідовні та паралельні агрегатні операції.
// Подсчет общего количества очков всех активных задач с использованием sum()
final long totalPointsOfOpenTasks = tasks
.stream()
.filter( task -> task.getStatus() == Status.OPEN )
.mapToInt( Task::getPoints )
.sum();
System.out.println( "Total points: " + totalPointsOfOpenTasks );
І висновок на консоль виглядатиме як:
Total points: 18
Розглянемо, що тут відбувається. По-перше, колекція завдань конвертується в потокове уявлення. Потім операція filter
відфільтровує всі завдання зі статусом CLOSED . На наступному кроці операція mapToInt
перетворює потік Task
s на потік Integer
s використовуючи метод Task::getPoints
для кожного екземпляра Task
. І нарешті, всі окуляри підсумовуються з використанням методу sum
, який надає кінцевий результат. Перш ніж перейти до наступних прикладів, є деякі зауваження про потоки, які потрібно мати на увазі (детальніше тут ). Операції stream
поділяються на проміжні та кінцеві операції. Проміжні операціїповертають новий потік Вони завжди ліниві, при виконанні проміжних операцій, таких як filter
, вони не виконують фільтрацію насправді, натомість створюється новий потік, який по завершенню формування містить елементи вихідного потоку, які відповідають заданому предикату. Кінцеві операції , такі як forEach
іsum
можуть пройти через потік для отримання результату або побічного ефекту. Після того, як кінцева операція виконується, потік вважається використаним і не може бути використаний. Практично у всіх випадках кінцеві операції прагнуть завершити своє проходження базовим джерелом даних. Інша цінна можливість потоків – це підтримка паралельних процесів із коробки. Давайте подивимося цей приклад, який знаходить суму очок всіх завдань.
// 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
. Ось висновок у консоль:
Total points (all tasks): 26.0
Часто виникає потреба у групуванні елементів за певним критерієм. Приклад демонструє, як потоки можуть допомогти з цим.
// Группировка задач по их статусу
final Map<Status, List<Task>> map = tasks
.stream()
.collect( Collectors.groupingBy( Task::getStatus ) );
System.out.println( map );
Виведення в консоль буде наступним:
{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
Щоб закінчити з прикладами для завдань, давайте обчислимо загальний відсоток (або вага) кожного завдання в колекції, заснований на загальній кількості очок:
// Подсчет веса каждой задачи (як процент от общего количества очков)
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 );
Виведення в консоль буде таким:
[19%, 50%, 30%]
І нарешті, як ми зазначали раніше, Stream API призначено не лише для колекцій Java. Типова операція вводу-виводу, така як читання текстових файлів рядковим чином, є дуже хорошим кандидатом для використання потокової обробки. Ось невеликий приклад для підтвердження цього.
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 );
}
Метод onConsole
, який викликається в потоці, повертає еквівалентний потік із додатковим закритим обробником. Закритий обробник викликається, коли метод close()
викликається в потоці. Stream API разом з лямбдами та посилальними методами в сукупності з методами за замовчуванням та статичними методами Java 8 є відповідями сучасним парадигмам у розробці програмного забезпечення. Для більш детальної інформації звертайтесь до офіційної документації .
5.3. API для дати/часу (JSR 310)
Java 8 дає новий погляд на керування датою та часом надаючи новий API для дати та часу (JSR 310) . Маніпуляції з датою та часом є однією з найгірших больових точок для Java розробників. Стандартнийjava.util.Date
наступний загалом java.util.Calendar
не покращив ситуацію (можливо, зробив її навіть більш заплутаною). Ось як народився Joda-Time : чудова альтернатива API дати/часу для Java . Новий API для дати/часу Java 8 (JSR 310) знаходиться під сильним впливом Joda-Time і взяв найкраще з неї. Новий пакет java.time
містить усі класи для дати, часу, дати/часу, часових поясів, тривалостей та маніпуляцій з часом. У дизайні API незмінність була врахована дуже серйозно: зміни не допускаються (жорсткий урок з java.util.Calendar
). Якщо потрібна модифікація – буде повернено новий екземпляр відповідного класу. Давайте подивимося на основні класи та приклади їх використання. Перший клас Clock
, який надає доступ до поточної миті, дати та часу, використовуючи часовий пояс. Clock
може бути використаний замість System.currentTimeMillis()
та TimeZone.getDefault()
.
// Получить системное время як смещение UTC
final Clock clock = Clock.systemUTC();
System.out.println( clock.instant() );
System.out.println( clock.millis() );
Приклад виведення на консоль:
2014-04-12T15:19:29.282Z
1397315969360
Інші нові класи, які ми розглядатимемо – це LocaleDate
і LocalTime
. LocaleDate
містить лише частину дати без часового поясу в системі календаря ISO-8601. Відповідно, LocalTime
містить тільки частину часу code>.
// получить местную дату и время время
final LocalDate date = LocalDate.now();
final LocalDate dateFromClock = LocalDate.now( clock );
System.out.println( date );
System.out.println( dateFromClock );
// получить местную дату и время время
final LocalTime time = LocalTime.now();
final LocalTime timeFromClock = LocalTime.now( clock );
System.out.println( time );
System.out.println( timeFromClock );
Приклад виведення на консоль:
2014-04-12
2014-04-12
11:25:54.568
15:25:54.568
LocalDateTime
поєднує разом LocaleDate
і LocalTime
містить дату і час, але без часового поясу в календарній системі ISO-8601. Простий приклад наведено нижче.
// Get the local date/time
final LocalDateTime datetime = LocalDateTime.now();
final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );
System.out.println( datetime );
System.out.println( datetimeFromClock );
Приклад виведення на консоль:
2014-04-12T11:37:52.309
2014-04-12T15:37:52.309
Якщо вам потрібна дата/час для конкретного часового поясу, вам допоможе ZonedDateTime
. Він містить дату та час у календарній системі ISO-8601. Ось кілька прикладів для різних часових поясів.
// Получение даты/времени для временной зоны
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 );
Приклад виведення на консоль:
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
: проміжок часу в секундах і наносекундах. Це робить обчислення між двома датами дуже простим. Давайте подивимося як це зробити:
// Получаем разницу между двумя датами
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 року . Ось приклад виведення на консоль:
Duration in days: 365
Duration in hours: 8783
Загальне враження про нову дату/час у Java 8 дуже, дуже позитивне. Частково тому, що зміни ґрунтуються на перевіреному в боях фундаменті (Joda-Time), частково тому, що цього разу це питання переглядалося серйозно і голоси розробників були почуті. Для отримання деталей зверніться до офіційної документації .
5.4. Двигун Nashorn JavaScript
Java 8 поставляється з новим двигуном Nashorn JavaScript , який дозволяє вести розробку та запуск певних видів додатків JavaScript на JVM. Двигун Nashorn JavaScript це просто ще одна реалізація javax.script.ScriptEngine, яка виконує той же набір правил для вирішення взаємодії Java та JavaScript. Ось невеликий приклад.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;" ) );
Приклад виведення на консоль:
jdk.nashorn.api.scripting.NashornScriptEngine
Result: 2
5.5. Base64
Нарешті, підтримка кодування Base64 знайшла своє місце у стандартній бібліотеці Java з появою релізу Java 8. Це дуже просто у використанні, приклад демонструє це.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 );
}
}
Виведення в консоль програми показує як кодований, так і декодований текст:
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!
Є також класи URL-дружні кодери/декодери, а також MIME-дружні кодери/декодери ( Base64.getUrlEncoder()
/ Base64.getUrlDecoder()
, Base64.getMimeEncoder()
/ Base64.getMimeDecoder()
).
5.6. Паралельні масиви
Реліз Java 8 додає багато нових методів для паралельної обробки масивів. Можливо, найбільш важливим єparallelSort()
, який може значно прискорити сортування на багатоядерних машинах. Невеликий приклад нижче демонструє сімейство нових методів ( parallelXxx
) у дії.
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 випадковими значеннями. Після цього застосовується parallelSort()
. Програма виводить перші 10 елементів до та після сортування, щоб показати, що масив дійсно відсортований. Приклад виведення програми може бути наступним чином (Зверніть увагу, що елементи масиву задані випадковим чином).
Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378
Sorted: 39 220 263 268 325 607 655 678 723 793
5.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
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 з production підтримкою Java 8 . Ви можете зробити свій внесок про захоплюючі нові можливості в Java 8 в коментарях.8. Джерела
Деякі додаткові ресурси, які глибоко обговорюють різні аспекти функцій Java 8:- What's New in JDK 8
- The Java Tutorials
- WildFly 8, JDK 8, NetBeans 8, Java EE 7
- Java 8 Tutorial
- JDK 8 Command-line Static Dependency Checker
- The Illuminating Javadoc of JDK 8
- The Dark Side of Java 8
- Installing Java™ 8 Support in Eclipse Kepler SR2
- Java 8
- Oracle Nashorn. A Next-Generation JavaScript Engine for the JVM
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ