Check chains of "get" calls for null

Let's say I'd like to perform the following command:

house.getFloor(0).getWall(WEST).getDoor().getDoorknob();

To avoid a NullPointerException, I'd have to do the following if:

if (house != null && house.getFloor(0) && house.getFloor(0).getWall(WEST) != null
  && house.getFloor(0).getWall(WEST).getDoor() != null) ...

Is there a way or an already existing Utils class that does this more elegantly, let's say something like the following?

checkForNull(house.getFloor(0).getWall(WEST).getDoor().getDoorknob());

Solution 1:

In case you can't avoid breaking Law of Demeter (LoD) as stated in the chosen answer, and with Java 8 introducing Optional, it would be probably the best practice to handle nulls in chains of gets such as yours.

The Optional type will enable you to pipe multiple map operations (which contain get calls) in a row. Null checks are automatically handled under the hood.

For example, when the objects aren't initialized, no print() will be made and no Exceptions will be thrown. It all we be handled gently under the hood. When objects are initialized, a print will be made.

System.out.println("----- Not Initialized! -----");

Optional.ofNullable(new Outer())
        .map(out -> out.getNested())
        .map(nest -> nest.getInner())
        .map(in -> in.getFoo())
        .ifPresent(foo -> System.out.println("foo: " + foo)); //no print

System.out.println("----- Let's Initialize! -----");

Optional.ofNullable(new OuterInit())
        .map(out -> out.getNestedInit())
        .map(nest -> nest.getInnerInit())
        .map(in -> in.getFoo())
        .ifPresent(foo -> System.out.println("foo: " + foo)); //will print!

class Outer {
    Nested nested;
    Nested getNested() {
        return nested;
    }
}
class Nested {
    Inner inner;
    Inner getInner() {
        return inner;
    }
}
class Inner {
    String foo = "yeah!";
    String getFoo() {
        return foo;
    }
}

class OuterInit {
    NestedInit nested = new NestedInit();
    NestedInit getNestedInit() {
        return nested;
    }
}
class NestedInit {
    InnerInit inner = new InnerInit();
    InnerInit getInnerInit() {
        return inner;
    }
}
class InnerInit {
    String foo = "yeah!";
    String getFoo() {
        return foo;
    }
}

So, with your getters chain it will look like this:

Optional.ofNullable(house)
        .map(house -> house.getFloor(0))
        .map(floorZero -> floorZero.getWall(WEST))
        .map(wallWest -> wallWest.getDoor())
        .map(door -> wallWest.getDoor())

The return of it will be something like Optional<Door> which will allow you much safer work without worrying of null exceptions.

Solution 2:

In order to check a chain of gets for null you may need to call your code from a closure. The closure call code will look like this:

public static <T> T opt(Supplier<T> statement) {       
    try {
        return statement.get();
    } catch (NullPointerException exc) {
        return null;
    }   
}

And you call it using the following syntax:

Doorknob knob = opt(() -> house.getFloor(0).getWall(WEST).getDoor().getDoorknob());

This code is also type safe and in general works as intended:

  1. Returns an actual value of the specified type if all the objects in the chain are not null.
  2. Returns null if any of the objects in the chain are null.

You may place opt method into shared util class and use it everywhere in your application.

Solution 3:

The best way would be to avoid the chain. If you aren't familiar with the Law of Demeter (LoD), in my opinion you should. You've given a perfect example of a message chain that is overly intimate with classes that it has no business knowing anything about.

Law of Demeter: http://en.wikipedia.org/wiki/Law_of_Demeter

Solution 4:

You could of course simply wrap the whole expression up in a try-catch block, but that's a bad idea. Something cleaner is the Null object pattern. With that, if your house doesn't have floor 0, it just returns a Floor that acts like a regular Floor, but has no real content; Floors, when asked for Walls they don't have, return similar "Null" Walls, etc, down the line.