サーチ…


備考

nullは、型が参照型であるフィールドの初期化されていない値のデフォルト値です。

NullPointerException (またはNPE)は、 nullオブジェクト参照に対して不適切な操作を実行しようとするとスローされる例外です。そのような操作には、

  • nullターゲットオブジェクト上でインスタンスメソッドを呼び出す、
  • nullターゲット・オブジェクトのフィールドにアクセスし、
  • null配列オブジェクトを索引付けしようとするか、その長さにアクセスしようとしましたが、
  • synchronizedブロック内のnullオブジェクト参照をミューテックスとして使用して、
  • nullオブジェクト参照をキャストし、
  • nullオブジェクト参照をアンボックスする
  • nullオブジェクト参照をスローしnull

NPEの最も一般的な根本原因:

  • 参照型を持つフィールドを初期化するのを忘れると、
  • 参照型の配列の要素を初期化するのを忘れる
  • 特定の状況でnullを返すように指定された特定のAPIメソッドの結果をテストしません。

nullを返す一般的に使用されるメソッドの例には、

  • Map APIのget(key)メソッドは、マッピングを持たないキーで呼び出すとnullを返しnull
  • ClassLoaderおよびClass APIのgetResource(path)およびgetResourceAsStream(path)メソッドは、リソースが見つからない場合はnullを返しnull
  • ガベージコレクタが参照をクリアした場合、 Reference APIのget()メソッドはnullを返しnull
  • 存在しないリクエストパラメータ、セッションまたはセッション属性などを取得しようとすると、Java EEサーブレットAPIのさまざまなgetXxxxメソッドはnullを返しnull

nullを明示的にテストするか、「Yoda Notation」を使用するなど、望ましくないNPEを回避するための戦略がありますが、これらの戦略では、実際に修正する必要のある問題を隠すという望ましくない結果が生じることがあります。

落とし穴 - プリミティブラッパーを不必要に使用すると、NullPointerExceptionが発生する可能性があります。

新しいJavaのプログラマーは、プリミティブ型とラッパーを同じ意味で使用することがあります。これは問題を引き起こす可能性があります。この例を考えてみましょう。

public class MyRecord {
    public int a, b;
    public Integer c, d;
}

...
MyRecord record = new MyRecord();
record.a = 1;               // OK
record.b = record.b + 1;    // OK
record.c = 1;               // OK
record.d = record.d + 1;    // throws a NullPointerException

MyRecordクラス1では、デフォルトの初期化を使用してフィールドの値を初期化しています。したがって、レコードをnewすると、 aおよびbフィールドはゼロに設定され、 cおよびdフィールドはnullに設定されnull

デフォルトの初期化されたフィールドを使用しようとすると、 intフィールドは常に動作することがわかりますが、 Integerフィールドは動作しない場合もあります。具体的には、失敗した場合( d場合)は、右側の式がnull参照をアンボックスしようとし、それが原因でNullPointerExceptionがスローされます。

これを見るにはいくつかの方法があります:

  • フィールドcdがプリミティブラッパーである必要がある場合は、デフォルトの初期化に頼るべきではないか、またはnullテストする必要がありnull 。前者は、 null状態のフィールドに明確な意味がない限り 、正しいアプローチです。

  • フィールドがプリミティブラッパーである必要がない場合は、プリミティブラッパーにするのは間違いです。この問題に加えて、プリミティブラッパーはプリミティブ型に比べて余分のオーバーヘッドを持ちます。

ここでの教訓は、本当に必要な場合を除き、基本ラッパー型を使用しないことです。


1 - このクラスは良いコーディングの例ではありません。例えば、うまく設計されたクラスはパブリックフィールドを持たないでしょう。しかし、これはこの例のポイントではありません。

Pitfall - 空の配列やコレクションを表すためにnullを使う

いくつかのプログラマーは、空の配列やコレクションを表すためにnullを使用してスペースを節約することをお勧めします。少量のスペースを節約できるのは間違いありませんが、コードが複雑で壊れやすくなるということです。配列を合計する方法の2つのバージョンを比較する:

最初のバージョンは、メソッドを通常どおりにコーディングする方法です。

/**
 * Sum the values in an array of integers.
 * @arg values the array to be summed
 * @return the sum
 **/
public int sum(int[] values) {
    int sum = 0;
    for (int value : values) {
        sum += value;
    }
    return sum;
}

2番目のバージョンは、 nullを使用して空の配列を表す習慣がある場合に、メソッドをコーディングする必要がある方法です。

/**
 * Sum the values in an array of integers.
 * @arg values the array to be summed, or null.
 * @return the sum, or zero if the array is null.
 **/
public int sum(int[] values) {
    int sum = 0;
    if (values != null) {
        for (int value : values) {
            sum += value;
        }
    }
    return sum;
}

ご覧のとおり、コードはもう少し複雑です。これは、このようにnullを使用するという決定に直接起因するものです。

次に、 null可能性があるこの配列が多くの場所で使用されているかどうかを検討します。あなたがそれを使用する各場所で、 nullをテストする必要があるかどうかを検討する必要がありnull 。あなたがそこに存在する必要のあるnullを逃した場合、あなたはNullPointerExceptionを危険にさらします。したがって、このようにnullを使用すると、アプリケーションがより脆弱になります。プログラマのエラーの影響をより受けやすくなります。


ここでの教訓は、空の配列と空のリストを使用してそれが意味するときです。

int[] values = new int[0];                     // always empty
List<Integer> list = new ArrayList();          // initially empty
List<Integer> list = Collections.emptyList();  // always empty

スペースのオーバーヘッドは小さく、これは価値のあることであれば最小限に抑える他の方法があります。

落とし穴 - 予期せぬ空白を作る

StackOverflowでは、Answersに次のようなコードがよく表示されます。

public String joinStrings(String a, String b) {
    if (a == null) {
        a = "";
    }
    if (b == null) {
        b = "";
    }
    return a + ": " + b;
}

しばしば、これは、 NullPointerExceptionを回避するために、このようなnullをテストする "ベストプラクティス"であるアサーションを伴いnull

それはベストプラクティスですか?要するに:いいえ。

joinStringsこれを行うことをお勧めする前に、質問する必要があるいくつかの基本的な前提があります:

"a"または "b"がnullであるとはどういう意味ですか?

String値はゼロ以上の文字である可能性があります。したがって、すでに空の文字列を表す方法があります。 null""とは何か違う意味ですか?いいえの場合は、空の文字列を表す2つの方法があることが問題になります。

nullは初期化されていない変数から来たのですか?

nullは、初期化されていないフィールド、または初期化されていない配列要素から来る可能性がありnull 。値は設計によって、または偶然によって初期化されないことがあります。それが偶然だった場合、これはバグです。

ヌルは「知らない」か「欠損値」を表していますか?

ときにはnullが真の意味を持つこともありnull 。例えば、変数の実際の値が不明であるか、利用できないか、または「オプション」であるかどうか。 Java 8では、 Optionalクラスはそれを表現するより良い方法を提供します。

これがバグ(または設計ミス)であれば、「良い」ものにする必要がありますか?

コードの解釈の1つは、空の文字列をその場所に使用することによって予期せぬnull 「良くする」ことです。正しい戦略ですか? NullPointerException発生するようにして、例外をスタックのNullPointerExceptionキャッチしてバグとして記録する方が良いでしょうか?

「うまくいく」という問題は、問題を隠すか、診断するのが難しくなることです。

これはコード品質に効率的か良いか?

一貫して "make good"アプローチを使用すると、コードには多くの "守備的な"ヌルテストが含まれます。これは、読むのをより長くし、より困難にするでしょう。さらに、このテストと「うまくいく」のすべてが、アプリケーションのパフォーマンスに影響する可能性があります。

要約すれば

nullが意味のある値である場合、 null場合のテストが正しい方法です。結果として、 null値が意味を持つ場合、 null値を受け入れるか返すメソッドのjavadocでこれを明確に文書化する必要がありnull

それ以外の場合は、予期しないnullをプログラミングエラーとして扱い、開発者がコードに問題があることがわかるようにNullPointerException発生させることをお勧めします。

Pitfall - 例外をスローする代わりにnullを返す

Javaプログラマの中には、例外を投げたり伝播したりするという一般的な嫌悪感を持っているものがあります。これにより、次のようなコードが生成されます。

public Reader getReader(String pathname) {
    try {
        return new BufferedReader(FileReader(pathname));
    } catch (IOException ex) {
        System.out.println("Open failed: " + ex.getMessage());
        return null;
    }

}

では、それで何が問題なのですか?

問題は、 getReaderReaderオープンできなかったことを示すためにnullを特別な値として返すことです。戻り値は、使用する前にnullかどうかを調べる必要がありnull 。テストが省略された場合、結果はNullPointerExceptionます。

実際には3つの問題があります。

  1. あまりにも早くIOExceptionがキャッチされました。
  2. このコードの構造は、リソースを漏らすリスクがあることを意味します。
  3. 「実際の」 Readerが返されなかったため、 nullが返されました。

実際、例外をこのように早期に捕らえる必要があると仮定すると、 nullを返す代替手段がいくつかありました。

  1. NullReaderクラスを実装することは可能です。あたかもリーダーがすでに「ファイルの終わり」の位置にあるかのようにAPIの動作が振る舞うようなものです。
  2. Java 8では、 getReaderOptional<Reader>を返すように宣言することができます。

落とし穴 - I / Oストリームを閉じたときにI / Oストリームが初期化されていないかどうかをチェックしない

メモリリークを防ぐために、入力ストリームまたは出力ストリームを閉じることを忘れないでください。これは通常、 catch部分のないtry - catch - finally文で行います。

void writeNullBytesToAFile(int count, String filename) throws IOException {
    FileOutputStream out = null;
    try {
        out = new FileOutputStream(filename);
        for(; count > 0; count--)
            out.write(0);
    } finally {
        out.close();
    }
}

上記のコードは無害に見えるかもしれませんが、デバッグを不可能にする欠陥があります。 outが初期化outた行( out = new FileOutputStream(filename) )が例外をスローすると、 out.close()が実行さoutoutnullになり、 NullPointerExceptionます。

これを防ぐには、ストリームを閉じる前にストリームがnullでないことを確認するだけです。

void writeNullBytesToAFile(int count, String filename) throws IOException {
    FileOutputStream out = null;
    try {
        out = new FileOutputStream(filename);
        for(; count > 0; count--)
            out.write(0);
    } finally {
        if (out != null)
            out.close();
    }
}

より良いアプローチは、リソースを-with-withでtryことです。これは、 finallyブロックを必要とせずにNPEを投げるために0の確率でストリームを自動的に閉じます。

void writeNullBytesToAFile(int count, String filename) throws IOException {
    try (FileOutputStream out = new FileOutputStream(filename)) {
        for(; count > 0; count--)
            out.write(0);
    }
}

落とし穴 - NodaPointerExceptionを避けるために "Yoda記法"を使う

StackOverflowに投稿されたサンプルコードの多くには次のようなスニペットが含まれています:

if ("A".equals(someString)) {
    // do something
}

someStringnull場合、 NullPointerExceptionするのを「防止」または「回避」しnull 。さらに、

    "A".equals(someString)

よりも良い:

    someString != null && someString.equals("A")

(より簡潔で、場合によってはより効率的かもしれませんが、以下で議論するように、簡潔さは否定できます。)

しかし、実際の落とし穴は、習慣の問題としてNullPointerExceptionsを避けるために Yodaテスト使用しています。

"A".equals(someString)と書くとsomeStringnullになるケースが実際にはsomeStringようになりnull 。しかし、別の例( Pitfall - "予期しない "良い "良い"を作る)が説明するように、 "良い" null値を作ることはいろいろな理由で有害な可能性があります。

これは、ヨーダの条件が「ベストプラクティス」ではないことを意味します1nullが予想されない限り、ユニットテストの失敗(またはバグレポート)を得るためにNullPointerException発生するようにしてください。これにより、予期せぬ/望ましくないnull原因となったバグを見つけて修正することができます。

Yoda条件は、テストしているオブジェクトがnullを返すとして文書化されているAPIから来ているため、 null予想される場合にのみ使用する必要がありnull 。そして間違いなく、それが強調表示することができますので、テストを表現するあまり、かなりのいずれかの方法を使用するより良いかもしれないnullあなたのコードを検討している誰かにテストを。


1 - Wikipediaによると、 「最良のコーディング慣行は、ソフトウェア開発コミュニティが時間をかけて学んだ、ソフトウェアの品質を向上させるのに役立つ非公式のルールのセットです」 。 Yodaの表記を使ってもこれは成立しません。多くの場合、コードが悪化します。



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow