タイトル: 単項演算子
SEOタイトル: Java 単項演算子完全ガイド (+/-/++/--/!/~)
| この記事の要点 |
|
単項演算子とは
単項演算子 (unary operator) は、オペランド (操作対象) が 1 つだけの演算子です。a + b のような 2 項演算子 (binary operator) と対比される概念で、Java では以下の 9 種類があります:
| 演算子 | 名称 | 例 | 意味 |
|---|---|---|---|
+ | 単項プラス | +x | 正の符号 (実質何もしない) |
- | 単項マイナス | -x | 符号反転 |
++ | インクリメント | ++x / x++ | 1 加算 |
-- | デクリメント | --x / x-- | 1 減算 |
! | 論理否定 | !flag | boolean を反転 |
~ | ビット反転 | ~x | 各ビットを反転 (NOT) |
(type) | キャスト | (int)d | 型変換 |
単項プラス / 単項マイナス (+x / -x)
int a = 10;
int b = +a; // 10 (実質的に意味なし、可読性のため明示する場合のみ)
int c = -a; // -10 (符号反転)
// 数値リテラルとの組み合わせ
int min = -2147483648; // Integer.MIN_VALUE
int max = -(-2147483648); // ★ オーバーフロー! Integer.MIN_VALUE のまま
// 単項マイナスは int に昇格する
byte x = 10;
byte y = -x; // ❌ コンパイルエラー: int から byte への暗黙変換不可
byte z = (byte)(-x); // ✅ キャスト必要
落とし穴: -Integer.MIN_VALUE は数学的には 2^31 ですが、int の上限を超えるためオーバーフローして Integer.MIN_VALUE のまま返ります。Math.abs にも同じ罠があります。
インクリメント / デクリメント (++ / --)
前置 (prefix) と 後置 (postfix) で評価タイミングが異なります。これが最頻出の混乱ポイントです:
int x = 5;
int a = ++x; // x を 6 にしてから a に代入 → a=6, x=6
int y = 5;
int b = y++; // b に 5 を代入してから y を 6 に → b=5, y=6
// 式の中で混在
int i = 0;
int[] arr = new int[3];
arr[i++] = 10; // arr[0]=10, i=1
arr[i++] = 20; // arr[1]=20, i=2
arr[++i] = 30; // i=3 → arr[3] ❌ ArrayIndexOutOfBoundsException
// for 文の典型
for (int k = 0; k < 10; k++) { // ★ 後置でも前置でも結果は同じ
System.out.println(k);
}
// → for の更新部は"値を使わない"ので前後置どちらでも OK
| 式 | 動作 | 返り値 |
|---|---|---|
++x | 先に +1 → 値を返す | 新しい値 |
x++ | 古い値を返す → +1 | 元の値 |
--x | 先に -1 → 値を返す | 新しい値 |
x-- | 古い値を返す → -1 | 元の値 |
論理否定 (!) とビット反転 (~)
名前は似ていますが、対象型と動作が全く異なります:
// ! : boolean 専用、真偽反転
boolean flag = true;
boolean inv = !flag; // false
if (!list.isEmpty()) { ... } // 空でないなら
// ~ : 整数型のビット反転 (1 の補数)
int n = 5; // 0000...0101
int r = ~n; // 1111...1010 = -6
// ビットマスクと組み合わせ
int FLAG_A = 0b001;
int FLAG_B = 0b010;
int all = FLAG_A | FLAG_B; // 0b011
int notA = all & ~FLAG_A; // 0b010 (FLAG_A を落とす)
// ❌ よくある間違い
int x = 5;
if (~x) { ... } // コンパイルエラー: ~x は int (boolean ではない)
if (!x) { ... } // コンパイルエラー: ! は boolean のみ
キャスト演算子 (type)
// プリミティブのキャスト
double d = 3.7;
int i = (int) d; // 3 (小数切り捨て、四捨五入ではない)
long l = 100_000_000_000L;
int n = (int) l; // オーバーフローして変な値
// 参照型のキャスト (ダウンキャスト)
Object obj = "hello";
String s = (String) obj; // OK
Object obj2 = Integer.valueOf(1);
String s2 = (String) obj2; // ❌ ClassCastException
// instanceof でガード
if (obj instanceof String s3) { // Java 16+ pattern
System.out.println(s3.length());
}
副作用と評価順序の罠
1 つの式で同じ変数を複数回 ++ すると、可読性が壊滅的に下がります:
// ❌ 何が起こるか即答できる人は少ない
int i = 1;
int r = i++ + ++i + i++;
// 1 + 3 + 3 = 7, 最終的に i=4
// ❌ Cでは未定義動作だが Java では&quot;左から右&quot;評価が保証されている
// JLS 15.7: オペランドは左から右に評価される
// ✅ 分割して書く
int r2 = i++;
r2 += ++i;
r2 += i++;
for ループでの使い分け
// for 文では前置 / 後置どちらでも同じ (更新部の値は捨てられる)
for (int i = 0; i < n; i++) { ... }
for (int i = 0; i < n; ++i) { ... }
// while でカウンタを使うケース
int i = 0;
while (i < arr.length) {
process(arr[i++]); // i 番目を処理 → i を進める
}
// reverse: 後置 -- で末尾から
int j = arr.length;
while (j-- > 0) { // j > 0 を判定 → j を 1 減らす
process(arr[j]);
}
他言語との比較
| 言語 | ++ / -- | 備考 |
|---|---|---|
| Java | あり (前置/後置) | JLS で左→右評価が保証 |
| JavaScript | あり | Java とほぼ同じ |
| C / C++ | あり | 同一式内の重複は未定義動作 |
| Python | 無し | x += 1 のみ。++x は "+ 単項プラス" ×2 で何もしない |
| Go | あり (文のみ) | x++ は式ではなく文。y = x++ はエラー |
| Rust | 無し | 意図的に省略 (副作用の混乱回避) |
FAQ
Q: ++x と x = x + 1 はどちらが速い?
A: 現代の JIT では完全に同じ機械語にコンパイルされます。可読性で選んでください。
Q: boolean b = !!x は何?
A: 2 重否定で値はそのままですが、Java では x が boolean でないとエラーになります。C/JS の 「真偽値化」テクニックは Java では使えません。
Q: 配列インデックスで arr[i++] はアリ?
A: 動きますが、ループ内で 1 つの式に複数の i++ が混ざるとバグの温床。可能なら for 文 + arr[i] が安全です。