やり直しJava

演算子

算術演算子

算術演算子を以下に示します。

算術演算子
演算子演算内容
+加算。オペランドのいずれかが文字列の場合は文字列連結になります。
減算。
*乗算。
/除算。右項が0の場合 ArithmeticExceptionが発生します。
%剰余。右項が0の場合 ArithmeticExceptionが発生します。
++インクリメント。
デクリメント。

浮動小数点の演算

floatやdoubleの浮動小数点型は 正確な近似を素早く行うためのデータ型です。そのため、正確な結果が必要な場面では使うことができません丸め誤差の生じない厳密な計算が必要な場合は、floatやdoubleではなくBigDecimalクラスを用います。詳細は「変数とデータ型」の「floatやdoubleでは正確な演算ができない」を参照してください。

strictfp修飾子

Java言語仕様では floatは32ビットの単精度浮動少数点数型、doubleは64ビットの倍精度浮動小数点数型と定められていますが、中には doubleよりも多くの指数ビットを持つ 拡張浮動小数点数を扱えるレジスタなどもあります。拡張浮動小数点数を扱えるレジスタで演算を行うと、極端な場合 floatやdoubleで表現できる数値集合だけを使用すると オーバーフローやアンダーフローが発生するような場合でも、オーバーフローやアンダーフローが発生することなく 演算結果を得られるようなことが起こります。

例えば(Double.MAX_VALUE * 1.1 / 1.1)という演算を行う場合、doubleで表現できる数値集合だけを使用するとオーバーフローが発生しますが、拡張浮動小数点数を扱えるレジスタで演算を行うと Double.MAX_VALUEに近い値を算出するかも知れません。JVMやプラットフォーム・ハードウェアによって 同じ計算結果が得られないことになってしまいます

このような違いを防ぐためには strictfpを指定します。strictfpは 標準のfloatやdoubleで表現できる数値集合だけを使用して浮動小数点数演算を行うように制限することによって、このような違いの発生を防ぎます

strictfpを指定しても 前述の丸め誤差がなくなるわけではないことに注意が必要です。

「+」演算子

+演算子は算術演算子として加算を行いますが、オペランドのいずれかが文字列の場合 文字列連結が行わます。

"1" + 2;    // "12"
1 + "2";    // "12"

二項数値昇格(Binary Numeric Promotion)

「+」や「-」のような二項演算子による演算を行う場合、次のルールに従って オペランドの型が自動変換されます。

  1. いずれかのオペランドがdouble型であれば、もう片方はdouble型に変換されます。
  2. そうでない場合、いずれかのオペランドがfloat型であれば、もう片方はfloat型に変換されます。
  3. そうでない場合、いずれかのオペランドがlong型であれば、もう片方はlong型に変換されます。
  4. そうでない場合、両方のオペランドはint型に変換されます。

これは 二項数値昇格(Binary Numeric Promotion)と呼ばれます。一言で言うと、大きい方の型に合わせて もう片方がワイドニング変換され、int型より小さな型は int型にワイドニング変換されるということです。

注意したいのが4番目です。1~3の条件に合わない場合は 4が適用され 両方のオペランドがint型に変換されます。例えば byte型の変数同士を加算する場合は、4番目の条件が適用されて 両方のオペランドが int型に変換され、結果がint型になります。同様に char型の変数同士を加算する場合や short型の変数同士を加算する場合も 結果はint型になります。つまり、二項数値昇格は 両方のオペランドのデータ型が異なる場合だけに適用されるわけではないということです。

整数型のラップアラウンドの検出と防止

整数型の演算を行う場合、演算結果が値範囲から外れると 例外などは発生せずにラップアラウンドが行われます。ラップアラウンドの検出と防止については、「変数とデータ型」の章の「整数型のラップアラウンドの検出や防止」を参照してください。

ビット演算子

ビット演算子を以下に示します。

ビット演算子
演算子演算内容
& 論理積(AND)0b0101 & 0b1111;    // 0b0101
|論理和(OR)0b0100 | 0b0001;    // 0b0101
^排他的論理和(XOR)0b1111 ^ 0b1010;    // 0b0101
~反転~0b0101;    // 0b1010
<<左シフト0b000110 << 2;    // 0b011000
>>右シフト(符号あり)、MSBはシフト前のMSBの値0b011000 >> 2;    // 0b000110
0b110000 >> 2;    // 0b111100
>>>右シフト(符号なし)、MSBは0固定0b011000 >>> 2;    // 0b000110
0b110000 >>> 2;    // 0b001100

※ MSB:Most Significant Bit(最上位ビット。左端のビット)

代入演算子

代入演算子「=」は右辺を左辺に代入します。

算術演算子とビット演算子の前に代入演算子を付けて省略形で記述することもできます。

a += 2;       // a = a + 2;  の省略形
b &= 0b0011;  // b = b & 0b0011;  の省略形

比較演算子

比較演算子を以下に示します。

比較演算子
演算子演算内容
==左辺と右辺が等しければ true
!=左辺と右辺が等しくなければ true
<左辺が右辺より小さければ true
<=左辺が右辺以下であれば true
>左辺が右辺より大きければ true
>=左辺が右辺以上であれば true

論理演算子

論理演算子を以下に示します。

論理演算子
演算子演算内容
&& 左辺と右辺が true ならば true。左辺が false ならば右辺は評価しない。
& 左辺と右辺が true ならば true。左辺が false でも右辺を評価する。
||左辺と右辺どちらかが true ならば true。左辺が true ならば右辺は評価しない。
|左辺と右辺どちらかが true ならば true。左辺が false でも右辺を評価する。
!続く値や変数、式が false ならば true。

ビット演算子で出てきた「&」や「|」を論理演算子として用いることもできます。「&&」と「&」はAND、「||」と「|」はORの働きをしますが、評価の短絡を行うかどうかが異なります

「&&」は左辺がfalseなら右辺を評価しません。(評価の短絡)一方で「&」は左辺がfalseでも右辺を評価します。同様に「||」は左辺がtrueなら右辺を評価しませんが、「|」は左辺がtrueでも右辺を評価します。

int d1;
int d2;
d1 = d2 = 0;
if(++d1 > 1 && ++d2 > 1) {  // 左辺が false なら右辺を評価しない
	
}
System.out.println("d1=" + d1 + ", d2=" + d2);  // d1=1, d2=0

d1 = d2 = 0;
if(++d1 > 1 & ++d2 > 1) {  // 左辺が false でも右辺を評価する
	
}
System.out.println("d1=" + d1 + ", d2=" + d2); // d1=1, d2=1

「&」や「|」は右辺の評価式やメソッドが副作用を起こす式やメソッドで 短絡させたくないような場合に有用です

初心者向けのJava解説本やサイトには「&」や「|」を論理演算子として用いる場合の説明がないものも見られるため、「&」や「|」を論理演算しとして使う場合は コメントを併せて記述した方が良いと思います。

演算子の優先順位

演算子の優先順位を以下に示します。

演算子の優先順位
優先度結合演算子
↑ 高い(引数)  [配列添字]  .  ++(後置)  –(後置)
!  ~  -(単項演算子)  ++(前置)  –(前置)
new  (キャスト)
*  /  %
+  -(算術演算子)
<<  >>  >>>
>  >=  <  <=  instanceof
==  !=
&
^
|
&&
||
?:(条件演算子)
↓低い=  =(算術演算子/ビット演算子)

優先順位が同じ演算子が並んでいる場合は、左結合なら左から右の順番に、右結合なら右から左の順番に実行されます。右結合は以下のようになります。

int a = 10;
int b = - ++a;          // 右結合のため、++a 、 -a の順番で演算が行われる。
System.out.println(b);  // -11

演算子の優先順位を正確に覚えている人は多くはないと思いますので、優先順位の意図が伝わるように できるだけ () で囲むと 開発メンバの間の齟齬を防ぐことができます

演算子の優先順位とオペランドの評価順序

演算子の優先順位は前述の通りですが、それとは別にオペランドの評価順序があり、オペランドの評価順序は左から右と決まっています。そして、オペランドの評価は 演算子の評価より前に行われるため、オペランドの評価に副作用を伴う場合は 意図しない結果になるおそれがあります。

int number, result;

number = 1;
result = (number > 1? 1 : 0) + (++ number);  // 式①
System.out.println(result);    // 2

number = 1;
result = (++ number) + (number > 1? 1 : 0);  // 式②
System.out.println(result);    // 3

式①と式②で結果が異なります。いずれも演算子の優先順位だけを考えてしまうと (++ number)を評価してから「+」演算子による評価が行われ、同じ結果になると思うかも知れません。しかし 実際には「+」演算子の左のオペランドが評価され、次に右のオペランドが評価され、最後に「+」演算子の評価が行われるため、式①と式②で結果が異なってきます。

一般的に 一つの式の中で 同じ変数に対して読み出しと書き込みを行う場合に、このような問題に直面します。もちろん 仕様を正しく理解していれば 誤ることはないと思いますが、一つの式の中で同じ変数に対して 読み出しと書き込みを行う場合は 誤解を生み易いので、一時変数を用意するのが無難です。

一時変数を用意すると 上のコードは次のようになります。

int number, result;

number = 1;
int tmp = ++ number;
result = (number > 1? 1 : 0) + tmp;  // オペランドの左右が入れ替わっても結果は同じ。
System.out.println(result);    // 3