java división - Evitando! = Declaraciones nulas




15 Answers

Para mí, esto me parece un problema bastante común que los desarrolladores junior a intermedios tienden a enfrentar en algún momento: o no saben o no confían en los contratos en los que están participando y defensivamente realizan una verificación excesiva de los nulos. Además, cuando escriben su propio código, tienden a confiar en que devuelvan nulos para indicar algo, por lo que requieren que la persona que llama compruebe si hay nulos.

Para poner esto de otra manera, hay dos instancias donde aparece la comprobación nula:

  1. Donde null es una respuesta válida en términos del contrato; y

  2. Donde no es una respuesta válida.

(2) es fácil. Utilice declaraciones de aserción (aserciones) o permita fallos (por ejemplo, NullPointerException ). Las aserciones son una característica de Java muy infrautilizada que se agregó en 1.4. La sintaxis es:

assert <condition>

o

assert <condition> : <object>

donde <condition> es una expresión booleana y <object> es un objeto cuya salida del método toString() se incluirá en el error.

Una declaración de afirmación arroja un Error ( AssertionError ) si la condición no es verdadera. Por defecto, Java ignora las aserciones. Puede habilitar aserciones pasando la opción -ea a la JVM. Puede habilitar y deshabilitar aserciones para clases individuales y paquetes. Esto significa que puede validar el código con las aserciones durante el desarrollo y la prueba, y deshabilitarlas en un entorno de producción, aunque mis pruebas han demostrado casi sin impacto en el rendimiento de las aserciones.

No usar aserciones en este caso está bien porque el código simplemente fallará, que es lo que ocurrirá si usa aserciones. La única diferencia es que, con las afirmaciones, podría suceder antes, de una manera más significativa y posiblemente con información adicional, lo que puede ayudarlo a descubrir por qué sucedió si no lo esperaba.

(1) es un poco más difícil. Si no tienes control sobre el código al que estás llamando, estás atascado. Si null es una respuesta válida, debe verificarla.

Sin embargo, si es el código que controlas (y esto suele ser el caso), entonces es una historia diferente. Evite utilizar nulos como respuesta. Con los métodos que devuelven colecciones, es fácil: devolver colecciones vacías (o matrices) en lugar de valores nulos casi todo el tiempo.

Con no-colecciones podría ser más difícil. Considera esto como un ejemplo: si tienes estas interfaces:

public interface Action {
  void doSomething();
}

public interface Parser {
  Action findAction(String userInput);
}

donde Parser toma la entrada de usuario sin formato y encuentra algo que hacer, tal vez si está implementando una interfaz de línea de comandos para algo. Ahora puede hacer que el contrato que devuelve sea nulo si no hay una acción apropiada. Eso lleva a la comprobación nula de la que estás hablando.

Una solución alternativa es nunca devolver el valor nulo y, en su lugar, utilizar el patrón de objeto nulo :

public class MyParser implements Parser {
  private static Action DO_NOTHING = new Action() {
    public void doSomething() { /* do nothing */ }
  };

  public Action findAction(String userInput) {
    // ...
    if ( /* we can't find any actions */ ) {
      return DO_NOTHING;
    }
  }
}

Comparar:

Parser parser = ParserFactory.getParser();
if (parser == null) {
  // now what?
  // this would be an example of where null isn't (or shouldn't be) a valid response
}
Action action = parser.findAction(someInput);
if (action == null) {
  // do nothing
} else {
  action.doSomething();
}

a

ParserFactory.getParser().findAction(someInput).doSomething();

que es un diseño mucho mejor porque conduce a un código más conciso.

Dicho esto, tal vez sea totalmente apropiado que el método findAction () lance una excepción con un mensaje de error significativo, especialmente en este caso en el que confía en la información del usuario. Sería mucho mejor para el método findAction lanzar una excepción que para el método de llamada explotar con una simple NullPointerException sin explicación.

try {
    ParserFactory.getParser().findAction(someInput).doSomething();
} catch(ActionNotFoundException anfe) {
    userConsole.err(anfe.getMessage());
}

O si cree que el mecanismo try / catch es demasiado feo, en lugar de hacer nada, su acción predeterminada debe proporcionar comentarios al usuario.

public Action findAction(final String userInput) {
    /* Code to return requested Action if found */
    return new Action() {
        public void doSomething() {
            userConsole.err("Action not found: " + userInput);
        }
    }
}
excepción division

Uso object != null mucho para evitar NullPointerException .

¿Hay una buena alternativa a esto?

Por ejemplo:

if (someobject != null) {
    someobject.doCalc();
}

Esto evita una NullPointerException , cuando se desconoce si el objeto es null o no.

Tenga en cuenta que la respuesta aceptada puede estar desactualizada, consulte https://.com/a/2386013/12943 para obtener un enfoque más reciente.




Si no se permiten valores nulos

Si su método se llama externamente, comience con algo como esto:

public void method(Object object) {
  if (object == null) {
    throw new IllegalArgumentException("...");
  }

Luego, en el resto de ese método, sabrás que el object no es nulo.

Si es un método interno (que no es parte de una API), solo documente que no puede ser nulo, y eso es todo.

Ejemplo:

public String getFirst3Chars(String text) {
  return text.subString(0, 3);
}

Sin embargo, si su método simplemente pasa el valor, y el siguiente método lo pasa, etc. podría resultar problemático. En ese caso, es posible que desee comprobar el argumento como se indica arriba.

Si se permite nulo

Esto realmente depende. Si encuentro que a menudo hago algo como esto:

if (object == null) {
  // something
} else {
  // something else
}

Así que me ramifico, y hago dos cosas completamente diferentes. No hay un fragmento de código feo, porque realmente necesito hacer dos cosas diferentes dependiendo de los datos. Por ejemplo, ¿debo trabajar en la entrada o debo calcular un buen valor predeterminado?

En realidad, es raro que use el idioma " if (object != null && ... ".

Puede ser más fácil darle ejemplos, si muestra ejemplos de dónde usa normalmente el idioma.




Si no se permiten valores indefinidos:

Puede configurar su IDE para advertirle sobre una posible anulación de referencia nula. Por ejemplo, en Eclipse, consulte Preferencias> Java> Compilador> Errores / Advertencias / Análisis nulo .

Si se permiten valores indefinidos:

Si desea definir una nueva API en la que los valores no definidos tengan sentido , use el Patrón de opción (puede ser familiar para los lenguajes funcionales). Tiene las siguientes ventajas:

  • Se establece explícitamente en la API si una entrada o salida existe o no.
  • El compilador le obliga a manejar el caso "indefinido".
  • La opción es una mónada , por lo que no es necesario realizar una comprobación nula detallada, simplemente use map / foreach / getOrElse o un combinador similar para usar el valor de forma segura (example) .

Java 8 tiene una clase Optional incorporada (recomendada); para versiones anteriores, hay alternativas de biblioteca, por ejemplo, la Optional Guava o la Option FunctionalJava . Pero al igual que muchos patrones de estilo funcional, el uso de Opción en Java (incluso 8) da como resultado una gran cantidad de repeticiones, que puede reducir utilizando un lenguaje JVM menos detallado, por ejemplo, Scala o Xtend.

Si tiene que lidiar con una API que podría devolver nulos , no puede hacer mucho en Java. Xtend y Groovy tienen el operador de Elvis y el operador de anulación de seguridad nula ?. , pero tenga en cuenta que esto devuelve nulo en caso de una referencia nula, por lo que simplemente "difiere" el manejo adecuado de nulo.




Con Java 8 viene la nueva clase java.util.Optional que posiblemente resuelva algunos de los problemas. Al menos, se puede decir que mejora la legibilidad del código y, en el caso de las API públicas, el contrato de la API es más claro para el desarrollador del cliente.

Funcionan así:

Un objeto opcional para un tipo dado ( Fruit ) se crea como el tipo de retorno de un método. Puede estar vacío o contener un objeto Fruit :

public static Optional<Fruit> find(String name, List<Fruit> fruits) {
   for (Fruit fruit : fruits) {
      if (fruit.getName().equals(name)) {
         return Optional.of(fruit);
      }
   }
   return Optional.empty();
}

Ahora mire este código donde buscamos una lista de Fruit ( fruits ) para una instancia de Fruta determinada:

Optional<Fruit> found = find("lemon", fruits);
if (found.isPresent()) {
   Fruit fruit = found.get();
   String name = fruit.getName();
}

Puede utilizar el operador map() para realizar un cálculo o extraer un valor de un objeto opcional. orElse() permite proporcionar un respaldo para los valores faltantes.

String nameOrNull = find("lemon", fruits)
    .map(f -> f.getName())
    .orElse("empty-name");

Por supuesto, la verificación del valor nulo / vacío sigue siendo necesaria, pero al menos el desarrollador es consciente de que el valor podría estar vacío y el riesgo de olvidarse de la verificación es limitado.

En una API creada desde cero utilizando Optional siempre que un valor devuelto esté vacío, y devolviendo un objeto plano solo cuando no pueda ser null (convención), el código del cliente podría abandonar las comprobaciones nulas en valores de retorno de objetos simples ...

Por supuesto, Optional también podría usarse como un argumento de método, quizás una mejor manera de indicar argumentos opcionales que 5 o 10 métodos de sobrecarga en algunos casos.

Optional ofrece otros métodos convenientes, como orElse que permiten el uso de un valor predeterminado, y ifPresent que funciona con expresiones lambda .

Los invito a leer este artículo (mi fuente principal para escribir esta respuesta) en el que la NullPointerException (y en general el puntero nulo) es problemática, así como la solución (parcial) presentada por Optional están bien explicadas: Objetos opcionales de Java .




  • Si considera que un objeto no debe ser nulo (o es un error) use una aserción.
  • Si su método no acepta parámetros nulos, dígalo en el javadoc y use una aserción.

Debe verificar el objeto! = Null solo si desea manejar el caso donde el objeto puede ser nulo ...

Hay una propuesta para agregar nuevas anotaciones en Java7 para ayudar con los parámetros null / notnull: http://tech.puredanger.com/java7/#jsr308




El marco de las colecciones de Google ofrece una forma buena y elegante de lograr el cheque nulo.

Hay un método en una clase de biblioteca como esta:

static <T> T checkNotNull(T e) {
   if (e == null) {
      throw new NullPointerException();
   }
   return e;
}

Y el uso es (con import static ):

...
void foo(int a, Person p) {
   if (checkNotNull(p).getAge() > a) {
      ...
   }
   else {
      ...
   }
}
...

O en tu ejemplo:

checkNotNull(someobject).doCalc();



En lugar de un patrón de objeto nulo, que tiene sus usos, puede considerar situaciones en las que el objeto nulo es un error.

Cuando se lanza la excepción, examine el seguimiento de la pila y trabaje a través del error.




Nulo no es un 'problema'. Es una parte integral de un conjunto complete herramientas de modelado. El software apunta a modelar la complejidad del mundo y null lleva su carga. Nulo indica 'No hay datos' o 'Desconocido' en Java y similares. Por eso es apropiado usar nulos para estos propósitos. No prefiero el patrón 'Objeto nulo'; Creo que surge el problema de ' quién cuidará a los guardianes '.
Si me preguntas cuál es el nombre de mi novia, te diré que no tengo novia. En el lenguaje Java devolveré nulo. Una alternativa sería lanzar una excepción significativa para indicar algún problema que no puede ser (o no)t quiere ser) resuelto allí mismo y delegarlo en un lugar más alto en la pila para reintentar o reportar un error de acceso de datos al usuario.

  1. Para una 'pregunta desconocida' dé 'respuesta desconocida'. (Esté a salvo de nulos cuando esto sea correcto desde el punto de vista comercial). Verificar los argumentos para null una vez dentro de un método antes de que el uso libere a varias personas que llaman antes de una llamada.

    public Photo getPhotoOfThePerson(Person person) {
        if (person == null)
            return null;
        // Grabbing some resources or intensive calculation
        // using person object anyhow.
    }
    

    Anterior conduce al flujo lógico normal para no obtener una foto de una novia inexistente de mi biblioteca de fotos.

    getPhotoOfThePerson(me.getGirlfriend())
    

    Y encaja con la nueva API de Java que viene (mirando hacia adelante)

    getPhotoByName(me.getGirlfriend()?.getName())
    

    Si bien es más bien 'flujo comercial normal' no encontrar una foto almacenada en la base de datos para alguna persona, solía usar pares como abajo para otros casos

    public static MyEnum parseMyEnum(String value); // throws IllegalArgumentException
    public static MyEnum parseMyEnumOrNull(String value);
    

    Y no deteste escribir <alt> + <shift> + <j>(genere javadoc en Eclipse) y escriba tres palabras adicionales para su API pública. Esto será más que suficiente para todos menos para aquellos que no leen la documentación.

    /**
     * @return photo or null
     */
    

    o

    /**
     * @return photo, never null
     */
    
  2. Este es un caso bastante teórico y, en la mayoría de los casos, debería preferir una API nula segura de Java (en caso de que se publique en otros 10 años), pero NullPointerExceptiones una subclase de Exception. Por lo tanto, es una forma de Throwableque indica las condiciones que una aplicación razonable podría querer capturar ( javadoc ). Para usar la primera ventaja más grande de las excepciones y el código separado de manejo de errores del código 'regular' (de acuerdo con los creadores de Java ) es apropiado, como para mí, capturar NullPointerException.

    public Photo getGirlfriendPhoto() {
        try {
            return appContext.getPhotoDataSource().getPhotoByName(me.getGirlfriend().getName());
        } catch (NullPointerException e) {
            return null;
        }
    }
    

    Pueden surgir preguntas:

    P. ¿Qué pasa si getPhotoDataSource()devuelve nulo?
    A. Está a la altura de la lógica empresarial. Si no encuentro un álbum de fotos no te mostraré fotos. ¿Qué pasa si appContext no está inicializado? La lógica de negocios de este método soporta esto. Si la misma lógica debería ser más estricta, lanzar una excepción es parte de la lógica de negocios y se debe utilizar la comprobación explícita de nulos (caso 3). La nueva API de Java Null-safe se adapta mejor aquí para especificar de manera selectiva lo que implica y lo que no implica que se inicialice para fallar en caso de errores de programador.

    P. El código redundante se puede ejecutar y los recursos innecesarios se pueden tomar.
    A. Podría ocurrir si getPhotoByName()intentara abrir una conexión de base de datos, crear PreparedStatementy usar el nombre de la persona como un parámetro de SQL por fin. El enfoque para una pregunta desconocida da una respuesta desconocida (caso 1) que funciona aquí. Antes de tomar recursos, el método debe verificar los parámetros y devolver el resultado 'desconocido' si es necesario.

    P. Este enfoque tiene una penalización de rendimiento debido a la apertura del cierre de prueba.
    A. El software debe ser fácil de entender y modificar en primer lugar. Solo después de esto, uno podría pensar en el rendimiento, y solo si es necesario. y donde sea necesario! ( source ), y muchos otros).

    PD.Este enfoque será tan razonable de usar como el código de manejo de errores separado del principio del código "regular" que se puede usar en algún lugar. Considera el siguiente ejemplo:

    public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) {
        try {
            Result1 result1 = performSomeCalculation(predicate);
            Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
            Result3 result3 = performThirdCalculation(result2.getSomeProperty());
            Result4 result4 = performLastCalculation(result3.getSomeProperty());
            return result4.getSomeProperty();
        } catch (NullPointerException e) {
            return null;
        }
    }
    
    public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) {
        SomeValue result = null;
        if (predicate != null) {
            Result1 result1 = performSomeCalculation(predicate);
            if (result1 != null && result1.getSomeProperty() != null) {
                Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
                if (result2 != null && result2.getSomeProperty() != null) {
                    Result3 result3 = performThirdCalculation(result2.getSomeProperty());
                    if (result3 != null && result3.getSomeProperty() != null) {
                        Result4 result4 = performLastCalculation(result3.getSomeProperty());
                        if (result4 != null) {
                            result = result4.getSomeProperty();
                        }
                    }
                }
            }
        }
        return result;
    }
    

    PPS. Para aquellos que votan rápido (y no tan rápido para leer la documentación), me gustaría decir que nunca he detectado una excepción de puntero nulo (NPE) en mi vida. Pero esta posibilidad fue diseñada intencionalmente por los creadores de Java porque NPE es una subclase de Exception. Tenemos un precedente en la historia de Java cuando ThreadDeathes un Errorno, ya que es en realidad un error de aplicación, sino únicamente porque no tenía la intención de ser atrapado! ¿Cuánto NPE se ajusta para ser un Errorque ThreadDeath? Pero no lo es.

  3. Verifique 'No hay datos' solo si la lógica de negocios lo implica.

    public void updatePersonPhoneNumber(Long personId, String phoneNumber) {
        if (personId == null)
            return;
        DataSource dataSource = appContext.getStuffDataSource();
        Person person = dataSource.getPersonById(personId);
        if (person != null) {
            person.setPhoneNumber(phoneNumber);
            dataSource.updatePerson(person);
        } else {
            Person = new Person(personId);
            person.setPhoneNumber(phoneNumber);
            dataSource.insertPerson(person);
        }
    }
    

    y

    public void updatePersonPhoneNumber(Long personId, String phoneNumber) {
        if (personId == null)
            return;
        DataSource dataSource = appContext.getStuffDataSource();
        Person person = dataSource.getPersonById(personId);
        if (person == null)
            throw new SomeReasonableUserException("What are you thinking about ???");
        person.setPhoneNumber(phoneNumber);
        dataSource.updatePerson(person);
    }
    

    Si appContext o dataSource no se inicializa en tiempo de ejecución no manejado, NullPointerException eliminará el hilo actual y será procesado por Thread.defaultUncaughtExceptionHandler (para que defina y use su registrador favorito u otro Thread.defaultUncaughtExceptionHandler notificación). Si no está configurado, ThreadGroup#uncaughtException imprimirá stacktrace en el error del sistema. Uno debe monitorear el registro de errores de la aplicación y abrir el problema de Jira para cada excepción no manejada que, de hecho, es un error de la aplicación. El programador debería corregir un error en algún lugar de la inicialización.




"Problema" común en Java de hecho.

Primero, mis pensamientos sobre esto:

Considero que es malo "comer" algo cuando se pasó NULL donde NULL no es un valor válido. Si no está saliendo del método con algún tipo de error, significa que no hubo ningún error en su método, lo cual no es cierto. Entonces, probablemente devuelva nulo en este caso, y en el método de recepción nuevamente verifique que no haya nulo, y nunca termina, y termina con "if! = Null", etc.

Por lo tanto, en mi humilde opinión, el valor nulo debe ser un error crítico que impida la ejecución adicional (es decir, donde nulo no es un valor válido).

La forma en que resuelvo este problema es la siguiente:

Primero, sigo esta convención:

  1. Todos los métodos públicos / API siempre comprueban sus argumentos para null
  2. Todos los métodos privados no verifican si es nulo, ya que son métodos controlados (solo se deja morir con la excepción de nullpointer en caso de que no se haya manejado anteriormente)
  3. Los únicos otros métodos que no verifican si son nulos son los métodos de utilidad. Son públicos, pero si los llama por alguna razón, sabe qué parámetros pasa. Esto es como tratar de hervir agua en el hervidor sin proporcionar agua ...

Y finalmente, en el código, la primera línea del método público es la siguiente:

ValidationUtils.getNullValidator().addParam(plans, "plans").addParam(persons, "persons").validate();

Tenga en cuenta que addParam () devuelve self, para que pueda agregar más parámetros para verificar.

El método se validate()activará ValidationExceptionsi alguno de los parámetros es nulo (marcado o sin marcar es más un problema de diseño / gusto, pero mi ValidationExceptionestá marcado).

void validate() throws ValidationException;

El mensaje contendrá el siguiente texto si, por ejemplo, "planes" es nulo:

" Se encontró un valor de argumento no válido nulo para el parámetro [planes] "

Como puede ver, el segundo valor en el método addParam () (cadena) es necesario para el mensaje del usuario, porque no puede detectar fácilmente el nombre de la variable pasada, incluso con la reflexión (no es el tema de esta publicación de todos modos ...).

Y sí, sabemos que más allá de esta línea ya no encontraremos un valor nulo, por lo que simplemente invocamos métodos en esos objetos de forma segura.

De esta manera, el código es limpio, fácil de mantener y legible.




Además de usar assertpuedes usar lo siguiente:

if (someobject == null) {
    // Handle null here then move on.
}

Esto es un poco mejor que:

if (someobject != null) {
    .....
    .....



    .....
}



La guayaba, una biblioteca central muy útil de Google, tiene una API agradable y útil para evitar los nulos. Me parece muy útil Guava .

Como se explica en la wiki:

Optional<T>es una forma de reemplazar una referencia T anulable con un valor no nulo. Una Opcional puede contener una referencia T no nula (en cuyo caso decimos que la referencia es "presente"), o puede no contener nada (en cuyo caso decimos que la referencia está "ausente"). Nunca se dice que "contenga nulo".

Uso:

Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get(); // returns 5



Me gustan los artículos de Nat Pryce. Aquí están los enlaces:

En los artículos también hay un enlace a un repositorio Git para un Java Maybe Type que me parece interesante, pero no creo que por sí solo pueda disminuir la cantidad de códigos de verificación. Después de investigar un poco en Internet, creo que = el código nulo podría disminuir debido a un diseño cuidadoso.




Probablemente la mejor alternativa para Java 8 o más reciente es usar la Optionalclase.

Optional stringToUse = Optional.of("optional is there");
stringToUse.ifPresent(System.out::println);

Esto es especialmente útil para largas cadenas de posibles valores nulos. Ejemplo:

Optional<Integer> i = Optional.ofNullable(wsObject.getFoo())
    .map(f -> f.getBar())
    .map(b -> b.getBaz())
    .map(b -> b.getInt());

Ejemplo de cómo lanzar una excepción en nulo:

Optional optionalCarNull = Optional.ofNullable(someNull);
optionalCarNull.orElseThrow(IllegalStateException::new);

Java 7 introdujo el Objects.requireNonNullmétodo que puede ser útil cuando se debe comprobar que no haya nulidad. Ejemplo:

String lowerVal = Objects.requireNonNull(someVar, "input cannot be null or empty").toLowerCase();



Ignoro las respuestas que sugieren el uso de objetos nulos en cada situación. Este patrón puede romper el contrato y enterrar los problemas más y más profundamente en lugar de resolverlos, sin mencionar que el uso inadecuado creará otra pila de código repetitivo que requerirá mantenimiento futuro.

En realidad, si algo devuelto por un método puede ser nulo y el código de llamada debe tomar una decisión al respecto, debe haber una llamada anterior que garantice el estado.

También tenga en cuenta que ese patrón de objeto nulo tendrá hambre de memoria si se utiliza sin cuidado. Para esto, la instancia de NullObject debe compartirse entre los propietarios y no debe ser una instancia de Unigue para cada uno de estos.

Además, no recomendaría usar este patrón en el que se supone que el tipo es una representación de tipo primitiva, como las entidades matemáticas, que no son escalares: vectores, matrices, números complejos y objetos POD (datos antiguos) que están destinados a mantener el estado en forma de tipos incorporados de Java. En este último caso, terminarías llamando a métodos getter con resultados arbitrarios. Por ejemplo, ¿qué debería devolver un método NullPerson.getName ()?

Vale la pena considerar estos casos para evitar resultados absurdos.




Este es el error más común ocurrido para la mayoría de los desarrolladores.

Tenemos varias formas de manejar esto.

Enfoque 1:

org.apache.commons.lang.Validate //using apache framework

notNull (objeto objeto, mensaje de cadena)

Enfoque 2:

if(someObject!=null){ // simply checking against null
}

Enfoque 3:

@isNull @Nullable  // using annotation based validation

Enfoque 4:

// by writing static method and calling it across whereever we needed to check the validation

static <T> T isNull(someObject e){  
   if(e == null){
      throw new NullPointerException();
   }
   return e;
}



Related

java object nullpointerexception null