2.8 式と演算
2.8.1 数の計算
おそらく、この本を読んでいるほとんどの読者が使っているコンピュータはパソコンだと思います。中には並列プロセッサにギガバイトの主記憶、RAID 機能などを装備した贅沢なワークステーションや、モバイルコンピュータを使っている読者もいることでしょう。しかし、どれを使っているにせよ、これらのすべてはコンピュータであり目的は異なれど元は計算機にすぎません。コンピュータの日本語訳は電子計算機なのですから。
そして、計算機が最も得意とすること、それは計算です。簡単に言ってしまえばプログラミングの原理は計算の手順を記述することです。計算を行い、その結果を保存する一連の流れこそが、プログラミングの基本中の基本なのです。
計算を行うには、式文を記述します。Java 言語プログラムの大部分は、この式文で構成されることになります。プログラミングの世界の式とは、数学で使われる式とは異なり、変数に結果を代入したり、メソッドを起動することも含まれます。一般的な数値の計算式は次のようになります。
変数 = 計算式;
このとき注意しなければならないのは、計算の結果を保存する変数を左側に配置しなければならないということです。計算式は左側から右側に向かって評価され、その結果を変数に代入します。評価とは式を実行することを表し、評価によって導き出された値を結果と呼びます。記号= の左側は常に変数でなければならず、それ以外の場合はエラーとなります。例えば、次の式文は変数 iValue に 10 を代入します。
iValue = 10;
このとき、記号 = のことを演算子と呼び、iValue と 10 のような演算対象をオペランドと呼びます。演算子は加算や減算を行う単純なものから、プログラミング言語特有のものもあります。計算を行うために必要な基本的な演算子を表1に示します。
演算子 | 名称 | 例 |
---|---|---|
+ | 加減演算子 | A + B |
- | 加減演算子 | A - B |
* | 乗算演算子 | A * B |
/ | 除算演算子 | A / B |
% | 剰余演算子 | A % B |
= | 代入演算子 | A = B |
計算式は、算数と同じと考えてかまいません。ただし、コンピュータの世界では掛け算は *、割り算は / 記号を使います。足し算と引き算に使う + や - 記号の意味はそのままです。当然、計算順序を制御するための括弧 ( ) を指定することもできます。
1 + 2 * 3
と書いた場合は、算数で習った規則と同じで 2 * 3 が評価されたあとに 1 + 6 が評価されますが、
(1 + 2) * 3
と書けば、1 + 2 が最初に評価され、その後に 3 * 3 が評価されます。
もちろん、オペランドにはリテラルだけではなく、変数を指定することもできます。オペランドに識別子を指定すれば、その変数が格納している値に対して評価が行われます。
class Test { public static void main(String args[]) { int iValue1 = 10 + 20 / 2; int iValue2 = (10 + 20) / 2; System.out.println(iValue1); System.out.println(iValue2); } }
>java Test 20 15
コード1は、整数型変数 iValue1 と iValue 2 を宣言し、それぞれ初期化しています。iValue1 の初期化で指定した式文は、最初に 20 / 2 が評価され、その後 10 + 10 が計算されています。iValue2 の初期化に用いられている式文は、括弧を使って最初の加算処理を優先することを表しています。そのため、10 + 20 が最初に評価され、その後 30 / 2 が評価されます。結果は、見ての通りです。
因みに、ローカル変数宣言文における = は式文における代入演算子とは異質の存在です。このときの = はローカル変数宣言文の構文として定められているものであり、ローカル変数宣言文は式文ではありません。
2.8.2 代入演算
変数は情報を保存する格納庫であり、その情報は自由に書き換えることができます。これまでは変数をリテラルで初期化して表示するだけでしたが、代入演算子を使って異なる値に書き換えられるのです。例えば、現在の値に 10 を加算したい場合は次のように計算します。
iValue = iValue + 10;
この式文は、整数型変数 iValue の現在の値に 10 加算した結果を iValue に保存することを意味しています。
class Test { public static void main(String args[]) { int iValue = 10; System.out.println(iValue); iValue = 100; System.out.println(iValue); iValue = iValue * 10; System.out.println(iValue); } }
>java Test 10 100 1000
コード2では、代入演算子 = を使って iValue 変数の内容を更新しています。代入演算子の左オペランドに指定された変数には、右オペランドの評価結果が格納されます。プログラムの実行結果を見れば、 iValue 変数の内容が変化していることを確認できます。
通常、式文は結果を返しますが、中には結果が存在しない式文も存在します。例えば println() メソッドを呼び出す式文は結果を返しません。このように、結果が存在しない式のことを「式は void である」と表現します。コード2の式 System.out.println(iValue); は void です。
変数に値を格納することが役割の代入演算も、計算を行うわけではありません。よって、iValue = 100; という式は void であるような気がします。しかし、実は代入演算子は結果を返すのです。
代入演算子の結果は常に左オペランドの変数となります。この事実は A=B=C という計算を行うことができることを表しています。代入演算子は他の計算用の演算子とは異なり、右から左に向かって評価されるため、この計算は C の結果を B に代入し、B の結果を A に代入することになります。
class Test { public static void main(String args[]) { int iValue1 , iValue2 , iValue3; iValue1 = 10 * (iValue2 = 10 * (iValue3 = 10)); System.out.println(iValue1); System.out.println(iValue2); System.out.println(iValue3); } }
>java Test 1000 100 10
コード3は、複数の変数の代入と計算を 1 つの式文で行うというテクニックを披露しています。このような式を作る時は、代入演算子の優先順位が低いことと、代入演算子の左オペランドは変数でなければならないことに注意します。
まず、iValue3 に 10 を代入します。この処理が最も優先して行われるように ( ) で囲みます。次に iValue2 に 10 * iValue3 の結果を格納します。これも、iValue3 の次の行われるように ( ) で囲みます。最後に、括弧のない最も外側の iValue1 の代入演算が評価され 10 * iValue2 の結果が iValue1 に代入される仕組みになっています。
2.8.3 演算子の項
表1で示した演算子は、すべて左右にオペランドを指定しなければなりません。このような演算子は2つのオペランドによって構成されることから2項演算子と呼ばれます。しかし、演算子によっては受けるオペランドが必ず2つの項とは限りません。1 つのオペランドしか受けない単項演算子というものも存在するのです。計算に用いられる単項演算子を表 02_08_01 に示します。
演算子 | 名称 | 例 |
---|---|---|
+ | プラス演算子 | +A |
- | マイナス演算子 | -A |
++ | 前置インクリメント演算子 | ++A |
後置インクリメント演算子 | A++ | |
-- | 前置デクリメント演算子 | --A |
後置インクリメント演算子 | A-- |
最も直観的な単項演算子はプラス演算子とマイナス演算子です。これらは、結果に対して正なのか負なのかを明示的に表すものです。整数リテラルだけの場合は常に正の数であると判断されますが、マイナス演算子を使うことで負数を表現できるようになります。
加減演算子とプラス、及びマイナス演算子の区別は、2項演算子か単項演算子の違いで判断されます。コンパイラは2項演算子として + が使われていれば加算演算と判断し、単項演算子として + が使われていればプラス演算子と判断します。
class Test { public static void main(String args[]) { int iValue1 = 100; int iValue2 = 10 + -iValue1; System.out.println(iValue2); } }
>java Test -90
コード4では iValue2 を初期化するときにマイナス演算子を使っています。これらのプラス、マイナス記号はリテラルの一部ではなく演算子であると定義されていることから、対象のオペランドはリテラルでも変数でもかまいません。そのため、iValue1 の値は 100 ですが、マイナス単項演算子の効果によって符号が反転し -100 という結果になり、これに10を加算した結果 -90 が iValue2 に代入されるのです。
ただし、可読性を考慮して次のように記述するべきでしょう。
int iValue2 = 10 + (-iValue1);
このほうが、人間にとっては読みやすいでしょう。誤解を招くような記述はできるだけ避けるのもプログラマの務めです。
プラス演算子に大きな効果はないため、プラス演算子を明示的に使う必要があるケースはほとんどありません。しかし、マイナス演算子には符号を反転させるという効果があります。例えば、コード4の iValue1 変数の初期化を次のように変更します。
int iValue1 = -100;
この場合、iValue2 の初期化では、負数の値に対してマイナス演算子が使われることになります。これは、次の式に等しいと解釈することができます。
int iValue2 = 10 + (-(-100));
マイナス演算子は、負の整数リテラルを表現するだけではなく、変数に対して使ったり、ある数の符号を反転させる時に利用することができるのです。
2.8.4 前置式と後置式
コンピュータプログラムでは、しばしば変数の値を 1 だけ加算したり、1 だけ減算するという必要に迫られます。この、1 だけ加算することをインクリメントすると呼び、1 だけ減算することをデクリメントすると呼びます。一般的に考えれば、この問題は次の式で解決することができます。
iValue =iValue = iValue - 1;
表2で示したインクリメント演算子やデクリメント演算子を使えば、この計算をより簡単に記述することができます。ただし、これに与えるオペランドの結果は、常に整数型の変数でなければなりません。リテラルを渡すことはできないのです。
インクリメント、及びデクリメント演算子には、前置式と後置式の2つに分かれています。前置インクリメント演算子、デクリメント演算子は次のようになります。
++iValue; --iValue;
これらの式の結果は void ではありません。前置インクリメント演算子と前置デクリメント演算子の結果は、新しい値が格納された変数の値となります。
class Test { public static void main(String args[]) { int iValue = 10; int iTemp = ++iValue; System.out.print("iValue = "); System.out.println(iValue); System.out.print("iTemp = "); System.out.println(iTemp); } }
>java Test iValue = 11 iTemp = 11
コード5は、インクリメント演算子の評価結果を証明するためのプログラムです。10 で初期化された変数 iValue をインクリメントし、さらにインクリメント演算子が結果を返していることを iTemp で証明しています。整数型変数 iTemp には、iValue 変数をインクリメントした結果が格納されていることを確認することができます。もちろん、iValue 変数自体もインクリメントされています。
単項演算子は右側にオペランドを配置する前置式ですが、インクリメントとデクリメント演算子には、オペランドの後ろに演算子を配置する後置インクリメント式と後置デクリメント式が存在します。これらを総称して後置式と呼びます。
後置式でも基本的な効果は前置インクリメント、前置デクリメント演算子と同じですが、新しい値が格納される前の変数の値を返すという特性を持ちます。これは、式の中で用いる場合、前置式との違いに注意する必要があります。
class Test { public static void main(String args[]) { int iValue = 10; int iTemp = iValue--; System.out.print("iValue = "); System.out.println(iValue); System.out.print("iTemp = "); System.out.println(iTemp); } }
>java Test iValue = 9 iTemp = 10
コード6では、後置デクリメント演算子の効果と結果を証明するプログラムです。10 で初期化された変数 iValue は、やはりデクリメントされて 9 になっています。ここまでは予想できたものですが、前置式とはことなり、デクリメント演算子が返した結果で初期化された変数 iTemp は iValue の元々の値 10 であることが確認できます。
2.8.5 文字列連結演算子
2項演算子 + が使われたとき、一方が String 型であれば、それは文字列連結演算子であると解釈されます。文字列連結演算子は、2つの文字列を結合した結果を返します。
class Test { public static void main(String args[]) { String str = "Kitty on " + "your lap"; System.out.println(str); } }
>java Test Kitty on your lap
コード7は、文字列型変数 str を、文字列リテラルを文字列連結演算子で結合した結果で初期化しています。結果は見ての通り、2つのリテラルが正しく結合しています。
文字列連結演算子は、もう一方のオペランドが文字列型以外の場合は、適切な文字列に変換して結合されます。この言語仕様は直観的な文字列操作と変換処理が可能であることを表しています。
class Test { public static void main(String args[]) { String str1 = "int = " + 100; String str2 = "boolean = " + true; String str3 = "double = " + 3.14; String str4 = "char = " + '猫'; System.out.println(str1); System.out.println(str2); System.out.println(str3); System.out.println(str4); } }
>java Test int = 100 boolean = true double = 3.14 char = 猫
コード8は、文字列と整数、boolean、浮動小数、文字をそれぞれ結合しています。文字列とは異なる型が、最終的に文字列に変換されていることが確認できます。