Webサービスは巨大なXMLを返すので、その深くネストされたフィールドにアクセスする必要があります。例えば:
return wsObject.getFoo().getBar().getBaz().getInt()
問題は、getFoo()
、getBar()
、getBaz()
がすべてnull
を返す可能性があることです。
ただし、すべての場合にnull
をチェックすると、コードは非常に冗長になり読みにくくなります。さらに、一部のフィールドのチェックを見逃す場合があります。
if (wsObject.getFoo() == null) return -1;
if (wsObject.getFoo().getBar() == null) return -1;
// maybe also do something with wsObject.getFoo().getBar()
if (wsObject.getFoo().getBar().getBaz() == null) return -1;
return wsObject.getFoo().getBar().getBaz().getInt();
書くことは許されますか
try {
return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
return -1;
}
それともアンチパターンと見なされますか?
NullPointerException
をキャッチすることは、ほとんどどこでも発生する可能性があるため、本当に問題のあることです。バグから1つを取得し、偶然にそれをキャッチし、すべてが正常であるかのように続行することは非常に簡単であるため、実際の問題を隠します。 対処するのは非常に難しいため、完全に避けるのが最善です。(たとえば、null Integer
の自動アンボックス化について考えてください。)
代わりに Optional
クラスを使用することをお勧めします。多くの場合、これは、存在する値または存在しない値で作業する場合の最良のアプローチです。
それを使用して、次のようなコードを書くことができます。
public Optional<Integer> m(Ws wsObject) {
return Optional.ofNullable(wsObject.getFoo()) // Here you get Optional.empty() if the Foo is null
.map(f -> f.getBar()) // Here you transform the optional or get empty if the Bar is null
.map(b -> b.getBaz())
.map(b -> b.getInt());
// Add this if you want to return an -1 int instead of an empty optional if any is null
// .orElse(-1);
// Or this if you want to throw an exception instead
// .orElseThrow(SomeApplicationException::new);
}
存在しない可能性のある値にOptional
の代わりにnull
sを使用すると、その事実が読者に非常にわかりやすく明確になり、型システムは誤ってそれを忘れないようにします。
map
や orElse
のような、より便利にそのような値を操作するためのメソッドにもアクセスできます。
しかし、中間メソッドがnullを返すことが有効な結果であるか、それがエラーの兆候であるかについても考えてください。常にエラーである場合は、特別な値を返すよりも、中間メソッド自体が例外をスローするよりも、おそらく例外をスローする方が良いでしょう。
一方、中間メソッドからの欠落値が有効である場合、それらについてもOptional
sに切り替えることができますか?
次に、次のように使用できます。
public Optional<Integer> mo(Ws wsObject) {
return wsObject.getFoo()
.flatMap(f -> f.getBar())
.flatMap(b -> b.getBaz())
.flatMap(b -> b.getInt());
}
Optional
を使用しないと考えることができる唯一の理由は、これがコードの本当にパフォーマンスに重要な部分にあり、ガベージコレクションのオーバーヘッドが問題になる場合です。これは、コードが実行されるたびにいくつかのOptional
オブジェクトが割り当てられ、VMmightがそれらを最適化できないためです。その場合、元のifテストの方が良いかもしれません。
Objects.requireNonNull(T obj, String message)
を検討することをお勧めします。次のように、例外ごとに詳細なメッセージを含むチェーンを構築できます。
requireNonNull(requireNonNull(requireNonNull(
wsObject, "wsObject is null")
.getFoo(), "getFoo() is null")
.getBar(), "getBar() is null");
-1
などの特別な戻り値を使用しないことをお勧めします。それはJavaスタイルではありません。 Javaは、C言語から来たこの昔ながらの方法を避けるために、例外のメカニズムを設計しました。
NullPointerException
を投げることも最適なオプションではありません。独自の例外を提供して(checkedにして、ユーザーによって処理されることを保証するか、uncheckedより簡単な方法で処理するか、使用しているXMLパーサーからの特定の例外を使用します。
ケースのように、クラス構造が実際に制御できないと仮定すると、パフォーマンスが主要な懸念事項でない限り、質問で提案されているようにNPEをキャッチすることは実際に合理的なソリューションであると思います。小さな改善の1つは、スロー/キャッチロジックをラップして混乱を避けることです。
static <T> T get(Supplier<T> supplier, T defaultValue) {
try {
return supplier.get();
} catch (NullPointerException e) {
return defaultValue;
}
}
これで、次のことが簡単にできます。
return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), -1);
コメントの Tom で既に指摘したように、
次のステートメントは、 デメテルの法則 に違反しています。
wsObject.getFoo().getBar().getBaz().getInt()
必要なのはint
で、Foo
から取得できます。 デメテルの法則は言う、見知らぬ人とは決して話さない。あなたの場合、Foo
とBar
の内部で実際の実装を隠すことができます。
これで、Foo
からint
を取得するメソッドをBaz
に作成できます。最終的に、Foo
はBar
になり、Bar
ではInt
に直接Baz
を公開せずにFoo
にアクセスできます。したがって、nullチェックはおそらく異なるクラスに分割され、必要な属性のみがクラス間で共有されます。
私の答えは@jankiとほぼ同じ行にありますが、コードスニペットを次のように少し変更したいと思います。
if (wsObject.getFoo() != null && wsObject.getFoo().getBar() != null && wsObject.getFoo().getBar().getBaz() != null)
return wsObject.getFoo().getBar().getBaz().getInt();
else
return something or throw exception;
そのオブジェクトがnullになる可能性がある場合は、wsObject
のnullチェックも追加できます。
読みやすくするために、次のような複数の変数を使用できます。
Foo theFoo;
Bar theBar;
Baz theBaz;
theFoo = wsObject.getFoo();
if ( theFoo == null ) {
// Exit.
}
theBar = theFoo.getBar();
if ( theBar == null ) {
// Exit.
}
theBaz = theBar.getBaz();
if ( theBaz == null ) {
// Exit.
}
return theBaz.getInt();
一部のメソッドは「null
」を返す場合がありますが、どのような状況でnull
を返すかについては述べていません。 NullPointerException
をキャッチすると言いますが、なぜキャッチするのかは言いません。この情報不足は、例外が何のためのものであり、なぜそれらが代替より優れているのかを明確に理解していないことを示唆しています。
アクションを実行することを意図しているクラスメソッドを考えてみてください。ただし、そのメソッドは制御できません(実際には -の場合)ため、アクションを実行できませんguaranteeall Javaのメソッド )。そのメソッドを呼び出して戻ります。そのメソッドを呼び出すコードは、成功したかどうかを知る必要があります。どうやって知ることができますか?成功または失敗の2つの可能性に対処するために、どのように構成できますか?
例外を使用して、事後条件として成功したメソッドを記述することができます。メソッドが戻る場合、成功しました。例外をスローした場合、失敗していました。これは明確にするための大きな勝利です。通常の成功事例を明確に処理するコードを記述し、すべてのエラー処理コードをcatch
句に移動できます。メソッドが失敗した方法または理由の詳細は呼び出し側にとって重要ではないことがよくあるため、同じcatch
句をいくつかのタイプの失敗の処理に使用できます。また、メソッドは例外をキャッチする必要がない場合がよくありますat allが、例外をits呼び出し元に伝搬できるようにするだけです。プログラムのバグによる例外は、後者のクラスにあります。バグがある場合、適切に反応できるメソッドはほとんどありません。
したがって、null
を返すメソッド。
null
値はコードのバグを示していますか?その場合は、例外をまったくキャッチしないでください。また、コードが自分自身を推測しようとしてはなりません。それが機能すると仮定して、明確で簡潔なものを書いてください。メソッド呼び出しのチェーンは明確かつ簡潔ですか?次に、それらを使用します。null
値は、プログラムへの無効な入力を示していますか?その場合、NullPointerException
は通常、バグを示すために予約されているため、スローする適切な例外ではありません。おそらく、IllegalArgumentException
( 未チェックの例外 が必要な場合)またはIOException
(チェック済みの例外が必要な場合)から派生したカスタム例外をスローする必要があります。無効な入力がある場合、プログラムは詳細な構文エラーメッセージを提供する必要がありますか?その場合、各メソッドでnull
戻り値をチェックしてから、適切な診断例外をスローすることしかできません。プログラムが詳細な診断を提供する必要がない場合、メソッド呼び出しを連結して、NullPointerException
をキャッチし、カスタム例外をスローするのが最も明確で簡潔です。答えの1つは、連鎖メソッド呼び出しが Demeterの法則 に違反しているため、悪いと主張しています。その主張は間違っています。
昨日からこの投稿をフォローしています。
私は、NPEをキャッチするのは悪いと言うコメントにコメント/投票してきました。これが私がそうしている理由です。
package com.todelete;
public class Test {
public static void main(String[] args) {
Address address = new Address();
address.setSomeCrap(null);
Person person = new Person();
person.setAddress(address);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
try {
System.out.println(person.getAddress().getSomeCrap().getCrap());
} catch (NullPointerException npe) {
}
}
long endTime = System.currentTimeMillis();
System.out.println((endTime - startTime) / 1000F);
long startTime1 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
if (person != null) {
Address address1 = person.getAddress();
if (address1 != null) {
SomeCrap someCrap2 = address1.getSomeCrap();
if (someCrap2 != null) {
System.out.println(someCrap2.getCrap());
}
}
}
}
long endTime1 = System.currentTimeMillis();
System.out.println((endTime1 - startTime1) / 1000F);
}
}
public class Person {
private Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
package com.todelete;
public class Address {
private SomeCrap someCrap;
public SomeCrap getSomeCrap() {
return someCrap;
}
public void setSomeCrap(SomeCrap someCrap) {
this.someCrap = someCrap;
}
}
package com.todelete;
public class SomeCrap {
private String crap;
public String getCrap() {
return crap;
}
public void setCrap(String crap) {
this.crap = crap;
}
}
出力
3.216
0.002
ここに明確な勝者がいます。 ifチェックは、例外をキャッチするよりもはるかに安価です。そのJava-8のやり方を見てきました。現在のアプリケーションの70%がまだJava-7で実行されていることを考えると、この答えを追加しています。
下線ミッションクリティカルなアプリケーションでは、NPEの処理にコストがかかります。
NullPointerException
は実行時例外であるため、一般的に言えばキャッチすることはお勧めできませんが、回避するために。
メソッドを呼び出す場所で例外をキャッチする必要があります(または、スタックを伝播します)。それにもかかわらず、あなたの場合、値-1でその結果で作業を続けることができ、nullである可能性のある「ピース」を使用していないため、伝播しないことが確実である場合、それは私にとって正しいようですそれを捕まえて
編集:
後の answer @xenterosに同意します。たとえば、InvalidXMLException
と呼ぶことができる-1を返す代わりに、独自の例外を起動する方が良いでしょう。
コードをリファクタリングしたくない場合にJava 8を使用できれば、メソッド参照を使用できます。
最初の簡単なデモ(静的内部クラスを言い訳する)
public class JavaApplication14
{
static class Baz
{
private final int _int;
public Baz(int value){ _int = value; }
public int getInt(){ return _int; }
}
static class Bar
{
private final Baz _baz;
public Bar(Baz baz){ _baz = baz; }
public Baz getBar(){ return _baz; }
}
static class Foo
{
private final Bar _bar;
public Foo(Bar bar){ _bar = bar; }
public Bar getBar(){ return _bar; }
}
static class WSObject
{
private final Foo _foo;
public WSObject(Foo foo){ _foo = foo; }
public Foo getFoo(){ return _foo; }
}
interface Getter<T, R>
{
R get(T value);
}
static class GetterResult<R>
{
public R result;
public int lastIndex;
}
/**
* @param args the command line arguments
*/
public static void main(String[] args)
{
WSObject wsObject = new WSObject(new Foo(new Bar(new Baz(241))));
WSObject wsObjectNull = new WSObject(new Foo(null));
GetterResult<Integer> intResult
= getterChain(wsObject, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt);
GetterResult<Integer> intResult2
= getterChain(wsObjectNull, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt);
System.out.println(intResult.result);
System.out.println(intResult.lastIndex);
System.out.println();
System.out.println(intResult2.result);
System.out.println(intResult2.lastIndex);
// TODO code application logic here
}
public static <R, V1, V2, V3, V4> GetterResult<R>
getterChain(V1 value, Getter<V1, V2> g1, Getter<V2, V3> g2, Getter<V3, V4> g3, Getter<V4, R> g4)
{
GetterResult result = new GetterResult<>();
Object tmp = value;
if (tmp == null)
return result;
tmp = g1.get((V1)tmp);
result.lastIndex++;
if (tmp == null)
return result;
tmp = g2.get((V2)tmp);
result.lastIndex++;
if (tmp == null)
return result;
tmp = g3.get((V3)tmp);
result.lastIndex++;
if (tmp == null)
return result;
tmp = g4.get((V4)tmp);
result.lastIndex++;
result.result = (R)tmp;
return result;
}
}
出力
241
4ヌル
2
インターフェイスGetter
は単なる機能的なインターフェイスであり、同等のものを使用できます。GetterResult
クラス、わかりやすくするためにアクセサーを削除し、ゲッターチェーンの結果(ある場合)、または最後に呼び出されたゲッターのインデックスを保持します。
メソッドgetterChain
は、自動(または必要に応じて手動)で生成できる単純な定型コードです。
繰り返しブロックが自明になるようにコードを構造化しました。
ゲッターの数ごとにgetterChain
のオーバーロードを1つ定義する必要があるため、これは完全なソリューションではありません。
代わりにコードをリファクタリングしますが、できない場合は、長いゲッターチェーンを使用して自分自身を見つけることができます。多くの場合、2〜10のゲッターを取るオーバーロードでクラスを構築することを検討できます。
他の人が言ったように、デメテルの法則を尊重することは間違いなく解決策の一部です。別の部分は、可能な限り、null
を返さないように、これらのチェーンされたメソッドを変更することです。代わりに、空のnull
、空のString
、または呼び出し元がCollection
で行うことを意味または実行するその他のダミーオブジェクトを返すことで、null
を返すことを回避できます。
NullPointerException
をキャッチしないでください。あなたはそれがどこから来たのかわかりません(あなたの場合はそうではないかもしれませんが、多分何か他のものがそれを投げた)そしてそれは遅いです。指定されたフィールドにアクセスしたいので、他のすべてのフィールドはnullでない必要があります。これは、すべてのフィールドをチェックする完全な正当な理由です。読みやすくするためのメソッドを作成する場合は、おそらく1つチェックします。他の人が指摘したように、すでに-1を返すことは非常に古いですが、理由があるかどうかはわかりません(たとえば、別のシステムと話す)。
public int callService() {
...
if(isValid(wsObject)){
return wsObject.getFoo().getBar().getBaz().getInt();
}
return -1;
}
public boolean isValid(WsObject wsObject) {
if(wsObject.getFoo() != null &&
wsObject.getFoo().getBar() != null &&
wsObject.getFoo().getBar().getBaz() != null) {
return true;
}
return false;
}
編集:WsObjectはおそらくデータ構造にすぎないため、デメテルの法則に反するかどうかは議論の余地があります( https://stackoverflow.com/a/26021695/152888 を確認してください)。
エラーの意味に焦点を当てた回答を追加したいと思います。ヌル例外自体は、完全なエラーを意味するものではありません。そのため、それらを直接扱うことは避けてください。
コードがうまくいかないケースが数千あります:データベースに接続できない、IO例外、ネットワークエラー...これらを1つずつ処理する場合(ここでのnullチェックのように)、面倒すぎる。
コード内:
wsObject.getFoo().getBar().getBaz().getInt();
どのフィールドがヌルであるかを知っていても、何が間違っているのかわかりません。たぶんBarはnullですが、それは期待されていますか?それともデータエラーですか?あなたのコードを読む人について考えてください
Xenterosの答えのように、私はカスタム未チェック例外を使用することを提案します。たとえば、この状況では:Fooはnull(有効なデータ)である可能性がありますが、BarとBazは決してnull(無効なデータ)であってはなりません
コードは書き直すことができます:
void myFunction()
{
try
{
if (wsObject.getFoo() == null)
{
throw new FooNotExistException();
}
return wsObject.getFoo().getBar().getBaz().getInt();
}
catch (Exception ex)
{
log.error(ex.Message, ex); // Write log to track whatever exception happening
throw new OperationFailedException("The requested operation failed")
}
}
void Main()
{
try
{
myFunction();
}
catch(FooNotExistException)
{
// Show error: "Your foo does not exist, please check"
}
catch(OperationFailedException)
{
// Show error: "Operation failed, please contact our support"
}
}
独自の例外を作成することを検討する価値があります。 MyOperationFailedExceptionと呼びましょう。代わりに値を返す代わりに投げることができます。結果は同じになります-関数を終了しますが、ハードコードされた値-1(Javaアンチパターン)は返されません。 Javaでは、例外を使用します。
try {
return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
throw new MyOperationFailedException();
}
編集:
コメントの議論によると、以前の考えに何かを追加させてください。このコードには2つの可能性があります。 1つはnullを受け入れること、もう1つはエラーであることです。
エラーで発生した場合、ブレークポイントが十分でないときにデバッグ目的で他の構造を使用してコードをデバッグできます。
許容範囲内であれば、このnullがどこに現れるかは気にしません。そうした場合、これらのリクエストをチェーン化しないでください。
効率が問題になる場合は、「キャッチ」オプションを検討する必要があります。 「catch」が伝播するため(「SCouto」で述べたように)使用できない場合は、ローカル変数を使用して、メソッドgetFoo()
、getBar()
、およびgetBaz()
への複数の呼び出しを避けます。
return wsObject.getFooBarBazInt();
デメテルの法則を適用することにより、
class WsObject
{
FooObject foo;
..
Integer getFooBarBazInt()
{
if(foo != null) return foo.getBarBazInt();
else return null;
}
}
class FooObject
{
BarObject bar;
..
Integer getBarBazInt()
{
if(bar != null) return bar.getBazInt();
else return null;
}
}
class BarObject
{
BazObject baz;
..
Integer getBazInt()
{
if(baz != null) return baz.getInt();
else return null;
}
}
class BazObject
{
Integer myInt;
..
Integer getInt()
{
return myInt;
}
}
あなたが持っている方法は長いですが、非常に読みやすいです。私があなたのコードベースに来た新しい開発者であれば、あなたが何をしていたかをかなり早く見ることができました。他の回答のほとんど(例外のキャッチを含む)は物事をより読みやすくしているようには見えず、私の意見では読みにくいものもあります。
生成されたソースを制御できない可能性が高く、ここでいくつかの深くネストされたフィールドに本当にアクセスする必要があると仮定すると、それぞれの深くネストされたアクセスをメソッドでラップすることをお勧めします。
private int getFooBarBazInt() {
if (wsObject.getFoo() == null) return -1;
if (wsObject.getFoo().getBar() == null) return -1;
if (wsObject.getFoo().getBar().getBaz() == null) return -1;
return wsObject.getFoo().getBar().getBaz().getInt();
}
これらのメソッドをたくさん書いている場合、またはこれらのパブリックな静的メソッドを作成したい場合は、別のオブジェクトモデルを作成し、好きなようにネストし、関心のあるフィールドのみで、Webから変換しますオブジェクトモデルをオブジェクトモデルにサービスします。
リモートWebサービスと通信する場合、「リモートドメイン」と「アプリケーションドメイン」を持ち、2つを切り替えるのが非常に一般的です。リモートドメインは多くの場合、Webプロトコルによって制限されます(たとえば、純粋なRESTfulサービスでヘルパーメソッドを前後に送信できず、複数のAPI呼び出しを回避するために深くネストされたオブジェクトモデルが一般的です)。あなたのクライアント。
例えば:
public static class MyFoo {
private int barBazInt;
public MyFoo(Foo foo) {
this.barBazInt = parseBarBazInt();
}
public int getBarBazInt() {
return barBazInt;
}
private int parseFooBarBazInt(Foo foo) {
if (foo() == null) return -1;
if (foo().getBar() == null) return -1;
if (foo().getBar().getBaz() == null) return -1;
return foo().getBar().getBaz().getInt();
}
}
他のすべてとは異なるように見える答えを与える。
NULL
sでif
を確認することをお勧めします。
理由:
プログラムがクラッシュする可能性は一度も残さないでください。 NullPointerはシステムによって生成されます。 システム生成例外の動作は予測できません。自分でプログラムを処理する方法がすでにある場合は、プログラムをSystemの手に委ねないでください。さらに、安全性を高めるために例外処理メカニズムを追加します。!!
コードを読みやすくするには、条件を確認するためにこれを試してください:
if (wsObject.getFoo() == null || wsObject.getFoo().getBar() == null || wsObject.getFoo().getBar().getBaz() == null)
return -1;
else
return wsObject.getFoo().getBar().getBaz().getInt();
編集:
ここで、いくつかの変数にこれらの値
wsObject.getFoo()
、wsObject.getFoo().getBar()
、wsObject.getFoo().getBar().getBaz()
を保存する必要があります。その関数の戻り値の型がわからないので、私はそれをしていません。
どんな提案でも大歓迎です。
オブジェクトのツリーをナビゲートするパスを定義できるSnag
というクラスを作成しました。その使用例を次に示します。
Snag<Car, String> ENGINE_NAME = Snag.createForAndReturn(Car.class, String.class).toGet("engine.name").andReturnNullIfMissing();
インスタンスENGINE_NAME
は、渡されたインスタンスでCar?.getEngine()?.getName()
を効果的に呼び出し、いずれかの参照がnull
を返した場合はnull
を返します。
final String name = ENGINE_NAME.get(firstCar);
Mavenには公開されていませんが、誰かがこれを便利だと思ったら ここ (もちろん保証なし!)
それは少し基本的ですが、仕事をするようです。明らかに、Javaのより新しいバージョンと、安全なナビゲーションまたはOptional
をサポートする他のJVM言語ではより古くなっています。