Java: avoid checking for null in nested classes (Deep Null checking)

前端 未结 11 453
失恋的感觉
失恋的感觉 2020-12-05 09:12

Imagine I have a class Family. It contains a List of Person. Each (class) Person contains a (class) Address. Each (class) Address contains a (class) PostalCode. Any "i

相关标签:
11条回答
  • 2020-12-05 09:53

    Although this post is almost five years old, I might have another solution to the age old question of how to handle NullPointerExceptions.

    In a nutshell:

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

    Since there is a lot of legacy code still in use, using Java 8 and Optional isn't always an option.

    Whenever there are deeply nested classes involved (JAXB, SOAP, JSON, you name it...) and Law of Demeter isn't applied, you basically have to check everything and see if there are possible NPEs lurking around.

    My proposed solution strives for readibility and shouldn't be used if there aren't at least 3 or more nested classes involved (when I say nested, I don't mean Nested classes in the formal context). Since code is read more than it is written, a quick glance to the left part of the code will make its meaning more clear than using deeply nested if-else statements.

    If you need the else part, you can use this pattern:

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

    Certain IDEs will break this formatting, unless you instruct them not to (see this question).

    Your conditionals must be inverted - you tell the code when it should break, not when it should continue.

    One more thing - your code is still prone to breakage. You must use if(family.getPeople() != null && !family.getPeople().isEmpty()) as the first line in your code, otherwise an empty list will throw a NPE.

    0 讨论(0)
  • 2020-12-05 09:55

    Instead of using null, you could use some version of the "null object" design pattern. For example:

    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
    

    Then your if statement can become:

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

    It's suboptimal since:

    • You're stuck making NULL objects for every class,
    • It's hard to make these objects generic, so you're stuck making a null-object version of each List, Map, etc that you want to use, and
    • There are potentially some funny issues with subclassing and which NULL to use.

    But if you really hate your == nulls, this is a way out.

    0 讨论(0)
  • 2020-12-05 09:56

    If it is rare you could ignore the null checks and rely on NullPointerException. "Rare" due to possible performance problem (depends, usually will fill in stack trace which can be expensive).

    Other than that 1) a specific helper method that checks for null to clean up that code or 2) Make generic approach using reflection and a string like:

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

    Implementation left as an exercise.

    0 讨论(0)
  • 2020-12-05 09:58

    Your code behaves the same as

    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
    }
    

    Thanks to short circuiting evaluation, this is also safe, since the second condition will not be evaluated if the first is false, the 3rd won't be evaluated if the 2nd is false,.... and you will not get NPE because if it.

    0 讨论(0)
  • 2020-12-05 09:58

    The closest you can get is to take advantage of the short-cut rules in conditionals:

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

    By the way, catching an exception instead of testing the condition in advance is a horrible idea.

    0 讨论(0)
  • 2020-12-05 10:01

    You can use for:

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

    optional equivalent:

    Optional.ofNullable(product).map(
                Product::getLatestVersion
            ).map(
                ProductVersion::getProductData
            ).map(
                ProductData::getTradeItem
            ).map(
                TradeItemType::getInformationProviderOfTradeItem
            ).map(
                PartyInRoleType::getGln
            ).orElse(null);
    
    0 讨论(0)
提交回复
热议问题