java-avoid-null-check
JavaでのNullステートメントのチェックを避ける
[[java null]]
=== 1. 概要
通常、__ null __variables、参照、およびコレクションは、Javaコードで処理するのが困難です。 それらを特定するのは難しいだけでなく、対処するのも複雑です。
実際のところ、_null_の処理のミスはコンパイル時に識別できず、実行時に_NullPointerException_になります。
このチュートリアルでは、Javaで_null_をチェックする必要性と、コードで_null_チェックを回避するのに役立つさまざまな代替策を見ていきます。
[[NullPointer exception]]
=== 2. NullPointerExceptionとは?
https://docs.oracle.com/javase/8/docs/api/java/lang/NullPointerException.html[_NullPointerException_のJavadoc]によると、オブジェクトが存在する場合にアプリケーションが_null_を使用しようとするとスローされます次のような必須:
-
_null_オブジェクトのインスタンスメソッドの呼び出し
-
_null_オブジェクトのフィールドへのアクセスまたは変更
-
配列であるかのように_null_の長さを取得する
-
配列であるかのように_null_のスロットにアクセスまたは変更する
-
_Throwable_値であるかのように_null_を投げる
この例外を引き起こすJavaコードの例をいくつか簡単に見てみましょう。
public void doSomething() {
String result = doSomethingElse();
if (result.equalsIgnoreCase("Success"))
// success
}
}
private String doSomethingElse() {
return null;
}
ここでは、* null_参照のメソッド呼び出しを呼び出そうとしました。 これにより、_NullPointerException ._ *が発生します。
もう1つの一般的な例は、__ null __arrayにアクセスしようとした場合です。
public static void main(String[] args) {
findMax(null);
}
private static void findMax(int[] arr) {
int max = arr[0];
//check other elements in loop
}
これにより、6行目で_NullPointerException_が発生します。
したがって、上記の例からわかるように、__ null __objectのフィールド、メソッド、またはインデックスにアクセスすると、_NullPointerException_が発生します。
__NullPointerException __を回避する一般的な方法は、_null_を確認することです。
public void doSomething() {
String result = doSomethingElse();
if (result != null && result.equalsIgnoreCase("Success")) {
// success
}
else
// failure
}
private String doSomethingElse() {
return null;
}
現実の世界では、プログラマーはどのオブジェクトがヌルになる可能性があるかを識別するのが難しいと感じています。 __ **積極的に安全な戦略は、すべてのオブジェクトについて_null_をチェックすることです。 ただし、これにより冗長な__null __checksが大量に発生し、コードが読みにくくなります。**
次のいくつかのセクションでは、このような冗長性を回避するJavaの代替案をいくつか紹介します。
[[API contract]]
=== 3. APIコントラクトを介した_null_の処理
前のセクションで説明したように、__ null __objectsのメソッドまたは変数にアクセスすると、__ NullPointerExceptionが発生します。 __また、オブジェクトにアクセスする前にオブジェクトに__null __checkを設定すると、_NullPointerExceptionの可能性が排除されることも説明しました。
ただし、__null __valuesを処理できるAPIがよくあります。 例えば:
public void print(Object param) {
System.out.println("Printing " + param);
}
public Object process() throws Exception {
Object result = doSomething();
if (result == null) {
throw new Exception("Processing fail. Got a null response");
} else {
return result;
}
}
_print()_メソッド呼び出しは_“ null” _を出力しますが、例外はスローしません。 同様に、__process()___はその応答で__null __を決して返しません。 むしろ_Exception._をスローします
したがって、上記のAPIにアクセスするクライアントコードの場合、a __null __checkは必要ありません。
ただし、このようなAPIは、契約で明示的にする必要があります。 * APIがこのような契約を公開する一般的な場所はJavaDoc *です。
ただし、これは* APIコントラクトの明確な指示を与えないため、コンプライアンスを確保するためにクライアントコード開発者に依存しています*。
次のセクションでは、いくつかのIDEやその他の開発ツールがこれをどのように開発者に役立てるかを見ていきます。
[[API contract]]
=== 4. API契約の自動化
[[code analysis]]
==== 4.1. 静的コード分析の使用
link:/java-static-analysis-tools[Static code analysis]ツールは、コードの品質を大幅に改善するのに役立ちます。 また、このようないくつかのツールを使用すると、開発者は_null_契約を維持できます。 1つの例は、https://www.baeldung.com/intro-to-findbugs [FindBugs]です。
-
FindBugsは、 @ Nullable and @ NonNull annotationsを介してnull contractを管理するのに役立ちます。*これらの注釈は、任意のメソッド、フィールド、ローカル変数、またはパラメーターで使用できます。 これにより、注釈付きの型がnull _であるかどうかをクライアントコードに対して明示的にします。 例を見てみましょう。
public void accept(@Nonnull Object param) {
System.out.println(param.toString());
}
ここで、__ @ NonNull __は、引数を__nullにできないことを明確にします。 __ **クライアントコードが__nullの引数をチェックせずにこのメソッドを呼び出すと、__FindBugsはコンパイル時に警告を生成します。** _ _
[[IDE contracts]]
==== 4.2. IDEサポートの使用
開発者は通常、Javaコードを記述するためにIDEに依存しています。 また、スマートコード補完や変数が割り当てられていない可能性がある場合などの有用な警告などの機能は、確実に大幅に役立ちます。
一部のIDEでは、開発者がAPIコントラクトを管理できるため、静的コード分析ツールが不要になります。 * IntelliJ IDEAは__ @ NonNull __and __ @ Nullable __annotationsを提供します。* IntelliJでこれらの注釈のサポートを追加するには、次のMaven依存関係を追加する必要があります。
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>16.0.2</version>
</dependency>
これで、* IntelliJは、最後の例のように__null __checkが欠落している場合に警告を生成します。*
IntelliJは、複雑なAPIコントラクトを処理するための_https://www.jetbrains.com/help/idea/contract-annotations.html [Contract] _アノテーションも提供します。
5. アサーション
これまで、クライアントコードから__null __checksの必要性を取り除くことについてのみ話してきました。 ただし、実際のアプリケーションではほとんど適用されません。
ここで、__ null __parametersを受け入れられないか、クライアントが処理する必要があるa__null __responseを返すことができるAPIを使用していると仮定します*。 これにより、_null_値のパラメーターまたは応答を確認する必要があります。
ここでは、従来の_null_ check条件ステートメントの代わりにlink:/java-assert[Java Assertions]を使用できます。
public void accept(Object param){
assert param != null;
doSomething(param);
}
2行目では、_null_パラメーターを確認します。 **アサーションが有効になっている場合、** __ * AssertionError。* __が発生します
非_null_パラメータのような前提条件をアサートする良い方法ですが、このアプローチには2つの大きな問題があります*:
-
アサーションは通常、JVMで無効にされます
-
A false assertionは、チェックされていないエラーになります。
回復不能*したがって、プログラマが条件の確認にアサーションを使用することはお勧めしません。*次のセクションでは、__ null __validationsを処理する他の方法について説明します。
[[coding practice]]
=== 6. コーディングプラクティスによる_Null_チェックの回避
6.1. 前提条件
通常、早期に失敗するコードを作成することをお勧めします。 したがって、APIが_null_を許可されていない複数のパラメーターを受け入れる場合は、* APIの前提条件としてすべての_null_以外のパラメーターを確認することをお勧めします。*
たとえば、2つの方法を見てみましょう。1つは早く失敗する方法、もう1つは失敗しない方法です。
public void goodAccept(String one, String two, String three) {
if (one == null || two == null || three == null) {
throw new IllegalArgumentException();
}
process(one);
process(two);
process(three);
}
public void badAccept(String one, String two, String three) {
if (one == null) {
throw new IllegalArgumentException();
} else {
process(one);
}
if (two == null) {
throw new IllegalArgumentException();
} else {
process(two);
}
if (three == null) {
throw new IllegalArgumentException();
} else {
process(three);
}
}
明らかに、_badAccept()。よりも_goodAccept()_を優先する必要があります。
別の方法として、https://www.baeldung.com/guava-preconditions [Guava's Preconditions]を使用してAPIパラメーターを検証することもできます。
[[primitives wrappers]]
==== 6.2. ラッパークラスの代わりにプリミティブを使用する
__null __は__intのようなプリミティブには許容値ではないため、___のようなラッパーの対応物よりもprefer___weを優先すべきです。
2つの整数を合計するメソッドの2つの実装を検討してください。
public static int primitiveSum(int a, int b) {
return a + b;
}
public static Integer wrapperSum(Integer a, Integer b) {
return a + b;
}
それでは、クライアントコードでこれらのAPIを呼び出しましょう。
int sum = primitiveSum(null, 2);
-
null は_int ._ *の有効な値ではないため、これはコンパイル時エラーになります。
ラッパークラスでAPIを使用すると、_NullPointerException_が発生します。
assertThrows(NullPointerException.class, () -> wrapperSum(null, 2));
別のチュートリアルlink:/java-primitives-vs-objects[Java Primitives vs Objects]で説明したように、ラッパーでプリミティブを使用する他の要因もあります。
[[empty collections]]
==== 6.3. 空のコレクション
場合によっては、メソッドからの応答としてコレクションを返す必要があります。 そのようなメソッドの場合、常に_null _:*の代わりに空のコレクションを返すようにしてください。
public List<String> names() {
if (userExists()) {
return Stream.of(readName()).collect(Collectors.toList());
} else {
return Collections.emptyList();
}
}
したがって、このメソッドを呼び出すときにクライアントが_null_チェックを実行する必要性を回避しました。
7. _Objects _の使用
Java 7では、新しい__Objects __APIが導入されました。 このAPIには、多くの冗長コードを取り除く__static __utilityメソッドがいくつかあります。 そのようなメソッドの1つ、https://docs.oracle.com/javase/8/docs/api/java/util/Objects.html#requireNonNull-T- [_requireNonNull()_]を見てみましょう。
public void accept(Object param) {
Objects.requireNonNull(param);
// doSomething()
}
それでは、__ accept()__methodをテストしましょう。
assertThrows(NullPointerException.class, () -> accept(null));
したがって、__ null __が引数として渡されると、_accept()_は_NullPointerException._をスローします。
このクラスには、オブジェクトの_null ._ *をチェックする述語として使用できる* __ isNull()__and __nonNull()__methodsもあります。
[[java optional]]
=== 8. _Optional_の使用
Java 8では、新しい_https://www.baeldung.com/java-optional [Optional] _ APIが言語に導入されました。 これは、__ nullと比較して、オプション値を処理するためのより良いコントラクトを提供します。 __Optionalが__null __checksの必要性をどのように取り除くかを見てみましょう:
public Optional<Object> process(boolean processed) {
String response = doSomething(processed);
if (response == null) {
return Optional.empty();
}
return Optional.of(response);
}
private String doSomething(boolean processed) {
if (processed) {
return "passed";
} else {
return null;
}
}
上記の__Optional、___を返すことにより、* * _process()_メソッドは呼び出し側に対して、応答が空になる可能性があり、コンパイル時に処理する必要があることを明確にします。
これにより、クライアントコード内の__null __checksの必要性が著しくなくなります。 空の応答は、__ Optional __APIの宣言スタイルを使用して異なる方法で処理できます。
assertThrows(Exception.class, () -> process(false).orElseThrow(() -> new Exception()));
さらに、APIが空の応答を返すことができることをクライアントに示すために、API開発者とのより良い契約も提供します。
このAPIの呼び出し元でa __null __checkの必要性を排除しましたが、空の応答を返すためにそれを使用しました。 これを回避するために、* _Optional_は、指定された値で__OptionalN___を返す__ofNullable __methodを提供します。値がn_null_ *の場合は空になります。
public Optional<Object> process(boolean processed) {
String response = doSomething(processed);
return Optional.ofNullable(response);
}
[[java library]]
=== 9. 図書館
9.1. Lombokを使用する
link:/intro-to-project-lombok[Lombok]は、プロジェクトの定型コードの量を削減する優れたライブラリです。 いくつかの例を挙げると、ゲッター、セッター、および_toString()_など、Javaアプリケーションで私たちがよく書くコードの共通部分に代わる一連の注釈が付属しています。
もう1つのアノテーションは__ @ NonNullです。 __したがって、プロジェクトが既にLombokを使用して定型コードを排除している場合、* __ @ NonNull ___は__null __checks *の必要性を置き換えることができます。
いくつかの例を見る前に、Lombokのhttps://search.maven.org/search?q=g:org.projectlombok%20AND%20a:lombok&core=gav[Maven]の依存関係を追加しましょう。
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
</dependency>
これで、a ___ null __checkが必要な場合に__ @ NonNull __whereを使用できます。
public void accept(@NonNull Object param){
System.out.println(param);
}
したがって、__null __checkが必要となるオブジェクトに注釈を付けるだけで、Lombokはコンパイル済みクラスを生成します。
public void accept(@NonNull Object param) {
if (param == null) {
throw new NullPointerException("param");
} else {
System.out.println(param);
}
}
__param ___ is_null、_の場合、このメソッドは__NullPointerExceptionをスローします。 __ **メソッドはこれをコントラクトで明示的に指定する必要があり、クライアントコードは例外を処理する必要があります。**
[[string utils]]
==== 9.2. _StringUtils_の使用
通常、_String_検証には、_null_値に加えて空の値のチェックが含まれます。 したがって、一般的な検証ステートメントは次のようになります。
public void accept(String param){
if (null != param && !param.isEmpty())
System.out.println(param);
}
*多くの__String __typesを処理する必要がある場合、これはすぐに冗長になります。*これは、__StringUtils ___comesが便利な場所です。 この動作を確認する前に、https://search.maven.org/search?q = g:org.apache.commons%20AND%20a:commons-lang3&core = gav [commons-lang3]のMaven依存関係を追加しましょう。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
_StringUtils:_で上記のコードをリファクタリングしましょう
public void accept(String param) {
if (StringUtils.isNotEmpty(param))
System.out.println(param);
}
そこで、__null __orまたは空のチェックをa___static __utilityメソッド__isNotEmpty()に置き換えました。 __このAPIは、一般的な_String_関数を処理するための他のlink:/string-processing-commons-lang [強力なユーティリティメソッド]を提供します。
[[null handling]]
=== 10. 結論
この記事では、__NullPointerException __のさまざまな理由と、識別するのが難しい理由を調べました。 次に、パラメーター、戻り値の型、およびその他の変数を使用して_null_をチェックすることでコードの冗長性を回避するさまざまな方法を見ました。
すべての例は、https://github.com/eugenp/tutorials/tree/master/patterns/design-patterns-behavioral [GitHub]で入手できます。