演算子についての重要な点は以下になります。
- Java の式の多くは演算子とオペランドの組み合わせによって作られています。
- +演算子は文字列を連結する働きをします。
- インクリメント(デクリメント)演算子を使うことで、変数の値を 1 加算(減算)できます。
- 複合的な代入演算子を利用することで四則演算と代入を簡潔に記述できます。
- 演算子の優先順位を間違えると誤った実行結果になることがあります。
- キャスト演算子を使ってサイズが小さい型の変数にサイズが大きい型の値を代入できます。
それでは、プログラミングの基本となる演算子について解説していきます。
演算子
演算子の種類
プログラム中で計算などの処理(演算)を行う際には、演算の種類を表す記号として演算子(operator)を使用します。演算子は1個以上の情報に対して演算処理を行います。このとき、演算の対象となる情報をオペランド(operand)といいます
図6-1 演算子とオペランドの例
Javaでは様々な演算子が用意されています。
下記の負の数をあらわす「-」演算子のように、1つのオペランドに対して使用する演算子を、単項演算子と呼びます。
-3
一方で、同じ記号でも減算を行うための「-」演算子はオペランドが2つ必要です。
5 - 3
このように、演算子の種類、用途に応じて必要となるオペランドの個数は異なるため注意してください。
算術演算子
加算、減算、乗算、除算、剰余の5つは、算術演算子と呼ばれます。これらの演算子は数値の計算(四則演算)に使われます。
算術演算子を使った下記のサンプルを作成してみましょう。
public class Sample {
public static void main(String[] args)
{
int num1 = 10;
int num2 = 3;
// 算術演算子を使った演算
System.out.println("num1+num2は" + (num1 + num2) + "です。");
System.out.println("num1-num2は" + (num1 - num2) + "です。");
System.out.println("num1*num2は" + (num1 * num2) + "です。");
System.out.println("num1/num2は" + (num1 / num2) + "です。");
System.out.println("num1%num2は" + (num1 % num2) + "です。");
}
}
【実行結果】
num1+num2は13です。
num1-num2は7です。
num1*num2は30です。
num1/num2は3です。
num1%num2 は 1 です。
算術演算子を利用する際は以下の点に注意してください。
- 「%(剰余)」は割り算の余りを計算するための演算子です。
- 「-」「/」「%」は左右のオペランドの位置を変えると演算結果が変わります。
- 「/」「%」を使う際は、分母側のオペランドとして数値0をしてしないようにしましょう。プログラム実行時にエラーが発生します。
式の評価
演算子とオペランドの組み合わせを「式」といいます。【図6-2】の式の例では、「+」「=」が演算子、「num1」「num2」「10」がオペランドにあたります。
「+」は左右のオペランドを使用して計算を行い、結果を求めます。この結果を求める処理のことを評価と呼び、評価により得られた結果の値を評価値と呼びます。もう1つの演算子である「=」は右辺の評価値を左辺の変数に代入する機能を持ちます。
図6-2 式の例
算数や数学の世界では「=(代入演算子)」は「右辺と左辺の値が等しい」という意味になりますが、プログラミングの世界では「右辺の評価値を左辺に代入する」という意味になります。
それでは、評価値を扱った処理の動きを確認してみましょう。下記のサンプルコードを作成していきましょう。
public class Sample {
public static void main(String[] args)
{
int num1 = 3;
int num2 = 5;
// ① num1+num2の値をsumに代入
int sum = num1 + num2;
System.out.println("変数num1の値は" + num1 + "です。");
System.out.println("変数num2の値は" + num2 + "です。");
System.out.println("num1+num2の値は" + sum + "です。");
// ② num1+1の値をnum1に代入
num1 = num1 + 1;
System.out.println("変数num1の値に1を足すと" + num1 + "です。");
}
}
【実行結果】
変数num1の値は3です。
変数num2の値は5です。
num1+num2の値は8です。
変数 num1 の値に 1 を足すと 4 です。
①と②の式では右辺の評価値を、左辺の変数に代入しています。①の式では、変数num1とnum2の加算を行い、その評価値を変数sumに代入しています。
また、②の式では、変数num1の値に1加算して、その評価値を同じくnum1に代入しています、前述のとおり、「=」は「右辺の評価値を左辺の変数に代入する」という意味であるため、②のように記述しても問題ありません。
演算子の種類
文字列連結演算子
「+」には加算以外に。文字列を連結する文字列連結演算子の機能もあります。下記のサンプルコードを見てください。
System.out.println("num1+num2 は" + (num1 + num2));
文字列と式の間に演算子「+」が記述されています。「+」のオペランド2つのうち、一方でも文字列である場合、その「+」は文字列連結演算子として機能します。文字列連結演算子は、文字列ともう片方の値を1つの文字列として連結させます。
重要:「+」演算子は数字の加算だけでなく、文字列を連結することもできます。
インクリメントとデクリメント
次のような「ある変数の値に1加算して同じ変数に代入する処理」や「ある変数の値から1減算して同じ変数に代入する処理」はインクリメント演算子(++)、またはデクリメント演算子(–)を使用して表すことができます。
num = num + 1;
num = num - 1;
各演算子の機能、記述方法を下表で紹介します。
名前 | 記号 | 特徴 | 記述例 |
インクリメント演算子 | ++ | 値を 1 増やす(「num = num + 1」と同義) | num++ |
デクリメント演算子 | — | 値を 1 減らす(「num = num – 1」と同義) | num– |
【図6-3 インクリメント/デクリメント演算子】
重要:インクリメント(デクリメント)演算子を使うと、変数の値を1加算(減算)できます。
インクリメントとデクリメントの前置・後置
インクリメント(デクリメント)演算子は、オペランドの前と後ろの両方に記述できます。たとえば、変数aの後ろに演算子を置く場合(後置)は「後置インクリメント演算子」と呼び、以下のように記述します。
a++
一方で、演算子を前に置く場合(前置)は「前置インクリメント演算子」と呼び、以下のように記述します。
++a
どちらの書き方でも、変数の値を1増やすという機能自体は変わりません。しかし、場合によっては前置と後置で実行結果が変わることがあります。
この実行結果の違いについては以下のサンプルコードを利用して確認してみましょう。
public class Sample {
public static void main(String[] args)
{
int num1 = 1;
int num2 = 0;
// 後置インクリメントを使用
num2 = num1++;
System.out.println("num2 の値は" + num2 + "です。");
}
}
【実行結果】
num2 の値は 1 です。
後置インクリメント演算子を使った場合、変数num2の値は1となります。それでは次に、コメントの下の処理を下記のように、前置インクリメント演算子を使用した処理に書き直して実行してみましょう。
num2 = ++num1;
【変更後の実行結果】
num2 の値は 2 です。
すると、num2の値が後置の場合と異なることが分かります。これは、前置と後置でインクリメント(デクリメント)演算子が実行されるタイミングが異なるためです。
後置の場合は、「変数num2にnum1の値を1増やした後に、変数num2にnum1の値を代入する」という処理が行われます。
このように、インクリメント(デクリメント)演算子と他の演算子の処理を組み合わせると想定外の処理結果になる恐れがあります。そのため開発現場では、下記のようにインクリメント(デクリメント)演算子の処理は、他の演算とは切り離して実行できるように記述することが推奨されています。
**num1;
num2 = num1;
代入演算子
代入演算子とは、これまで使ってきた「=」にことで、「右辺の値を左辺に代入する」という機能を持ちます。代入演算子の中には、四則演算と代入をまとめて実行できる複合的な演算子があります。
【表6-3 代入演算子】
記号 | 名前 | 使用例 | 同義の処理 |
+= | 加算代入 | num1 += num2 | num1 = num1 + num2 |
-= | 減算代入 | num1 -= num2 | num1 = num1 – num2 |
*= | 乗算代入 | num1 *= num2 | num1 = num1 * num2 |
/= | 除算代入 | num1 /= num2 | num1 = num1 / num2 |
%= | 剰余代入 | num1 %= num2 | num1 = num1 % num2 |
「num1 += num2」は「num1の値にnum2の値を加算して、その値をnum1に代入する」という処理を行います。この処理は「num = num1 + num2」と同じ結果になります。
ちなみに、「+」などの算術演算子と「=」の間にはスペースを空けないように記述しましょう。
【図6-4 複合的な代入演算子】
複合的な代入演算子を利用することで、四則演算から代入までをより簡潔に記述することができます。
演算子の優先順位
優先順位の一覧
演算子には優先順位があり、1つの式に複数種類の演算子が存在する場合は優先順位が高い演算子から評価されます。たとえば、「3+5*2」では、「*」が「+」よりも先に先に評価されます。また、「(4+3)*2」のように式の「()」で囲むと、「()」内の演算子が優先的に評価されます。
【表6-4 演算子の優先順位】
記号 | 名前 | 結合の種類 |
++ — | 後置インクリメント 後置デクリメント | 左結合 |
! ++ — + – | 論理否定 前置インクリメント 前置デクリメント プラス(単項) マイナス(単項) | 右結合 |
New (型) | new 演算子 キャスト演算子 | 左結合 |
* / % | 乗算 除算 剰余 | 左結合 |
+ – | 加算、文字列連結 減算 | 左結合 |
> >= < <= | より大きい 以上 未満 以下 | 左結合 |
== != | 等価 非等価 | 左結合 |
&& | 論理積 | 左結合 |
|| | 論理和 | 左結合 |
?: | 条件演算子(三項演算子) | 右結合 |
= | 代入演算子 ※複合的な代入演算子も同じ優先順位 | 右結合 |
表6-4の通り、後置インクリメント、後置デクリメントが最も優先順位が高い演算子です。前述のインクリメント、デクリメントの前置・後置の内容では、下記のように後置での値の増減は代入処理の後で行われるため、後置の演算子は最も優先度が低いように思われるかもしれません。
num2 = num1++;
このように思う原因は、後置インクリメント、後置デクリメントが値を返すタイミングを誤解しているからです。延安市の優先順位は「演算子により値が返されるタイミング」で決まります。後置インクリメント・後置デクリメントはオペランドに対する増減処理の前に、変数内の値を返す仕組みとなっています。しかも、他のどの演算子よりも先に値を返すため、結果的に優先順位が最も高いという扱いとなります。
優先順位が同じ演算子の評価順
前述の通り、優先順位が異なる演算子が1つの式に存在する場合は、優先順位が高い演算子から評価されます。では、同じ優先順位の演算子が存在する場合はどのような順番で評価されるのでしょうか。優先順が同じ演算子の場合、評価される順番は「結合の種類」で決まります。
表6-4を改めて確認してください。各演算子は「左結合」「右結合」のいずれかの種類に分類されます。左結合とは、同じ優先度の演算子が存在する場合、左に記述された演算子から優先して評価されるという性質のことです。
たとえば、下記の式ではnum1とnum2の加算が先に評価され、次にその評価値と5が加算されます。
num1 + num2 + 5
一方で、右結合は同じ優先度の演算子が存在する場合、右に記述された演算子から優先して評価されるという性質のことです。
たとえば、下記の式では5をnum2に代入した後、num2の値がnum1に代入されます。
num1 = num2 = 5
このように、同じ優先度の演算子が存在する場合は結合の種類によって評価される順番が決まります。
演算子の結合の注意点
演算子の結合の種類に関して、単純な算術演算などであれば特別意識する必要はありません。しかし、文字列連結と組み合わせる場合は注意が必要です。
public class Sample {
public static void main(String[] args)
{
System.out.println("2*10は" + 2 * 10 + "です。");
System.out.println("2+10は" + 2 + 10 + "です。");
}
}
【実行結果】
2*10は20です。
2+10は210です。
「2*10」の計算結果は期待通り「20」と出力されました。しかし、「2+10」の計算結果は「210」と想定外の値となりました。なぜこのような結果になったのでしょうか。
その理由は演算子の結合の種類が原因です。加算演算子の「+」と文字列連結演算子の「+」は同じ優先順位です。そのため、下記のように1つの式に混在すると、一番左側の「+(つまり文字列連結演算子)」が優先的に実行されます。
"2+10 は" + 2 + 10 + "です。"
すると、最初に文字列「2+10は」と整数値「2」の間で文字列連結が評価され、「2+10は2」という文字列が作られます。その後、文字列「2+10は2」と整数値「10」の間で文字列連結が評価され、「2+10は 210」という文字れるが作られます。結果として、「2+10 は 210 です。」という文字列が完成します。
このように、算術演算の結果が文字列と連結したくても、結合の性質が影響して想定外の結果となってしまう恐れがあります。もし、算術演算を優先して評価させたい場合は、下記のように記述しましょう。
"2+10 は" + (2 + 10) + "です。"
「()」で算術演算の式を囲むことで、算術演算を優先的に評価されます。その結果、想定通りの評価値が出力されます。
型変換
サイズが大きい型に代入する
ここでは、変数の型と演算子の関係について紹介します。まずは、代入を行う際に代入する値の型よりも変数の型のサイズが大きい場合に代入を行うとどのような結果になるかを確認します。
サイズについては、章「変数」の「型」を参照してください。
public class Sample {
public static void main(String[] args)
{
int inum1 = 170;
int inum2 = 65;
System.out.println("身長は" + inum1 + "cmです。");
System.out.println("体重は" + inum2 + "kgです。");
// サイズが大きい型に代入
double dnum1 = inum1;
double dnum2 = inum2;
System.out.println("身長は" + dnum1 + "cmです。");
System.out.println("体重は" + dnum2 + "kgです。");
}
}
【実行結果】
身長は170cmです。
体重は65kgです。
身長は170.0cmです。
体重は 65.0kg です。
処理の途中で int 型の変数 inum1、inum2 の値を double 型の変数 dnum1、dnum2 に代入しています。そのあと、dnum1 と dnum2 の値を出力すると、それらの値が浮動小数点型になっていることがわかります。このように、代入する値の型よりも変数の型のサイズが大きい場合、その変数に代入すると、自動的に変数の型に合わせて値の型が変換されます。この処理のことを型変換と呼びます。
型変換には「暗黙的な型変換」と「明示的な型変換(キャスト)」があります。上記のサンプルコードの処理は暗黙的な型変換となります。明示的な型変換については次で紹介します。
【図6-5 サイズが大きい型への代入】
サイズが小さい型に代入する
次は代入する値よりも変数の型のサイズが小さい場合の代入を見てみましょう。
public class Sample {
public static void main(String[] args)
{
double dnum1 = 170.5;
double dnum2 = 65.3;
System.out.println("身長は" + dnum1 + "cmです。");
System.out.println("体重は" + dnum2 + "kgです。");
//このままだと代入不可
int inum1 = dnum1;
int inum2 = dnum2;
System.out.println("身長は" + inum1 + "cm です。");
System.out.println("体重は" + inum2 + "kg です。");
}
}
上記のサンプルコードでは、double型の変数からint型の変数への代入処理でコンパイルエラーが発生しましまい、実行することができません。これは、double型よりもint型のサイズが小さいことが原因です。小さいサイズの型に代入を行うには、「明示的な型変換(キャスト)」を行う必要があります。キャストには演算子「()」を使用します。
<構文 キャスト演算子>
(変換先の型名)式
キャスト演算子は、式の型を「()」内で指定した方に強制的に変換します。
上記のサンプルコードで変数を宣言している部分を下記のように編集してください。
int inum1 = (int) dnum1;
int inum2 = (int) dnum2;
【実行結果】
身長は170.5cmです。
体重は65.3kgです。
身長は170cm です。
体重は65kg です。
編集後はソースコードが正常に実行され、double型の値はint型にキャストされたことが確認できました。出力結果から分かるように、double型の値をint型にキャストした場合には小数点以下の値は切り捨てられます。
このようにキャスト演算子を使用して、明示的に型の変換を行うことができます。
また、キャスト演算子はサイズが小さい方から大きい型へ変換にも利用できます。
int inum = 170;
double dnum = (double) inum;
【図6-6 サイズが大きい型への代入】
重要:キャスト演算子で明示的な型変換が行える
異なるサイズの型の演算
異なるサイズの型の数値を算術演算子で評価する場合の特徴について紹介します。
public class Sample {
public static void main(String[] args)
{
int width = 5;
double height = 2.5;
// int型とdouble型の数値の演算
System.out.println("四角形の面積は" + (width * height) + "cm2です。");
}
}
【実行結果】
四角形の面積は12.5cm2です。
上記のサンプルコードでは、int型とdouble型の数値を算術演算子で評価します。そして、出力結果が「12.5cm2」となっていることから、その評価値はdouble型であることが分かります。これは異なる型の値で評価した場合、小さいサイズの型を大きいサイズの型に変換してから演算を行っているためです。このルールに則って評価を行った結果として上記のサンプルコードでの評価値はint型よりもサイズが大きいたdouble型となります。
型変換のタイミング
最後に、型変換の応用として、型変換が発生するタイミングについて説明します。
演算を行う際にはどのタイミングで型変換が発生するかを理解しておくことが重要です。例えば、「6÷4」の計算結果が「1.5」という小数点以下まで商を求めるために、下記のサンプルコードを作成したとします。このサンプルコードは期待通りの結果を出力してくれるでしょうか。
public class Sample0608 {
public static void main(String[] args)
{
int inum1 = 6;
int inum2 = 4;
double dnum = inum1 / inum2;
System.out.println("6
÷4 は" + dnum + "です。");
}
}
【実行結果】
6÷4 は1.0です。
実行結果を確認すると、「6 ÷ 4」の評価値は「1.0」となりました。この値は期待していた値とは異なります。何故このような結果になったのでしょうか。その原因は型変換のタイミングにあります。
それでは順を追って演算の流れを確認してみましょう。まず、変数 num1 と num2 の除算が行われます。num1とnum2 はどちらもint 型であるため、「6 ÷ 4」の評価値は 1 の位までしか求められません。そのため、評価値は「1」となります。そして、その評価値を double 型の変数 dnum に代入しています。double 型は int 型よりもサイズが大きいため、暗黙的な型変換が発生し、変数 dnum には「1.0」が代入されます。
このように、除算の段階で整数値型同士の演算を行ったことが、想定外の評価値となってしまった原因
です。
【図6-8 同じ型通しの演算】
実行結果として「1.5」を得るためには、下記のように int 型の変数のいずれかを double 型に型変換してから除算を行い、小数点以下の計算も行えるように編集する必要があります。
double dnum = (double) inum1 / inum2;
【変更後の実行結果】
6÷4 は1.5です。
このように、演算を行う際は、どのタイミングでキャストが行われるか把握する必要があります。そして、必要な場合は適宜キャスト演算子を使用して、意図したタイミングで型変換が行われるように実装することが重要です。
最後までご覧いただきありがとうございました!
コメント