В Data Geekery мы любим Java. И так как мы действительно входим в свободный API jOOQ и запросы DSL , мы абсолютно взволнованы тем, что Java 8 принесет в нашу экосистему.
Ява 8 Пятница
Каждую пятницу мы показываем вам пару замечательных новых функций Java 8 в виде учебника, в которых используются лямбда-выражения, методы расширения и другие замечательные вещи. Вы найдете исходный код на GitHub .
Темная сторона Java 8
До сих пор мы демонстрировали захватывающие части этого нового основного выпуска . Но есть и предостережения. Многие из них. То, что
- … Сбивают с толку
- … не правы
- … Опущены (на данный момент)
- … Опущены (надолго)
У основных выпусков Java всегда есть две стороны. С другой стороны, мы получаем много новой функциональности, которая, по мнению большинства людей, просрочена . В других языках платформы были дженерики задолго до Java 5. В других языках платформы были лямбды задолго до Java 8. Но теперь мы наконец-то получили эти возможности. В обычном изворотливом Java-стиле.
Лямбда-выражения были введены довольно элегантно. Идея возможности записать каждый анонимный экземпляр SAM в качестве лямбда-выражения очень убедительна с точки зрения обратной совместимости. Так каковы темные стороны Java 8?
Перегрузка становится еще хуже
Перегрузка, дженерики и varargs не дружат. Мы объяснили это в предыдущей статье , а также в этом вопросе переполнения стека . Это может быть не повседневные проблемы в вашем странном приложении, но они очень важные проблемы для разработчиков и сопровождающих API.
С лямбда-выражениями все становится «хуже». Таким образом, вы думаете, что можете предоставить некоторый удобный API, перегружающий существующий метод run()
который принимает Callable
чтобы также принять новый тип Supplier
:
1
2
3
4
5
6
7
|
static <T> T run(Callable<T> c) throws Exception { return c.call(); } static <T> T run(Supplier<T> s) throws Exception { return s.get(); } |
То, что выглядит как совершенно полезный код Java 7, сейчас является основной проблемой в Java 8. Потому что вы не можете просто вызвать эти методы с лямбда-аргументом:
1
2
3
4
5
|
public static void main(String[] args) throws Exception { run(() -> null ); // ^^^^^^^^^^ ambiguous method call } |
Везет, как утопленнику. Вам придется прибегнуть к любому из этих «классических» решений:
1
2
3
4
5
6
7
|
run((Callable<Object>) (() -> null )); run( new Callable<Object>() { @Override public Object call() throws Exception { return null ; } }); |
Таким образом, хотя всегда есть обходной путь, эти обходные пути всегда «отстой». Это довольно обидно, даже если вещи не ломаются с точки зрения обратной совместимости.
Не все ключевые слова поддерживаются по умолчанию
Методы по умолчанию являются хорошим дополнением. Некоторые могут утверждать, что у Java наконец есть черты . Другие явно отмежевываются от этого термина, например, Брайан Гетц:
Основной целью добавления методов по умолчанию в Java была «эволюция интерфейса», а не «черты бедняка».
Как найдено в списке рассылки lambda-dev.
На самом деле методы по умолчанию — это немного ортогональная и нерегулярная особенность для всего остального в Java. Вот несколько критических замечаний:
Они не могут быть окончательными
Учитывая, что методы по умолчанию также можно использовать как удобные методы в API:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
public interface NoTrait { // Run the Runnable exactly once default final void run(Runnable r) { // ^^^^^ modifier final not allowed run(r, 1 ); } // Run the Runnable "times" times default void run(Runnable r, int times) { for ( int i = 0 ; i < times; i++) r.run(); } } |
К сожалению, вышесказанное невозможно, и поэтому первый перегруженный удобный метод может быть переопределен в подтипах, даже если это не имеет смысла для разработчика API.
Они не могут быть синхронизированы
Облом! Это было бы трудно реализовать на языке?
1
2
3
4
5
6
7
|
public interface NoTrait { default synchronized void noSynchronized() { // ^^^^^^^^^^^^ modifier synchronized // not allowed System.out.println( "noSynchronized" ); } } |
Да, synchronized
используется редко, как финал. Но когда у вас есть этот вариант использования, почему бы просто не позволить это? Что делает тела метода интерфейса такими особенными?
Ключевое слово по умолчанию
Это, пожалуй, самый странный и нерегулярный из всех функций. Само ключевое слово по default
. Давайте сравним интерфейсы и абстрактные классы:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// Interfaces are always abstract public /* abstract */ interface NoTrait { // Abstract methods have no bodies // The abstract keyword is optional /* abstract */ void run1(); // Concrete methods have bodies // The default keyword is mandatory default void run2() {} } // Classes can optionally be abstract public abstract class NoInterface { // Abstract methods have no bodies // The abstract keyword is mandatory abstract void run1(); // Concrete methods have bodies // The default keyword mustn't be used void run2() {} } |
Если бы язык был переработан с нуля, он, вероятно, обойдется без abstract
или default
ключевых слов. Оба не нужны. Сам факт наличия или отсутствия тела является достаточной информацией для компилятора, чтобы оценить, является ли метод абстрактным. Т.е. как все должно быть:
1
2
3
4
5
6
7
8
9
|
public interface NoTrait { void run1(); void run2() {} } public abstract class NoInterface { void run1(); void run2() {} } |
Выше было бы гораздо скуднее и регулярнее. Жаль, что полезность default
никогда не обсуждалась EG. Ну, это обсуждалось, но ЭГ никогда не хотела принимать это как вариант. Я попытал счастья с этим ответом :
Я не думаю, что # 3 — вариант, потому что интерфейсы с телами методов изначально неестественны. По крайней мере, указание ключевого слова «по умолчанию» дает читателю некоторый контекст, почему язык допускает тело метода. Лично я хотел бы, чтобы интерфейсы оставались чистыми контрактами (без реализации), но я не знаю лучшего варианта развития интерфейсов.
Опять же, это четкое обязательство со стороны EG не придерживаться концепции «черт» в Java. Методы по умолчанию были чисто необходимыми средствами для реализации 1-2 других функций. Они не были хорошо спроектированы с самого начала.
Другие модификаторы
К счастью, static
модификатор попал в спецификации в конце проекта. Таким образом, теперь возможно указывать статические методы в интерфейсах. По некоторым причинам, однако, эти методы не нуждаются (и не допускают!) В ключевом слове по default
, которое должно быть абсолютно случайным решением EG, точно так же, как вы, очевидно, не можете определить static final
методы в интерфейсах.
Хотя модификаторы видимости обсуждались в списке рассылки lambda-dev , но они выходили за рамки этого выпуска. Возможно, мы можем получить их в будущем выпуске.
Несколько методов по умолчанию были реализованы
Некоторые методы имеют разумные реализации по умолчанию на интерфейсе — можно догадаться. Интуитивно понятно, что интерфейсы коллекций, такие как List
или Set
будут иметь их в своих методах equals()
и hashCode()
, потому что контракт для этих методов четко определен для интерфейсов. Он также реализован в AbstractList
с использованием listIterator()
, который является разумной реализацией по умолчанию для большинства индивидуальных списков.
Было бы замечательно, если бы эти API были модифицированы, чтобы упростить реализацию пользовательских коллекций с помощью Java 8. Я мог бы сделать все свои бизнес-объекты, например, реализующими List
, не тратя при этом одно наследование базового класса на AbstractList
.
Вероятно, однако, была веская причина, связанная с обратной совместимостью, которая помешала команде Java 8 в Oracle реализовать эти методы по умолчанию. Кто бы ни отправил нам причину, по которой это было пропущено, получит бесплатную наклейку jOOQ !
Не был изобретен здесь — менталитет
Это тоже несколько раз подвергалось критике в списке рассылки lambda-dev EG. И пока я пишу эту серию блогов , я могу только подтвердить, что новые функциональные интерфейсы очень запутанны для запоминания. Они сбивают с толку по этим причинам:
Некоторые примитивные типы более равны, чем другие
Типы int
, long
, double
примитивы являются предпочтительными по сравнению со всеми остальными, поскольку они имеют функциональный интерфейс в пакете java.util.function и во всем API-интерфейсе Streams. boolean
является гражданином второго сорта, поскольку он все еще превращен в пакет в форме BooleanSupplier
или Predicate
, или еще хуже: IntPredicate
.
Все остальные примитивные типы на самом деле не существуют в этой области. Т.е. нет специальных типов для byte
, short
, float
и char
. Хотя аргумент о соблюдении сроков, безусловно, является верным, этот причудливый статус-кво еще больше усложнит изучение языка для новичков.
Типы не просто называются Function
Будем откровенны. Все эти типы просто «функции». Никто на самом деле не заботится о неявной разнице между Consumer
, Predicate
, UnaryOperator
и т. Д.
На самом деле, когда вы ищете тип с не void
возвращаемым значением и двумя аргументами, как бы вы его назвали? Function2
? Ну, ты был не прав. Это называется BiFunction
.
Вот дерево решений, чтобы узнать, как называется искомый тип:
- Ваша функция возвращает
void
? Это называетсяConsumer
- Ваша функция возвращает
boolean
? Это называетсяPredicate
- Ваша функция возвращает
int
,long
,double
? Это называетсяXXToIntYY
,XXToLongYY
,XXToDoubleYY
что-то - Ваша функция не принимает аргументов? Это называется
Supplier
- Ваша функция принимает один
int
,long
,double
аргумент? Это называетсяIntXX
,LongXX
,DoubleXX
что-то - Ваша функция принимает два аргумента? Это называется
BiXX
- Ваша функция принимает два аргумента одного типа? Это называется
BinaryOperator
- Ваша функция возвращает тот же тип, что и один аргумент? Это называется
UnaryOperator
- Ваша функция принимает два аргумента, первый из которых является ссылочным типом, а второй — примитивным? Он называется
ObjXXConsumer
(только пользователи существуют с этой конфигурацией) - Остальное: это называется
Function
О Боже! Мы, безусловно, должны перейти к Oracle Education, чтобы проверить, резко ли выросла цена на курсы Oracle Certified Java Programmer в последнее время … К счастью, с помощью лямбда-выражений нам вряд ли когда-нибудь придется запоминать все эти типы!
Подробнее о Java 8
Обобщения Java 5 принесли много замечательных новых возможностей в язык Java. Но было также немало предостережений, связанных с стиранием типов. Методы по умолчанию в Java 8, Streams API и лямбда-выражения снова принесут множество замечательных новых возможностей в язык и платформу Java. Но мы уверены, что переполнение стека скоро наполнится вопросами запутанных программистов, которые теряются в джунглях Java 8.
Изучить все новые функции будет непросто, но новые функции (и предостережения) останутся здесь. Если вы Java-разработчик, вам лучше начать практиковать сейчас, когда у вас есть такая возможность. Потому что нам предстоит долгий путь.
Ссылка: | Java 8 Пятница: темная сторона Java 8 от нашего партнера по JCG Лукаса Эдера из блога JAVA, SQL и JOOQ . |