Как получить первое ненулевое значение в Java?

154

Есть ли Java-эквивалент функции SQL COALESCE? То есть есть ли способ вернуть первое ненулевое значение нескольких переменных?

например

Double a = null;
Double b = 4.4;
Double c = null;

Я хочу , чтобы как - то заявление , которое будет возвращать первое значение ненулевого из a, bи c- в этом случае было бы вернуться b, или 4,4. (Что-то вроде метода sql - возврат COALESCE(a,b,c)). Я знаю, что могу сделать это явно с помощью чего-то вроде:

return a != null ? a : (b != null ? b : c)

Но мне было интересно, есть ли какая-нибудь встроенная, принятая функция для достижения этой цели.

froadie
источник
3
Вам не нужна такая функция, так как вы не вычислили бы «c», если «b» имеет ответ, который вы хотите. то есть вы бы не составили список возможных ответов только для того, чтобы сохранить один.
Питер Лори
Предостережение: не все RDBMS короткого замыкания на COALESCE. Oracle только недавно начал это делать.
Адам Гент
3
@ BrainSlugs83 Серьезно? Ява должна?
Дмитрий Гинзбург

Ответы:

108

Нет, нет

Самое близкое, что вы можете получить:

public static <T> T coalesce(T ...items) {
    for(T i : items) if(i != null) return i;
    return null;
}

По эффективным причинам вы можете справиться с общими случаями следующим образом:

public static <T> T coalesce(T a, T b) {
    return a == null ? b : a;
}
public static <T> T coalesce(T a, T b, T c) {
    return a != null ? a : (b != null ? b : c);
}
public static <T> T coalesce(T a, T b, T c, T d) {
    return ...
}
les2
источник
3
Причины эффективности, о которых я упоминал выше, состоят в том, что распределение массива будет происходить каждый раз, когда вы вызываете версию метода var arg. это может быть расточительным для полных предметов, которые, я подозреваю, будут широко использоваться.
les2
Прохладно. Спасибо. В этом случае я, вероятно, буду придерживаться вложенных условных операторов в этом случае, так как это единственный раз, когда его нужно использовать, и пользовательский метод будет излишним ...
froadie
8
Я бы все-таки вытащил его в приватный вспомогательный метод, а не оставлял в коде «страшно выглядящий» условный блок - «что это делает?» таким образом, если вам когда-либо понадобится использовать его снова, вы можете использовать инструменты рефакторинга в вашей IDE, чтобы переместить метод в служебный класс. Именованный метод помогает документировать намерения кода, что всегда хорошо, IMO. (и накладные расходы на версию без var-args, вероятно, едва ли измеримы.)
les2
10
Осторожно: In coalesce(a, b), если bэто сложное выражение, а aне нет null, bвсе еще оценивается. Это не относится к условному оператору?: Смотрите этот ответ .
Пан
это требует, чтобы каждый аргумент был предварительно вычислен перед вызовом объединения, бессмысленно по соображениям производительности
Иван Г.
59

Если нужно проверить только две переменные и вы используете Guava, вы можете использовать MoreObjects.firstNonNull (T first, T second) .

Дейв
источник
49
Objects.firstNonNull принимает только два аргумента; В Гуаве нет эквивалента варагонов. Кроме того, он генерирует исключение NullPointerException, если оба аргумента равны нулю - это может быть или не быть желательным.
2
Хороший комментарий, Джейк. Это исключение NullPointerException часто ограничивает использование Objects.firstNonNull. Тем не менее, это подход Гуавы, чтобы вообще избежать нулей.
Антон Щастный
4
Этот метод теперь устарел, и рекомендуемая альтернатива - MoreObjects.firstNonNull
davidwebster48
1
Если NPE нежелателен, тогда посмотрите этот ответ
OrangeDog
51

Если есть только две ссылки для тестирования, и вы используете Java 8, вы можете использовать

Object o = null;
Object p = "p";
Object r = Optional.ofNullable( o ).orElse( p );
System.out.println( r );   // p

Если вы импортируете static Optional, выражение не так уж плохо.

К сожалению, ваш случай с «несколькими переменными» невозможен при использовании Optional-метода. Вместо этого вы можете использовать:

Object o = null;
Object p = null;
Object q = "p";

Optional<Object> r = Stream.of( o, p, q ).filter( Objects::nonNull ).findFirst();
System.out.println( r.orElse(null) );   // p
Christian Ullenboom
источник
23

Следуя ответу LES2, вы можете устранить некоторые повторения в эффективной версии, вызвав перегруженную функцию:

public static <T> T coalesce(T a, T b) {
    return a != null ? a : b;
}
public static <T> T coalesce(T a, T b, T c) {
    return a != null ? a : coalesce(b,c);
}
public static <T> T coalesce(T a, T b, T c, T d) {
    return a != null ? a : coalesce(b,c,d);
}
public static <T> T coalesce(T a, T b, T c, T d, T e) {
    return a != null ? a : coalesce(b,c,d,e);
}
Эрик
источник
5
+1 за симпатичную Не уверен насчет преимуществ в эффективности по сравнению с простым циклом, но если вы собираетесь получить какую-то крошечную эффективность таким образом, она также может быть симпатичной.
Карл Манастер
3
этот способ делает намного менее болезненным и менее подверженным ошибкам писать перегруженные варианты!
les2
2
Смысл эффективной версии состоял в том, чтобы не тратить память, выделяя массив с помощью varargs. Здесь вы тратите впустую память, создавая кадр стека для каждого вложенного coalesce()вызова. Вызов coalesce(a, b, c, d, e)создает до 3 кадров стека для расчета.
Люк
10

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

Некоторые функции, подобные этой

public static <T> T coalesce(T ...items) 

должен использоваться, но перед компиляцией в байт-код должен быть препроцессор, который находит использование этой «функции объединения» и заменяет ее конструкцией, подобной

a != null ? a : (b != null ? b : c)

Обновление 2014-09-02:

Благодаря Java 8 и Lambdas появилась возможность объединиться в Java! Включая критическую особенность: определенные выражения оцениваются только при необходимости - если ранее одно из них не является нулевым, то следующие не оцениваются (методы не вызываются, вычисления или операции с диском / сетью не выполняются).

Я написал статью об этом Java 8: coalesce - hledáme neNULLové hodnoty - (написано на чешском языке, но я надеюсь, что примеры кода понятны для всех).

Franta
источник
1
Хорошая статья - было бы неплохо, если бы она была на английском.
квантовый
1
В этой странице блога есть что-то, что не работает с Google Translate. :-(
HairOfTheDog
5

С Guava вы можете сделать:

Optional.fromNullable(a).or(b);

который не бросает NPE, если оба aи bесть null.

РЕДАКТИРОВАТЬ: я был неправ, он бросает NPE. Правильный путь, который прокомментировал Михал Чизмия, таков :

Optional.fromNullable(a).or(Optional.fromNullable(b)).orNull();
Jamol
источник
1
Эй, это делает:java.lang.NullPointerException: use Optional.orNull() instead of Optional.or(null)
Михал Čizmazia
1
Это делает Optional.fromNullable(a).or(Optional.fromNullable(b)).orNull()
свое дело
4

Просто для полноты, случай «нескольких переменных» действительно возможен, хотя и вовсе не элегантен. Например, для переменных o, pи q:

Optional.ofNullable( o ).orElseGet(()-> Optional.ofNullable( p ).orElseGet(()-> q ) )

Пожалуйста, обратите внимание на orElseGet()то o, что использование , pи qне являются переменными, а выражениями, дорогими или с нежелательными побочными эффектами.

В самом общем случае coalesce(e[1],e[2],e[3],...,e[N])

coalesce-expression(i) ==  e[i]  when i = N
coalesce-expression(i) ==  Optional.ofNullable( e[i] ).orElseGet(()-> coalesce-expression(i+1) )  when i < N

Это может генерировать выражения чрезмерно долго. Однако, если мы пытаемся перейти в мир без null, то v[i], скорее всего, это уже тип Optional<String>, а не просто String. В таком случае,

result= o.orElse(p.orElse(q.get())) ;

или в случае выражений:

result= o.orElseGet(()-> p.orElseGet(()-> q.get() ) ) ;

Кроме того, если вы также переход к функционально-декларативному стилю o, pи qдолжно быть типа , Supplier<String>как в:

Supplier<String> q= ()-> q-expr ;
Supplier<String> p= ()-> Optional.ofNullable(p-expr).orElseGet( q ) ;
Supplier<String> o= ()-> Optional.ofNullable(o-expr).orElseGet( p ) ;

И тогда все coalesceсводится просто к o.get().

Для более конкретного примера:

Supplier<Integer> hardcodedDefaultAge= ()-> 99 ;
Supplier<Integer> defaultAge= ()-> defaultAgeFromDatabase().orElseGet( hardcodedDefaultAge ) ;
Supplier<Integer> ageInStore= ()-> ageFromDatabase(memberId).orElseGet( defaultAge ) ;
Supplier<Integer> effectiveAge= ()-> ageFromInput().orElseGet( ageInStore ) ;

defaultAgeFromDatabase(), ageFromDatabase()И ageFromInput()было бы уже вернуться Optional<Integer>, естественно.

И тогда coalesceстановится effectiveAge.get()или просто effectiveAgeесли мы довольны собой Supplier<Integer>.

ИМХО, с Java 8 мы увидим все больше и больше такого структурированного кода, поскольку он чрезвычайно понятен и эффективен одновременно, особенно в более сложных случаях.

Я действительно пропускаю класс, Lazy<T>который вызывает Supplier<T>только один раз, но лениво, а также последовательность в определении Optional<T>(то есть Optional<T>- Optional<T>операторы или даже Supplier<Optional<T>>).

Марио Росси
источник
4

Вы можете попробовать это:

public static <T> T coalesce(T... t) {
    return Stream.of(t).filter(Objects::nonNull).findFirst().orElse(null);
}

На основании этого ответа

Лукас Леон
источник
3

Как насчет использования поставщиков, если вы хотите избежать оценки какого-либо дорогостоящего метода?

Как это:

public static <T> T coalesce(Supplier<T>... items) {
for (Supplier<T> item : items) {
    T value = item.get();
    if (value != null) {
        return value;
    }
    return null;
}

И затем использовать это так:

Double amount = coalesce(order::firstAmount, order::secondAmount, order::thirdAmount)

Вы также можете использовать перегруженные методы для вызовов с двумя, тремя или четырьмя аргументами.

Кроме того, вы также можете использовать потоки с чем-то вроде этого:

public static <T> T coalesce2(Supplier<T>... s) {
    return Arrays.stream(s).map(Supplier::get).filter(Objects::nonNull).findFirst().orElse(null);
}
трики
источник
Зачем заключать первый аргумент в, Supplierесли он все равно будет проверен? Ради единообразия?
Inego
0

Как насчет:

firstNonNull = FluentIterable.from(
    Lists.newArrayList( a, b, c, ... ) )
        .firstMatch( Predicates.notNull() )
            .or( someKnownNonNullDefault );

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

Лонни
источник
-3
Object coalesce(Object... objects)
{
    for(Object o : object)
        if(o != null)
            return o;
    return null;
}
Эрик
источник
2
Боже, я ненавижу дженерики. Я сразу понял, что ты имел в виду. Мне пришлось дважды взглянуть на @ LES2, чтобы понять, что он делает то же самое (и, вероятно, «лучше»)! +1 для ясности
Билл К
Да, дженерики - это путь. Но я не настолько знаком с тонкостями.
Эрик
10
Время выучить дженерики :-). Существует небольшая разница между примером @ LES2 и этим, за исключением T вместо Object. -1 для построения функции, которая принудительно возвращает приведенное значение обратно в Double. Также для именования метода Java в заглавных буквах, что может быть хорошо в SQL, но не подходит в Java.
Ави
1
Я понимаю, что все заглавные буквы - плохая практика. Я просто показывал ОП, как написать функцию под именем, которое они запрашивали. Согласен, откат Doubleдалеко не идеален. Я просто не знал, что статическим функциям могут быть заданы параметры типа. Я думал, что это были просто уроки.
Эрик