Множественные нулевые проверки в Java 8

99

У меня есть приведенный ниже код, который немного уродлив для множественных нулевых проверок.

String s = null;

if (str1 != null) {
    s = str1;
} else if (str2 != null) {
    s = str2;
} else if (str3 != null) {
    s = str3;
} else {
    s = str4;
}

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

String s = Optional.ofNullable(str1)
                   .orElse(Optional.ofNullable(str2)
                                   .orElse(Optional.ofNullable(str3)
                                                   .orElse(str4)));

В Java 9 мы можем использовать Optional.ofNullableс OR, но в Java8 есть ли другой подход?

спаркер
источник
4
orСинтаксис Java9 String s = Optional.ofNullable(str1) .or(() -> Optional.ofNullable(str2)) .or(() -> Optional.ofNullable(str3)) .orElse(str4);выглядит не так хорошо, как Stream.ofя бы сказал.
Наман
2
@ OleV.V. Ничего страшного, OP уже знает об этом и ищет что-то особенное для Java-8.
Наман
3
Я знаю, что пользователь запрашивает конкретное решение для Java-8, но в целом я бы пошел сStringUtils.firstNonBlank()
Mohamed Anees A
3
Проблема в том, что Java 8 / streams - не лучшее решение для этого. Этот код действительно пахнет рефакторингом, но без дополнительного контекста это действительно сложно сказать. Для начала - почему трех объектов, которые, вероятно, так тесно связаны, еще нет в коллекции?
Bill K
2
@MohamedAneesA предоставил бы лучший ответ (в качестве комментария), но в этом случае не указал источник StringUtils. В любом случае, если вам НЕОБХОДИМО иметь их в виде набора отдельных строк, кодирование его как метода vargs, такого как "firstNonBlank", является идеальным, синтаксис внутри будет массивом, создающим простой цикл for-each с возвратом при нахождении ненулевое значение тривиально и очевидно. В этом случае потоки java 8 являются привлекательной неприятностью. они соблазняют вас встроить и усложнить что-то, что должно быть простым методом / циклом.
Bill K

Ответы:

173

Сделать это можно так:

String s = Stream.of(str1, str2, str3)
    .filter(Objects::nonNull)
    .findFirst()
    .orElse(str4);
Равиндра Ранвала
источник
16
Это. Думайте о том, что вам нужно, а не о том, что у вас есть.
Thorbjørn Ravn Andersen
21
Каковы накладные расходы по скорости? Создание объектов Stream, вызов 4 методов, создание временных массивов ( {str1, str2, str3}) выглядят намного медленнее, чем локальные ifили ?:, которые среда выполнения Java может оптимизировать. Есть ли какая-то оптимизация для Stream javacи среда выполнения Java, которая делает это так же быстро, как ?:? В противном случае я не буду рекомендовать это решение в критически важном для производительности коде.
Очков
16
@pts это, скорее всего, будет медленнее, чем ?:код, и я уверен, что вам следует избегать этого в критичном к производительности коде. Однако он намного более читабелен, и вам следует рекомендовать его в коде IMO, не критичном к производительности, который, я уверен, составляет более 99% кода.
Аарон
7
@pts Нет никаких оптимизаций для потока, ни javacво время выполнения, ни во время его выполнения. Это не препятствует общей оптимизации, такой как встраивание всего кода с последующим устранением избыточных операций. В принципе, конечный результат может быть таким же эффективным, как и простые условные выражения, однако маловероятно, что он когда-либо будет достигнут, так как среда выполнения потратит необходимые усилия только на самые горячие пути кода.
Хольгер
22
Отличное решение! Чтобы немного повысить читабельность, я бы предложил добавить str4к параметрам Stream.of(...)и использовать orElse(null)в конце.
Дэниелп
73

Как насчет тернарного условного оператора?

String s = 
    str1 != null ? str1 : 
    str2 != null ? str2 : 
    str3 != null ? str3 : str4
;
Эран
источник
50
на самом деле он ищет «лучший способ сделать это на Java8». Этот подход можно использовать в Java8, так что все зависит от того, что OP подразумевает под «лучшим» и на каких основаниях он принимает решение о том, что лучше.
Stultuske
17
Обычно мне не нравятся вложенные троичные файлы, но это выглядит довольно чисто.
JollyJoker
11
Это огромная боль для тех, кто не читает вложенные тернарные операторы каждый день.
Cubic
2
@Cubic: Если вам сложно разобрать, напишите комментарий вроде // take first non-null of str1..3. Как только вы знаете, что он делает, становится легко увидеть, как это сделать.
Питер Кордес
4
@Cubic Тем не менее, это простой повторяющийся шаблон. После того, как вы проанализировали строку 2, вы проанализировали все, независимо от того, сколько случаев включено. И как только вы закончите, вы научились кратко и относительно просто выражать подобную выборку из десяти различных случаев. В следующий раз, когда вы увидите ?:лестницу, вы поймете, что она делает. (Между прочим, функциональные программисты знают это как (cond ...)предложения: это всегда охранник, за которым следует соответствующее значение, которое следует использовать, когда оно истинно.)
cmaster - восстановить
35

Вы также можете использовать цикл:

String[] strings = {str1, str2, str3, str4};
for(String str : strings) {
    s = str;
    if(s != null) break;
}
ernest_k
источник
27

Текущие ответы хороши, но вы действительно должны поместить это в служебный метод:

public static Optional<String> firstNonNull(String... strings) {
    return Arrays.stream(strings)
            .filter(Objects::nonNull)
            .findFirst();
}

Этот метод был в моем Utilклассе много лет, он делает код намного чище:

String s = firstNonNull(str1, str2, str3).orElse(str4);

Вы даже можете сделать его общим:

@SafeVarargs
public static <T> Optional<T> firstNonNull(T... objects) {
    return Arrays.stream(objects)
            .filter(Objects::nonNull)
            .findFirst();
}

// Use
Student student = firstNonNull(student1, student2, student3).orElseGet(Student::new);
Вален
источник
10
FWIW, в SQL эта функция называется coalesce , поэтому я тоже называю это в моем коде. Сработает ли это для вас, зависит от того, насколько вам действительно нравится SQL.
Том Андерсон
5
Если вы собираетесь поместить его в служебный метод, вы также можете сделать его эффективным.
Тодд Сьюэлл
1
@ToddSewell, что ты имеешь в виду?
Густаво Сильва
5
@GustavoSilva Todd, вероятно, имеет в виду, что, поскольку это мой служебный метод, нет смысла использовать, Arrays.stream()когда я могу сделать то же самое с a forи a != null, что более эффективно. И Тодд был бы прав. Однако, когда я кодировал этот метод, я искал способ сделать это с использованием функций Java 8, как и OP, так что вот оно.
walen
4
@GustavoSilva. Да, в основном это так: если вы собираетесь использовать эту служебную функцию, опасения по поводу чистого кода больше не так важны, поэтому вы можете использовать более быструю версию.
Тодд Сьюэлл
13

Я использую вспомогательную функцию, что-то вроде

T firstNonNull<T>(T v0, T... vs) {
  if(v0 != null)
    return v0;
  for(T x : vs) {
    if (x != null) 
      return x;
  }
  return null;
}

Тогда такой код можно записать как

String s = firstNonNull(str1, str2, str3, str4);
Майкл Андерсон
источник
1
Почему лишний v0параметр?
tobias_k
1
@tobias_k Дополнительный параметр при использовании varargs - это идиоматический способ в Java требовать 1 или более аргументов, а не 0 или более. (См. Пункт 53 Эффективной Java, изд. 3., который используется minв качестве примера.) Я менее уверен, что здесь это уместно.
Ник
3
@Nick Да, я так и предполагал, но функция будет работать так же хорошо (фактически, ведет себя точно так же) и без нее.
tobias_k
1
Одна из ключевых причин передачи дополнительного первого аргумента - явное поведение при передаче массива. В этом случае я хочу firstNonNull(arr)вернуть arr, если он не равен нулю. Если бы это было firstNonNull(T... vs)так, вместо этого он вернул бы первую ненулевую запись в arr.
Майкл Андерсон
4

Решение, которое можно применить к любому количеству элементов, может быть:

Stream.of(str1, str2, str3, str4)
      .filter(Object::nonNull)
      .findFirst()
      .orElseThrow(IllegalArgumentException::new)

Вы можете представить решение, подобное приведенному ниже, но первое обеспечивает non nullityдля всех элементов

Stream.of(str1, str2, str3).....orElse(str4)
Азро
источник
6
orElse, str4несмотря на то, что на самом деле это
Наман
1
@nullpointer Или orElseNull, что равносильно str4нулю.
tobias_k
3

Вы также можете объединить все строки в массив String, а затем выполнить цикл for для проверки и выхода из цикла после его назначения. Предполагая, что s1, s2, s3, s4 - все строки.

String[] arrayOfStrings = {s1, s2, s3};


s = s4;

for (String value : arrayOfStrings) {
    if (value != null) { 
        s = value;
        break;
    }
}

Отредактировано для включения условия по умолчанию для s4, если ничего не назначено.

danielctw
источник
Вы пропустили s4(или str4), которое должно быть присвоено sв конце, даже если оно равно нулю.
displayName
3

Метод основан и прост.

String getNonNull(String def, String ...strings) {
    for(int i=0; i<strings.length; i++)
        if(strings[i] != null)
             return s[i];
    return def;
}

И используйте его как:

String s = getNonNull(str4, str1, str2, str3);

Это просто делать с массивами и выглядит красиво.

Мадхусудан П.
источник
0

Если вы используете Apache Commons Lang 3, это можно записать так:

String s = ObjectUtils.firstNonNull(str1, str2, str3, str4);

Использование ObjectUtils.firstNonNull(T...)взято из этого ответа . В смежном вопросе также были представлены различные подходы .

lczapski
источник