Статьи

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

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

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

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

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

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 (а также  типы OptionalIntOptionalLongOptionalDouble — подробнее об этом позже)

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

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

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. Например:

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

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

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

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

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

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

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

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

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

// 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), чтобы «обернуть» потенциально обнуляемое значение. Теперь у нас есть:

// 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;

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

// 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;

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

// 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<? super Number>. В приведенном выше примере мы можем написать:

// Read a T-value from the source
T s = source.iterator().next();

// ... and put it into the target
target.add(s);

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

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. По сравнению с простотой использования Ceylon, чувствительного к потоку ввода, или даже  оператора elvis GroovyOptional это как кувалдой в лицо.

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

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

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

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

// 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 8 от Eugen Paraschiv.