检查“ get”调用是否为 null

假设我想执行以下命令:

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

要避免 NullPointerException,我必须执行以下操作:

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

有没有一种方法或者已经存在的 Utils 类可以更优雅地完成这个任务,比如说下面这样的事情?

checkForNull(house.getFloor(0).getWall(WEST).getDoor().getDoorknob());
39865 次浏览

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.

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

Make sure things that can't logically be null are not. For example - a house always has a West wall. In order to avoid such exceptions in state, you can have methods to check whether the state you expect is present:

if (wall.hasDoor()) {
wall.getDoor().etc();
}

This is essentially a null-check, but might not always be.

The point is that you should do something in case you have a null. For example - return or throw an IllegalStateException

And what you shouldn't do - don't catch NullPointerException. Runtime exceptions are not for catching - it is not expected that you can recover from them, nor it is a good practice to rely on exceptions for the logic flow. Imagine that you actually don't expect something to be null, and you catch (and log) a NullPointerException. This will not be very useful information, since many things can be null at that point.

There is no checkForNull method that you can write that will facilitate this (that's simply not how method invokation and argument evaluation works in Java).

You can break down the chained statements into multiple statements, checking at every step. However, perhaps a better solution is to not have these methods return null in the first place. There is something called the Null Object Pattern that you may want to use instead.

Related questions

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.

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.

Very old question, but still adding my suggestion:

I would suggest instead of getting the DoorKnob from deep within the House in one method call chain, you should try to let the DoorKnob be provided to this class from the calling code, or by creating a central lookup facility specifically for this purpose (e.g. a DoorKnob service)

Simplified example of design with loose coupling:

class Architect {


FloorContractor floorContractor;


void build(House house) {
for(Floor floor: house.getFloors()) {
floorContractor.build(floor);
}
}
}


class FloorContractor {


DoorMaker doorMaker;


void build(Floor floor) {
for(Wall wall: floor.getWalls()) {
if (wall.hasDoor()) {
doorMaker.build(wall.getDoor());
}
}
}
}


class DoorMaker {


Tool tool;


void build(Door door) {
tool.build(door.getFrame());
tool.build(door.getHinges());
tool.build(door.getDoorKnob());
}
}

implementing nullPointer try/catch with a Supplier you can send it all chain of get

public static <T> T getValue(Supplier<T> getFunction, T defaultValue) {
try {
return getFunction.get();
} catch (NullPointerException ex) {
return defaultValue;
}
}

and then call it in this way.

ObjectHelper.getValue(() -> object1.getObject2().getObject3().getObject4()));
// Example
LazyObject.from(curr).apply(A.class, A::getB).apply(B.class, B::getC).apply(C.class, C::getD).to(String.class);


// LazyObject.java
public class LazyObject {


private Object value;


private LazyObject(Object object) {
this.value = object;
}


public <F, T> LazyObject apply(Class<F> type, Function<F, T> func) {
Object v = value;
if (type.isInstance(v)) {
value = func.apply(type.cast(v));
} else {
value = null; // dead here
}
return this;
}


public <T> void accept(Class<T> type, Consumer<T> consumer) {
Object v = value;
if (type.isInstance(v)) {
consumer.accept(type.cast(v));
}
}


public <T> T to(Class<T> type) {
Object v = value;
if (type.isInstance(v)) {
return type.cast(v);
}
return null;
}


public static LazyObject from(Object object) {
return new LazyObject(object);
}


}

Better solution for me is to use java.util.Optional.map(..) to chain these checks : https://stackoverflow.com/a/67216752/1796826