JavaRush /Java блог /Random UA /Особливості Java 8 – максимальний посібник (частина 1)
0xFF
9 рівень
Донецк

Особливості Java 8 – максимальний посібник (частина 1)

Стаття з групи Random UA
Перша частина перекладу статті Java 8 Features - The ULTIMATE Guide . Друга частина тут (посилання може змінитися). Особливості Java 8 - максимальний посібник (частина 1) - 1 Від редакції : стаття опублікована в той час, як 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. Аналізатор залежностей класу: ideps

ideps справді відмінний інструмент командного рядка. Він показує залежність лише на рівні пакета чи класу для Java класів. Він приймає файл .class , папку або JAR-файл на вхід. За умовчанням ideps виводить залежність у стандартну систему виведення (консоль). Як приклад давайте подивимося на звіт залежностей популярної бібліотеки 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
Для більш детальної інформації звертайтесь до офіційної документації .
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ