Как получить первое ненулевое значение в 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)

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


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 ...
}

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

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 - (написано на чешском языке, но я надеюсь, что примеры кода понятны для всех).


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();

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далеко не идеален. Я просто не знал, что статическим функциям могут быть заданы параметры типа. Я думал, что это были просто уроки.
Эрик
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.