Кілька нульових перевірок на 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я би sya.
Наман

2
@ OleV.V. Нічого поганого, ОП це вже знає і шукає чогось специфічного для Java-8.
Наман

3
Я знаю, що користувач просить вирішити конкретне рішення для Java-8, але, загалом, я б пішовStringUtils.firstNonBlank()
Mohamed Anees

3
Проблема в тому, що Java 8 / потоки - не найкраще рішення для цього. Цей код справді пахне рефактором в порядку, але без іншого контексту його важко сказати. Для початку - чому три колекції, напевно, не так пов’язані між собою, вже не є в колекції?
Білл К

2
@MohamedAneesA дав би найкращу відповідь (як коментар), але не вказав джерело StringUtils у цьому випадку. У будь-якому випадку, якщо у вас є цілий ряд окремих рядків, кодування його як методу vargs типу "firstNonBlank" є ідеальним, синтаксис всередині буде масивом, що робить простий для кожного циклу з поверненням на пошук ненульове значення тривіальне і очевидне. У цьому випадку потоки java 8 - приваблива неприємність. вони спокушають вас накреслити і ускладнити щось, що має бути простим методом / циклом.
Білл К

Відповіді:


172

Ви можете зробити це так:

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 може оптимізувати. Чи є якась потокова оптимізація javacта час виконання Java, що робить це так само швидко ?:? Якщо ні, то я не рекомендую це рішення в критичному для продуктивності коді.
пт

16
@pts це, швидше за все, повільніше, ніж ?:код, і я впевнений, що вам слід уникати цього в критичному для продуктивності коді. Однак він набагато легше читається, і вам слід рекомендувати його в неізоляційному коді IMO, який, я впевнений, складає більше 99% коду.
Аарон

7
@pts Немає специфічних оптимізацій потоку, javacні в час виконання, ні в процесі виконання. Це не виключає загальних оптимізацій, як, наприклад, вбудовування всього коду з подальшим усуненням зайвих операцій. В принципі, кінцевий результат може бути таким же ефективним, як і звичайні умовні вирази, однак, навряд чи він коли-небудь потрапить, оскільки час виконання витратить необхідні зусилля лише на найгарячіші кодові шляхи.
Холгер

22
Чудове рішення! Щоб трохи збільшити читабельність, я б запропонував додати str4до параметрів Stream.of(...)і використати orElse(null)врешті-решт.
danielp

73

Як щодо потрійного умовного оператора?

String s = 
    str1 != null ? str1 : 
    str2 != null ? str2 : 
    str3 != null ? str3 : str4
;

50
насправді він шукає "найкращий підхід для цього в Java8". Цей підхід може бути використаний у Java8, тому все залежить лише від того, що означає ОП "найкращий" та на яких підставах він базує рішення, на чому краще.
Стултуске

17
Я зазвичай не люблю вкладені тернеари, але це виглядає досить чисто.
JollyJoker

11
Це великий біль для розбору для кожного, хто не читає вкладених потрійних операторів щодня.
Кубік

2
@Cubic: Якщо ви думаєте, що важко розібратися, напишіть коментар, як // take first non-null of str1..3. Коли ви знаєте, що це робить, стає легко зрозуміти, як це робити.
Пітер Кордес

4
@Cubic Тим не менш, це проста повторена модель. Після того, як ви проаналізували рядок 2, ви проаналізували всю справу, незалежно від того, скільки справ включено. І як тільки ви закінчите, ви навчилися висловлювати подібний вибір з десяти різних випадків коротким та відносно простим способом. Наступного разу, коли ви побачите ?:драбину, ви будете знати, що це робить. (Btw, функціональні програмісти знають це як (cond ...)пункти: Це завжди охорона, за якою слід відповідне значення, яке слід використовувати, коли захист правдивий.)
cmaster - відновити monica

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
Або, str4незважаючи на те, що це фактично недійсне значення,
Наман,

1
@nullpointer Або orElseNull, що дорівнює тому самому, якщо str4є нульовим.
tobias_k

3

Ви також можете зібрати всі рядки в масив String, потім зробіть цикл для перевірки та виходу з циклу, коли він призначений. Припустимо, що 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врешті-решт, навіть якщо це недійсне.
показНазву

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

Робити з масивами це просто і виглядає красиво.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.