Необязательно: новая опция в Java
До сих пор мы были в восторге от всех дополнений к Java 8. В целом, это революция больше, чем что-либо прежде. Но есть также одно или два воспаленных места. Одним из них является то, как Java никогда не избавится от
В предыдущем сообщении в блоге мы объяснили достоинства обработки 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
Другие языки ввели Option
тип. Наиболее заметно: Скала . Java 8 теперь также представила тип Optional (а также типы OptionalInt , OptionalLong , OptionalDouble — подробнее об этом позже)
Как работает 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 Groovy , Optional
это как кувалдой в лицо.
Будьте осторожны, когда применяете его к своему API!
Примитивные типы
Одной из основных причин, почему Optional
это все еще очень полезное дополнение, является тот факт, что «объектный поток» и «примитивные потоки» имеют «унифицированный API» благодаря тому, что у нас также есть типы OptionalInt , OptionalLong , OptionalDouble .
Другими словами, если вы работаете с примитивными типами, вы можете просто переключить конструкцию потока и повторно использовать оставшуюся часть исходного кода использования потокового 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.