なぜObjects.requireNonNull()を使用する必要があるのですか?


213

Oracle JDKの多くのJava 8メソッドはを使用することに注意しましたObjects.requireNonNull()。これNullPointerExceptionは、指定されたオブジェクト(引数)がnull

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}

しかしNullPointerException、とにかくスローされますnullオブジェクトが逆参照。では、なぜこの余分なnullチェックを実行してスローする必要があるの NullPointerExceptionでしょうか。

1つの明白な答え(または利点)は、コードを読みやすくすることであり、私も同意します。Objects.requireNonNull()メソッドの最初に使用する他の理由を知りたいと思ってい ます。




1
引数チェックへのこのアプローチにより、単体テストを作成するときに不正行為を行うことができます。Springにもそのようなユーティリティがあります(docs.spring.io/spring/docs/current/javadoc-api/org/…を参照 )。「if」があり、高い単体テストカバレッジが必要な場合は、条件が満たされた場合と条件が満たされていない場合の両方のブランチをカバーする必要があります。を使用するObjects.requireNonNullと、コードに分岐がないため、単体テストを1回パスすると100%のカバレッジが得られます:-)
xorcus

回答:


214

そうすることで物事を明確にすることができるからです。お気に入り:

public class Foo {
  private final Bar bar;

  public Foo(Bar bar) {
    Objects.requireNonNull(bar, "bar must not be null");
    this.bar = bar;
  }

またはより短い:

  this.bar = Objects.requireNonNull(bar, "bar must not be null");

今あなたは知っています:

  • Fooオブジェクトが正常に作成されたときnew()
  • その後、そのbarフィールドはnullでないことが保証されます。

それと比較してください。今日Fooオブジェクトを作成し、明日はそのフィールドを使用してスローするメソッドを呼び出します。ほとんどの場合、その参照が昨日コンストラクタに渡されたときにnullであった理由が明日わかりません!

つまり、このメソッドを明示的に使用して着信参照をチェックすることで、例外がスローされる時点を制御できます。そして、ほとんどの場合、あなたはできるだけ早く失敗したいと思っています

主な利点は次のとおりです。

  • 前述のように、制御された動作
  • より簡単なデバッグ-オブジェクト作成のコンテキストでスローするため。ログ/トレースが何が問題だったかを通知する可能性がある特定の時点で!
  • 上記のように、このアイデアの真の力はファイナルフィールドと関連して展開します。今ので、他のコードあなたのクラスでは、安全にそれを想定することができbar、したがって、あなたが任意の必要はありません- nullでなく、if (bar == null)他の場所でチェックを!

23
次のように記述することで、コードをよりコンパクトにすることができますthis.bar = Objects.requireNonNull(bar, "bar must not be null");
Kirill Rakhman '15

3
@KirillRakhman GhostCatについて知らないクールなことを教える-> GhostCatの賛成抽選でチケットを獲得。
GhostCat 2017

1
ここでのコメント#1はの実際の利点だと思いますObjects.requireNonNull(bar, "bar must not be null");。これをありがとう。
Kawu 2018

1
どちらが最初で、なぜ「言語」の人々はいくつかの図書館の作者に従うべきなのでしょうか。なぜguavaはJava langの振る舞いに従わないのですか?
GhostCat

1
this.bar = Objects.requireNonNull(bar, "bar must not be null");二つ以上の変数が同じ方法で設定されている場合はコンストラクタで[OK]をクリックし、他の方法で潜在的に危険です。例:talkwards.com/2018/11/03/...
Erkの

99

フェイルファスト

コードはできるだけ早くクラッシュするはずです。作業の半分を実行してからnullを間接参照してクラッシュしないようにし、一部の作業を半分にしてシステムを無効な状態にする必要があります。

これは一般に「フェイルアーリー」または「フェイルファスト」と呼ばれます。


40

ただし、nullオブジェクトが逆参照されると、NullPointerExceptionがスローされます。では、なぜこの余分なnullチェックを実行してNullPointerExceptionをスローする必要があるのでしょうか。

これは、問題を即座確実に検出することを意味します。

考慮してください:

  • 参照は、コードがすでにいくつかの副作用を実行した後、メソッドの後半まで使用できません。
  • このメソッドでは、参照が逆参照されていない可能性があります
    • 完全に異なるコードに渡される可能性があります(つまり、原因とエラーはコード空間で大きく離れています)
    • それはずっと後で使用される可能性があります(つまり、原因とエラーは時間的に離れています)
  • null参照有効な場所で使用される可能性がありますが、意図しない影響があります

.NETは、NullReferenceException(「null値を逆参照しました」)をArgumentNullException(「nullを引数として渡すべきではありませんでした-そして、それはこのパラメーターのためでした。)Javaが同じことを望んでいますただNullPointerException、それはまだだずっとエラーがそれを検出することができる最も早い時点でスローされた場合の修正コードに容易になります。


12

requireNonNull()メソッドの最初のステートメントとして使用すると、例外の原因をすぐに特定できます。
スタックトレースは、呼び出し元が要件/契約を尊重しなかったため、メソッドのエントリと同時に例外がスローされたことを明確に示しています 渡すnull別のメソッドにオブジェクトはあり確かに一度の例外を引き起こすが、問題の原因は、例外が上の特定の呼び出しでスローされるように理解することは、より複雑かもしれnull更に多くかもしれオブジェクト。


ここでは、一般的に高速で失敗優先する必要があり、より具体使っ理由を示しているコンクリートと実際の例であるObject.requireNonNull()かどうかになるように設計パラメータにはヌルチェックを実行する方法はnull

に含まれる単語を表すDictionarya LookupServiceとa を構成するクラスを想定します。これらのフィールドはそうではないように設計されており、これらの1つが コンストラクターに渡され ます。 ListStringnullDictionary

ここで、メソッドエントリ(ここではコンストラクタです)にチェックを付けDictionaryない「悪い」実装を想定しますnull

public class Dictionary {

    private final List<String> words;
    private final LookupService lookupService;

    public Dictionary(List<String> words) {
        this.words = this.words;
        this.lookupService = new LookupService(words);
    }

    public boolean isFirstElement(String userData) {
        return lookupService.isFirstElement(userData);
    }        
}


public class LookupService {

    List<String> words;

    public LookupService(List<String> words) {
        this.words = words;
    }

    public boolean isFirstElement(String userData) {
        return words.get(0).contains(userData);
    }
}

次に、パラメーターの参照を使用Dictionaryしてコンストラクターを呼び出します。nullwords

Dictionary dictionary = new Dictionary(null); 

// exception thrown lately : only in the next statement
boolean isFirstElement = dictionary.isFirstElement("anyThing");

JVMは次のステートメントでNPEをスローします。

return words.get(0).contains(userData); 
スレッド「メイン」の例外java.lang.NullPointerException
    LookupService.isFirstElement(LookupService.java:5)で
    Dictionary.isFirstElement(Dictionary.java:15)で
    Dictionary.main(Dictionary.java:22)で

例外はLookupServiceクラスでトリガーされますが、その発生元はかなり早いです(Dictionaryコンストラクタ)。これにより、全体的な問題分析があまり明白でなくなります。
ですかwords null?ですかwords.get(0) null?両方とも ?なぜ一方、他方、あるいは両方がnullそうなのでしょうか。それはコーディングエラーですか?Dictionary(コンストラクタ?呼び出されたメソッド?)のですか?それはコーディングエラーLookupServiceですか?(コンストラクタ?呼び出されたメソッド?)?
最後に、エラーの原因を見つけるためにさらにコードを検査する必要があります。さらに複雑なクラスでは、デバッガを使用して、何が起こったのかをより簡単に理解できます。
しかし、なぜ単純なもの(nullチェックの欠如)が複雑な問題になるのでしょうか。 想像してみろ
それは、特定のコンポーネントで特定された初期のバグ/欠如を下位コンポーネントのリークで許可したためです。
LookupServiceがローカルサービスではなく、リモートサービスまたはデバッグ情報の少ないサードパーティのライブラリであると想像してください。または、2層ではなく4層または5層のオブジェクト呼び出しnullが検出される前に想像してみてください。問題はさらに分析が複雑になります。

したがって、支持する方法は次のとおりです。

public Dictionary(List<String> words) {
    this.words = Objects.requireNonNull(words);
    this.lookupService = new LookupService(words);
}

このようにして、頭痛の種はありません。これを受け取るとすぐに例外がスローされます。

// exception thrown early : in the constructor 
Dictionary dictionary = new Dictionary(null);

// we never arrive here
boolean isFirstElement = dictionary.isFirstElement("anyThing");
スレッド「メイン」の例外java.lang.NullPointerException
    java.util.Objects.requireNonNull(Objects.java:203)で
    com.Dictionary。(Dictionary.java:15)で
    com.Dictionary.main(Dictionary.java:24)で

ここではコンストラクターで問題を説明しましたが、メソッドの呼び出しで同じnull以外のチェック制約を設定できることに注意してください。


うわー!素晴らしい説明。
Jonathas Nascimento

2

余談ですが、これObject#requireNotNullは以前はフェイルファストでしたが、一部のjreクラス自体の内部ではjava-9の前にわずかに異なって実装されていました。ケースを考えてみましょう:

 Consumer<String> consumer = System.out::println;

java-8では、これは(関連する部分のみ)としてコンパイルされます。

getstatic Field java/lang/System.out
invokevirtual java/lang/Object.getClass

基本的に次のような操作:yourReference.getClass-yourRefercenceが失敗すると失敗しnullます。

同じコードが次のようにコンパイルされるjdk-9での変更

getstatic Field java/lang/System.out
invokestatic java/util/Objects.requireNonNull

または基本的に Objects.requireNotNull (yourReference)


1

基本的な使い方は、チェックしてNullPointerExceptionすぐに投げることです。

同じ要件を満たすためのより良い代替手段(ショートカット)は、lombokによる@NonNullアノテーションです。


1
アノテーションは、Objectsメソッドの代わりではなく、一緒に機能します。@ NonNull x = mightBeNullと言うことはできず、@ NonNull x = Objects.requireNonNull(mightBeNull、 "inconceivable!");と言うことができます。
ビルK

@BillK申し訳ありません、私はuを取得しません
Supun Wijerathne

NonnullアノテーションはrequireNonNullで機能すると言いましたが、これは代替手段ではありませんが、非常にうまく連携しています。
ビルK

フェイルファストの性質を実装するために、それは代替の権利ですか?ええ、私はそれが割り当ての代替ではないことに同意します。
Supun Wijerathne

requireNonNull()を@ Nullableから@ NonNullへの「変換」と呼んでいると思います。アノテーションを使用しない場合、メソッドはそれほど興味深いものではありません(保護するコードのようにNPEをスローするだけなので)、意図はかなり明確に示されます。
ビルK

1

null後であるオブジェクトのメンバーにアクセスすると、nullポインター例外がスローされます。Objects.requireNonNull()すぐに値をチェックし、先に進むことなく即座に例外をスローします。


0

コピーコンストラクターや、入力パラメーターがオブジェクトであるDIなどの他のいくつかのケースで使用する必要があると思います。パラメーターがnullかどうかを確認する必要があります。このような状況では、この静的メソッドを便利に使用できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.