Як отримати перше ненулеве значення на Java?


153

Чи існує еквівалент 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. (а накладні витрати на версію non-args, ймовірно, ледь не піддаються вимірюванню.)
— les2

10
Слідкуйте за тим, що coalesce(a, b), якщо bє складним виразом і aні null, bвсе ще оцінюється. Це не стосується умовного оператора ?:. Дивіться цю відповідь .
— Панг

для цього потрібно заздалегідь обчислити кожен аргумент перед закликом до поєднання, безглуздо з причин виконання
— Іван Григорович


59

Якщо для перевірки є лише дві змінні, і ви використовуєте Guava, ви можете використовувати MoreObjects.firstNonNull (T перше, T друге) .


49
Objects.firstNonNull бере лише два аргументи; у Гуаві немає рівнозначних варагрів. Крім того, він видає NullPointerException, якщо обидві аргументи є нульовими - це може бути, а може і не бажано.

2
Гарний коментар, Джейк. Ця NullPointerException часто обмежує використання об'єктів.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

Якщо ви імпортуєте статичну необов'язково, вираз не надто поганий.

На жаль, ваш випадок із "декількома змінними" неможливий із необов'язковим методом. Замість цього ви можете використовувати:

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) …

слід використовувати, але перед складанням байтового коду повинен бути препроцесор, який знайде звичаї цієї функції "coalesce" і замінить її такою конструкцією, як

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

Оновлення 2014-09-02:

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

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


1
Приємна стаття - було б непогано, щоб вона була англійською, хоча.
— квантовий

1
Існує щось про цю сторінку блогу, яка не працює з Google Translate. :-(
— HairOfTheDog

5

З Guava ви можете:

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

який не викидає NPE , якщо обидва aі bє null.

EDIT: Я помилявся, він все-таки кидає NPE. Правильний спосіб, який коментує Міхал Чізмазія :

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

1
Гей, це так:java.lang.NullPointerException: use Optional.orNull() instead of Optional.or(null)
— Michal Čizmazia

1
Це робить трюк:Optional.fromNullable(a).or(Optional.fromNullable(b)).orNull()
— Michal Čizmazia

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

IMHO, з 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);
}

Навіщо обгортати перший аргумент у a, Supplierякщо він все одно буде перевірятися? Заради рівномірності?
— Інего

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 у all-caps, що може бути чудовим у SQL, але це не гарний стиль у Java.
— Аві

1
Я розумію, що all-caps - це погана практика. Я щойно показував ОП, як написати функцію під іменем, яке вони запитували. Погоджено, акторський склад Doubleдалеко не ідеальний. Я просто не знав, що статичним функціям можна задавати параметри типу. Я думав, що це просто заняття.
— Ерік
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.