Cadena de verificación nula frente a la captura de NullPointerException

118

Un servicio web devuelve un XML enorme y necesito acceder a campos profundamente anidados. Por ejemplo:

return wsObject.getFoo().getBar().getBaz().getInt()

El problema es que getFoo(), getBar(), getBaz()puede volver todos null.

Sin embargo, si verifico nullen todos los casos, el código se vuelve muy detallado y difícil de leer. Además, puedo perder los cheques de algunos de los campos.

if (wsObject.getFoo() == null) return -1;
if (wsObject.getFoo().getBar() == null) return -1;
// maybe also do something with wsObject.getFoo().getBar()
if (wsObject.getFoo().getBar().getBaz() == null) return -1;
return wsObject.getFoo().getBar().getBaz().getInt();

Es aceptable escribir

try {
    return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
    return -1;
}

¿O eso sería considerado un antipatrón?

David Frank
fuente
29
No me importarían tanto los nullcheques, ya que wsObject.getFoo().getBar().getBaz().getInt()es un olor a código. Lea lo que es la "Ley de Demeter" y prefiera refactorizar su código en consecuencia. Entonces el problema con los nullcheques desaparecerá también. Y piensa en usar Optional.
Tom
9
¿Qué hay de usar XPath y dejarlo a su evaluación?
Joop Eggen
15
Ese código probablemente es generado por wsdl2java, que no respeta la Ley de Demeter.
Adrian Cox

Respuestas:

143

La captura NullPointerExceptiones algo realmente problemático, ya que pueden ocurrir en casi cualquier lugar. Es muy fácil sacar uno de un error, atraparlo por accidente y continuar como si todo fuera normal, ocultando así un problema real. Es tan complicado de manejar que es mejor evitarlo por completo. (Por ejemplo, piense en el desempaquetado automático de un nulo Integer).

Sugiero que uses la Optionalclase en su lugar. Este suele ser el mejor enfoque cuando desea trabajar con valores presentes o ausentes.

Usando eso, podrías escribir tu código así:

public Optional<Integer> m(Ws wsObject) {
    return Optional.ofNullable(wsObject.getFoo()) // Here you get Optional.empty() if the Foo is null
        .map(f -> f.getBar()) // Here you transform the optional or get empty if the Bar is null
        .map(b -> b.getBaz())
        .map(b -> b.getInt());
        // Add this if you want to return null instead of an empty optional if any is null
        // .orElse(null);
        // Or this if you want to throw an exception instead
        // .orElseThrow(SomeApplicationException::new);
}

¿Por qué opcional?

El uso de Optionals en lugar de nullpara valores que podrían estar ausentes hace que ese hecho sea muy visible y claro para los lectores, y el sistema de tipos se asegurará de que no lo olvide accidentalmente.

También obtiene acceso a métodos para trabajar con dichos valores de manera más conveniente, como map y orElse.


¿La ausencia es válida o es un error?

Pero también piense si es un resultado válido para que los métodos intermedios devuelvan nulo o si eso es un signo de error. Si siempre es un error, entonces probablemente sea mejor lanzar una excepción que devolver un valor especial, o que los propios métodos intermedios lancen una excepción.


¿Quizás más opcionales?

Si, por otro lado, los valores ausentes de los métodos intermedios son válidos, tal vez pueda cambiar a Optional s para ellos?

Entonces podrías usarlos así:

public Optional<Integer> mo(Ws wsObject) {
    return wsObject.getFoo()
        .flatMap(f -> f.getBar())
        .flatMap(b -> b.getBaz())
        .flatMap(b -> b.getInt());        
}

¿Por qué no opcional?

La única razón en la que puedo pensar para no usar Optionales si se trata de una parte del código realmente crítica para el rendimiento, y si la sobrecarga de recolección de basura resulta ser un problema. Esto se debe a unos Optionalobjetos se asignan cada vez que se ejecuta el código, y la máquina virtual podría no ser capaz de optimizar los distancia. En ese caso, sus pruebas if originales podrían ser mejores.

Lii
fuente
Muy buena respuesta. Cabe mencionar que si hay otras posibles condiciones de falla y necesita distinguirlas, puede usar en Trylugar de Optional. A pesar de que no está Tryen la API de Java, hay muchas bibliotecas que proporcionan una, por ejemplo javaslang.io , github.com/bradleyscollins/try4j , functionaljava.org o github.com/jasongoodwin/better-java-monads
Landei
8
FClass::getBaretc sería más corto.
Boris the Spider
1
@BoristheSpider: Quizás un poco. Pero generalmente prefiero las lambdas a las referencias de métodos porque a menudo los nombres de las clases son mucho más largos y las lambdas son un poco más fáciles de leer.
Lii
6
@Lii es bastante justo, pero tenga en cuenta que una referencia de método puede ser un poco más rápida, ya que una lambda puede requerir construcciones de tiempo de compilación más complejas. La lambda requerirá la generación de un staticmétodo, que incurrirá en una penalización mínima.
Boris the Spider
1
@Lii En realidad, encuentro que las referencias de métodos son más limpias y descriptivas, incluso cuando son un poco más largas.
shmosel
14

Sugiero considerarlo Objects.requireNonNull(T obj, String message). Puede crear cadenas con un mensaje detallado para cada excepción, como

requireNonNull(requireNonNull(requireNonNull(
    wsObject, "wsObject is null")
        .getFoo(), "getFoo() is null")
            .getBar(), "getBar() is null");

Le sugiero que no use valores de retorno especiales, como -1. Ese no es un estilo Java. Java ha diseñado el mecanismo de excepciones para evitar esta forma anticuada que provenía del lenguaje C.

Lanzar NullPointerExceptiontampoco es la mejor opción. Puede proporcionar su propia excepción ( marcándola para garantizar que será manejada por un usuario o desmarcada para procesarla de una manera más fácil) o usar una excepción específica del analizador XML que está utilizando.

Andrew Tobilko
fuente
1
Objects.requireNonNulleventualmente lanza NullPointerException. Así que esto no hace que la situación diferente quereturn wsObject.getFoo().getBar().getBaz().getInt()
Arka Ghosh
1
@ArkaGhosh, también evita muchos if
mensajes de correo electrónico
4
Ésta es la única solución sensata. Todos los demás aconsejan usar excepciones para el control de flujo, que es un olor a código. En una nota al margen: considero que el método de encadenamiento realizado por el OP también es un olor. Si trabajara con tres variables locales y los correspondientes si, la situación sería mucho más clara. También creo que el problema es más profundo que simplemente trabajar alrededor de una NPE: OP debería preguntarse por qué los getters pueden devolver null. ¿Qué significa nulo? ¿Quizás algún objeto nulo sería mejor? ¿O chocar con un getter con una excepción significativa? Básicamente, todo es mejor que las excepciones para el control de flujo.
Marius K.
1
El consejo incondicional de utilizar excepciones para señalar la ausencia de un valor de retorno válido no es muy bueno. Las excepciones son útiles cuando un método falla de una manera que es difícil para la persona que llama recuperarse y que se maneja mejor en una declaración try-catch en alguna otra parte del programa. Para señalar simplemente la ausencia de un valor de retorno, es mejor usar la Optionalclase, o tal vez devolver un valor anulableInteger
Lii
6

Suponiendo que la estructura de clases está fuera de nuestro control, como parece ser el caso, creo que detectar la NPE como se sugiere en la pregunta es de hecho una solución razonable, a menos que el rendimiento sea una preocupación importante. Una pequeña mejora podría ser ajustar la lógica de lanzar / atrapar para evitar el desorden:

static <T> T get(Supplier<T> supplier, T defaultValue) {
    try {
        return supplier.get();
    } catch (NullPointerException e) {
        return defaultValue;
    }
}

Ahora puedes simplemente hacer:

return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), -1);
shmosel
fuente
return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), "");no da un error en el tiempo de compilación que podría ser problemático.
Philippe Gioseffi
5

Como ya señaló Tom en el comentario,

La siguiente declaración desobedece la Ley de Deméter ,

wsObject.getFoo().getBar().getBaz().getInt()

Lo que quieres es inty puedes conseguirlo Foo. Law of Demeter dice, nunca hables con extraños . Para su caso, puede ocultar la implementación real bajo el capó de Fooy Bar.

Ahora, puede crear método en el Fooque se ha podido ir inta Baz. En definitiva, Footendremos Bary en Barnosotros podremos acceder Intsin exponernos Bazdirectamente a Foo. Por lo tanto, las comprobaciones nulas probablemente se dividen en diferentes clases y solo los atributos requeridos se compartirán entre las clases.

CoderCroc
fuente
4
Es discutible si desobedece la Ley de Demeter, ya que WsObject es probablemente solo una estructura de datos. Vea aquí: stackoverflow.com/a/26021695/1528880
DerM
2
@DerM Sí, eso es posible, pero como OP ya tiene algo que analiza su archivo XML, también puede pensar en crear clases de modelo adecuadas para las etiquetas requeridas, para que la biblioteca de análisis pueda mapearlas. Entonces, estas clases de modelo contienen la lógica para una nullverificación de sus propias etiquetas secundarias.
Tom
4

Mi respuesta va casi en la misma línea que @janki, pero me gustaría modificar el fragmento de código ligeramente como se muestra a continuación:

if (wsObject.getFoo() != null && wsObject.getFoo().getBar() != null && wsObject.getFoo().getBar().getBaz() != null) 
   return wsObject.getFoo().getBar().getBaz().getInt();
else
   return something or throw exception;

También puede agregar una verificación nula wsObject, si existe alguna posibilidad de que ese objeto sea nulo.

Arka Ghosh
fuente
4

Dice que algunos métodos "pueden regresar null" pero no dice en qué circunstancias regresan null. Dices que lo atrapas NullPointerExceptionpero no dices por qué lo atrapas. Esta falta de información sugiere que no tiene una comprensión clara de para qué sirven las excepciones y por qué son superiores a la alternativa.

Considere un método de clase que está destinado a realizar una acción, pero el método no puede garantizar que realizará la acción, debido a circunstancias fuera de su control (que de hecho es el caso de todos los métodos en Java ). Llamamos a ese método y regresa. El código que llama a ese método necesita saber si tuvo éxito. ¿Cómo puede saberlo? ¿Cómo puede estructurarse para hacer frente a las dos posibilidades, el éxito o el fracaso?

Usando excepciones, podemos escribir métodos que tengan éxito como condición posterior . Si el método regresa, fue exitoso. Si lanza una excepción, ha fallado. Esta es una gran victoria por claridad. Podemos escribir código que procese claramente el caso de éxito normal y mover todo el código de manejo de errores a catchcláusulas. A menudo resulta que los detalles de cómo o por qué un método no tuvo éxito no son importantes para la persona que llama, por lo que la misma catchcláusula se puede usar para manejar varios tipos de fallas. Y a menudo sucede que un método no necesita detectar excepciones en absoluto , sino que solo puede permitir que se propaguen a su llamador. Las excepciones debidas a errores del programa se encuentran en esta última clase; pocos métodos pueden reaccionar adecuadamente cuando hay un error.

Entonces, esos métodos que regresan null.

  • ¿Un nullvalor indica un error en su código? Si es así, no debería detectar la excepción en absoluto. Y su código no debería intentar adivinarse. Simplemente escriba lo que sea claro y conciso asumiendo que funcionará. ¿Es una cadena de llamadas a métodos clara y concisa? Entonces solo úsalas.
  • ¿Un nullvalor indica una entrada no válida a su programa? Si lo hace, NullPointerExceptionno es una excepción apropiada para lanzar, porque convencionalmente está reservado para indicar errores. Probablemente desee lanzar una excepción personalizada derivada de IllegalArgumentException(si desea una excepción sin marcar ) o IOException(si desea una excepción marcada). ¿Es necesario que su programa proporcione mensajes de error de sintaxis detallados cuando hay una entrada no válida? Si es así, lo nullúnico que puede hacer es verificar cada método en busca de un valor de retorno y luego lanzar una excepción de diagnóstico adecuada. Si su programa no necesita proporcionar diagnósticos detallados, encadenar las llamadas al método juntas, detectar cualquiera NullPointerExceptiony luego lanzar su excepción personalizada es más claro y conciso.

Una de las respuestas afirma que las llamadas al método encadenado violan la Ley de Demeter y, por lo tanto, son malas. Esa afirmación está equivocada.

  • Cuando se trata del diseño de programas, en realidad no existen reglas absolutas sobre lo que es bueno y lo que es malo. Solo hay heurísticas: reglas que son correctas la mayor parte del tiempo (incluso casi todo). Parte de la habilidad de la programación es saber cuándo está bien romper ese tipo de reglas. Entonces, una afirmación concisa de que "esto va en contra de la regla X " no es realmente una respuesta en absoluto. ¿Es esta una de las situaciones en las que debería romperse la regla ?
  • La Ley de Demeter es realmente una regla sobre API o diseño de interfaz de clase. Al diseñar clases, es útil tener un jerarquía de abstracciones. Tiene clases de bajo nivel que usan las primitivas del lenguaje para realizar operaciones directamente y representar objetos en una abstracción que es de un nivel más alto que las primitivas del lenguaje. Tienes clases de nivel medio que delegan a las clases de nivel bajo e implementan operaciones y representaciones a un nivel superior que las clases de nivel bajo. Tiene clases de alto nivel que delegan a las clases de nivel medio e implementan operaciones y abstracciones de nivel aún más alto. (He hablado de solo tres niveles de abstracción aquí, pero son posibles más). Esto permite que su código se exprese en términos de abstracciones apropiadas en cada nivel, ocultando así la complejidad. El fundamento de la Ley de Demeteres que si tiene una cadena de llamadas a métodos, eso sugiere que tiene una clase de alto nivel llegando a través de una clase de nivel medio para tratar directamente con detalles de bajo nivel y, por lo tanto, su clase de nivel medio no ha proporcionado una operación abstracta de nivel medio. que necesita la clase de alto nivel. Pero parece que esa no es la situación que tienes aquí: no diseñaste las clases en la cadena de llamadas a métodos, son el resultado de algún código de serialización XML autogenerado (¿no?), Y la cadena de llamadas no es descendente a través de una jerarquía de abstracción porque el XML des-serializado está todo en el mismo nivel de la jerarquía de abstracción (¿verdad?)
Raedwald
fuente
3

Para mejorar la legibilidad, es posible que desee utilizar varias variables, como

Foo theFoo;
Bar theBar;
Baz theBaz;

theFoo = wsObject.getFoo();

if ( theFoo == null ) {
  // Exit.
}

theBar = theFoo.getBar();

if ( theBar == null ) {
  // Exit.
}

theBaz = theBar.getBaz();

if ( theBaz == null ) {
  // Exit.
}

return theBaz.getInt();
JimmyB
fuente
En mi opinión, esto es mucho menos legible. Cubre el método con un montón de lógica de verificación de nulos que es completamente irrelevante de la lógica real del método.
Desarrollador102938
2

No lo atrapes NullPointerException. No sabes de dónde viene (sé que no es probable en tu caso, pero quizás algo más lo arrojó) y es lento. Desea acceder al campo especificado y, para ello, todos los demás campos no deben ser nulos. Esta es una razón perfecta y válida para comprobar todos los campos. Probablemente lo verificaría en uno y luego crearía un método de legibilidad. Como otros señalaron, regresar -1 es muy antiguo, pero no sé si tiene una razón para ello o no (por ejemplo, hablando con otro sistema).

public int callService() {
    ...
    if(isValid(wsObject)){
        return wsObject.getFoo().getBar().getBaz().getInt();
    }
    return -1;
}


public boolean isValid(WsObject wsObject) {
    if(wsObject.getFoo() != null &&
        wsObject.getFoo().getBar() != null &&
        wsObject.getFoo().getBar().getBaz() != null) {
        return true;
    }
    return false;
}

Editar: Es discutible si desobedece la Ley de Demeter, ya que WsObject es probablemente solo una estructura de datos (consulte https://stackoverflow.com/a/26021695/1528880 ).

Cuero
fuente
2

Si no desea refactorizar el código y puede usar Java 8, es posible usar referencias de métodos.

Primero una demostración simple (disculpe las clases internas estáticas)

public class JavaApplication14 
{
    static class Baz
    {
        private final int _int;
        public Baz(int value){ _int = value; }
        public int getInt(){ return _int; }
    }
    static class Bar
    {
        private final Baz _baz;
        public Bar(Baz baz){ _baz = baz; }
        public Baz getBar(){ return _baz; }   
    }
    static class Foo
    {
        private final Bar _bar;
        public Foo(Bar bar){ _bar = bar; }
        public Bar getBar(){ return _bar; }   
    }
    static class WSObject
    {
        private final Foo _foo;
        public WSObject(Foo foo){ _foo = foo; }
        public Foo getFoo(){ return _foo; }
    }
    interface Getter<T, R>
    {
        R get(T value);
    }

    static class GetterResult<R>
    {
        public R result;
        public int lastIndex;
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) 
    {
        WSObject wsObject = new WSObject(new Foo(new Bar(new Baz(241))));
        WSObject wsObjectNull = new WSObject(new Foo(null));

        GetterResult<Integer> intResult
                = getterChain(wsObject, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt);

        GetterResult<Integer> intResult2
                = getterChain(wsObjectNull, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt);


        System.out.println(intResult.result);
        System.out.println(intResult.lastIndex);

        System.out.println();
        System.out.println(intResult2.result);
        System.out.println(intResult2.lastIndex);

        // TODO code application logic here
    }

    public static <R, V1, V2, V3, V4> GetterResult<R>
            getterChain(V1 value, Getter<V1, V2> g1, Getter<V2, V3> g2, Getter<V3, V4> g3, Getter<V4, R> g4)
            {
                GetterResult result = new GetterResult<>();

                Object tmp = value;


                if (tmp == null)
                    return result;
                tmp = g1.get((V1)tmp);
                result.lastIndex++;


                if (tmp == null)
                    return result;
                tmp = g2.get((V2)tmp);
                result.lastIndex++;

                if (tmp == null)
                    return result;
                tmp = g3.get((V3)tmp);
                result.lastIndex++;

                if (tmp == null)
                    return result;
                tmp = g4.get((V4)tmp);
                result.lastIndex++;


                result.result = (R)tmp;

                return result;
            }
}

Salida

241
4

nulo
2

La interfaz Getteres solo una interfaz funcional, puede usar cualquier equivalente.
GetterResultclass, descriptores de acceso eliminados para mayor claridad, contienen el resultado de la cadena de captadores, si existe, o el índice del último captador llamado.

El método getterChaines una pieza de código simple y repetitiva que se puede generar automáticamente (o manualmente cuando sea necesario).
Estructuré el código para que el bloque repetido sea evidente.


Esta no es una solución perfecta ya que aún necesita definir una sobrecarga de getterChain por número de captadores.

En su lugar, refactorizaría el código, pero si no puede y se encuentra usando cadenas de captadores largas, a menudo puede considerar construir una clase con las sobrecargas que llevan de 2 a, digamos, 10 captadores.

Margaret Bloom
fuente
2

Como han dicho otros, respetar la Ley de Deméter es definitivamente parte de la solución. Otra parte, siempre que sea posible, es cambiar esos métodos encadenados para que no puedan regresar null. Puede evitar regresar devolviendo nullen su lugar un objeto vacío String, vacío Collectiono algún otro objeto ficticio que signifique o haga lo que haría la persona que llama null.

Kevin Krumwiede
fuente
2

Me gustaría agregar una respuesta que se centre en el significado del error . La excepción nula en sí misma no proporciona ningún significado de error completo. Así que le aconsejo que evite tratar con ellos directamente.

Hay miles de casos en los que su código puede salir mal: no se puede conectar a la base de datos, excepción de E / S, error de red ... Si los trata uno por uno (como la comprobación nula aquí), sería demasiado complicado.

En el código:

wsObject.getFoo().getBar().getBaz().getInt();

Incluso cuando sabe qué campo es nulo, no tiene idea de lo que sale mal. Quizás Bar sea nulo, pero ¿se espera? ¿O es un error de datos? Piense en las personas que leen su código

Como en la respuesta de xenteros, propondría usar una excepción personalizada sin marcar . Por ejemplo, en esta situación: Foo puede ser nulo (datos válidos), pero Bar y Baz nunca deben ser nulos (datos no válidos)

El código se puede reescribir:

void myFunction()
{
    try 
    {
        if (wsObject.getFoo() == null)
        {
          throw new FooNotExistException();
        }

        return wsObject.getFoo().getBar().getBaz().getInt();
    }
    catch (Exception ex)
    {
        log.error(ex.Message, ex); // Write log to track whatever exception happening
        throw new OperationFailedException("The requested operation failed")
    }
}


void Main()
{
    try
    {
        myFunction();
    }
    catch(FooNotExistException)
    {
        // Show error: "Your foo does not exist, please check"
    }
    catch(OperationFailedException)
    {
        // Show error: "Operation failed, please contact our support"
    }
}
Hoàng Long
fuente
Las excepciones sin marcar indican que el programador está haciendo un mal uso de la API. Los problemas externos como "no se puede conectar a la base de datos, excepción de E / S, error de red" deben indicarse mediante excepciones marcadas.
Kevin Krumwiede
Realmente depende de la necesidad de la persona que llama. Ayuda de excepción marcada porque le obliga a procesar el error. Sin embargo, en otros casos, no es necesario y puede contaminar el código. Por ejemplo, tiene una IOException en su capa de datos, ¿la lanzará a la capa de presentación? Eso significaría que tienes que capturar la excepción y volver a lanzarla a cada persona que llama. Preferiría envolver la IOException con una BusinessException personalizada, con un mensaje relevante, y dejar que aparezca en el seguimiento de la pila, hasta que un filtro global la capture y muestre el mensaje al usuario.
Hoàng Long
Las personas que llaman no tienen que detectar y volver a lanzar las excepciones marcadas, simplemente declarar que se lanzarán.
Kevin Krumwiede
@KevinKrumwiede: tienes razón, solo necesitamos declarar la excepción que se lanzará. Sin embargo, todavía tenemos que declararlo. Editar: Echando un segundo vistazo, hay bastantes debates sobre usos de excepciones marcadas y no marcadas (por ejemplo: programmers.stackexchange.com/questions/121328/… ).
Hoàng Long
2

NullPointerException es una excepción en tiempo de ejecución, por lo que, en general, no se recomienda detectarlo, sino evitarlo.

Tendrá que capturar la excepción donde quiera que desee llamar al método (o se propagará por la pila). No obstante, si en tu caso puedes seguir trabajando con ese resultado con valor -1 y estás seguro de que no se propagará porque no estás usando ninguna de las "piezas" que pueden ser nulas, entonces me parece correcto Atrapalo

Editar:

Estoy de acuerdo con la respuesta posterior de @xenteros, será mejor lanzar su propia excepción en lugar de devolver -1, puede llamarlo, InvalidXMLExceptionpor ejemplo.

SCouto
fuente
3
¿Qué quiere decir con "no importa si lo detecta, puede propagarse a otras partes del código"?
Hulk
Si el nulo está en esta oración wsObject.getFoo () Y en partes posteriores del código ejecutas nuevamente esa consulta o usas wsObject.getFoo (). GetBar () (por ejemplo) generará nuevamente una NullPointerException.
SCouto
Esa es una redacción inusual para "Tendrá que detectar la excepción donde quiera que desee llamar al método (o se propagará por la pila)". si entiendo correctamente. Estoy de acuerdo con eso (y eso puede ser un problema), simplemente encuentro que la redacción es confusa.
Hulk
Lo arreglaré, lo siento, el inglés no es mi primer idioma, por lo que esto puede suceder a veces :) Gracias
SCouto
2

He estado siguiendo esta publicación desde ayer.

He estado comentando / votando los comentarios que dicen, atrapar NPE es malo. He aquí por qué lo he estado haciendo.

package com.todelete;

public class Test {
    public static void main(String[] args) {
        Address address = new Address();
        address.setSomeCrap(null);
        Person person = new Person();
        person.setAddress(address);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            try {
                System.out.println(person.getAddress().getSomeCrap().getCrap());
            } catch (NullPointerException npe) {

            }
        }
        long endTime = System.currentTimeMillis();
        System.out.println((endTime - startTime) / 1000F);
        long startTime1 = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            if (person != null) {
                Address address1 = person.getAddress();
                if (address1 != null) {
                    SomeCrap someCrap2 = address1.getSomeCrap();
                    if (someCrap2 != null) {
                        System.out.println(someCrap2.getCrap());
                    }
                }
            }
        }
        long endTime1 = System.currentTimeMillis();
        System.out.println((endTime1 - startTime1) / 1000F);
    }
}

  public class Person {
    private Address address;

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }
}

package com.todelete;

public class Address {
    private SomeCrap someCrap;

    public SomeCrap getSomeCrap() {
        return someCrap;
    }

    public void setSomeCrap(SomeCrap someCrap) {
        this.someCrap = someCrap;
    }
}

package com.todelete;

public class SomeCrap {
    private String crap;

    public String getCrap() {
        return crap;
    }

    public void setCrap(String crap) {
        this.crap = crap;
    }
}

Salida

3.216

0,002

Veo un claro ganador aquí. Tener cheques si es mucho menos costoso que detectar una excepción. He visto esa forma de hacer Java-8. Teniendo en cuenta que el 70% de las aplicaciones actuales todavía se ejecutan en Java-7, estoy agregando esta respuesta.

Conclusión Para cualquier aplicación de misión crítica, el manejo de NPE es costoso.

Nuevo Usuario
fuente
Tres segundos adicionales en un millón de solicitudes en el peor de los casos se pueden medir, pero rara vez sería un factor decisivo, incluso en "aplicaciones de misión crítica". Hay sistemas en los que agregar 3.2 microsegundos a una solicitud es un gran problema, y ​​si tiene un sistema de este tipo, piense cuidadosamente en las excepciones. Pero llamar a un servicio web y deserializar su salida, según la pregunta original, probablemente lleve mucho más tiempo que eso, y preocuparse por el rendimiento del manejo de excepciones no viene al caso.
Jeroen Mostert
@JeroenMostert: 3 segundos por cheque / millón. Entonces, la cantidad de cheques aumentará el costo
NewUser
Cierto. Incluso con eso, todavía lo consideraría un caso de "perfil primero". Necesitaría más de 300 comprobaciones en una solicitud antes de que la solicitud tome un milisegundo adicional completo. Las consideraciones de diseño pesarían en mi alma mucho antes que eso.
Jeroen Mostert
@JeroenMostert: :) ¡De acuerdo! ¡Me gustaría dejarlo en manos del programador con el resultado y dejar que ellos atiendan una llamada!
NewUser
1

Si la eficiencia es un problema, entonces se debe considerar la opción de 'captura'. Si 'catch' no se puede usar porque se propagaría (como lo menciona 'SCouto'), use variables locales para evitar múltiples llamadas a métodos getFoo(), getBar()y getBaz().

JEP
fuente
1

Vale la pena considerar la posibilidad de crear su propia excepción. Llamémoslo MyOperationFailedException. Puedes lanzarlo en lugar de devolver un valor. El resultado será el mismo: saldrá de la función, pero no devolverá el valor -1 codificado de forma rígida, que es un anti-patrón de Java. En Java usamos Excepciones.

try {
    return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
    throw new MyOperationFailedException();
}

EDITAR:

De acuerdo con la discusión en los comentarios, permítanme agregar algo a mis pensamientos anteriores. En este código hay dos posibilidades. Uno es que aceptas nulo y el otro es que es un error.

Si es un error y se produce, puede depurar su código utilizando otras estructuras con fines de depuración cuando los puntos de interrupción no sean suficientes.

Si es aceptable, no le importa dónde apareció este nulo. Si lo hace, definitivamente no debería encadenar esas solicitudes.

xenteros
fuente
2
¿No cree que es una mala idea suprimir la excepción? En tiempo real, si perdemos el rastro de una excepción, ¡es un verdadero dolor en el fondo descubrir qué diablos está pasando! Siempre sugeriría no usar encadenamiento. El segundo problema que veo es: este código no se puede beneficiar en un momento dado, cuál de los resultados fue nulo.
NewUser
No, su excepción puede tener un mensaje que sin duda señalará el lugar donde se lanzó. Estoy de acuerdo que el encadenamiento no es la mejor solución :)
xenteros
3
No, solo diría sobre el número de línea. Entonces, cualquiera de las llamadas en la cadena puede dar lugar a una excepción.
NewUser
"Si es un error y ocurre, puede depurar su código", no en producción. Prefiero saber QUÉ falló cuando todo lo que tengo es un registro que tratar de adivinar qué sucedió para que falle. Con ese consejo (y ese código), todo lo que realmente sabe es que una de las 4 cosas era nula, pero no cuál ni por qué.
VLAZ
1

El método que tiene es extenso, pero muy legible. Si fuera un desarrollador nuevo que llegara a su base de código, podría ver lo que estaba haciendo con bastante rapidez. La mayoría de las otras respuestas (incluida la detección de la excepción) no parecen hacer las cosas más legibles y algunas lo hacen menos legible en mi opinión.

Dado que es probable que no tenga control sobre la fuente generada y asumiendo que realmente solo necesita acceder a algunos campos profundamente anidados aquí y allá, recomendaría envolver cada acceso profundamente anidado con un método.

private int getFooBarBazInt() {
    if (wsObject.getFoo() == null) return -1;
    if (wsObject.getFoo().getBar() == null) return -1;
    if (wsObject.getFoo().getBar().getBaz() == null) return -1;
    return wsObject.getFoo().getBar().getBaz().getInt();
}

Si se encuentra escribiendo muchos de estos métodos o si se siente tentado a hacer estos métodos estáticos públicos, entonces crearía un modelo de objeto separado, anidado como le gustaría, con solo los campos que le interesan, y convertiría desde la web. modelo de objetos de servicios a su modelo de objetos.

Cuando se comunica con un servicio web remoto, es muy típico tener un "dominio remoto" y un "dominio de aplicación" y cambiar entre los dos. El dominio remoto a menudo está limitado por el protocolo web (por ejemplo, no puede enviar métodos auxiliares de un lado a otro en un servicio RESTful puro y los modelos de objetos profundamente anidados son comunes para evitar múltiples llamadas API) y, por lo tanto, no es ideal para el uso directo en tu cliente.

Por ejemplo:

public static class MyFoo {

    private int barBazInt;

    public MyFoo(Foo foo) {
        this.barBazInt = parseBarBazInt();
    }

    public int getBarBazInt() {
        return barBazInt;
    }

    private int parseFooBarBazInt(Foo foo) {
        if (foo() == null) return -1;
        if (foo().getBar() == null) return -1;
        if (foo().getBar().getBaz() == null) return -1;
        return foo().getBar().getBaz().getInt();
    }

}
Paso
fuente
1
return wsObject.getFooBarBazInt();

aplicando la Ley de Demeter,

class WsObject
{
    FooObject foo;
    ..
    Integer getFooBarBazInt()
    {
        if(foo != null) return foo.getBarBazInt();
        else return null;
    }
}

class FooObject
{
    BarObject bar;
    ..
    Integer getBarBazInt()
    {
        if(bar != null) return bar.getBazInt();
        else return null;
    }
}

class BarObject
{
    BazObject baz;
    ..
    Integer getBazInt()
    {
        if(baz != null) return baz.getInt();
        else return null;
    }
}

class BazObject
{
    Integer myInt;
    ..
    Integer getInt()
    {
        return myInt;
    }
}
Khaled.K
fuente
0

Dar una respuesta que parece diferente a todas las demás.

Te recomiendo que consultes NULLen ifs.

Razón:

No debemos dejar una sola oportunidad para que nuestro programa se bloquee. NullPointer es generado por system. El comportamiento de las excepciones generadas por el sistema no se puede predecir . No debe dejar su programa en manos de System cuando ya tiene una forma de manejarlo por su cuenta. Y ponga el mecanismo de manejo de excepciones para mayor seguridad. !!

Para que su código sea fácil de leer, intente esto para verificar las condiciones:

if (wsObject.getFoo() == null || wsObject.getFoo().getBar() == null || wsObject.getFoo().getBar().getBaz() == null) 
   return -1;
else 
   return wsObject.getFoo().getBar().getBaz().getInt();

EDITAR:

Aquí es necesario almacenar estos valores wsObject.getFoo(), wsObject.getFoo().getBar(), wsObject.getFoo().getBar().getBaz()en algunas variables. No lo estoy haciendo porque no conozco los tipos de retorno de esas funciones.

Cualquier sugerencia será apreciada..!!

Janki Gadhiya
fuente
¿Consideró getFoo () una operación que consumía mucho tiempo? Debe almacenar los valores devueltos en variables, sin embargo, es una pérdida de memoria. Su método es perfecto para la programación en C.
xenteros
pero en algún momento es mejor llegar 1 milisegundo tarde y luego el programa se bloquea @xenteros .. !!
Janki Gadhiya
getFoo () podría obtener un valor de un servidor ubicado en otro continente. Puede durar en cualquier momento: minutos / horas ...
xenteros
wsObjectcontendrá el valor devuelto por Webservice .. !! El Servicio ya se llamará y wsObjectobtendrá una gran cantidad de XMLdatos como respuesta del servicio web .. !! Así que no hay nada como un servidor ubicado en otro continente porque getFoo()es solo un elemento que obtiene un método getter, no una llamada a un servicio web .. !! @xenteros
Janki Gadhiya
1
Bueno, por los nombres de los captadores, supongo que devuelven los objetos Foo, Bar y Baz: P También considere eliminar la doble seguridad mencionada de su respuesta. No creo que proporcione ningún valor real aparte de la contaminación del código. Con variables locales sensatas y verificación nula, hemos hecho más que suficiente para garantizar la exactitud del código. Si puede ocurrir una excepción, debe tratarse como tal.
Marius K.
0

Escribí una clase llamada Snagque te permite definir una ruta para navegar a través de un árbol de objetos. A continuación se muestra un ejemplo de su uso:

Snag<Car, String> ENGINE_NAME = Snag.createForAndReturn(Car.class, String.class).toGet("engine.name").andReturnNullIfMissing();

Lo que significa que la instancia ENGINE_NAMEllamaría efectivamente Car?.getEngine()?.getName()a la instancia que se le pasó y devolvería nullsi se devolviera alguna referencia null:

final String name =  ENGINE_NAME.get(firstCar);

No está publicado en Maven, pero si alguien lo encuentra útil, está aquí. (¡sin garantía, por supuesto!)

Es un poco básico, pero parece funcionar. Obviamente, es más obsoleto con versiones más recientes de Java y otros lenguajes JVM que admiten navegación segura o Optional.

Rico
fuente