Varias comprobaciones nulas en Java 8

99

Tengo el siguiente código que es un poco feo para múltiples comprobaciones nulas.

String s = null;

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

Así que intenté usar Optional.ofNullablecomo se muestra a continuación, pero aún es difícil de entender si alguien lee mi código. cuál es el mejor enfoque para hacerlo en Java 8.

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

En Java 9, podemos usar Optional.ofNullablecon OR, pero en Java8, ¿hay algún otro enfoque?

chispeador
fuente
4
La orsintaxis de Java9 String s = Optional.ofNullable(str1) .or(() -> Optional.ofNullable(str2)) .or(() -> Optional.ofNullable(str3)) .orElse(str4);no se ve tan bien como la que Stream.ofyo haría.
Naman
2
@ OleV.V. Nada malo, el OP ya lo sabe y está buscando algo específico para Java-8.
Naman
3
Sé que el usuario está solicitando una solución específica de Java-8, pero en una nota general, iría conStringUtils.firstNonBlank()
Mohamed Anees A
3
El problema es que Java 8 / streams no es la mejor solución para esto. Ese código realmente huele a que un refactor está en orden, pero sin más contexto es realmente difícil de decir. Para empezar, ¿por qué tres objetos que probablemente están tan estrechamente relacionados no están ya en una colección?
Bill K
2
@MohamedAneesA habría proporcionado la mejor respuesta (como comentario) pero no especificó la fuente de StringUtils en este caso. En cualquier caso, si TIENE que tenerlos como un grupo de cadenas separadas, codificarlo como un método vargs como "firstNonBlank" es ideal, la sintaxis interna será una matriz que haga un bucle simple para cada ciclo con un retorno al encontrar un valor no nulo trivial y obvio. En este caso, las secuencias de Java 8 son una molestia atractiva. te tientan a integrar y complicar algo que debería ser un método / bucle simple.
Bill K

Respuestas:

172

Puedes hacerlo así:

String s = Stream.of(str1, str2, str3)
    .filter(Objects::nonNull)
    .findFirst()
    .orElse(str4);
Ravindra Ranwala
fuente
16
Esta. Piense en lo que necesita, no en lo que tiene.
Thorbjørn Ravn Andersen
21
¿A cuánto asciende la velocidad por encima de la cabeza? Crear objetos Stream, llamar a 4 métodos, crear matrices temporales ( {str1, str2, str3}) parece mucho más lento que un local ifo ?:, que el tiempo de ejecución de Java puede optimizar. ¿Hay alguna optimización específica de Stream javacy el tiempo de ejecución de Java que lo hace tan rápido como ?:? De lo contrario, no recomendaré esta solución en código de rendimiento crítico.
pts
16
@pts es muy probable que esto sea más lento que el ?:código y estoy seguro de que debería evitarlo en el código de rendimiento crítico. Sin embargo, es mucho más legible y debería recomendarlo en un código que no es crítico para el rendimiento, en mi opinión, que estoy seguro de que genera más del 99% del código.
Aaron
7
@pts No hay optimizaciones específicas de Stream, ni en javacni en el tiempo de ejecución. Esto no excluye las optimizaciones generales, como incluir todo el código, seguido de la eliminación de operaciones redundantes. En principio, el resultado final podría ser tan eficiente como las expresiones condicionales simples, sin embargo, es bastante poco probable que llegue allí, ya que el tiempo de ejecución gastaría el esfuerzo necesario solo en las rutas de código más calientes.
Holger
22
¡Gran solución! Para aumentar un poco la legibilidad, sugeriría agregar str4a los parámetros de Stream.of(...)y usar orElse(null)al final.
danielp
73

¿Qué hay del operador condicional ternario?

String s = 
    str1 != null ? str1 : 
    str2 != null ? str2 : 
    str3 != null ? str3 : str4
;
Eran
fuente
50
de hecho, está buscando "el mejor enfoque para hacer esto en Java8". Este enfoque se puede utilizar en Java8, por lo que todo depende de lo que el OP quiera decir con "el mejor", y en qué base basa la decisión de qué es mejor.
Stultuske
17
Por lo general, no me gustan los ternaries anidados, pero esto se ve bastante limpio.
JollyJoker
11
Este es un gran dolor de analizar para cualquiera que no esté leyendo operadores ternarios anidados todos los días.
Cubic
2
@Cubic: Si cree que es difícil de analizar, escriba un comentario como // take first non-null of str1..3. Una vez que sepa lo que hace, es fácil ver cómo.
Peter Cordes
4
@Cubic Sin embargo, este es un patrón simple repetido. Una vez que analizó la línea 2, habrá analizado todo, sin importar cuántos casos se incluyan. Y una vez que haya terminado, habrá aprendido a expresar una selección similar de diez casos diferentes de una manera breve y relativamente simple. La próxima vez que vea una ?:escalera, sabrá lo que hace. (Por cierto, los programadores funcionales saben esto como (cond ...)cláusulas:. Siempre es un guardia seguido por el valor apropiado utilizar cuando el guardia es verdad)
cmaster - Restablecer Monica
35

También puede utilizar un bucle:

String[] strings = {str1, str2, str3, str4};
for(String str : strings) {
    s = str;
    if(s != null) break;
}
ernest_k
fuente
27

Las respuestas actuales son buenas, pero realmente deberías ponerlas en un método de utilidad:

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

Ese método ha estado en mi Utilclase durante años, hace que el código sea mucho más limpio:

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

Incluso puedes hacerlo genérico:

@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);
walen
fuente
10
FWIW, en SQL, esta función se llama coalesce , así que también la llamo así en mi código. Si eso funciona para usted depende de cuánto le guste SQL, realmente.
Tom Anderson
5
Si lo va a poner en un método de utilidad, también puede hacerlo eficiente.
Todd Sewell
1
@ToddSewell, ¿qué quieres decir?
Gustavo Silva
5
@GustavoSilva Todd probablemente quiere decir que, al ser un método de utilidad mío, no tiene sentido usar Arrays.stream()cuando puedo hacer lo mismo con ay fora != null, que es más eficiente. Y Todd tendría razón. Sin embargo, cuando codifiqué este método, estaba buscando una forma de hacerlo usando las funciones de Java 8, al igual que OP, así que ahí está.
walen
4
@GustavoSilva Sí, eso es básicamente: si vas a poner esta es una función útil, las preocupaciones sobre el código limpio ya no son tan importantes, por lo que también puedes usar una versión más rápida.
Todd Sewell
13

Yo uso una función auxiliar, algo así como

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

Entonces este tipo de código se puede escribir como

String s = firstNonNull(str1, str2, str3, str4);
Michael Anderson
fuente
1
¿Por qué el v0parámetro extra ?
tobias_k
1
@tobias_k El parámetro adicional al usar varargs es la forma idiomática en Java de requerir 1 o más argumentos en lugar de 0 o más. (Consulte el artículo 53 de Effective Java, Ed. 3., que se utiliza mincomo ejemplo). Estoy menos convencido de que esto sea apropiado aquí.
Nick
3
@Nick Sí, lo supuse, pero la función funcionaría igual de bien (de hecho, se comportaría exactamente igual) sin ella.
tobias_k
1
Una de las razones clave para pasar un primer argumento adicional es ser explícito sobre el comportamiento cuando se pasa una matriz. En este caso, quiero firstNonNull(arr)devolver arr si no es nulo. Si lo fuera firstNonNull(T... vs), devolvería la primera entrada no nula en arr.
Michael Anderson
4

Una solución que se puede aplicar a tantos elementos como desee puede ser:

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

Podrías imaginar una solución como la siguiente, pero la primera asegura non nullitytodos los elementos.

Stream.of(str1, str2, str3).....orElse(str4)
azro
fuente
6
o bien, a str4pesar de que sea un nulo en realidad
Naman
1
@nullpointer Or orElseNull, que equivale a lo mismo si str4es nulo.
tobias_k
3

También puede agrupar todas las cadenas en una matriz de cadenas y luego hacer un bucle for para verificar y romper el bucle una vez que esté asignado. Suponiendo que s1, s2, s3, s4 son todas cadenas.

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


s = s4;

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

Editado para poner en condición por defecto a s4 si no se asigna ninguno.

danielctw
fuente
Ha omitido s4(o str4), que se supone que se asignará al sfinal, incluso si es nulo.
displayName
3

Método sencillo y basado en el método.

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

Y utilícelo como:

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

Es fácil de hacer con matrices y se ve bonito.

Madhusoodan P
fuente
0

Si usa Apache Commons Lang 3, entonces se puede escribir así:

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

Se ObjectUtils.firstNonNull(T...)tomó el uso de esta respuesta . También se presentaron diferentes enfoques en una pregunta relacionada .

lczapski
fuente