web-dev-qa-db-ja.com

Java:nullポインターを効率的にチェックする方法

メソッドのパラメーターにnull値が指定されているかどうかを確認するためのパターンがいくつかあります。

まず、古典的なもの。自作のコードでは一般的であり、理解するのは明らかです。

public void method1(String arg) {
  if (arg == null) {
    throw new NullPointerException("arg");
  }
}

次に、既存のフレームワークを使用できます。このコードは1行しか占めていないため、少し見栄えが良くなっています。欠点は、コンパイラによっては、別のメソッドを呼び出す可能性があることです。このメソッドはmightにより、コードの実行を少し遅くします。

public void method2(String arg) {
  Assert.notNull(arg, "arg");
}

3番目に、オブジェクトに影響を与えずにメソッドを呼び出そうとすることができます。これは最初は奇妙に見えるかもしれませんが、上記のバージョンよりもトークンが少なくなっています。

public void method3(String arg) {
  arg.getClass();
}

3番目のパターンが広く使用されているのを見たことがなく、自分で発明したかのように感じられます。私はその短さのためにそれを好きです、そしてコンパイラーはそれを完全に最適化するか、それを単一の機械命令に変換する良い機会があるので。また、コードを行番号情報を使用してコンパイルします。そのため、NullPointerExceptionがスローされた場合、1行に1つだけのチェックがあるため、正確な変数までトレースできます。

どちらのチェックがお好みですか。また、その理由は何ですか。

25
Roland Illig

アプローチ#3:arg.getClass();は賢いですが、このイディオムが広く見られない限り採用する場合は、数文字を保存するのではなく、より明確で詳細な方法を使用します。私は「一度だけ書いて、たくさん読んで」のようなプログラマーです。

他のアプローチは自己文書化です。何が起こったかを明確にするために使用できるログメッセージがあります。このログメッセージは、コードの読み取り時および実行時に使用されます。現状では、arg.getClass()は自己文書化されていません。少なくともコメントを使用して、コードのレビュー担当者に明確にすることができます。

_arg.getClass(); // null check
_

ただし、他のメソッドの場合のように、ランタイムに特定のメッセージを書き込む機会はまだありません。


アプローチ#1と#2(null-check + NPE/IAEとassert):私は試みますこのようなガイドラインに従うには:

http://data.opengeo.org/GEOT-290810-1755-708.pdf

  • assertを使用して、プライベートメソッドのパラメーターをチェックします
    _assert param > 0;_

  • nullチェック+ IllegalArgumentExceptionを使用して、パブリックメソッドのパラメーターをチェックします
    if (param == null) throw new IllegalArgumentException("param cannot be null");

  • 必要に応じてnullチェック+ NullPointerExceptionを使用します
    if (getChild() == null) throw new NullPointerException("node must have children");


[〜#〜]ただし[〜#〜]、これは質問に関するものであるため、潜在的なnullの問題が最も効率的に発生するため、nullを処理するための推奨方法は静的分析を使用することです。たとえば、型注釈(例:_@NonNull_)a laJSR-305それらをチェックするための私のお気に入りのツールは次のとおりです。

チェッカーフレームワーク:
Javaのカスタムプラガブルタイプ
https://checkerframework.org/manual/#checker-guarantees

そのプロジェクト(たとえば、パブリックAPIを備えたライブラリではない)で、チェッカーフレームワーク全体を使用できる場合:

  • 私はAPIで意図をより明確に文書化できます(たとえば、このパラメーターはnull(デフォルト)ではない可能性がありますが、これはnullである可能性があります(_@Nullable_;メソッドはnullを返す可能性があります)。この注釈は、宣言は、Javadocでさらに離れているのではなく、維持される可能性がはるかに高くなります。

  • 静的分析は、ランタイムチェックよりも効率的です

  • 静的分析は、実行時に発生する問題に依存するのではなく、潜在的なロジックの欠陥に事前にフラグを立てます(たとえば、nullである可能性のある変数をnull以外のパラメーターのみを受け入れるメソッドに渡そうとしました)。

他のもう1つのボーナスは、コメント(例: `/@ Nullable/)に注釈を付けることができるため、私のライブラリコードはタイプ注釈付きプロジェクトおよびタイプ注釈付きプロジェクト(私がこれらのいずれかを持っているわけではありません)。


リンクが再び停止した場合、ここにGeoTools開発者ガイドのセクションがあります:

http://data.opengeo.org/GEOT-290810-1755-708.pdf

5.1.7アサーション、IllegalArgumentException、NPEの使用

Java言語では数年間、assertキーワードが利用可能になりました。このキーワードを使用して、デバッグのみのチェックを実行できます。この機能にはいくつかの用途がありますが、一般的なのはプライベート(パブリックではない)メソッドのメソッドパラメータをチェックします。その他の用途は、事後条件と不変条件です。

参照:アサーションを使用したプログラミング

事前条件(プライベートメソッドの引数チェックなど)は、通常、アサーションの簡単なターゲットです。事後条件と不変条件は、それほど重要ではない条件が破られるリスクが高いため、場合によってはそれほど単純ではありませんが、より価値があります。

  • 例1:参照モジュールでのマップ投影の後、アサーションは逆マップ投影を実行し、元のポイントと結果をチェックします(事後条件) 。
  • 例2:DirectPosition.equals(Object)の実装では、結果がtrueの場合、アサーションはhashCode()がオブジェクトコントラクト。

Assertを使用してプライベートメソッドのパラメーターをチェックします

_private double scale( int scaleDenominator ){
 assert scaleDenominator > 0;
 return 1 / (double) scaleDenominator;
}
_

次のコマンドラインパラメータを使用してアサーションを有効にできます。

_Java -ea MyApp
_

以下のコマンドラインパラメータを使用して、GeoToolsアサーションのみをオンにすることができます。

_Java -ea:org.geotools MyApp
_

次に示すように、特定のパッケージのアサーションを無効にすることができます。

_Java -ea:org.geotools -da:org.geotools.referencing MyApp
_

IllegalArgumentExceptionsを使用してパブリックメソッドのパラメーターをチェックします

パブリックメソッドでのアサートの使用は固くお勧めできません。報告されている間違いはクライアントコードで行われたため-正直に言って、失敗した場合はIllegalArgumentExceptionを使用して前もって伝えます。

_public double toScale( int scaleDenominator ){
 if( scaleDenominator > 0 ){
 throw new IllegalArgumentException( "scaleDenominator must be greater than 0");
 }
 return 1 / (double) scaleDenominator;
}
_

必要に応じてNullPointerExceptionを使用します

可能であれば、独自のnullチェックを実行してください。 IllegalArgumentExceptionまたはNullPointerExceptionをスローして、問題の詳細についての情報を提供します。

_public double toScale( Integer scaleDenominator ){
 if( scaleDenominator == null ){
 throw new NullPointerException( "scaleDenominator must be provided");
 }
 if( scaleDenominator > 0 ){
 throw new IllegalArgumentException( "scaleDenominator must be greater than 0");
 }
 return 1 / (double) scaleDenominator;
}
_
23
Bert F

あなたはbiiiiiiiiiiiiiiitをあまりにも早く最適化していませんか?

私は最初のものを使います。明確で簡潔です。

私はめったにJavaを使用しませんが、Assertがデバッグビルドでのみ動作するようにする方法があると思いますので、それはノーノーです。

3番目は私にゾッとを与えます、そして、私がコードでそれを見たならば、私はすぐに暴力に訴えると思います。それが何をしているかは完全に不明確です。

12

Objectsユーティリティクラスを使用できます。

public void method1(String arg) {
  Objects.requireNonNull(arg);
}

参照 http://docs.Oracle.com/javase/7/docs/api/Java/util/Objects.html#requireNonNull%28T%29

6
räph

NullPointerExceptionをスローするべきではありません。 NullPointerExceptionが必要な場合は、値を確認しないでください。パラメータがnullであり、それを逆参照しようとすると、自動的にスローされます。

Apache commons lang ValidateクラスとStringUtilsクラスを調べてください。
Validate.notNull(variable)「変数」がnullの場合、IllegalArgumentExceptionをスローします。
Validate.notEmpty(variable)は、「変数」が空(nullまたはゼロ長)の場合、IllegalArgumentExceptionをスローします。
おそらくさらに良い:
String trimmedValue = StringUtils.trimToEmpty(variable)は、「trimmedValue」がnullになることが決してないことを保証します。 「variable」がnullの場合、「trimmedValue」は空の文字列(「」)になります。

5
DwB

最初の方法は、最も意図を伝えるため、私の好みです。多くの場合、プログラミングで使用できるショートカットがありますが、私の見解では、コードが短いほどコードが優れているとは限りません。

3
Bnjmn

私の意見では、3番目の方法には3つの問題があります。

  1. その意図は、何気ない読者には不明確です。
  2. 行番号情報があっても、行番号は変わります。実際の本番システムでは、100行目のSomeClassに問題があることを知っていても、必要なすべての情報が得られるわけではありません。また、問題のファイルのリビジョンを知っている必要があり、そのリビジョンにアクセスできる必要があります。全体として、メリットがほとんどないように見えるものに対して多くの手間がかかります。
  3. arg.getClassの呼び出しを最適化できると考える理由はまったく明らかではありません。これはネイティブメソッドです。 HotSpotがこの正確な不測の事態に対するメソッドの特定の知識を持つようにコード化されていない限り、呼び出されるCコードの潜在的な副作用について知ることができないため、おそらく呼び出しをそのままにします。

私の好みは、nullチェックが必要だと感じたときはいつでも#1を使用することです。エラーメッセージに変数名を含めると、正確に何が問題なのかをすばやく理解するのに役立ちます。

追伸ソースファイルのトークン数を最適化することは、非常に有用な基準だとは思いません。

3
NPE

x == nullは超高速で、CPUクロックのカップルになる可能性があります(成功する分岐予測を含む)。 AssertNotNullはインライン化されるため、違いはありません。

x.getClass()は、トラップを使用する場合でも、x == nullより高速であってはなりません。 (理由:xはいくつかのレジスターにあり、レジスターと即時値のチェックは高速です。ブランチも適切に予測されます)

結論:本当に奇妙なことをしない限り、JVMによって最適化されます。

2
bestsss

GetClass()ハックを回避することを好むという一般的なコンセンサスに同意しますが、OpenJDKバージョン1.8.0_121以降、javacはラムダ式を作成する前にgetClass()ハックを使用してnullチェックを挿入することに注意してください。たとえば、次のことを考慮してください。

public class NullCheck {
  public static void main(String[] args) {
    Object o = null;
    Runnable r = o::hashCode;
  }
}

これをjavacでコンパイルした後、javapを使用してjavap -c NullCheckを実行し、バイトコードを表示できます。出力は(一部)です。

Compiled from "NullCheck.Java"
public class NullCheck {
  public NullCheck();
    Code:
       0: aload_0
       1: invokespecial #1    // Method Java/lang/Object."<init>":()V
       4: return

  public static void main(Java.lang.String[]);
    Code:
       0: aconst_null
       1: astore_1
       2: aload_1
       3: dup
       4: invokevirtual #2    // Method Java/lang/Object.getClass:()Ljava/lang/Class;
       7: pop
       8: invokedynamic #3, 0 // InvokeDynamic #0:run:(Ljava/lang/Object;)Ljava/lang/Runnable;
      13: astore_2
      14: return
}

「行」3、4、および7に設定された命令は、基本的にo.getClass()を呼び出し、結果を破棄しています。 NullCheckを実行すると、4行目からNullPointerExceptionがスローされます。

これがJavaの人々が必要な最適化であると結論付けたものであるか、それとも単なる安価なハックであるかはわかりません。しかし、ジョンローズのコメントに基づいて https ://bugs.openjdk.Java.net/browse/JDK-8042127?focusedCommentId = 13612451&page = com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-13612451 、確かにそれが疑われる暗黙のnullチェックを生成するgetClass()ハックは、明示的な対応よりもわずかにパフォーマンスが向上する場合があります。つまり、注意ベンチマークは、かなりの違いがあることを示しました。

(興味深いことに、Java(ECJ)のEclipseコンパイラーはこのnotにはこのnullチェックを含めず、コンパイルされたNullCheckを実行しますECJはNPEをスローしません。)

1
Ian Robertson

最初のオプションは最も簡単で、最も明確です。

これはJavaでは一般的ではありませんが、CおよびC++では、if演算子の式に=演算子を含めることができ、そのためエラーが発生するため、次のように変数と定数の場所を切り替えることをお勧めします。

if (NULL == variable) {
   ...
}

の代わりに:

if (variable == NULL) {
   ...
}

タイプのエラーを防ぐ:

if (variable = NULL) { // Assignment!
   ...
}

変更すると、コンパイラーはそのようなエラーを検出します。

1
Marcelo

組み込みのJava assertメカニズムを使用します。

assert arg != null;

他のすべての方法に対するこれの利点は、オフにできることです。

0
biziclop

これには投票はありませんが、私は#2のわずかなバリエーションを使用します。

erStr += nullCheck (varName, String errMsg); // returns formatted error message

理論的根拠:(1)一連の引数をループ処理できる、(2)nullCheckメソッドがスーパークラスに隠れている、(3)ループの終わりに、

if (erStr.length() > 0)
    // Send out complete error message to client
else
    // do stuff with variables

スーパークラスメソッドでは、#3は見栄えが良いですが、例外をスローしません(要点は誰かが処理する必要があることです。サーブレットコンテナーとして、Tomcatはそれを無視するので、これも( ))よろしく、-MS.

0

私はメソッド4、5、または6を好みます。#4はパブリックAPIメソッドに適用され、5/6は内部メソッドに適用されますが、#6はパブリックメソッドに頻繁に適用されます。

/**
 * Method 4.
 * @param arg A String that should have some method called upon it. Will be ignored if
 * null, empty or whitespace only.
 */
public void method4(String arg) {
   // commons stringutils
   if (StringUtils.isNotBlank(arg) {
       arg.trim();
   }
}

/**
 * Method 5.
 * @param arg A String that should have some method called upon it. Shouldn't be null.
 */
public void method5(String arg) {
   // Let NPE sort 'em out.
   arg.trim();
}

/**
 * Method 6.
 * @param arg A String that should have some method called upon it. Shouldn't be null.
 */
public void method5(String arg) {
   // use asserts, expect asserts to be enabled during dev-time, so that developers
   // that refuse to read the documentations get slapped on the wrist for still passing
   // null. Assert is a no-op if the -ae param is not passed to the jvm, so 0 overhead.

   assert arg != null : "Arg cannot be null"; // insert insult here.
   arg.trim();
}

Nullを処理する最善の解決策は、nullを使用しないことです。 nullをnullガードで返す可能性があるサードパーティまたはライブラリのメソッドをラップし、値を意味のあるもの(空の文字列など)に置き換えますが、使用しても何もしません。特に渡されたオブジェクトがすぐに呼び出されないセッターメソッドでは、nullが本当に渡されない場合はNPEをスローします。

0
fwielstra

最初の方法。基礎となるJVMによって効率的に実装されない限り、2番目または3番目の方法を実行することはありません。それ以外の場合、これら2つは時期尚早の最適化の主な例にすぎません(3番目はパフォーマンスペナルティの可能性があるため、一般的なアクセスポイントでクラスメタデータを処理およびアクセスする必要はありません)。

NPEの問題は、それらがプログラミングの多くの側面を横断するものであるということです(そして私の側面は、AOPよりも深く、より深い何かを意味します)。これは言語設計の問題です(言語が悪いことを言っているのではありませんが、ヌルポインターまたは参照を許可する言語の基本的な欠点の1つです)。

したがって、最初の方法と同様に、明示的に対処するのが最善です。他のすべての方法は、操作のモデルを単純化しようとする(失敗した)試みであり、基礎となるプログラミングモデルに存在する避けられない複雑さです。

噛むことを避けられない弾丸です。そのまま、一般的なケースでは、明確に対処してください。

0
luis.espinal