Javaで論理演算子を使用する場合は主に|| や && を使用していると思いますが、| や &でも指定可能です。ということで「|| と |」、「&& と &」の違いについてメモしておきます。
「|| と |」の違い
- Javaサンプルソース
以下のソースでは「||」で判定している11行目では左辺の結果はtrueにより右辺の式は評価されずにnullポで落ちることはないですが、17行目の「|」では右辺の式も評価されてしまうのでnullポで落ちます。123456789101112131415161718192021222324package sample;import java.util.Objects;public class SampleClass2 {public static void main(String[] args) {String str1 = "abc";String str2 = null;if(Objects.nonNull(str1) || str2.equals("A")) {// 左辺の式がtrueにより右辺の式は評価されないのでNullPointerExceptionは発生しませんSystem.out.println("||演算子");}if(Objects.nonNull(str1) | str2.equals("A")) {// 左辺の式がtrueでも右辺の式も評価されるのでNullPointerExceptionがしますSystem.out.println("|演算子");}}} - 実行結果:コンソール
以下の様に17行目でNullPointerExceptionが発生しています。123||演算子Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.equals(Object)" because "str2" is nullat sample.SampleClass2.main(SampleClass2.java:17)
「&& と &」の違い
- Javaサンプルソース
以下のソースでは「&&」で判定している10行目では左辺の結果はfalseにより右辺の式は評価されずにnullポで落ちることはないですが、16行目の「&」では右辺の式も評価されてしまうのでnullポで落ちます。12345678910111213141516171819202122package sample;import java.util.Objects;public class SampleClass3 {public static void main(String[] args) {String str1 = null;if(Objects.nonNull(str1) && str1.equals("A")) {// 左辺の式がfalseなら右辺の式が評価されないのでNullPointerExceptionは発生しません} else {System.out.println("&&演算子");}if(Objects.nonNull(str1) & str1.equals("A")) {// 左辺の式がfalseでも右辺の式も評価されるのでNullPointerExceptionが発生しますSystem.out.println("&演算子");}}} - 実行結果:コンソール
以下の様に16行目でNullPointerExceptionが発生しています。123&&演算子Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.equals(Object)" because "str1" is nullat sample.SampleClass3.main(SampleClass3.java:16)
補足:評価のタイミングと副作用に関する注意点
論理演算子において「短絡(ショートサーキット)評価/非短絡評価」の違いは、単に右辺の評価が省略されるか否かという話に留まりません。実際の現場では、右辺式に 副作用(例えばメソッド呼び出し、変数更新、例外発生など) が含まれている場合、その違いがバグの原因となることがあります。
以下に、実務的に押さえておきたいポイントを補足します。
・副作用を伴う式を用いる場面
もし右辺に「メソッド呼び出し」や「変数のインクリメント/デクリメント」、「リスト・マップへの変更操作」などが含まれているとき、
-
||(または&&)の場合、左辺で結果が確定すれば右辺は評価されないため 副作用が起きない -
|(または&)の場合、左辺の結果にかかわらず右辺まで評価されるので 副作用が必ず起きる
例えば次のような疑似コードを考えます:
ここで someMethod() がログ出力や状態更新を含んでいたとすれば、|| では flag == false なので someMethod() が呼ばれますが、
とした場合は、flag の値に関わらず someMethod() が 必ず実行されます。
実務では「右辺の処理を必ず実行したいのか」「左辺が確定したら右辺を処理させたくないのか」を明確にしておくことが重要です。
・例外発生の観点からの注意
記事中の例で示されていた通り、nullを参照する可能性のある式を右辺に置いた場合、||/&&では評価を省略して例外を回避できるのに対し、|/&では評価されてしまい NullPointerException が発生することがあります。これは「短絡評価のメリット」の典型例です。
したがって、右辺に「安全ではない参照」や「例外を起こす可能性のある処理」を含む場合は、原則として || や && を使うべきです。
・パフォーマンスと可読性の観点
-
||/&&を用いた場合、場合によっては右辺を評価しないため、不要な計算やメソッド呼び出しを避けられ、軽微ながらパフォーマンス上有利です。 -
一方で、
|/&は右辺も必ず評価されるため、処理負荷が無駄に増える場合があります。 -
また、コードを読む人にとって「なぜ右辺を必ず評価しているのか」が明示されていないと、バグや意図しない副作用の温床となるため、可読性・保守性の観点でも慎重に選ぶべきです。
・使い分けのガイドライン
-
左辺のみで判断可能か? →
||/&&が適切 -
右辺の処理を必ず「実行させたい/副作用を発生させたい」か? →
|/&の検討が可能 -
右辺に例外の可能性があるか? →
||/&&を優先 -
コードレビュー時に意図が不明瞭ならば、コメントや明示的なメソッド呼び出しの分離が望ましい。
総括
||/&& と |/& の違いを「右辺が評価されるかどうか」という観点だけで捉えると、見落としが生じることがあります。副作用、例外、安全性、パフォーマンス、可読性といった複数の観点から 意図を明確に持って演算子を選択すること が、安定したコードを維持する上で非常に重要です。
ぜひ、コード上での演算子選定の際には「なぜこの演算子を使うのか」を明文化できるよう、意識してみてください。
