1.

Java 単項演算子完全ガイド (+/-/++/--/!/~)

編集
この記事の要点
  • 単項演算子はオペランドが 1 つだけの演算子。Java では +x / -x / ++x / x++ / --x / x-- / !x / ~x / (type)x の 9 種類
  • 前置 ++x は"先にインクリメントしてから値を返す"、後置 x++ は"値を返してからインクリメント"
  • !x は論理否定 (boolean のみ)、~x はビット反転 (整数型)。両者は別物
  • 副作用に注意: a[i++] = b[i++] のような式は読みづらく、評価順序の罠あり
  • Python には ++ 演算子は存在しない (x += 1 のみ)。JavaScript は Java と同じ挙動

単項演算子とは

単項演算子 (unary operator) は、オペランド (操作対象) が 1 つだけの演算子です。a + b のような 2 項演算子 (binary operator) と対比される概念で、Java では以下の 9 種類があります:

演算子名称意味
+単項プラス+x正の符号 (実質何もしない)
-単項マイナス-x符号反転
++インクリメント++x / x++1 加算
--デクリメント--x / x--1 減算
!論理否定!flagboolean を反転
~ビット反転~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: ++xx = 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] が安全です。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 単項演算子
  2. 算術演算子
  3. 代入演算子
  4. 比較演算子
  5. 論理演算子

最近更新/作成されたページ