Статьи

Сверни свой пиратский оператор Элвис

Итак, в Java нет оператора Элвиса (или, как это более формально известно, оператора объединения нулей или выбора элементов, безопасных для нулей)… Хотя мне лично это не особо важно, некоторым людям это действительно нравится. И когда коллеге понадобился один пару дней назад, я сел и изучил наши варианты.

А что вы знаете! Вы можете быть довольно близки со ссылками на методы.

обзор

Сначала мы посмотрим, что такое оператор Элвиса и почему в этом участвуют пираты. Затем я покажу, как реализовать это с помощью служебного метода.

Реализация, демонстрация и большинство примеров из этого поста можно найти в специальном проекте GitHub . Код является общественным достоянием, поэтому вы можете использовать его без ограничений.

Элвис? Разве он не мертв?

Я тоже так думал, но, видимо, нет . И очень похоже на слухи о том, что Король жив, люди, желающие иметь оператора Элвиса, также никогда не вымирают. Итак, посмотрим, чего они хотят.

(Если вы хотите прочитать одно обсуждение об этом, посмотрите эту ветку в списке рассылки OpenJDK , где Стивен Колебурн предложил эти операторы для Java 7.)

Элвис Оператор

В простейшем виде Элвис — это бинарный оператор, который выбирает ненулевой операнд, предпочитая левый. Так что вместо …

1
2
3
4
5
private String getStreetName() {
    return streetName == null ? "Unknown Street" : streetName;
//  or like this?
//  return streetName != null ? streetName : "Unknown Street";
}

…ты можешь написать…

1
2
3
private String getStreetName() {
    return streetName ?: "Unknown Street";
}

Я был бы в порядке, чтобы получить этот на Java. Это хороший ярлык для часто используемого шаблона, и он не позволяет мне тратить время на решение о том, какой способ упорядочить операнды для троичного символа? : «(Потому что мне всегда интересно, хочу ли я ставить обычный случай первым или хочу избежать двойного отрицания).

Эмуляция статической функции полезности, конечно, тривиальна, но, я бы сказал, также бессмысленна. Усилия по статическому импорту этого метода и заставлению всех читателей кода искать то, что он означает, перевешивают небольшое преимущество, которое он обеспечивает.

Так что я не говорю об этом Элвисе. Кстати, это называется, потому что ?: выглядит как смайлик с помпадур. И кто бы это мог быть, если не Элвис … И да, именно так мы в индустрии постоянно выбираем имена! Более формально это также известно как нулевой оператор объединения.

Пират-Элвис Оператор

Тогда есть еще одна вещь, которая, кажется, не имеет своего собственного имени, и это то, о чем я хочу поговорить. Иногда его также называют Элвисом, но в других случаях он получает удобные имена, такие как «оператор выбора члена, не имеющий значения». По крайней мере, это довольно хорошо объясняет, что он делает: он замыкает выбор элемента, если экземпляр, для которого вызывается элемент, является нулевым, так что весь вызов возвращает нулевое значение.

Это удобно, когда вы хотите объединить вызовы методов, но некоторые из них могут возвращать ноль. Конечно, вам придется проверить это, или вы столкнетесь с NullPointerExeption. Это может привести к довольно уродливому коду. Вместо…

1
2
3
private String getStreetName(Order order) {
    return order.getCustomer().getAddress().getStreetName();
}

… вам придется написать …

1
2
3
4
5
private String getStreetName(Order order) {
    Customer customer = order == null ? null : order.getCustomer();
    Address address = customer == null ? null : customer.getAddress();
    return address.getStreetName();
}

Это явно ужасно. Но с «нулевым оператором выбора члена»:

1
2
3
private String getStreetName(Order order) {
    return order?.getCustomer()?.getAddress()?.getStreetName();
}

Выглядит лучше, правда? Да. И давайте забудем обо всех этих неприятных нолях, а? Да. Вот почему я думаю, что это плохая идея.

Поля, часто обнуляемые, пахнут плохим дизайном. А с Java 8 вы можете вместо этого избежать нуля, используя Optional . Так что действительно должно быть мало причин, чтобы сделать броски пустыми вокруг еще проще. Тем не менее, иногда вы все еще хотите, так что давайте посмотрим, как приблизиться.

Кстати, поскольку пока что нет официального термина для этого варианта, я называю? оператор Пират-Элвис (обратите внимание на отсутствующий глаз). Помните, вы сначала прочитали это здесь! 😉

Реализация оператора Пират-Элвис

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

С дополнительным

Просто оберните первый экземпляр в Optional и примените связанные функции как карты:

1
2
3
4
5
6
7
private String getStreetName(Order order) {
    return Optional.ofNullable(order)
            .map(Order::getCustomer)
            .map(Customer::getAddress)
            .map(Address::getStreetName)
            .orElse(null);
}

Это требует большого количества шаблонов, но уже содержит критические аспекты: укажите методы для вызова со ссылками на методы, и если что-то имеет значение null (что в данном случае приводит к пустому необязательному значению), не вызывайте эти методы.

Мне все еще нравится это решение, потому что оно четко документирует возможность этих звонков. Также легко (и фактически делает код короче) делать правильные вещи и возвращать название улицы в качестве Optional<String> .

С помощью специальных утилитарных методов

Начиная с решения с помощью Optional, найти более короткий путь для этого особого случая довольно просто: просто передайте экземпляр и метод ссылки на выделенный метод и дайте ему разобраться, когда первое значение равно нулю.

Применение функции Null Coalescing

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
public static <T1, T2> T2 applyNullCoalescing(T1 target,
        Function<T1, T2> f) {
    return target == null ? null : f.apply(target);
}
 
public static <T1, T2, T3> T3 applyNullCoalescing(T1 target,
        Function<T1, T2> f1, Function<T2, T3> f2) {
    return applyNullCoalescing(applyNullCoalescing(target, f1), f2);
}
 
public static <T1, T2, T3, T4> T4 applyNullCoalescing(T1 target,
        Function<T1, T2> f1, Function<T2, T3> f2,
        Function<T3, T4> f3) {
    return applyNullCoalescing(applyNullCoalescing(target, f1, f2), f3);
}
 
public static <T1, T2, T3, T4, T5> T5 applyNullCoalescing(T1 target,
        Function<T1, T2> f1, Function<T2, T3> f2,
        Function<T3, T4> f3, Function<T4, T5> f4) {
    return applyNullCoalescing(applyNullCoalescing(target, f1, f2, f3), f4);
}

(Эта реализация оптимизирована для краткости. Если бы каждый метод был реализован явно, производительность могла бы быть улучшена.)

Используя ссылки на методы, эти методы можно вызывать очень читабельным способом:

1
2
3
4
private String getStreetName(Order order) {
    return applyNullCoalescing(order,
            Order::getCustomer, Customer::getAddress, Address::getStreetName);
}

Еще нет order?.getCustomer()?.getAddress()?.getStreetName(); но близко.

отражение

Мы видели, что такое оператор слияния нуля (? 🙂 и оператор выбора нулевого элемента (?.). Несмотря на то, что последний может поощрять вредные привычки (пропуская нули вокруг), мы затем реализовали его с помощью служебного метода, который можно вызывать с помощью ссылок на методы.

Любой код, который вам нравится, можно использовать бесплатно.

Ссылка: Раскройте свой собственный оператор Pirate-Elvis от нашего партнера JCG Николая Парлога в блоге CodeFx .