Java "?" Operador para verificar nulo - ¿Qué es? (¡No ternario!)


86

Estaba leyendo un artículo vinculado a una historia de slashdot y encontré este pequeño dato:

Tome la última versión de Java, que intenta facilitar la verificación de puntero nulo al ofrecer una sintaxis abreviada para las pruebas de puntero sin fin. Solo agregar un signo de interrogación a cada invocación de método incluye automáticamente una prueba para punteros nulos, reemplazando un nido de ratas de declaraciones if-then, como:

    public String getPostcode(Person person) {
      String ans= null;
      if (person != null) {
        Name nm= person.getName();
        if (nm!= null) {
          ans= nm.getPostcode();
        }
      }
      return ans
    } 

Con este:

public String getFirstName(Person person) {
      return person?.getName()?.getGivenName();
    } 

Busqué en Internet (está bien, pasé al menos 15 minutos buscando en Google variaciones sobre el "signo de interrogación de Java") y no obtuve nada. Entonces, mi pregunta: ¿hay alguna documentación oficial sobre esto? Descubrí que C # tiene un operador similar (el operador "??"), pero me gustaría obtener la documentación para el idioma en el que estoy trabajando. O, ¿es solo un uso del operador ternario que he nunca antes visto.

¡Gracias!

EDITAR: Enlace al artículo: http://infoworld.com/d/developer-world/12-programming-mistakes-avoid-292


3
¿Podríamos tener un enlace al artículo al menos?
Karl Knechtel

¿Y la fuente del fragmento?
Khachik

5
El articulo esta mal infoworld.com/print/145292 Creo que se envió una presentación de Project Coin. Pero no se seleccionó (por las razones mencionadas en el artículo, si desea hacer ese tipo de cosas, use C # o algo así), y ciertamente no está en la versión actual del lenguaje Java.
Tom Hawtin - tackline

4
Eso no es lo mismo que el C # ?? operador: ?? fusiona nulos, es decir A ?? B == (A != null) ? A : B. Esto parece evaluar una propiedad en un objeto si la referencia del objeto no es nula, es decir A?.B == (A != null) ? A.B : null.
Rup

1
@Erty: como un gran usuario de la anotación @NotNull, que está básicamente en todas partes del código que escribo, ya no sé muy bien qué son las NPE (excepto cuando se usan API mal diseñadas). Sin embargo, encuentro esta "notación de atajo" linda e interesante. Por supuesto, el artículo tiene razón cuando dice: Después de todo, no elimina la raíz del problema: la proliferación de valores nulos debido a una programación rápida y flexible. 'null' no existe en el nivel OOA / OOD. Es otra tontería idiosincrática de Java que en su mayoría se puede solucionar. Para mí es @NotNull en todas partes.
Sintaxis T3rr0r

Respuestas:


78

La idea original proviene de groovy. Se propuso para Java 7 como parte de Project Coin: https://wiki.openjdk.java.net/display/Coin/2009+Proposals+TOC (Elvis y otros operadores de seguridad nula), pero aún no se ha aceptado .

El operador de Elvis relacionado?: Se propuso para x ?: yabreviar x != null ? x : y, especialmente útil cuando x es una expresión compleja.


3
En java (donde no hay coerción automática para anular) una abreviatura dex!=null ? x : y
Michael Borgwardt

@Michael Borgwardt: buen punto, estaba pensando en la semántica maravillosa.
ataylor

50
?es el tupé característico de Elvis Presley; el :justo representa un par de ojos como de costumbre. Quizás ?:-osea ​​más evocador ...
Andrzej Doyle

4
?:0Debe ser un operador "No nulo ni 0". Hazlo así.
azz

3
En realidad, fue un error referirse a la propuesta como un operador de Elvis. Explicación en mail.openjdk.java.net/pipermail/coin-dev/2009-July/002089.html " Desreferenciación segura nula" es un término mejor para usar.
JustinKSU

62

Esta sintaxis no existe en Java, ni está programada para ser incluida en ninguna de las próximas versiones que conozco.


9
¿Por qué el voto negativo? Esta respuesta es 100% correcta hasta donde yo sé ... si sabe algo diferente, por favor dígalo.
ColinD

6
@Webinator: No estará en Java 7 u 8, y no hay otras "próximas versiones" en este momento. También me parece poco probable que lo haga, ya que fomenta las malas prácticas. Tampoco creo que "todavía" sea necesario, ya que "no existe en Java" no es lo mismo que "nunca existirá en Java".
ColinD

9
@Webinator: varios carteles han comentado que se envió una propuesta pero se rechazó. Por tanto, la respuesta es 100% precisa. Voto a favor para contrarrestar el voto en contra.
JeremyP

2
La mala práctica de @ColinD es cuando te rindes con este feo código y decides usar Optionaly mapesas cosas. no estamos ocultando el problema si el valor es anulable, lo que significa que se espera que sea nulo a veces y usted tiene que manejar eso. a veces los valores predeterminados son perfectamente razonables y no es una mala práctica.
M.kazem Akhgary

2
Java simplemente se niega a ser algo moderno. Simplemente termine este idioma ya.
Plagón


19

Una forma de solucionar la falta de "?" El operador que usa Java 8 sin la sobrecarga de try-catch (que también podría ocultar un NullPointerExceptionoriginado en otro lugar, como se mencionó) es crear una clase para "canalizar" métodos en un estilo Java-8-Stream.

public class Pipe<T> {
    private T object;

    private Pipe(T t) {
        object = t;
    }

    public static<T> Pipe<T> of(T t) {
        return new Pipe<>(t);
    }

    public <S> Pipe<S> after(Function<? super T, ? extends S> plumber) {
        return new Pipe<>(object == null ? null : plumber.apply(object));
    }

    public T get() {
        return object;
    }

    public T orElse(T other) {
        return object == null ? other : object;
    }
}

Entonces, el ejemplo dado se convertiría en:

public String getFirstName(Person person) {
    return Pipe.of(person).after(Person::getName).after(Name::getGivenName).get();
}

[EDITAR]

Pensándolo bien, descubrí que en realidad es posible lograr lo mismo solo usando clases estándar de Java 8:

public String getFirstName(Person person) {
    return Optional.ofNullable(person).map(Person::getName).map(Name::getGivenName).orElse(null);
}

En este caso, incluso es posible elegir un valor predeterminado (como "<no first name>" ) en lugar de nullpasarlo como parámetro de orElse.


Me gusta más tu primera solución. ¿Cómo mejoraría su Pipeclase para adaptar una orElsefuncionalidad de modo que pudiera pasar un objeto arbitrario no nulo dentro del orElsemétodo?
ThanosFisherman

@ThanosFisherman He agregado el orElsemétodo a la Pipeclase.
Helder Pereira



6

Java no tiene la sintaxis exacta, pero a partir de JDK-8, tenemos la API opcional con varios métodos a nuestra disposición. Entonces, la versión C # con el uso del operador condicional nulo :

return person?.getName()?.getGivenName(); 

se puede escribir de la siguiente manera en Java con la API opcional :

 return Optional.ofNullable(person)
                .map(e -> e.getName())
                .map(e -> e.getGivenName())
                .orElse(null);

si alguno de person, getNameogetGivenName es nulo, se devuelve nulo.


2

Es posible definir métodos útiles que resuelvan esto de una manera casi bonita con Java 8 lambda.

Esta es una variación de la solución H-MAN, pero utiliza métodos sobrecargados con múltiples argumentos para manejar múltiples pasos en lugar de capturarNullPointerException .

Incluso si creo que esta solución es algo genial, creo que prefiero el segundo de Helder Pereira, ya que no requiere ningún método útil.

void example() {
    Entry entry = new Entry();
    // This is the same as H-MANs solution 
    Person person = getNullsafe(entry, e -> e.getPerson());    
    // Get object in several steps
    String givenName = getNullsafe(entry, e -> e.getPerson(), p -> p.getName(), n -> n.getGivenName());
    // Call void methods
    doNullsafe(entry, e -> e.getPerson(), p -> p.getName(), n -> n.nameIt());        
}

/** Return result of call to f1 with o1 if it is non-null, otherwise return null. */
public static <R, T1> R getNullsafe(T1 o1, Function<T1, R> f1) {
    if (o1 != null) return f1.apply(o1);
    return null; 
}

public static <R, T0, T1> R getNullsafe(T0 o0, Function<T0, T1> f1, Function<T1, R> f2) {
    return getNullsafe(getNullsafe(o0, f1), f2);
}

public static <R, T0, T1, T2> R getNullsafe(T0 o0, Function<T0, T1> f1, Function<T1, T2> f2, Function<T2, R> f3) {
    return getNullsafe(getNullsafe(o0, f1, f2), f3);
}


/** Call consumer f1 with o1 if it is non-null, otherwise do nothing. */
public static <T1> void doNullsafe(T1 o1, Consumer<T1> f1) {
    if (o1 != null) f1.accept(o1);
}

public static <T0, T1> void doNullsafe(T0 o0, Function<T0, T1> f1, Consumer<T1> f2) {
    doNullsafe(getNullsafe(o0, f1), f2);
}

public static <T0, T1, T2> void doNullsafe(T0 o0, Function<T0, T1> f1, Function<T1, T2> f2, Consumer<T2> f3) {
    doNullsafe(getNullsafe(o0, f1, f2), f3);
}


class Entry {
    Person getPerson() { return null; }
}

class Person {
    Name getName() { return null; }
}

class Name {
    void nameIt() {}
    String getGivenName() { return null; }
}

1

No estoy seguro de que esto funcione; si, digamos, la referencia de la persona fuera nula, ¿con qué la reemplazaría el tiempo de ejecución? ¿Una nueva persona? Eso requeriría que la Persona tenga alguna inicialización predeterminada que esperaría en este caso. Puede evitar las excepciones de referencia nula, pero aún así obtendrá un comportamiento impredecible si no planifica este tipo de configuraciones.

Los ?? El operador en C # podría denominarse mejor operador de "fusión"; puede encadenar varias expresiones y devolverá la primera que no es nula. Desafortunadamente, Java no lo tiene. Creo que lo mejor que puede hacer es usar el operador ternario para realizar comprobaciones nulas y evaluar una alternativa a la expresión completa si algún miembro de la cadena es nulo:

return person == null ? "" 
    : person.getName() == null ? "" 
        : person.getName().getGivenName();

También puede usar try-catch:

try
{
   return person.getName().getGivenName();
}
catch(NullReferenceException)
{
   return "";
}

1
"¿Con qué lo reemplazaría el tiempo de ejecución?" ... leer la pregunta podría ayudar :-P Lo reemplazaría con nulo. En general, la idea parece ser que person? .GetName evalúa como nulo si person es nulo o como person.getName si no. Así que es muy parecido a reemplazar "" con nulo en todos sus ejemplos.
subsub

1
También se podría incluir una NullReferenceException getName()o getGivenName()que no sabrá si simplemente devuelve una cadena vacía para todas las ocurrencias.
Jimmy T.

1
En Java es NullPointerException.
Tuupertunut

en C #, person?.getName()?.getGivenName() ?? ""es el equivalente de su primer ejemplo, con la excepción de que si getGivenName()devuelve un valor nulo, todavía dará""
Austin_Anderson

0

Ahí lo tienes, invocación de seguridad nula en Java 8:

public void someMethod() {
    String userName = nullIfAbsent(new Order(), t -> t.getAccount().getUser()
        .getName());
}

static <T, R> R nullIfAbsent(T t, Function<T, R> funct) {
    try {
        return funct.apply(t);
    } catch (NullPointerException e) {
        return null;
    }
}

Tendré que intentar esto. Tengo serias dudas sobre todo este asunto "Opcional". Parece un truco desagradable.
ggb667

3
Todo el propósito de la propuesta de Elvis Operator era hacer esto en una línea. Este enfoque no es mejor que el enfoque "if (! = Null) anterior. De hecho, diría que es peor, ya que no es sencillo. Además, debe evitar lanzar y atrapar errores debido a la sobrecarga.
JustinKSU

Como dijo @Darron en otra respuesta, lo mismo se aplica aquí: "El problema con este estilo es que la NullPointerException puede no haber venido de donde esperaba. Y por lo tanto, puede ocultar un error real".
Helder Pereira

0

Si alguien está buscando una alternativa para las versiones antiguas de Java, puede probar esta que escribí:

/**
 * Strong typed Lambda to return NULL or DEFAULT VALUES instead of runtime errors. 
 * if you override the defaultValue method, if the execution result was null it will be used in place
 * 
 * 
 * Sample:
 * 
 * It won't throw a NullPointerException but null.
 * <pre>
 * {@code
 *  new RuntimeExceptionHandlerLambda<String> () {
 *      @Override
 *      public String evaluate() {
 *          String x = null;
 *          return x.trim();
 *      }  
 *  }.get();
 * }
 * <pre>
 * 
 * 
 * @author Robson_Farias
 *
 */

public abstract class RuntimeExceptionHandlerLambda<T> {

    private T result;

    private RuntimeException exception;

    public abstract T evaluate();

    public RuntimeException getException() {
        return exception;
    }

    public boolean hasException() {
        return exception != null;
    }

    public T defaultValue() {
        return result;
    }

    public T get() {
        try {
            result = evaluate();
        } catch (RuntimeException runtimeException) {
            exception = runtimeException;
        }
        return result == null ? defaultValue() : result;
    }

}

0

Puede probar el código que ha proporcionado y dará un error de sintaxis, por lo que no es compatible con Java. Groovy lo admite y fue propuesto para Java 7 (pero nunca se incluyó).

Sin embargo, puede utilizar el Opcional proporcionado en Java 8. Esto podría ayudarlo a lograr algo en una línea similar. https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html

Código de ejemplo para opcional


0

Dado que Android no es compatible con Lambda Functions a menos que su sistema operativo instalado sea> = 24, debemos usar la reflexión.

// Example using doIt function with sample classes
public void Test() {
    testEntry(new Entry(null));
    testEntry(new Entry(new Person(new Name("Bob"))));
}

static void testEntry(Entry entry) {
    doIt(doIt(doIt(entry,  "getPerson"), "getName"), "getName");
}

// Helper to safely execute function 
public static <T,R> R doIt(T obj, String methodName) {
    try {
       if (obj != null) 
           return (R)obj.getClass().getDeclaredMethod(methodName).invoke(obj);
    } catch (Exception ignore) {
    }
    return null;
}
// Sample test classes
    static class Entry {
        Person person;
        Entry(Person person) { this.person = person; }
        Person getPerson() { return person; }
    }

    static class Person {
        Name name;
        Person(Name name) { this.name = name; }
        Name getName() { return name; }
    }

    static class Name {
        String name;
        Name(String name) { this.name = name; }
        String getName() {
            System.out.print(" Name:" + name + " ");
            return name;
        }
    }
}

-4

Si esto no es un problema de rendimiento para usted, puede escribir

public String getFirstName(Person person) {
  try {
     return person.getName().getGivenName();
  } catch (NullPointerException ignored) {
     return null;
  }
} 

8
El problema con este estilo es que es posible que la NullPointerException no provenga de donde esperaba. Y por lo tanto, puede ocultar un error real.
Darron

2
@Darron, ¿puede dar un ejemplo de un captador que haya escrito que podría lanzar una NPE y cómo le gustaría manejarlo de manera diferente?
Peter Lawrey

2
lo ejecuta en una variable separada y lo prueba con un if como es normal. La idea detrás de este operador es eliminar esa fealdad. Darron tiene razón, su solución puede ocultar y descartar las excepciones que desee lanzar. Por ejemplo, si getName()lanzara una excepción internamente que no desea desechar.
Mike Miller

2
No es una excepción, tendría que ser una NullPointerException. Está tratando de protegerse de una situación que no ha comenzado a explicar cómo podría ocurrir en una aplicación real.
Peter Lawrey

1
En una aplicación real, Personpodría haber un proxy que acceda a una base de datos o alguna memoria que no sea del montón y podría haber un error en alguna parte ... No es muy realista, pero, Peter, apuesto a que nunca has escrito un fragmento de código como el anterior. .
maaartinus
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.