- JAVA
Java演算子一覧と優先順位表|三項演算子・インクリメントの注意点
Javaの演算子とは、+や==や&&のように、値を計算・比較・代入するための記号です。複数の演算子が1つの式に並んだときは「優先順位」が高いものから、同じ優先順位なら「結合規則」(左から/右から)に従って計算されます。 たとえば2 + 3 * 4は*が先に計算されて14になります。
この記事では、Javaの演算子を優先順位・結合規則つきの1枚の表にまとめ、掲載したすべてのコードをOpenJDK 21.0.10で実行して出力を載せています。 i++と++iの差、整数の割り算と負数の%、&&と&の短絡評価、三項演算子でIntegerとDoubleが混ざったときの型昇格、byte b += 1の暗黙キャスト、オーバーフロー、1 + 2 + "a"の評価順まで、結果が直感とずれやすい箇所を実行結果で確認できます。
Java演算子の優先順位早見表
上の行ほど優先順位が高く、先に計算されます。 順位はOracleのJava Tutorials「Operators」の表に沿っています(キャスト・new・ラムダの->は、Java言語仕様の文法に基づいて補いました)。
| 順位 | 種類 | 演算子 | 結合規則 | 例 |
|---|---|---|---|---|
| 1 | 後置 | expr++ expr-- |
左から | i++ |
| 2 | 単項 | ++expr --expr +expr -expr ~ ! (型) |
右から | -x、!ok、(int) d |
| 3 | 乗除 | * / % |
左から | a * b % c |
| 4 | 加減・文字列連結 | + - |
左から | a + b - c |
| 5 | シフト | << >> >>> |
左から | x << 2 |
| 6 | 関係 | < > <= >= instanceof |
左から | a < b、o instanceof String |
| 7 | 等価 | == != |
左から | a == b |
| 8 | ビットAND(論理AND) | & |
左から | flags & MASK |
| 9 | ビットXOR | ^ |
左から | a ^ b |
| 10 | ビットOR(論理OR) | | |
左から | a | b |
| 11 | 条件AND(短絡) | && |
左から | a != null && a.isEmpty() |
| 12 | 条件OR(短絡) | || |
左から | a || b |
| 13 | 三項(条件) | ? : |
右から | x > 0 ? "正" : "0以下" |
| 14 | 代入・複合代入 | = += -= *= /= %= &= ^= |= <<= >>= >>>= |
右から | x = y = 0、sum += n |
| 15 | ラムダ | -> |
右から | (a, b) -> a + b |
覚え方は「単項 → 掛け算・割り算 → 足し算・引き算 → シフト → 比較 → == → ビット → && → || → 三項 → 代入」です。迷ったときはかっこ()を付ければ、その中が先に計算されます。
結合規則とは
結合規則は、同じ優先順位の演算子が並んだときに、左右どちらから組にするかの決まりです。
| 結合規則 | 対象 | 例 | 解釈 |
|---|---|---|---|
| 左から右(左結合) | 代入以外の二項演算子 | 10 - 4 - 3 |
(10 - 4) - 3 = 3 |
| 右から左(右結合) | 代入・複合代入 | x = y = z = 7 |
x = (y = (z = 7)) |
| 右から左(右結合) | 三項演算子 | a ? 1 : b ? 2 : 3 |
a ? 1 : (b ? 2 : 3) |
| 右から左(右結合) | 単項演算子 | - -x、!!ok |
内側(右)から適用 |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java演算子一覧と優先順位表|三項演算子・インクリメントの注意点」
動作確認した環境
| 項目 | 内容 |
|---|---|
| JDK | OpenJDK 21.0.10(LTS) |
| 実行方法 | java ファイル名.java(単一ファイル実行) |
| 文字コード | ソースはUTF-8。出力の日本語が「?」になる環境ではjava -Dstdout.encoding=UTF-8 ファイル名.java |
以降のコードは、クラス名と同じ名前のファイルに保存すれば、そのまま実行できます。
種類別の演算子一覧
算術演算子
| 演算子 | 意味 | 例 | 結果 |
|---|---|---|---|
+ |
足し算(文字列なら連結) | 7 + 2 |
9 |
- |
引き算 | 7 - 2 |
5 |
* |
掛け算 | 7 * 2 |
14 |
/ |
割り算(整数どうしは小数を切り捨て) | 7 / 2 |
3 |
% |
余り(剰余) | 7 % 2 |
1 |
++ |
1増やす(インクリメント) | i++、++i |
— |
-- |
1減らす(デクリメント) | i--、--i |
— |
代入・複合代入演算子
| 演算子 | 同じ意味の書き方 | 例(int x = 10から) |
結果 |
|---|---|---|---|
= |
代入 | x = 3 |
3 |
+= |
x = (型)(x + 値) |
x += 3 |
13 |
-= |
x = (型)(x - 値) |
x -= 3 |
7 |
*= |
x = (型)(x * 値) |
x *= 3 |
30 |
/= |
x = (型)(x / 値) |
x /= 3 |
3 |
%= |
x = (型)(x % 値) |
x %= 3 |
1 |
&= ^= |= |
ビット演算して代入 | x &= 6 |
2 |
<<= >>= >>>= |
シフトして代入 | x <<= 2 |
40 |
比較(関係・等価)演算子
| 演算子 | 意味 | 例 | 結果 |
|---|---|---|---|
== |
等しい | 3 == 3 |
true |
!= |
等しくない | 3 != 3 |
false |
< > |
より小さい・より大きい | 3 < 5 |
true |
<= >= |
以下・以上 | 5 >= 5 |
true |
instanceof |
その型のインスタンスか | "a" instanceof String |
true |
論理演算子
| 演算子 | 意味 | 右側の評価 | 例 |
|---|---|---|---|
&& |
かつ(AND) | 左がfalseなら評価しない(短絡) |
a > 0 && b > 0 |
|| |
または(OR) | 左がtrueなら評価しない(短絡) |
a > 0 || b > 0 |
! |
否定(NOT) | — | !done |
& |
かつ(AND) | 必ず評価する | a > 0 & b > 0 |
| |
または(OR) | 必ず評価する | a > 0 | b > 0 |
^ |
排他的論理和(片方だけtrue) | 必ず評価する | a ^ b |
ビット・シフト演算子
| 演算子 | 意味 | 例 | 結果 |
|---|---|---|---|
& |
ビットAND(両方1なら1) | 0b1100 & 0b1010 |
1000 |
| |
ビットOR(どちらか1なら1) | 0b1100 | 0b1010 |
1110 |
^ |
ビットXOR(片方だけ1なら1) | 0b1100 ^ 0b1010 |
110 |
~ |
ビット反転 | ~12 |
-13 |
<< |
左シフト(2倍ずつ増える) | 1 << 3 |
8 |
>> |
右シフト(符号を保つ) | -16 >> 2 |
-4 |
>>> |
右シフト(左を0で埋める) | -16 >>> 2 |
1073741820 |
そのほかの演算子・記号
| 記号 | 名前 | 用途 | 例 |
|---|---|---|---|
? : |
三項演算子(条件演算子) | 条件で値を選ぶ | n > 0 ? "正" : "0以下" |
instanceof |
型比較演算子 | 型の判定とパターンマッチング | o instanceof String s |
-> |
アロー | ラムダ式、switchのcase |
x -> x * 2 |
:: |
メソッド参照 | メソッドをラムダの代わりに渡す | System.out::println |
(型) |
キャスト | 型を変換する | (int) 3.7 |
new |
インスタンス生成 | オブジェクトを作る | new ArrayList<>() |
. [] () |
メンバーアクセス・添字・メソッド呼び出し | どの演算子よりも先に結び付く | list.size()、arr[0] |
優先順位と結合規則を実行で確かめる
public class Precedence {
public static void main(String[] args) {
System.out.println(2 + 3 * 4); // * が先
System.out.println((2 + 3) * 4);
System.out.println(10 - 4 - 3); // 左結合: (10 - 4) - 3
System.out.println(100 / 10 / 5); // 左結合
System.out.println(100 / 10 * 5); // 同じ優先順位は左から
System.out.println(-2 * -3);
System.out.println(1 + 2 > 2 && 3 < 4); // 算術 > 比較 > 論理
System.out.println(1 << 2 + 1); // + が << より先: 1 << 3
System.out.println((6 & 3) == 2); // == は & より先なので、かっこが必要
int x = 5;
boolean r = x > 3 ? x < 10 : false;
System.out.println(r);
int a = 1;
a += a++ * 2; // a = 1 + (1 * 2)
System.out.println(a);
System.out.println(!true == false); // ! が == より先
// 被演算子は左から右へ評価される
int i = 1;
int v = i + (i = 10) * 2; // 1 + 10 * 2
System.out.println(v + ", i=" + i);
}
}
実行結果:
14
20
3
2
50
6
true
8
true
true
3
true
21, i=10
出力から読み取れるポイントは次のとおりです。
100 / 10 * 5は50です。/と*は同じ優先順位なので、左から(100 / 10) * 5と計算されます。「掛け算が先」ではありません。1 << 2 + 1は8です。+のほうが<<より優先順位が高いので、1 << 3になります。シフトと足し算を組み合わせるときは、かっこを付けると意図が伝わります。1 + 2 > 2 && 3 < 4は、算術 → 比較 → 論理の順に計算されます。 かっこなしで(3 > 2) && (3 < 4)と同じ意味になります。- 最後の
i + (i = 10) * 2は21です。 優先順位は「どの演算子と結び付くか」を決めるもので、値を取り出す順番は常に左から右です。左のiは代入の前に読まれるので1のままです。
ビット演算と==を組み合わせるときはかっこを付ける
&・^・|は、==より優先順位が低い演算子です。 かっこを付けずに書くと、==のほうが先に結び付きます。
public class PrecErr {
public static void main(String[] args) {
int flags = 6;
if (flags & 2 == 2) {
System.out.println("ok");
}
}
}
実行結果(コンパイルエラー):
PrecErr.java:4: error: bad operand types for binary operator '&'
if (flags & 2 == 2) {
^
first type: int
second type: boolean
1 error
error: compilation failed
flags & (2 == 2)と解釈され、intとbooleanの&になったためエラーになっています。(flags & 2) == 2とかっこを付けると、意図どおりに判定できます。
インクリメント:i++と++iの違い
i++(後置)は「今の値を式の結果にしてから1増やす」、++i(前置)は「1増やしてからその値を式の結果にする」という違いがあります。 単独の文(i++;)として書くなら、どちらも結果は同じです。
public class IncDec {
public static void main(String[] args) {
int i = 5;
int a = i++; // 先に値を使い、後で 1 増やす
System.out.println("a=" + a + ", i=" + i);
int j = 5;
int b = ++j; // 先に 1 増やし、その値を使う
System.out.println("b=" + b + ", j=" + j);
int k = 5;
k = k++; // 代入で元の値に戻る
System.out.println("k = k++ の後: k=" + k);
int m = 1;
int r = m++ + ++m; // 1 + 3
System.out.println("m++ + ++m = " + r + ", m=" + m);
int[] arr = {10, 20, 30};
int idx = 0;
arr[idx++] = arr[idx] + 1; // 左辺の添字が先に決まる
System.out.println(java.util.Arrays.toString(arr) + ", idx=" + idx);
}
}
実行結果:
a=5, i=6
b=6, j=6
k = k++ の後: k=5
m++ + ++m = 4, m=3
[21, 20, 30], idx=1
| 書き方 | 式の値 | 実行後のi(元が5) |
|---|---|---|
i++ |
5(増やす前の値) | 6 |
++i |
6(増やした後の値) | 6 |
i-- |
5(減らす前の値) | 4 |
--i |
4(減らした後の値) | 4 |
k = k++は、kが5のまま変わりません。k++の値(5)を取り出した後にkが6になり、最後に取り出しておいた5が代入されるためです。1増やしたいだけならk++;と書きます。m++ + ++mは4です。 左のm++が1を返してmが2になり、右の++mでmが3になって3を返します。arr[idx++] = arr[idx] + 1は、左辺の添字(0)が先に決まり、右辺では増えた後のidx(1)が使われます。arr[0] = arr[1] + 1と同じ意味になり、21が入っています。
1つの式の中で同じ変数を2回以上増減させると、読む人が結果を追いにくくなります。 インクリメントは単独の文に分けて書くと、意図がそのまま伝わります。
整数の割り算と%(余り):負数のときの結果
intどうしの/は小数点以下を切り捨てます(0に近い方へ丸めます)。%の結果の符号は「割られる数(左側)」と同じになります。
public class Arith {
public static void main(String[] args) {
System.out.println("7 / 2 = " + (7 / 2)); // 整数どうしは小数を切り捨て
System.out.println("7 / 2.0 = " + (7 / 2.0)); // 片方が double なら double
System.out.println("(double)7/2= " + ((double) 7 / 2));
System.out.println("7 % 2 = " + (7 % 2));
System.out.println("-7 / 2 = " + (-7 / 2)); // 0 に近い方へ切り捨て
System.out.println("-7 % 2 = " + (-7 % 2)); // 符号は割られる数に合う
System.out.println("7 % -2 = " + (7 % -2));
System.out.println("floorDiv = " + Math.floorDiv(-7, 2));
System.out.println("floorMod = " + Math.floorMod(-7, 2));
System.out.println("5.5 % 2 = " + (5.5 % 2)); // 小数にも使える
System.out.println("1.0 / 0 = " + (1.0 / 0));
System.out.println("0.0 / 0 = " + (0.0 / 0));
System.out.println("1 / 0 = " + (1 / 0)); // 整数の 0 除算は例外
}
}
実行結果:
7 / 2 = 3
7 / 2.0 = 3.5
(double)7/2= 3.5
7 % 2 = 1
-7 / 2 = -3
-7 % 2 = -1
7 % -2 = 1
floorDiv = -4
floorMod = 1
5.5 % 2 = 1.5
1.0 / 0 = Infinity
0.0 / 0 = NaN
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Arith.main(Arith.java:15)
| 式 | 結果 | 理由 |
|---|---|---|
7 / 2 |
3 | 整数どうしの割り算は整数になる |
7 / 2.0 |
3.5 | 片方がdoubleなら全体がdouble |
-7 / 2 |
-3 | 0に近い方へ切り捨て(-3.5 → -3) |
-7 % 2 |
-1 | 符号は左側(-7)に合う |
Math.floorMod(-7, 2) |
1 | 符号は右側(2)に合う。常に0以上にしたいとき |
1.0 / 0 |
Infinity | 小数の0除算は例外にならない |
1 / 0 |
ArithmeticException | 整数の0除算は例外になる |
奇数の判定をn % 2 == 1と書くと、負の奇数(-7など)は-1になるためfalseになります。 n % 2 != 0と書けば、正負どちらの奇数も判定できます。曜日や配列の位置を循環させる計算で負の値が入る可能性があるときは、Math.floorModを使うと結果が常に0以上になります。
小数の結果がほしいときは、割る前にどちらかをdoubleにします。 (double) (7 / 2)のように割った後でキャストすると、3.0になります。
&&と&、||と|の違い(短絡評価)
&&と||は、左側だけで結果が決まると右側を評価しません。これを短絡評価(ショートサーキット)と呼びます。&と|は、左右の両方を必ず評価します。
public class ShortCircuit {
static boolean check(String name, boolean result) {
System.out.println(" " + name + " を評価");
return result;
}
public static void main(String[] args) {
System.out.println("false && :");
boolean r1 = check("左", false) && check("右", true);
System.out.println("false & :");
boolean r2 = check("左", false) & check("右", true);
System.out.println("true || :");
boolean r3 = check("左", true) || check("右", false);
System.out.println("true | :");
boolean r4 = check("左", true) | check("右", false);
System.out.println(r1 + " " + r2 + " " + r3 + " " + r4);
// null チェックと組み合わせる
String s = null;
System.out.println("&& : " + (s != null && s.length() > 0));
try {
System.out.println("& : " + (s != null & s.length() > 0));
} catch (NullPointerException e) {
System.out.println("& : NullPointerException");
}
// && は || より優先順位が高い
System.out.println(true || false && false);
System.out.println((true || false) && false);
}
}
実行結果:
false && :
左 を評価
false & :
左 を評価
右 を評価
true || :
左 を評価
true | :
左 を評価
右 を評価
false false true true
&& : false
& : NullPointerException
true
false
| 演算子 | 右側を評価する条件 | 向いている場面 |
|---|---|---|
&& |
左がtrueのときだけ |
条件判定の基本。nullチェックの後に続ける |
|| |
左がfalseのときだけ |
条件判定の基本 |
& |
常に評価する | 整数のビット演算。左右とも必ず実行したい判定 |
| |
常に評価する | 整数のビット演算。フラグの合成 |
- 結果(true/false)は
&&と&で同じですが、右側が実行されるかどうかが違います。 s != null && s.length() > 0は、sがnullなら右側を評価しないので例外になりません。&にすると右側のs.length()も実行され、NullPointerExceptionになります。&&は||より優先順位が高いので、true || false && falseはtrue || (false && false)と解釈されてtrueです。&&と||を混ぜるときは、かっこで組を示すと読み違いがなくなります。
if文の条件には&&と||を使い、&と|はビット演算に使う、と分けておくとすっきりします。
三項演算子の使い方と型昇格
三項演算子は条件 ? trueのときの値 : falseのときの値の形で、条件に応じて値を選びます。 if-elseで変数に代入する処理を1行で書けます。
public class Ternary {
public static void main(String[] args) {
int stock = 0;
String label = stock > 0 ? "在庫あり" : "在庫なし";
System.out.println(label);
// 入れ子(右から結合する)
int score = 72;
String rank = score >= 80 ? "A" : score >= 60 ? "B" : "C";
System.out.println(rank);
// 型昇格: Integer と Double が混ざると double にそろう
Integer i = 1;
Double d = 2.0;
Object o1 = true ? i : d;
System.out.println(o1 + " / " + o1.getClass().getSimpleName());
// if-else なら Integer のまま
Object o2;
if (true) { o2 = i; } else { o2 = d; }
System.out.println(o2 + " / " + o2.getClass().getSimpleName());
// int と char
System.out.println(true ? 'A' : 0); // 定数 0 は char に収まるので char
int zero = 0;
System.out.println(true ? 'A' : zero); // int 変数と混ざると int
// null のアンボクシング
Integer n = null;
try {
int x = true ? n : 0; // Integer と int → int にそろえる → アンボクシング
System.out.println(x);
} catch (NullPointerException e) {
System.out.println("NullPointerException");
}
}
}
実行結果:
在庫なし
B
1.0 / Double
1 / Integer
A
65
NullPointerException
IntegerとDoubleが混ざるとdoubleにそろう
true ? i : d(IntegerとDouble)の結果は、Integerの1ではなくDoubleの1.0になっています。 三項演算子は「式全体で1つの型」を持つため、2つの値がどちらも数値型(とそのラッパークラス)のときは、算術演算と同じ型昇格(数値昇格)が行われます。Integerはいったんintに、Doubleはdoubleに戻され、広い方のdoubleにそろえられます。
同じ処理をif-elseで書いたo2はIntegerのままです。「三項演算子はif-elseの単なる短縮形ではなく、型をそろえる式」と捉えると、この結果が理解しやすくなります。
| 2つの値の型 | 式全体の型 | 実行結果 |
|---|---|---|
Integer と Double |
double |
1.0(Double) |
char と 定数0 |
char |
A |
char と intの変数 |
int |
65 |
Integer と int |
int(アンボクシングされる) |
nullならNullPointerException |
String と String |
String |
そのまま |
最後のtrue ? n : 0は、Integerとintの組み合わせなので全体がintになり、nがアンボクシング(intValue()の呼び出し)されます。 nがnullだとそこでNullPointerExceptionになります。nullの可能性がある値は、n != null ? n : 0のように先にnullを判定します。
入れ子の三項演算子は右から結合する
score >= 80 ? "A" : score >= 60 ? "B" : "C"は、右結合なのでscore >= 80 ? "A" : (score >= 60 ? "B" : "C")と解釈されます。条件ごとに改行してそろえると読みやすくなります。
String rank = score >= 80 ? "A"
: score >= 60 ? "B"
: "C";
| 場面 | おすすめの書き方 |
|---|---|
| 2択で値を選ぶ | 三項演算子 |
| 3択程度で、条件が単純 | 改行をそろえた入れ子の三項演算子 |
| 条件が4つ以上、または処理が複数行 | if-else、またはswitch式 |
| 値を返さず処理だけ行う | if文(三項演算子は値を返す式なので、文としては書けません) |
==とequalsの違い
==は、基本型(intなど)では「値」を、参照型(StringやIntegerなど)では「同じオブジェクトか」を比べます。参照型の中身を比べるときはequalsを使います。
public class Equals {
public static void main(String[] args) {
int a = 1000, b = 1000;
System.out.println("int == int : " + (a == b));
String s1 = "java";
String s2 = new String("java");
System.out.println("String == : " + (s1 == s2));
System.out.println("String equals : " + s1.equals(s2));
Integer x = 1000, y = 1000;
System.out.println("Integer == : " + (x == y));
System.out.println("Integer equals : " + x.equals(y));
System.out.println("Integer == int : " + (x == a));
System.out.println("0.1 + 0.2 == 0.3 : " + (0.1 + 0.2 == 0.3));
System.out.println("NaN == NaN : " + (Double.NaN == Double.NaN));
System.out.println("10 == 10.0 : " + (10 == 10.0));
System.out.println("'a' == 97 : " + ('a' == 97));
}
}
実行結果:
int == int : true
String == : false
String equals : true
Integer == : false
Integer equals : true
Integer == int : true
0.1 + 0.2 == 0.3 : false
NaN == NaN : false
10 == 10.0 : true
'a' == 97 : true
| 比べるもの | ==の意味 |
おすすめの比べ方 |
|---|---|---|
int・long・char・boolean |
値が等しいか | == |
String |
同じオブジェクトか | a.equals(b)、nullがあり得るならObjects.equals(a, b) |
Integerなどのラッパークラス |
同じオブジェクトか | a.equals(b)、またはa.intValue() == b.intValue() |
double・float |
値が等しいか(誤差あり) | 差の絶対値が十分小さいかで判定 |
| enum | 同じ定数か | == |
Integerどうしの==は、値が同じ1000でもfalseです。 片方がintなら、Integerがintに戻されて値の比較になるためtrueになります。0.1 + 0.2 == 0.3はfalseです。doubleは2進数で値を持つため、0.1や0.2をぴったり表せず、わずかな誤差が出ます。10 == 10.0はtrueです。intがdoubleに昇格してから比べられます。
文字列比較の詳しい使い分けは、別の記事(Javaの文字列比較)で扱っています。
複合代入演算子の暗黙キャスト(byte b += 1)
b += 5はb = b + 5と同じに見えますが、正確にはb = (byte)(b + 5)です。複合代入演算子には、左辺の型へのキャストが含まれています。
public class Compound {
public static void main(String[] args) {
byte b = 10;
b += 5; // b = (byte)(b + 5) と同じ
System.out.println("b += 5 : " + b);
byte c = 127;
c += 1; // 暗黙のキャストで -128 になる
System.out.println("c += 1 : " + c);
int i = 10;
i += 3.7; // i = (int)(i + 3.7)
System.out.println("i += 3.7 : " + i);
int j = 10;
j *= 1.5;
System.out.println("j *= 1.5 : " + j);
char ch = 'A';
ch += 1;
System.out.println("ch += 1 : " + ch);
short s = 1;
s++;
System.out.println("s++ : " + s);
String str = "No.";
str += 1 + 2; // 右辺を先に計算してから連結
System.out.println("str += 1+2: " + str);
int x, y, z;
x = y = z = 7; // 代入は右から結合
System.out.println("x=y=z=7 : " + x + "," + y + "," + z);
}
}
実行結果:
b += 5 : 15
c += 1 : -128
i += 3.7 : 13
j *= 1.5 : 15
ch += 1 : B
s++ : 2
str += 1+2: No.3
x=y=z=7 : 7,7,7
同じ計算を=と+に分けて書くと、コンパイルエラーになります。
public class CompoundErr {
public static void main(String[] args) {
byte b = 10;
b = b + 5;
int i = 10;
i = i + 3.7;
}
}
実行結果(コンパイルエラー):
CompoundErr.java:4: error: incompatible types: possible lossy conversion from int to byte
b = b + 5;
^
CompoundErr.java:6: error: incompatible types: possible lossy conversion from double to int
i = i + 3.7;
^
2 errors
error: compilation failed
| 書き方 | 結果 | 理由 |
|---|---|---|
b = b + 5(bはbyte) |
コンパイルエラー | byte + intはintになり、byteに入れるにはキャストが要る |
b += 5 |
15 | キャストが含まれているのでコンパイルできる |
c += 1(cは127) |
-128 | byteの範囲(-128〜127)を超えて値が戻る |
i += 3.7(iは10) |
13 | 13.7がintにキャストされ、小数が切り捨てられる |
str += 1 + 2 |
No.3 | 右辺全体を先に計算してから連結する |
byteやshortの計算は、いったんintに昇格してから行われます。 そのためb = b + 5はエラーになり、b += 5は通ります。複合代入はエラーが出ない分、範囲を超えたときも小数を切り捨てたときも何も表示されません。 小数を足すなら変数をdoubleに、範囲を超える可能性があるならintやlongにしておくと、意図どおりの結果になります。
str += 1 + 2は、複合代入の優先順位がいちばん低いため右辺の1 + 2が先に計算され、「No.3」になります(str = str + 1 + 2と書いた場合は「No.12」です)。
オーバーフロー:intの上限を超えたとき
intは-2,147,483,648〜2,147,483,647の範囲を持ち、超えると例外にならずに反対側の値へ回り込みます。
public class Overflow {
public static void main(String[] args) {
int max = Integer.MAX_VALUE;
System.out.println("MAX_VALUE : " + max);
System.out.println("MAX_VALUE + 1 : " + (max + 1));
System.out.println("MIN_VALUE - 1 : " + (Integer.MIN_VALUE - 1));
// int どうしの計算は int のまま
long ms1 = 24 * 60 * 60 * 1000 * 30;
long ms2 = 24L * 60 * 60 * 1000 * 30;
System.out.println("30日のミリ秒(int) : " + ms1);
System.out.println("30日のミリ秒(long) : " + ms2);
System.out.println("abs(MIN_VALUE): " + Math.abs(Integer.MIN_VALUE));
System.out.println("-MIN_VALUE : " + (-Integer.MIN_VALUE));
try {
Math.addExact(max, 1);
} catch (ArithmeticException e) {
System.out.println("addExact : " + e.getMessage());
}
try {
Math.multiplyExact(100_000, 100_000);
} catch (ArithmeticException e) {
System.out.println("multiplyExact : " + e.getMessage());
}
}
}
実行結果:
MAX_VALUE : 2147483647
MAX_VALUE + 1 : -2147483648
MIN_VALUE - 1 : 2147483647
30日のミリ秒(int) : -1702967296
30日のミリ秒(long) : 2592000000
abs(MIN_VALUE): -2147483648
-MIN_VALUE : -2147483648
addExact : integer overflow
multiplyExact : integer overflow
long ms1 = 24 * 60 * 60 * 1000 * 30は、代入先がlongでも負の値になっています。 右辺はintどうしの計算なので、intの範囲であふれた後にlongへ変換されるためです。最初の値にLを付けて24L * ...とすると、計算全体がlongで行われます。Math.abs(Integer.MIN_VALUE)は負のままです。 対応する正の値(2,147,483,648)がintの範囲に収まらないためです。Math.addExact・Math.multiplyExactは、あふれたときにArithmeticExceptionを投げます。 あふれに気づきたい計算ではこれらを使います。
| 対策 | 書き方 | 使いどころ |
|---|---|---|
longで計算する |
24L * 60 * 60 * 1000 |
件数・ミリ秒・合計など大きくなる値 |
| あふれを例外で知る | Math.addExact(a, b) |
あふれたら処理を止めたいとき |
| 桁数に上限を設けない | BigInteger・BigDecimal |
longにも収まらない値、小数を正確に扱う計算 |
文字列連結の+の評価順(1+2+"a")
+は左から順に計算され、どちらかが文字列になった時点から「連結」に変わります。
public class Concat {
public static void main(String[] args) {
System.out.println(1 + 2 + "a"); // 左から: 3 → "3a"
System.out.println("a" + 1 + 2); // 左から: "a1" → "a12"
System.out.println("a" + (1 + 2)); // かっこが先
System.out.println(1 + 2 + "a" + 1 + 2);
System.out.println('a' + 'b'); // char どうしは数値の足し算
System.out.println("" + 'a' + 'b');
System.out.println('a' + 1);
System.out.println((char) ('a' + 1));
System.out.println("合計: " + 2 * 3); // * は + より先
System.out.println("null: " + null);
int x = 5;
System.out.println("x=" + x + ", x>3 は " + (x > 3));
}
}
実行結果:
3a
a12
a3
3a12
195
ab
98
b
合計: 6
null: null
x=5, x>3 は true
| 式 | 結果 | 計算の流れ |
|---|---|---|
1 + 2 + "a" |
3a |
1 + 2で3 → 3 + "a" |
"a" + 1 + 2 |
a12 |
"a" + 1で"a1" → "a1" + 2 |
"a" + (1 + 2) |
a3 |
かっこの中が先 |
'a' + 'b' |
195 |
charどうしは文字コード(97 + 98)の足し算 |
"" + 'a' + 'b' |
ab |
先頭が文字列なので連結になる |
"合計: " + 2 * 3 |
合計: 6 |
*が+より先 |
メッセージの中で足し算の結果を表示するときは、"合計: " + (a + b)のようにかっこで囲みます。 比較の結果を連結するときも、"結果: " + (x > 3)とかっこが必要です(+のほうが>より優先順位が高いため、かっこがないと"結果: 5" > 3という比較になり、コンパイルエラーになります)。
ビット演算とシフト(>>と>>>の違い)
>>は符号を保ったまま右へずらし、>>>は左側を必ず0で埋めます。 正の数では同じ結果になり、負の数で差が出ます。
public class Bits {
static String bin(int v) {
return String.format("%32s", Integer.toBinaryString(v)).replace(' ', '0');
}
public static void main(String[] args) {
int a = 0b1100, b = 0b1010;
System.out.println("a & b = " + Integer.toBinaryString(a & b));
System.out.println("a | b = " + Integer.toBinaryString(a | b));
System.out.println("a ^ b = " + Integer.toBinaryString(a ^ b));
System.out.println("~a = " + (~a));
System.out.println("1 << 3 = " + (1 << 3));
System.out.println("-16 >> 2 = " + (-16 >> 2)); // 符号を保つ
System.out.println("-16 >>> 2 = " + (-16 >>> 2)); // 0 で埋める
System.out.println(bin(-16));
System.out.println(bin(-16 >> 2));
System.out.println(bin(-16 >>> 2));
System.out.println("1 << 32 = " + (1 << 32)); // シフト量は下位 5 ビットだけ使う
System.out.println("1L << 32 = " + (1L << 32));
// フラグ(権限)の組み合わせ
final int READ = 1, WRITE = 2, DELETE = 4;
int perm = READ | WRITE;
System.out.println("WRITE を持つ : " + ((perm & WRITE) != 0));
System.out.println("DELETE を持つ: " + ((perm & DELETE) != 0));
perm &= ~WRITE;
System.out.println("WRITE を外す : " + perm);
}
}
実行結果:
a & b = 1000
a | b = 1110
a ^ b = 110
~a = -13
1 << 3 = 8
-16 >> 2 = -4
-16 >>> 2 = 1073741820
11111111111111111111111111110000
11111111111111111111111111111100
00111111111111111111111111111100
1 << 32 = 1
1L << 32 = 4294967296
WRITE を持つ : true
DELETE を持つ: false
WRITE を外す : 1
- 2進数の表示を見ると、
>>は左側が1(符号ビット)で埋まり、>>>は0で埋まっています。 1 << 32は1のままです。intのシフト量は下位5ビット(0〜31)だけが使われるため、32は0と同じ扱いになります。32ビット以上ずらすときは1L << 32のようにlongにします。- フラグの操作は「
|で付ける・&で調べる・& ~で外す」の3つが基本です。Javaでは同じ用途にEnumSetも使えます。
instanceofと->(ラムダ・switch式)
instanceofは、オブジェクトがその型かどうかを判定します。Java 16以降は、判定と同時に変数へ取り出す「パターンマッチング」が使えます。 ->はラムダ式とswitchのcaseで使う記号です。
import java.util.function.IntBinaryOperator;
public class InstanceOf {
public static void main(String[] args) {
Object o = "hello";
System.out.println(o instanceof String);
System.out.println(o instanceof Integer);
System.out.println(null instanceof String);
// パターンマッチング(Java 16〜): 判定と変数宣言を同時に
if (o instanceof String s && s.length() > 3) {
System.out.println(s.toUpperCase());
}
// -> はラムダ式で使う
IntBinaryOperator add = (p, q) -> p + q;
System.out.println(add.applyAsInt(3, 4));
// switch 式でも -> を使う
int day = 6;
String type = switch (day) {
case 6, 7 -> "休日";
default -> "平日";
};
System.out.println(type);
}
}
実行結果:
true
false
false
HELLO
7
休日
null instanceof Stringはfalseです。 nullチェックを別に書かなくても、instanceofがtrueなら値はnullではありません。o instanceof String s && s.length() > 3は、instanceof(関係演算子)が&&より先に結び付き、左がtrueのときだけ右でsを使えます。||でつなぐと、sが確定しないためコンパイルエラーになります。- ラムダ式の
->は、右側に式またはブロックを書きます。 優先順位はいちばん低く、(p, q) -> p + qは右側のp + q全体が本体になります。
Java 22〜25で関係する変更(公式で確認)
手元の検証環境はJava 21のため、この節はJDK 25の公式リリース情報(JEP一覧)で確認した内容で、Java 25での実行はしていません。 演算子の種類・優先順位・結合規則は、Java 21からJava 25まで変わっていません。
| バージョン | 変更 | 演算子との関係 |
|---|---|---|
| Java 23〜25 | パターン・instanceof・switchでの基本型(JEP 507、Java 25時点で3回目のプレビュー) | i instanceof byte bのように、値がその基本型に収まるかをinstanceofで判定できる。プレビュー機能のため--enable-previewが必要 |
| Java 25 | コンパクトなソースファイルとインスタンスmainメソッド(JEP 512、正式機能) | クラス宣言なしでvoid main() { ... }と書ける。演算子の動きは同じ |
演算子で迷ったときのチェックリスト
| 確認すること | 見るポイント |
|---|---|
| 割り算の結果に小数が要るか | 割る前に片方をdoubleにする |
%の左側が負になり得るか |
!= 0で判定する、またはMath.floorMod |
参照型を==で比べていないか |
String・Integerはequals |
条件に&・|を使っていないか |
条件判定は&&・|| |
&&と||が混ざっていないか |
かっこで組を明示する |
ビット演算と==を並べていないか |
(flags & MASK) == MASKとかっこを付ける |
| 文字列連結の中に計算がないか | "合計: " + (a + b) |
intどうしの掛け算が大きくならないか |
先頭にLを付けてlongで計算する |
| 1つの式で同じ変数を2回増減させていないか | インクリメントは単独の文にする |
| 三項演算子の2つの値の型が同じか | IntegerとDouble、Integerとintの混在に注意する |
よくある質問
Q. 優先順位は全部覚える必要がありますか?
A. 「掛け算・割り算 → 足し算・引き算 → 比較 → && → || → 代入」の流れを押さえておけば、ほとんどの式は読めます。ビット演算やシフトを混ぜるとき、&&と||を混ぜるときは、かっこを付けて意図を示すのがいちばん確実です。
Q. i++と++iは、for文ではどちらを使えばよいですか?
A. for文の更新式のように単独で書く場合、結果は同じです。Javaではi++と書くのが一般的です。差が出るのは、int a = i++;のように値を別の式で使うときだけです。
Q. a += bとa = a + bは同じですか?
A. 型が同じなら同じ結果です。型が違うときは、a += bに左辺の型へのキャストが含まれる点が異なります。byte b = 10; b += 5;はコンパイルできますが、b = b + 5;はコンパイルエラーになります。
Q. ^はべき乗(2の3乗など)に使えますか?
A. Javaの^はビットXOR(排他的論理和)で、2 ^ 3は1になります。べき乗はMath.pow(2, 3)(結果はdoubleの8.0)を使います。
まとめ
- 演算子は優先順位が高い順に、同じ順位なら結合規則(代入・三項・単項は右から、ほかは左から)に従って計算されます。 迷ったらかっこを付けます。
i++は増やす前の値、++iは増やした後の値を返します。k = k++ではkは変わりません。- 整数どうしの
/は切り捨て、%の符号は左側に合います。 奇数判定はn % 2 != 0、常に0以上の余りはMath.floorModです。 &&・||は短絡評価で、右側を評価しないことがあります。 nullチェックの後に続ける条件は&&で書きます。- 三項演算子は型をそろえる式です。
IntegerとDoubleが混ざるとdoubleに、Integerとintが混ざるとアンボクシングされます。 - 複合代入(
+=など)には左辺の型へのキャストが含まれます。byte b += 1は通り、範囲を超えると値が回り込みます。 1 + 2 + "a"は3a、"a" + 1 + 2はa12。 文字列の中で計算するときは(a + b)とかっこで囲みます。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java演算子一覧と優先順位表|三項演算子・インクリメントの注意点」
株式会社RJCでは、Javaの演算子や優先順位のような基本文法を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。