Статьи

Java 8 пятница: опционально останется опцией в Java

В Data Geekery мы любим Java. И так как мы действительно входим в свободный API jOOQ и запросы DSL , мы абсолютно взволнованы тем, что Java 8 принесет в нашу экосистему.

Ява 8 Пятница

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

Необязательно: новая опция в Java

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

Null: ошибка в миллиард долларов

В предыдущем сообщении в блоге мы объяснили достоинства обработки NULL на языке Цейлона , который нашел одно из лучших решений для решения этой проблемы — по крайней мере на JVM, которая обречена навсегда поддерживать нулевой указатель. В Цейлоне обнуляемость — это флаг, который можно добавить к каждому типу, добавив знак вопроса к имени типа. Пример:

01
02
03
04
05
06
07
08
09
10
11
void hello() {
    String? name = process.arguments.first;
    String greeting;
    if (exists name) {
        greeting = "Hello, ``name``!";
    }
    else {
        greeting = "Hello, World!";
    }
    print(greeting);
}

Это довольно гладко. В сочетании с типизацией , чувствительной к потоку , вы никогда больше не столкнетесь со страшным NullPointerException :

Недавно в операционной. Geek и Poke

Недавно в операционной. Geek и Poke

Другие языки представили тип Option . Наиболее заметно: Скала . Java 8 теперь также представила тип Optional (а также типы OptionalInt , OptionalLong , OptionalDouble — подробнее об этом позже)

Как работает Optional?

Основной момент, стоящий за Optional заключается в том, чтобы обернуть Object и предоставить удобный API для быстрой обработки обнуляемости. Это хорошо сочетается с лямбда-выражениями Java 8, которые допускают отложенное выполнение операций. Пример:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
Optional<String> stringOrNot = Optional.of("123");
 
// This String reference will never be null
String alwaysAString =
    stringOrNot.orElse("");
 
// This Integer reference will be wrapped again
Optional<Integer> integerOrNot =
    stringOrNot.map(Integer::parseInt);
 
// This int reference will never be null
int alwaysAnInt = stringOrNot
        .map(s -> Integer.parseInt(s))
        .orElse(0);

Есть некоторые достоинства вышеупомянутого в беглых API, особенно в новом Java 8 Streams API, который широко использует Optional . Например:

1
2
3
4
Arrays.asList(1, 2, 3)
      .stream()
      .findAny()
      .ifPresent(System.out::println);

Приведенный выше фрагмент кода выведет любое число из Stream на консоль, но только если такое число существует.

Старый API не модернизирован

По очевидным причинам обратной совместимости «старый API» не модифицируется. Другими словами, в отличие от Scala, Java 8 не использует Optional всем JDK. Фактически, единственное место, где используется Optional это API Streams . Как вы можете видеть в Javadoc, использование очень редко:

http://docs.oracle.com/javase/8/docs/api/java/util/class-use/Optional.html

Это делает Optional немного сложным в использовании. Мы уже писали об этой теме в блоге ранее . Конкретно, отсутствие Optional типа в API не является гарантией не обнуляемости. Это особенно неприятно, если вы преобразуете потоки в коллекции, а коллекции в потоки.

Дополнительный тип Java 8 предательский

Параметрический полиморфизм

Худшее значение Optional для его «зараженного» API — параметрический полиморфизм, или просто: дженерики. Когда вы рассуждаете о типах, вы быстро поймете, что:

1
2
3
4
5
6
// This is a reference to a simple type:
Number s;
 
// This is a reference to a collection of
// the above simple type:
Collection<Number> c;

Дженерики часто используются для того, что принято считать композицией. У нас есть Collection String . При использовании Optional эта композиционная семантика слегка нарушается (как в Scala, так и в Java), чтобы «обернуть» потенциально допустимое значение. Теперь у нас есть:

1
2
3
4
5
6
// This is a reference to a nullable simple type:
Optional<Number> s;
 
// This is a reference to a collection of
// possibly nullable simple types
Collection<Optional<Number>> c;

Все идет нормально. Мы можем заменить типы, чтобы получить следующее:

1
2
3
4
5
6
// This is a reference to a simple type:
T s;
 
// This is a reference to a collection of
// the above simple type:
Collection<T> c;

Но теперь введите групповые символы и используйте вариант сайта. Мы можем написать

1
2
3
4
5
6
7
// No variance can be applied to simple types:
T s;
 
// Variance can be applied to collections of
// simple types:
Collection<? extends T> source;
Collection<? super T> target;

Что означают вышеуказанные типы в контексте Optional ? Интуитивно, мы хотели бы, чтобы это было о таких вещах, как Optional<? extends Number> Optional<? extends Number> или Optional<? super Number> Optional<? super Number> . В приведенном выше примере мы можем написать:

1
2
3
4
5
// Read a T-value from the source
T s = source.iterator().next();
 
// ... and put it into the target
target.add(s);

Но это больше не работает с Optional

1
2
3
4
5
6
7
8
Collection<Optional<? extends T>> source;
Collection<Optional<? super T>> target;
 
// Read a value from the source
Optional<? extends T> s = source.iterator().next();
 
// ... cannot put it into the target
target.add(s); // Nope

… И нет другого способа рассуждать о разнице в использовании сайта, если у нас есть Optional и немного более сложный API.

Если вы добавите в обсуждение стирание универсального типа, все станет еще хуже. Мы больше не стираем тип компонента вышеупомянутой Collection , мы также стираем тип практически любой ссылки. С точки зрения времени выполнения / отражения это почти как использование Object повсюду!

Системы универсального типа невероятно сложны даже для простых вариантов использования. Optional делает вещи только хуже. Довольно сложно смешать Optional с традиционным API коллекций или другими API. По сравнению с простотой использования чувствительной к потоку печати Цейлона или даже оператора elvis от Groovy , Optional походит на кувалду в лицо.

Будьте осторожны, когда применяете его к своему API!

Примитивные типы

Одной из основных причин, по которой Optional по-прежнему является очень полезным дополнением, является тот факт, что «объектный поток» и «примитивные потоки» имеют «унифицированный API» благодаря тому, что у нас также есть типы OptionalInt , OptionalLong , OptionalDouble .

Другими словами, если вы работаете с примитивными типами, вы можете просто переключить конструкцию потока и повторно использовать оставшуюся часть исходного кода использования потокового API почти таким же образом. Сравните эти две цепочки:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
// Stream and Optional
Optional<Integer> anyInteger =
Arrays.asList(1, 2, 3)
      .stream()
      .filter(i -> i % 2 == 0)
      .findAny();
anyInteger.ifPresent(System.out::println);
 
// IntStream and OptionalInt
OptionalInt anyInt =
Arrays.stream(new int[] {1, 2, 3})
      .filter(i -> i % 2 == 0)
      .findAny();
anyInt.ifPresent(System.out::println);

Другими словами, учитывая скудное использование этих новых типов в JDK API, сомнительную полезность такого типа в целом (при переоборудовании в очень обратно-совместимую среду) и последствия стирания родовых типов для Optional мы смеем сказать, что

Единственная причина, по которой этот тип действительно был добавлен, заключается в предоставлении более унифицированного API-интерфейса Streams как для ссылочного, так и для примитивного типов.

Это сложно. И заставляет задуматься, должны ли мы наконец избавиться от примитивных типов вообще.

Ох, и …

Optional , не Serializable .

Нет. Не Serializable . В отличие от ArrayList , например. По обычной причине:

Создание чего-либо в сериализуемой JDK резко увеличивает наши затраты на обслуживание, потому что это означает, что представление заморожено на все времена. Это ограничивает нашу способность развивать реализации в будущем, и количество случаев, когда мы не можем легко исправить ошибку или предоставить усовершенствование, которое в противном случае было бы простым, огромно. Таким образом, хотя это может показаться простым вопросом «реализует Serializable» для вас, это нечто большее. Количество усилий, затрачиваемых на обход более раннего варианта создания сериализуемого, просто поражает.

Ссылаясь на Брайана Гетца, от: http://mail.openjdk.java.net/pipermail/jdk8-dev/2013-September/003276.html

Хотите обсудить Optional ? Прочитайте эти темы на Reddit:

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

Ссылка: Java 8, пятница: опционально останется опцией в Java от нашего партнера по JCG Лукаса Эдера из блога JAVA, SQL и JOOQ .