JavaRush /Java Blog /Random-TL /Mga Tampok ng Java 8 – Ang Pinakamahusay na Gabay (Bahagi...
0xFF
Antas
Донецк

Mga Tampok ng Java 8 – Ang Pinakamahusay na Gabay (Bahagi 2)

Nai-publish sa grupo
Ang ikalawang bahagi ng pagsasalin ng artikulong Java 8 Features – The ULTIMATE Guide . Narito ang unang bahagi (maaaring magbago ang link). Особенности Java 8 – максимальное руководство (часть 2) - 1

5. Mga bagong feature sa Java 8 library

Nagdagdag ang Java 8 ng maraming bagong klase at pinalawig ang mga dati nang klase para mas masuportahan ang modernong concurrency, functional programming, petsa/oras, at higit pa.

5.1. Opsyonal sa klase

Ang sikat na NullPointerException ay sa ngayon ang pinakakaraniwang sanhi ng mga pagkabigo sa Java application. Matagal na panahon na ang nakalipas, ang mahusay na proyekto ng Google na Guava ay ipinakita Optionalbilang isang solusyon NullPointerException, sa gayo'y pinipigilan ang code na marumi sa pamamagitan ng mga null check, at bilang resulta ay hinihikayat ang pagsulat ng mas malinis na code. Ang Google-inspired Guava class Optionalay bahagi na ngayon ng Java 8. OptionalIsa lang itong lalagyan: maaari itong maglaman ng value o ilang uri Т, o maging null lang. Nagbibigay ito ng maraming kapaki-pakinabang na pamamaraan upang ang mga tahasang null na pagsusuri ay hindi na makatwiran. Sumangguni sa opisyal na dokumentasyon para sa mas detalyadong impormasyon. Tingnan natin ang dalawang maliliit na halimbawa ng paggamit Optional: may at walang 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!" ) );
Ang pamamaraan isPresent()ay nagbabalik ng true kung ang instance Optionalay naglalaman ng isang hindi null na halaga at false kung hindi. Ang pamamaraan orElseGet()ay naglalaman ng isang fallback na mekanismo para sa resulta kung Optionalnaglalaman ito ng null, tumatanggap ng mga function upang makabuo ng isang default na halaga. Binabago ng paraan ng mapa () ang kasalukuyang halaga Optionalat nagbabalik ng bagong instance Optional. Ang pamamaraan orElse()ay katulad ng orElseGet(), ngunit sa halip na isang function ay nangangailangan ito ng isang default na halaga. Narito ang output ng program na ito:
Full Name is set? false
Full Name: [none]
Hey Stranger!
Tingnan natin ang isa pang halimbawa:
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();
Ang resulta ay magiging ganito:
First Name is set? true
First Name: Tom
Hey Tom!
Para sa mas detalyadong impormasyon, mangyaring sumangguni sa opisyal na dokumentasyon .

5.2. Batis

Ang bagong idinagdag na Stream API ( java.util.stream) ay nagpapakilala ng tunay na functional na istilo ng programming sa Java. Ito ay sa ngayon ang pinaka-komprehensibong karagdagan sa Java library at nagbibigay-daan sa mga developer ng Java na maging mas mahusay at nagbibigay-daan din sa kanila na lumikha ng mahusay, malinis at maigsi na code. Pinapadali ng Stream API ang pagpoproseso ng mga koleksyon (ngunit hindi limitado sa kanila, gaya ng makikita natin sa ibang pagkakataon). Kumuha tayo ng isang simpleng klase bilang isang halimbawa 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 );
        }
    }
}
Ang gawain ay may ilang kahulugan ng mga punto (o pseudo-difficulties) at maaaring maging OPEN o CLOSE . Ipakilala natin ang isang maliit na koleksyon ng mga problema upang paglaruan.
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 );
И вывод на консоль будет выглядеть How:
Total points: 18
Рассмотрим что здесь происходит. Во-первых, коллекция задач конвертируется в потоковое представление. Затем операция filter отфильтровывает все задачи со статусом CLOSED. На следующем шаге операция mapToInt преобразует поток Tasks в поток Integers используя метод Task::getPoints для каждого экземпляра Task. И наконец, все очки суммируются с использованием метода sum, который предоставляет конечный результат. Прежде чем перейти к следующим примерам, есть некоторые замечания о потоках, которые нужно иметь в виду (более детально здесь). Операции stream делятся на промежуточные и конечные операции. Промежуточные операции возвращают новый поток. Они всегда ленивые, при выполнении промежуточных операций, таких How filter, они не выполняют фильтрацию на самом деле, instead of этого создается новый поток, который по завершению формирования, содержит элементы исходного потока, которые соответствуют заданному предикату. Конечные операции, такие How forEach и sum, могут пройти через поток для получения результата or побочного эффекта. После того How конечная операция выполняется, поток считается использованным и не может быть больше использован. Практически во всех случаях конечные операции стремятся завершить свое прохождение по базовому источнику данных. Другая ценная возможность потоков – это поддержка параллельных процессов из коробки. Давайте посмотрим на этот пример, который находит сумму очков всех задач.
// 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
Часто возникает необходимость в группировке элементов по определенному критерию. Пример демонстрирует How потоки могут помочь с этим.
// Группировка задач по их статусу
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]]}
Whatбы закончить с примерами для задач, давайте вычислим общий процент (or вес) каждой задачи в коллекции основанный на общем количестве очков:
// Подсчет веса каждой задачи (How процент от общего количества очков)
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%]
И наконец, How мы отмечали ранее, Stream API предназначено не только для коллекций Java. Типичная операция ввода-вывода, такая How чтение текстовых файлов построчно, является очень хорошим кандидатом для использования потоковой обработки. Вот небольшой пример для подтверждения этого.
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 являются ответами современным парадигмам в разработке программного обеспечения. Для более детальной информации обращайтесь к официальной documentации.

5.3. API для даты/времени (JSR 310)

Java 8 дает новый взгляд на управление датой и временем предоставляя новый API для даты и времени (JSR 310). Манипуляции с датой и временем являются одной из худших болевых точек для Java разработчиков. Стандартный java.util.Date следующий за java.util.Calendar в общем не улучшил ситуацию (возможно, сделал ее даже более запутанной). Вот How родился Joda-Time: прекрасная альтернатива API даты/времени для Java. Новый API для даты/времени в Java 8 (JSR 310) находится под сильным влиянием Joda-Time и взял лучшее из нее. Новый пакет java.time содержит все классы для даты, времени, даты/времени, часовых поясов, продолжительностей и манипуляций со временем. В дизайне API неизменяемость была учтена очень серьезно: изменения не допускаются (жесткий урок извлеченный из java.util.Calendar). Если требуется модификация – будет возвращен новый экземпляр соответствующего класса. Давайте посмотрим на основные классы и примеры их использования. Первый класс Clock, который предоставляет доступ к текущему мгновению, дате и времени, используя часовой пояс. Clock может быть использован instead of System.currentTimeMillis() и TimeZone.getDefault().
// Получить системное время How смещение 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>.
// получить местную date и время время
final LocalDate date = LocalDate.now();
final LocalDate dateFromClock = LocalDate.now( clock );

System.out.println( date );
System.out.println( dateFromClock );

// получить местную date и время время
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 и содержит date и время, но без часового пояса в календарной системе 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. Он содержит date и время в календарной системе 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: промежуток времени в секундах и наносекундах. Это делает вычисление между двумя датами очень простым. Давайте посмотрим How это сделать:
// Получаем разницу между двумя датами
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), частично потому, что в этот раз этот вопрос пересматривался серьезно и голоса разработчиков были услышаны. Для получения деталей обратитесь к официальной documentации.

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

Наконец, поддержка codeирования 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 );
    }
}
Вывод в консоль программы показывает How codeированный так и деcodeированный текст:
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!
Есть также классы URL-дружественные codeеры/деcodeеры, а также MIME-дружественные codeеры/деcodeеры (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();
    }
}
Этот небольшой фрагмент codeа использует метод 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 для поддержки агрегатных операций на основе недавно добавленных an objectов потоков и лямбда выражений. Также новые методы были добавлены в класс java.util.concurrent.ForkJoinPool для поддержки общего пула (посмотрите также наш бесплатный курс по параллелизму Java). Новый класс java.util.concurrent.locks.StampedLock был добавлен для обеспечения блокировки на основе возможностей с тремя режимами доступа для управления чтением/записью (он может быть рассмотрен How лучшая альтернатива для не очень хорошего 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, но в ближайшие несколько месяцев эта адаптация должна медленно начинать расти. Тем не менее настало время для начала подготовки вашей codeовой базы для совместимости с Java 8 и быть готовым включить изменения Java 8, когда она окажется достаточно безопасной и стабильной. В качестве подтверждения принятия сообществом Java 8, недавно Pivotal был выпущен Spring Framework с production поддержкой Java 8. Вы можете внести свой вклад о захватывающих новых возможностях в Java 8 в комментариях.

8. Источники

Некоторые дополнительные ресурсы, которые глубоко обсуждают различные аспекты функций Java 8:
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION