Як отримати перше ненулеве значення на 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.