この関数をより多くに書き換える方法Java 8 with Optionals?またはそのままにしておくべきですか?
public void setMemory(ArrayList<Integer> memory) {
if (memory == null)
throw new IllegalArgumentException("ERROR: memory object can't be null.");
if (memory.contains(null))
throw new IllegalArgumentException("ERROR: memory object can't contain null value.");
this.memory = memory;
}
何が起こっているのかわかりにくくなる傾向があるので、私は通常Optional
を避けます。
ただし、最初に、元のコードでは、呼び出し元が、現在含まれているクラスの内部フィールドmemory
への参照を保持できるようにしたことを述べておきます。呼び出し元が悪意のないものであると信頼しているかもしれませんが、呼び出し元は引数として渡されたリストを誤って再利用する可能性があります。その場合、引数のチェックは細心の注意を払っていますが、memory
リストには結局nullが含まれる可能性があります。または、予期せず変更され、他の誤動作につながる可能性があります。
解決策は、引数リストの防御的なコピーを作成することです。これを行う簡単な方法は次のとおりです。
public void setMemory(ArrayList<Integer> memory) {
if (memory == null)
throw new IllegalArgumentException("memory is null");
List<Integer> temp = new ArrayList<>(memory);
if (temp.contains(null))
throw new IllegalArgumentException("memory contains null");
this.memory = temp;
}
コピーが作成され、チェックされる前にローカル変数temp
に保存されることに注意してください。リストにnullが含まれているかどうかをチェックする前に、フィールドに格納したくないのは明らかです。ただし、nullを含むかどうかのチェックは、引数リストではなく、コピーで行う必要があります。そうでない場合、呼び出し元は、チェック後、コピー前にリストを変更できます。 (はい、これは偏執的です。)
正確な例外メッセージを気にしない場合は、次のように短縮できます。
public void setMemory(ArrayList<Integer> memory) {
List<Integer> temp;
if (memory == null || ((temp = new ArrayList<>(memory)).contains(null)))
throw new IllegalArgumentException("memory is or contains null");
this.memory = temp;
}
これでcouldを使用してOptional
を使用するように書き換えます。
public void setMemory(ArrayList<Integer> memory) {
this.memory = Optional.ofNullable(memory)
.map(ArrayList::new)
.filter(list -> ! list.contains(null))
.orElseThrow(() -> new IllegalArgumentException("memory is or contains null"));
}
Optional
の通常の乱用と比較して:-)頻繁に見ますが、これはそれほど悪くはありません。ここでのチェーニングは、少し有利なローカル変数の作成を回避するのに役立ちます。特に前脳にOptional
がある場合、ロジックはかなり単純です。ただし、たとえば1か月後にこのコードを再検討することについては、少し心配です。おそらく、意図したとおりに機能することを確信する前に、しばらく目を細める必要があるでしょう。
最後に、いくつかの一般的なスタイルのコメント。
通常、(少なくともJDKでは)これらの場合はNullPointerException
を使用します。 OPが使用しているので、これらの例ではIllegalArgumentException
を使用しています。
引数のタイプとフィールドタイプには、List<Integer>
ではなくArrayList<Integer>
を使用することをお勧めします。これにより、適切な状況(たとえば、JDK 9のList.of
を使用)で変更不可能なリストを使用できるようになります。
パターンがありますcondition -> throw an exception
これはメソッドに移動できます:
private void checkOrElseThrow(boolean condition, Supplier<? extends RuntimeException> exceptionSupplier) {
if (condition) {
throw exceptionSupplier.get();
}
}
public void setMemory(List<Integer> memory) {
checkOrElseThrow(memory == null, () -> new IllegalArgumentException("message #1"));
checkOrElseThrow(memory.contains(null), () -> new IllegalArgumentException("message #2"));
this.memory = memory;
}
例外のタイプが変更されない場合は、例外のメッセージのみを渡すのが妥当です(指摘するために @ tobias_k をありがとう):
private void checkOrElseThrow(boolean condition, String exceptionMessage) {
if (condition) {
throw new IllegalArgumentException(exceptionMessage);
}
}
public void setMemory(List<Integer> memory) {
checkOrElseThrow(memory == null, "message #1");
checkOrElseThrow(memory.contains(null), "message #2");
this.memory = memory;
}
IllegalArgumentException
andに固執する場合は、クラスパスにグアバがあるので、これを使用できます。
Preconditions.checkArgument(memory != null,
"ERROR: memory object can't be null.");
Preconditions.checkArgument(!memory.contains(null),
"ERROR: memory object can't contain null value.");
ここでは実際にOptional
を使用することはできません。条件ごとに異なるエラーメッセージが必要だからです。
一方、単一のエラーメッセージが表示される場合は、次のようにできます。
this.memory = Optional.ofNullable(memory)
.filter(x -> !x.contains(null))
.orElseThrow(() -> new IllegalArgumentException(
"memory object is null or contains null values"));
最初のケースでは、Objects.requireNonNull()
を使用します。
Optional
は不正な値であるため、null
はここに行く方法ではないと思います。
まず、より一般的なリストタイプを入力パラメーターとして使用することをお勧めします。実装を次のように変更します。
public void setMemory(List<Integer> memory) {
//stuff
}
そして、他の人が述べたように、すべての「設定」操作に対してnull値をチェックすることは、少しやり過ぎです。
この「メモリリスト」がコードの一部からのものであり、guavaを使用できる場合、おそらくguavasの不変リストを使用します。このリストは、誰かが「null」をリストに追加しようとすると例外をスローします。
ImmutableList.of( //your Integers)
Guavaを使用できないが、そのアプローチを使用したくない場合は、常にこのnullチェックを行う独自のリスト実装を作成できます。
そして最後に、これがすべて可能でない場合は、コードをそのままにしておきます。非常に読みやすく、誰もがあなたのしていることを知っています。ここで他の回答を見るとわかるように、オプションを使用するとかなり混乱する可能性があります。
オプションは使用しないでください。ここではメリットがありません。
代わりに、ArrayListの代わりに、より適切なタイプを使用してください。コレクションに整数を格納すると、(アン)ボックス化のコストが発生し、nullが許可されていない場合は意味がありません。
必要なコレクションライブラリがほとんどないため、ニーズに適している場合があります。
これらのライブラリはすべて、リスト、マップ、およびその他のプリミティブ用のコンテナの特殊な実装を提供します。これらの実装は一般に、ArrayList<Integer>
を使用するものよりも大幅に高速です(ArrayListのすべての整数がグローバルInteger
インスタンスキャッシュに収まるほど小さい場合を除く)。
いい副作用として、プリミティブ整数の特殊なリストを使用すると、呼び出し側はデフォルトでnullを格納できません。
オプション付きのライナー:
public void setMemory(ArrayList<Integer> memory) {
this.memory = Optional.ofNullable(memory).map((a) -> Optional.ofNullable(a.contains(null) ? null : a).orElseThrow(() -> new IllegalArgumentException("ERROR: memory object can't contain null value."))).orElseThrow(() -> new IllegalArgumentException("ERROR: memory object can't be null."));
}
さらに別の回答を追加して申し訳ありませんが、質問へのコメントの読み取りに基づいて、メソッドの署名を変更するさらに良い方法があるかもしれません:ArrayList<Integer>
をIntStream
で置き換えます:
public void setMemory(@NonNull IntStream input) {
Objects.requireNonNull(input);
this.memory = ...; // collect the stream into the storage
}
プリミティブストリームでは、(アン)ボクシングのコストは発生しません。
この方法では、発信者が足元でリストのコンテンツを変更することを心配する必要がなく、私の other answer で説明されているように整数の適切なストレージを選択できます(またはストリームのコンテンツを解決することもできます)怠惰に!).