次のコマンドを実行したいとします。
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());
最良の方法は、チェーンを回避することです。デメテルの法則(LoD)に精通していない場合は、私の意見ではそうすべきです。あなたは、ビジネスが何も知らないクラスと過度に親密なメッセージチェーンの完璧な例を挙げました。
あなたができない場合は、選択した回答に記載されているように、デメテルの法則(LoD)に違反することを避け、 Java 8 導入 オプション 、あなたのようなgetのチェーンでnullを処理するのがおそらくベストプラクティスでしょう。
Optional
タイプを使用すると、複数のマップ操作(get呼び出しを含む)を連続してパイプ処理できます。ヌルチェックは、内部で自動的に処理されます。
たとえば、オブジェクトが初期化されていない場合、print()は作成されず、例外はスローされません。それはすべて私たちがボンネットの下で優しく取り扱われます。オブジェクトが初期化されると、印刷が行われます。
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;
}
}
したがって、ゲッターチェーンを使用すると、次のようになります。
Optional.ofNullable(house)
.map(house -> house.getFloor(0))
.map(floorZero -> floorZero.getWall(WEST))
.map(wallWest -> wallWest.getDoor())
.map(door -> wallWest.getDoor())
返されるのはOptional<Door>
のようなもので、null例外を心配することなくはるかに安全な作業を可能にします。
ゲットのチェーンをチェックするために ヌルクロージャからコードを呼び出す必要があるかもしれません。クロージャ呼び出しコードは次のようになります。
public static <T> T opt(Supplier<T> statement) {
try {
return statement.get();
} catch (NullPointerException exc) {
return null;
}
}
そして、次の構文を使用してそれを呼び出します。
Doorknob knob = opt(() -> house.getFloor(0).getWall(WEST).getDoor().getDoorknob());
このコードもタイプセーフであり、一般的に意図したとおりに機能します。
あなたは置くことができます opt メソッドを共有utilクラスに入れ、アプリケーションのあらゆる場所で使用します。
もちろん、式全体をtry-catchブロックにまとめることもできますが、それは悪い考えです。よりクリーンなものは Nullオブジェクトパターン です。これにより、家に0階がない場合、通常の階のように機能するが、実際のコンテンツがない階が返されます。床は、持っていない壁を求められたときに、同様の「ヌル」壁などを返します。
論理的にnull
にできないものはそうではないことを確認してください。たとえば、家には常に西の壁があります。状態でこのような例外を回避するために、予期する状態が存在するかどうかを確認するメソッドを使用できます。
if (wall.hasDoor()) {
wall.getDoor().etc();
}
これは本質的にヌルチェックですが、常にそうであるとは限りません。
重要なのは、null
がある場合に備えて何かをする必要があるということです。例-return
またはIllegalStateException
をスローします
そして、あなたがすべきではないこと-NullPointerException
を捕まえないでください。ランタイム例外はキャッチするためのものではありません-それらから回復できることは期待されていません。また、ロジックフローの例外に依存することもお勧めしません。実際に何かがnull
になることを期待しておらず、NullPointerException
をキャッチ(およびログ記録)するとします。その時点で多くのものがnull
になる可能性があるため、これはあまり有用な情報ではありません。
nullPointer try/catchをサプライヤーと実装すると、getのすべてのチェーンを送信できます
public static <T> T getValue(Supplier<T> getFunction, T defaultValue) {
try {
return getFunction.get();
} catch (NullPointerException ex) {
return defaultValue;
}
}
そしてそれをこのように呼びます。
ObjectHelper.getValue(() -> object1.getObject2().getObject3().getObject4()));
非常に古い質問ですが、それでも私の提案を追加しています:
HouseからDoorKnobを取得する代わりに、呼び出し元のコードからこのクラスにDoorKnobを提供するか、この目的専用の中央ルックアップ機能(DoorKnobサービスなど)を作成することをお勧めします。
これを容易にするcheckForNull
メソッドを作成することはできません(これは、Javaでメソッドの呼び出しと引数の評価がどのように機能するかではありません)。
チェーンされたステートメントを複数のステートメントに分割し、すべてのステップでチェックできます。ただし、おそらくより良い解決策は、これらのメソッドが最初にnull
を返さないようにすることです。 Null Object Pattern と呼ばれるものがあり、代わりに使用することをお勧めします。