Множественные нулевые проверки в 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;
}

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, если ничего не назначено.


Вы пропустили 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...)взято из этого ответа . В смежном вопросе также были представлены различные подходы .

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.