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