Java: evite verificar nulos en clases anidadas (comprobación profunda de nulos)

7 minutos de lectura

avatar de usuario de llappall
llappall

Imagina que tengo una familia de clase. Contiene una Lista de Persona. Cada persona (clase) contiene una dirección (clase). Cada dirección (de clase) contiene un código postal (de clase). Cualquier clase “intermedia” puede ser nula.

Entonces, ¿existe una manera simple de llegar a PostalCode sin tener que verificar el valor nulo en cada paso? es decir, ¿hay alguna manera de evitar el siguiente código de conexión en cadena? Sé que no hay una solución Java “nativa”, pero esperaba que alguien supiera de una biblioteca o algo así. (revisé Commons & Guava y no vi 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 sobre el que no tengo control.

No, no puedo usar Groovy y es un operador útil de “Elvis”.

No, prefiero no esperar a Java 8 😀

No puedo creer que sea el primer desarrollador en hartarse de escribir código como este, pero no he podido encontrar una solución.

  • Lo siento, estás atascado. Algunas personas usan el operador condicional trinario para hacerlo un poco menos denso, pero sigue siendo el mismo código de bytes, solo que más difícil de leer.

    –Paul Tomblin

    30 de abril de 2012 a las 22:32

  • No puedo creer que sea el primer desarrollador en hartarse de escribir código como este“Bueno, no lo eres.

    – usuario1329572

    30 de abril de 2012 a las 22:34

  • A pesar de todas las respuestas que le dicen que ignore los cheques nulos y que solo intente atrapar un NullPointerException, no lo hagas! Su código puede ser una monstruosidad, pero lanzar una excepción es una operación costosa que siempre querrá evitar si puede.

    – Jeffrey

    30 de abril de 2012 a las 22:47

  • Además, considere todas las cosas buenas que puede hacer en las cláusulas “else” si las coloca en su lugar: mensajes de error, rutas de código alternativas, etc. Dado todo eso, no se verá tan mal.

    – mazaneicha

    30 de abril de 2012 a las 23:03


  • Si tan solo pudieras portar Brototype para Java… github.com/letsgetrandy/brototipo

    – jonS90

    08/10/2014 a las 18:25


Puedes usar para:

product.getLatestVersion().getProductData().getTradeItem().getInformationProviderOfTradeItem().getGln();

equivalente opcional:

Optional.ofNullable(product).map(
            Product::getLatestVersion
        ).map(
            ProductVersion::getProductData
        ).map(
            ProductData::getTradeItem
        ).map(
            TradeItemType::getInformationProviderOfTradeItem
        ).map(
            PartyInRoleType::getGln
        ).orElse(null);

  • Requiere API 24 y superior 🙁

    – sandpat

    10 de abril de 2019 a las 11:25

  • Si ve la implementación de .map, usa Objects.requireNonNull(mapper); y este método arroja una excepción de puntero nulo si el mapeador se vuelve nulo.

    – Anirudh

    27 de febrero de 2020 a las 10:18

  • @Anirudh the mapper, en este caso, es la referencia del método y nunca es nulo. El valor devuelto por el mapeador puede ser nulo y, en ese caso, el mapa devuelve Optional.empty()

    – Andrea Polci

    22 oct 2020 a las 11:45

  • Esta debería ser la respuesta aceptada …

    – Taher

    8 de noviembre de 2021 a las 10:25

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 evaluación de cortocircuitoesto también es seguro, ya que la segunda condición no será evaluada si la primera es falsa, la 3ra no será evaluada si la 2da es falsa,…. y no obtendrá NPE porque si lo es.

Si, en el caso, está usando 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();
    }
}

ÁRBITRO: evitar cheques nulos

  • Esta solución se basa en la captura de NPE, que tendrá un rendimiento muy bajo.

    – mojoken

    22 de septiembre de 2017 a las 1:33

  • Estoy a favor de esto. ¿Alguien puede explicar por qué es pobre? ¿Es porque los captadores podrían internamente a través de un NPE debido a un error diferente, y se enmascarará?

    – daltonfury42

    16 de junio de 2021 a las 5:20


Avatar de usuario de Paul Tomblin
pablo tomblin

Lo más cerca que puede estar es aprovechar las reglas abreviadas en condicionales:

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.

Yo personalmente prefiero algo similar a:

nullSafeLogic(() -> family.people.get(0).getAddress().getPostalCode(), x -> doSomethingWithX(x))

public static <T, U> void nullSafeLogic(Supplier<T> supplier, Function<T,U> function) {
    try {
        function.apply(supplier.get());
    } catch (NullPointerException n) {
        return null;
    }
}

o algo asi

nullSafeGetter(() -> family.people.get(0).getAddress().getPostalCode())

public static <T> T nullSafeGetter(Supplier<T> supplier) {
    try {
        return supplier.get();
    } catch (NullPointerException n) {
        return null;
    }
}

La mejor parte es que los métodos estáticos son reutilizables con cualquier función 🙂

En lugar de usar nulo, podría usar alguna versión del patrón de diseño de “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

Entonces su declaración if puede convertirse en:

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

Es subóptimo ya que:

  • Estás atascado haciendo objetos NULL para cada clase,
  • Es difícil hacer que estos objetos sean genéricos, por lo que está atascado creando una versión de objeto nulo de cada Lista, Mapa, etc. que desea usar, y
  • Hay potencialmente algunos problemas divertidos con la subclasificación y qué NULL usar.

Pero si realmente odias a tu == nullS, esta es una salida.

Avatar de usuario de la comunidad
Comunidad

Aunque esta publicación tiene casi cinco años, podría tener otra solución a la vieja pregunta de cómo manejar NullPointerExceptions.

En una palabra:

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 todavía se usa mucho código heredado, usar Java 8 y Optional no siempre es una opción.

Siempre que haya clases profundamente anidadas involucradas (JAXB, SOAP, JSON, lo que sea…) y Ley de Deméter 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 usarse si no hay al menos 3 o más clases anidadas involucradas (cuando digo anidadas, no me refiero a Clases anidadas en el contexto formal). Dado que el código se lee más de lo que se escribe, un vistazo rápido a la parte izquierda del código hará que su significado sea más claro que usar sentencias if-else profundamente anidadas.

Si necesita la otra parte, puede usar 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 IDE romperán este formato, a menos que les indiques que no lo hagan (ver esta pregunta).

Tus condicionales deben invertirse: le dices al código cuándo debe romperse, no cuándo debe continuar.

Una cosa más: su código aún es propenso a romperse. Debes usar if(family.getPeople() != null && !family.getPeople().isEmpty()) como la primera línea de su código; de lo contrario, una lista vacía arrojará un NPE.

¿Ha sido útil esta solución?