2012-04-30 14 views
25

Imagine que tengo una familia de clase. Contiene una lista de personas. Cada persona (clase) contiene una dirección (de clase). Cada dirección (clase) contiene un Código Postal (de clase). Cualquier clase "intermedia" puede ser nula.Java: evite buscar null en clases anidadas (comprobación nula profunda)

Entonces, ¿hay una manera simple de llegar a PostalCode sin tener que verificar la nulidad en cada paso? es decir, ¿hay alguna forma de evitar el siguiente código de encadenamiento? Sé que no hay una solución Java "nativa", pero esperaba que alguien supiera de una biblioteca o algo así. (Comprobado Commons & guayaba y no ver nada)

if(family != null) { 
    if(family.getPeople() != null) { 
     if(family.people.get(0) != null) { 
      if(people.get(0).getAddress() != null) { 
       if(people.get(0).getAddress().getPostalCode() != null) { 
        //FINALLY MADE IT TO DO SOMETHING!!! 
       } 
      } 
     } 
    } 
} 

No, no se puede cambiar la estructura. Es de un servicio que no tengo control.

No, no puedo usar Groovy y su práctico operador "Elvis".

No, preferiría no esperar para Java 8: D

No puedo creer que soy la primera vez dev a enfermar 'n cansados ​​de escribir código como este, pero no he Pude encontrar una solución.

Ideas?

Gracias

-
llappall

+0

Lo siento, estás atascado. Algunas personas usan el operador trinario condicional para hacerlo un poco menos denso, pero sigue siendo el mismo bytecode, simplemente más difícil de leer. –

+4

"* No puedo creer que soy el primer desarrollador en enfermarme y estoy cansado de escribir código como este *" Bueno, no lo eres. – user1329572

+0

Claro. ¡Pero no creo que puedas beatificar más el código! ¡Lo siento! –

Respuesta

1

No es una idea tan genial, pero ¿qué hay de agarrar la excepción:

try 
    { 
     PostalCode pc = people.get(0).getAddress().getPostalCode(); 
    } 
    catch(NullPointerException ex) 
    { 
     System.out.println("Gotcha"); 
    } 
6

Lo más cerca que se puede conseguir es aprovechar el corto -cut reglas en condicional:

if(family != null && family.getPeople() != null && family.people.get(0) != null && family.people.get(0).getAddress() != null && family.people.get(0).getAddress().getPostalCode() != null) { 
        //FINALLY MADE IT TO DO SOMETHING!!! 

} 

Por cierto, capturar una excepción en lugar de probar la condición por adelantado es una idea horrible.

+0

tiene acceso '} 'allí (se olvidó de eliminarlos cuando se refactorizaron) – amit

+0

Gracias, @amit. Corregido ahora. –

11

Su código se comporta igual que

if(family != null && 
    family.getPeople() != null && 
    family.people.get(0) != null && 
    family.people.get(0).getAddress() != null && 
    family.people.get(0).getAddress().getPostalCode() != null) { 
     //My Code 
} 

Gracias a short circuiting evaluation, esto también es seguro, ya que la segunda condición no se evaluará si la primera es falsa, no se evaluará la tercera si el segundo es falso, .... y no obtendrás NPE porque si.

+0

'null' siempre quiere permanecer en nuestros códigos! –

3

Si es raro, puede ignorar los controles null y confiar en NullPointerException. "Rara" debido a un posible problema de rendimiento (depende, generalmente, completará el seguimiento de la pila, que puede ser costoso).

Aparte de eso 1) un método de ayuda específica que comprueba nula a limpiar ese código o 2) Hacer enfoque genérico mediante la reflexión y una cadena como:

checkNonNull(family, "people[0].address.postalcode") 

Implementación deja como ejercicio.

+0

La reflexión tampoco es exactamente barata. –

+0

Sí, de hecho, también puede ser lento @paul. Especialmente la búsqueda de métodos, etc. La invocación real del método puede ser bastante rápida, pero nuevamente depende (otras optimizaciones pueden ser más difíciles para la VM). Por lo tanto, la caché de las búsquedas de método/campo suele ser importante desde mi experiencia si es necesario. Sobre todo, por supuesto, depende de la frecuencia con la que se usa el código. –

1

En lugar de usar nulo, puede usar alguna versión del patrón de diseño "objeto nulo".Por ejemplo:

public class Family { 
    private final PersonList people; 
    public Family(PersonList people) { 
     this.people = people; 
    } 

    public PersonList getPeople() { 
     if (people == null) { 
      return PersonList.NULL; 
     } 
     return people; 
    } 

    public boolean isNull() { 
     return false; 
    } 

    public static Family NULL = new Family(PersonList.NULL) { 
     @Override 
     public boolean isNull() { 
      return true; 
     } 
    }; 
} 


import java.util.ArrayList; 

public class PersonList extends ArrayList<Person> { 
    @Override 
    public Person get(int index) { 
     Person person = null; 
     try { 
      person = super.get(index); 
     } catch (ArrayIndexOutOfBoundsException e) { 
      return Person.NULL; 
     } 
     if (person == null) { 
      return Person.NULL; 
     } else { 
      return person; 
     } 
    } 
    //... more List methods go here ... 

    public boolean isNull() { 
     return false; 
    } 

    public static PersonList NULL = new PersonList() { 
     @Override 
     public boolean isNull() { 
      return true; 
     } 
    }; 
} 

public class Person { 
    private Address address; 

    public Person(Address address) { 
     this.address = address; 
    } 

    public Address getAddress() { 
     if (address == null) { 
      return Address.NULL; 
     } 
     return address; 
    } 
    public boolean isNull() { 
     return false; 
    } 

    public static Person NULL = new Person(Address.NULL) { 
     @Override 
     public boolean isNull() { 
      return true; 
     } 
    }; 
} 

etc etc etc 

Luego, su sentencia if puede llegar a ser:

if (!family.getPeople().get(0).getAddress().getPostalCode.isNull()) {...} 

Es subóptima desde:

  • le pegan haciendo que los objetos nulos para cada clase,
  • Es difícil para hacer que estos objetos sean genéricos, por lo que está atrapado haciendo una versión de objeto nulo de cada Lista, Mapa, etc. que desee usar, y
  • Existen potencialmente algunos problemas divertidos con la creación de subclases y qué NULL usar.

Pero si realmente odias tu == null s, esta es una salida.

0

Solo buscaba lo mismo (mi contexto: un grupo de clases JAXB creadas automáticamente, y de alguna manera tengo estas largas cadenas tipo margarita de .getFoo().getBar().... Invariablemente, de vez en cuando una de las llamadas en el medio regresa nulo, causando NPE

Algo que comencé a jugar hace tiempo se basa en la reflexión. Estoy seguro de que podemos hacerlo más bonito y más eficiente (almacenar el reflejo, por un lado, y también definir métodos "mágicos") como ._all para iterar automáticamente en todos los elementos de una colección, si algún método en el centro devuelve una colección). No es bonito, pero quizás alguien podría decirnos si ya hay algo mejor por ahí:

/** 
* Using {@link java.lang.reflect.Method}, apply the given methods (in daisy-chain fashion) 
* to the array of Objects x. 
* 
* <p>For example, imagine that you'd like to express: 
* 
* <pre><code> 
* Fubar[] out = new Fubar[x.length]; 
* for (int i=0; {@code i<x.length}; i++) { 
* out[i] = x[i].getFoo().getBar().getFubar(); 
* } 
* </code></pre> 
* 
* Unfortunately, the correct code that checks for nulls at every level of the 
* daisy-chain becomes a bit convoluted. 
* 
* <p>So instead, this method does it all (checks included) in one call: 
* <pre><code> 
* Fubar[] out = apply(new Fubar[0], x, "getFoo", "getBar", "getFubar"); 
* </code></pre> 
* 
* <p>The cost, of course, is that it uses Reflection, which is slower than 
* direct calls to the methods. 
* @param type the type of the expected result 
* @param x the array of Objects 
* @param methods the methods to apply 
* @return 
*/ 
@SuppressWarnings("unchecked") 
public static <T> T[] apply(T[] type, Object[] x, String...methods) { 
    int n = x.length; 
    try { 
     for (String methodName : methods) { 
      Object[] out = new Object[n]; 
      for (int i=0; i<n; i++) { 
       Object o = x[i]; 
       if (o != null) { 
        Method method = o.getClass().getMethod(methodName); 
        Object sub = method.invoke(o); 
        out[i] = sub; 
       } 
      } 
      x = out; 
     } 
    T[] result = (T[])Array.newInstance(type.getClass().getComponentType(), n); 
    for (int i=0; i<n; i++) { 
      result[i] = (T)x[i]; 
    } 
      return result; 
    } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 
      throw new RuntimeException(e); 
    } 
} 
0

Si, en el caso, está utilizando java8, entonces puede usar;

resolve(() -> people.get(0).getAddress().getPostalCode()); 
    .ifPresent(System.out::println); 

: 
public static <T> Optional<T> resolve(Supplier<T> resolver) { 
    try { 
     T result = resolver.get(); 
     return Optional.ofNullable(result); 
    } 
    catch (NullPointerException e) { 
     return Optional.empty(); 
    } 
} 

REF: avoid null checks

+0

Esta solución se basa en atrapar el NPE que funcionará muy mal. – mojoken

0

Aunque este post es de casi cinco años de edad, podría haber otra solución a la vieja pregunta de cómo manejar NullPointerException s.

En pocas palabras:

end: { 
    List<People> people = family.getPeople();   if(people == null || people.isEmpty()) break end; 
    People person = people.get(0);      if(person == null) break end; 
    Address address = person.getAddress();    if(address == null) break end; 
    PostalCode postalCode = address.getPostalCode();  if(postalCode == null) break end; 

    System.out.println("Do stuff"); 
} 

Dado que existe una gran cantidad de código heredado todavía en uso, el uso de Java 8 y Optional no es siempre una opción.

Cuando hay clases profundamente anidadas involucradas (JAXB, SOAP, JSON, lo que sea ...) y Law of Demeter no se aplica, básicamente debe verificar todo y ver si hay posibles NPE al acecho.

Mi solución propuesta se esfuerza por la legibilidad y no debe utilizarse si no hay al menos 3 o más clases anidadas involucradas (cuando digo anidado, no me refiero a Nested classes en el contexto formal). Como el código se lee más de lo que está escrito, un vistazo rápido a la parte izquierda del código hará que su significado sea más claro que el uso de sentencias if-else profundamente anidadas.

Si necesita otra parte, se puede utilizar este patrón:

boolean prematureEnd = true; 

end: { 
    List<People> people = family.getPeople();   if(people == null || people.isEmpty()) break end; 
    People person = people.get(0);      if(person == null) break end; 
    Address address = person.getAddress();    if(address == null) break end; 
    PostalCode postalCode = address.getPostalCode();  if(postalCode == null) break end; 

    System.out.println("Do stuff"); 
    prematureEnd = false; 
} 

if(prematureEnd) { 
    System.out.println("The else part"); 
} 

Ciertos entornos de desarrollo van a romper este formato, a menos que las instrucciones de no (ver this question).

Sus condicionales deben invertirse: le dice al código cuándo debería romperse, no cuándo debería continuar.

Una cosa más: su código sigue siendo propenso a la rotura. Debe usar if(family.getPeople() != null && !family.getPeople().isEmpty()) como la primera línea en su código, de lo contrario, una lista vacía arrojará un NPE.