try traducir pointer new catch java exception nullpointerexception null custom-error-handling

traducir - throw new exception java



Cadena de comprobaciĆ³n nula frente a captura de NullPointerException (19)

Dando una respuesta que parece diferente de todas las demás.

Te recomiendo que compruebes NULL en if s.

Razón:

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

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 sé los tipos de retorno de esas funciones.

Cualquier sugerencia será apreciada..!!

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() pueden devolver null .

Sin embargo, si compruebo null en todos los casos, el código se vuelve muy detallado y difícil de leer. Además, puedo pasar por alto los controles 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?


Escribí una clase llamada Snag que te permite definir una ruta para navegar a través de un árbol de objetos. Aquí hay 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_NAME invocaría efectivamente Car?.getEngine()?.getName() la instancia que se le pasó y devolvería null si alguna referencia devuelve null :

final String name = ENGINE_NAME.get(firstCar);

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

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


Capturar NullPointerException es algo realmente problemático, ya que pueden ocurrir en casi cualquier lugar. Es muy fácil obtener uno de un error, atraparlo por accidente y continuar como si todo fuera normal, ocultando así un problema real. Es muy difícil lidiar con él, por lo que es mejor evitarlo por completo. (Por ejemplo, piense en el desempaquetado automático de un Integer nulo).

Le sugiero que use la clase Optional lugar. Este suele ser el mejor enfoque cuando desea trabajar con valores que están presentes o ausentes.

Usando eso, podría escribir su código de esta manera:

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 an -1 int instead of an empty optional if any is null // .orElse(-1); // Or this if you want to throw an exception instead // .orElseThrow(SomeApplicationException::new); }

¿Por qué opcional?

El uso de s Optional lugar de null para valores que pueden estar ausentes hace que este hecho sea muy visible y claro para los lectores, y el sistema de tipos se asegurará de que no lo olvide accidentalmente.

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

¿Es válida la ausencia o 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 métodos intermedios mismos arrojen 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 también pueda cambiarlos a Optional ?

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 por la que puedo pensar para no usar Optional es si esto está en una parte realmente crítica del código del rendimiento y si la sobrecarga de recolección de basura resulta ser un problema. Esto se debe a que se asignan algunos objetos Optional cada vez que se ejecuta el código, y es posible que la máquina virtual no pueda optimizarlos. En ese caso, sus pruebas if originales podrían ser mejores.


Como otros han dicho, respetar la Ley de Demeter es definitivamente parte de la solución. Otra parte, siempre que sea posible, es cambiar esos métodos encadenados para que no puedan devolver null . Puede evitar devolver null devolviendo una String vacía, una Collection vacía o algún otro objeto ficticio que signifique o haga lo que la persona que llama haría con null .


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 int y puedes obtenerlo de Foo . La ley de Deméter dice: nunca hables con extraños . Para su caso, puede ocultar la implementación real bajo el capó de Foo and Bar .

Ahora, puede crear un método en Foo para obtener int desde Baz . Finalmente, Foo tendrá Bar y en Bar podemos acceder a Int sin exponer a Baz directamente 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.


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

Dado que es probable que no tenga control sobre la fuente generada y suponiendo 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 te encuentras escribiendo muchos de estos métodos o si te sientes tentado a hacer estos métodos estáticos públicos, entonces crearía un modelo de objeto separado, anidado como quieras, con solo los campos que te interesan, y convertir desde la web servicios modelo de objeto a su modelo de objeto.

Cuando se comunica con un servicio web remoto, es muy típico tener un "dominio remoto" y un "dominio de aplicación" y alternar 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 son ideales para uso directo 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(); } }


He estado siguiendo esta publicación desde ayer.

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

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 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.


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 error completo de significado. Por lo tanto, aconsejaría evitar 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 codigo:

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? Piensa en las personas que leen tu código

Al igual que 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 deberían ser nulos (datos no válidos)

El código puede ser reescrito:

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" } }


Mi respuesta va casi en la misma línea que @janki, pero me gustaría modificar el fragmento de código ligeramente de la siguiente manera:

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 comprobación nula para wsObject , si hay alguna posibilidad de que ese objeto sea nulo.


No atrape NullPointerException . No sabe de dónde viene (sé que no es probable en su caso, pero tal vez algo más lo arrojó) y es lento. Desea acceder al campo especificado y para esto los demás campos no deben ser nulos. Esta es una razón válida perfecta para verificar cada campo. Probablemente lo verificaría en uno y luego crearía un método para facilitar la lectura. Como otros señalaron, ya regresar -1 es una escuela muy antigua, pero no sé si tiene una razón o no (por ejemplo, hablar 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 se desobedece la Ley de Demeter ya que el WsObject es probablemente solo una estructura de datos (consulte https://.com/a/26021695/1528880 ).


Para mejorar la legibilidad, es posible que desee utilizar múltiples 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();


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 se menciona en ''SCouto''), use variables locales para evitar múltiples llamadas a los métodos getFoo() , getBar() y getBaz() .


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

Una demostración simple primero (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 4

nulo
2

La interfaz Getter es solo una interfaz funcional, puede usar cualquier equivalente.
GetterResult clase GetterResult , los GetterResult despojados para mayor claridad mantienen el resultado de la cadena getter, si la hay, o el índice del último getter llamado.

El método getterChain es un código simple, repetitivo, que puede generarse automáticamente (o manualmente cuando sea necesario).
Estructuré el código para que el bloque repetitivo sea evidente.

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

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


Sugiero considerar 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");

Te sugiero que no uses 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 proviene del lenguaje C.

Lanzar NullPointerException tampoco es la mejor opción. Puede proporcionar su propia excepción (haciendo que esté marcada para garantizar que sea manejada por un usuario o desmarcada para procesarla de una manera más fácil) o utilice una excepción específica del analizador XML que está utilizando.


Suponiendo que la estructura de clases está fuera de nuestro control, como parece ser el caso, creo que atrapar el NPE como se sugiere en la pregunta es una solución razonable, a menos que el rendimiento sea una preocupación importante. Una pequeña mejora podría ser envolver 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 simplemente puedes hacer:

return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), -1);


Usted dice que algunos métodos "pueden devolver null " pero no dice en qué circunstancias devuelven null . Dices que capturas la excepción NullPointerException pero no dices por qué la capturas. Esta falta de información sugiere que no tiene una comprensión clara de para qué son 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 más allá de su control (que de hecho es el caso para todos los métodos en Java ). Llamamos a ese método y vuelve. El código que llama a ese método necesita saber si fue exitoso. ¿Cómo puede saberlo? ¿Cómo se puede estructurar para hacer frente a las dos posibilidades, de éxito o fracaso?

Usando excepciones, podemos escribir métodos que tengan éxito como condición posterior . Si el método regresa, fue exitoso. Si arroja una excepción, había fallado. Esta es una gran victoria para la claridad. Podemos escribir código que procese claramente el caso de éxito normal y mover todo el código de manejo de errores a cláusulas catch . A menudo se deduce 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 se puede usar la misma cláusula catch para manejar varios tipos de fallas. Y a menudo sucede que un método no necesita detectar excepciones en absoluto , sino que simplemente puede permitir que se propaguen a su interlocutor. Las excepciones debidas a errores del programa están en esa última clase; pocos métodos pueden reaccionar adecuadamente cuando hay un error.

Entonces, esos métodos que devuelven null .

  • ¿Un valor null indica un error en su código? Si lo hace, no debería estar captando la excepción en absoluto. Y su código no debería estar tratando de adivinar por sí mismo. Simplemente escriba lo que es claro y conciso suponiendo que funcionará. ¿Es una cadena de métodos llamados claros y concisos? Entonces solo úsalos.
  • ¿Un valor null indica una entrada no válida a su programa? Si lo hace, una excepción NullPointerException no es una excepción apropiada para lanzar, porque convencionalmente está reservada para indicar errores. Probablemente desee lanzar una excepción personalizada derivada de IllegalArgumentException (si desea una excepción no marcada ) o IOException (si desea una excepción marcada). ¿Se requiere que su programa proporcione mensajes de error de sintaxis detallados cuando hay una entrada no válida? Si es así, lo único que puede hacer es verificar cada método para obtener un null retorno null 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, capturar cualquier NullPointerException y 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 es errónea.

  • Cuando se trata del diseño del programa, en realidad no hay reglas absolutas sobre lo que es bueno y lo que es malo. Solo hay heurísticas: reglas que son correctas la mayoría de las veces (incluso casi todas). Parte de la habilidad de 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 se debe romper 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 una jerarquía de abstracciones . Tiene clases de bajo nivel que utilizan las primitivas del lenguaje para realizar operaciones directamente y representar objetos en una abstracción de nivel superior a las primitivas del lenguaje. Tiene clases de nivel medio que delegan a las clases de bajo nivel e implementan operaciones y representaciones en un nivel más alto que las clases de bajo nivel. Tiene clases de alto nivel que delegan en 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. La justificación de la Ley de Deméter es 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, que su clase de nivel medio no proporcionó una operación abstracta de nivel medio que necesita la clase de alto nivel. Pero parece que esa no es la situación que tiene aquí: no diseñó las clases en la cadena de llamadas a métodos, son el resultado de un código de serialización XML generado automáticamente (¿verdad?), Y la cadena de llamadas no desciende a través de una jerarquía de abstracción porque el XML deserializado está al mismo nivel de la jerarquía de abstracción (¿verdad?)?

Vale la pena considerar crear su propia excepción. Llamémoslo MyOperationFailedException. Puede lanzarlo en su lugar devolviendo un valor. El resultado será el mismo: saldrá de la función, pero no devolverá el valor codificado -1, que es el antipatró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. Una es que acepta nulo y la otra es que es un error.

Si se trata de un error y ocurre, puede depurar su código utilizando otras estructuras para fines de depuración cuando los puntos de interrupción no son suficientes.

Si es aceptable, no te importa dónde apareció este nulo. Si lo hace, definitivamente no debe encadenar esas solicitudes.


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

Tendrá que atrapar la excepción donde quiera que llame al método (o se propagará por la pila). Sin embargo, si en su caso puede seguir trabajando con ese resultado con el valor -1 y está seguro de que no se propagará porque no está utilizando ninguna de las "piezas" que pueden ser nulas, entonces me parece correcto Atrapalo

Editar:

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


return wsObject.getFooBarBazInt();

aplicando la Ley de Deméter,

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