はじめに
JavaSilverの黒本や紫本で下記のような図があり、個人学習でも実務でも出会ったことがなかった演算子があったのでまとめていきます。
ちなみにJavaSilverの試験対策として問題をいくつか解きましたが、
筆者は本記事で紹介する演算子が登場する問題は見たことがありません。
そのため、知らなくても合格はできるはず。
ビットを反転する演算子「~」
単項演算子のひとつ。
ビットを反転してくれます。
基本情報を学習したことがある人は思い出して欲しいのですが、
ビット反転とは「0」のものを「1」に。
もしくは逆のことを行います。
例えば、「1010」なら「0101」になります。
public class Hanten1
{
public static void main( String[] args )
{
int i = 1;
System.out.println("〜演算子なし:" + Integer.toBinaryString(i));
System.out.println("〜演算子あり:" + Integer.toBinaryString(~i));
}
}
〜演算子なし:1
〜演算子あり:11111111111111111111111111111110
2桁目から32桁目を0埋めしていないため、実行結果には「〜演算子なし:1」と表示されていますが、これは「00000000000000000000000000000001」で(int型は32ビットなので)、
「〜演算子あり」ではこれの「0」の部分を「1」に、「1」の部分を「0」にしています。
せっかくなので0埋めバージョンのコードと実行結果も載せておきます。
public class Hanten0ume
{
public static void main( String[] args )
{
int i = 1;
System.out.println("〜演算子なし:" + String.format("%32s",Integer.toBinaryString(i)).replace(" ", "0"));
System.out.println("〜演算子あり:" + Integer.toBinaryString(~i));
}
}
〜演算子なし:00000000000000000000000000000001
〜演算子あり:11111111111111111111111111111110
今度は変数の値を7に変えてみます。
7は32ビットの2進数で表すと「00000000000000000000000000000111」です。
ここでは理解を深めるために2進数と10進数それぞれで表現された結果が出る処理にします。
public class Hanten2
{
public static void main( String[] args )
{
int i = 7;
System.out.println("〜演算子なし(2進数):" + String.format("%32s",Integer.toBinaryString(i)).replace(" ", "0"));
System.out.println("〜演算子あり(2進数):" + Integer.toBinaryString(~i));
System.out.println("〜演算子なし(10進数):" + i);
System.out.println("〜演算子あり(10進数):" + ~i);
}
}
〜演算子なし(2進数):00000000000000000000000000000111
〜演算子あり(2進数):11111111111111111111111111111000
〜演算子なし(10進数):7
〜演算子あり(10進数):-8
-8は32ビットの2進数で表すと「11111111111111111111111111111000」です。
下記の進数変換のサイトも併せて使ってみるのをお勧めします。
この「~」ですが、これは整数値型でしか利用できません。
そのため、float型、double型の数値に「~」を使うと、
単項演算子の不正なオペランドタイプであるとしてコンパイルエラーになります。
public class Hanten3
{
public static void main( String[] args )
{
int i = 7;//コンパイル実行可能
System.out.println("〜演算子あり(int):" + ~i);
long l = 7;//コンパイル実行可能
System.out.println("〜演算子あり(long):" + ~l);
byte b = 7;//コンパイル実行可能
System.out.println("〜演算子あり(byte):" + ~b);
short s = 7;//コンパイル実行可能
System.out.println("〜演算子あり(short):" + ~s);
float f = (float)7.0;//コンパイルエラー
System.out.println("〜演算子あり(float):" + ~f);
double d = 7.0;//コンパイルエラー
System.out.println("〜演算子あり(double):" + ~d);
}
Main.java:18: error: bad operand type float for unary operator '~'
System.out.println("〜演算子あり(float):" + ~f);
^
Main.java:21: error: bad operand type double for unary operator '~'
System.out.println("〜演算子あり(double):" + ~d);
^
2 errors
参考記事
シフト演算子 「<<」「>>」「>>>」「<<<」
シフトってなんだっけ?というところから本記事ではおさらいできればと思います。
こちらも基本情報を学習された方は思い出していただくと理解しやすいです。
①「<<」
「a << n」でaを左にnビットシフトします。
例)
10進数の12を2進数にすると1100です。
1100 << 1 は 1100を1ビット左にシフトします。
2進数で左にシフトすると、シフトされた分だけ×2されます。
今回の場合、11000になります。
0が右に一つ増えましたね。
こちらを10進数に直すと24になります。(12×2がされた)
こちらを実際のプログラムで見てみましょう。
public class Shift1
{
public static void main( String[] args )
{
int a = 12; // 2進数(32ビット) : 1100
int n = 1;
System.out.println("10進数:" + (a << n));
System.out.println("2進数:" + Integer.toBinaryString(a << n));
}
10進数:24
2進数:11000
②「>>」
「a >> n」でaを右にnビットシフトします。
例)
1100 >> 1 は 1100を1ビット右にシフトします。
2進数で右にシフトすると、シフトされた分だけ÷2されます。
今回の場合、110になります。
0が一つ減りましたね。
こちらを10進数に直すと6になります。(12÷2がされた)
こちらを実際のプログラムで見てみましょう。
public class Shift2
{
public static void main( String[] args )
{
int a = 12; // 2進数(32ビット) : 1100
int n = 1;
System.out.println("10進数:" + (a >> n));
System.out.println("2進数:" + Integer.toBinaryString(a >> n));
}
10進数:6
2進数:110
③「>>>」
「a >>> n」でaを右にnビットシフトします。
「>>」と何が違うんだ?と思われるのが自然でしょう。
「>>」は符号を考慮する算術シフトで
「>>>」は符号を考慮しない論理シフトです。
下記の例でその違いについて説明します。
シフトする対象の数値を-8、
一つ右にシフトします。
10進数の-8を2進数にすると11111111111111111111111111111000(32ビット)です。
11111111111111111111111111111000 >> 1 の場合、1ビット右にシフトするのですが、
先頭に1が入り、末尾の0が一つ消えて11111111111111111111111111111100になります。
先頭に入った1はマイナスを表します。
これは符号を考慮する算術シフトをしているので、
先頭に1を入れます。
11111111111111111111111111111100を10進数にすると-4です。
補足
シフト対象の数値がプラスの値の場合、先頭には0が入っています。
これを算術シフトした場合、プラスの符号が考慮されるので
先頭に0(プラスを表す)が入り、末尾の0が一つ消えます。
*便宜上8ビットにしています。
00001000(10進数で8)→算術右シフト→00000100(10進数で4)
続いて11111111111111111111111111111000 >>> 1 の場合を見てみましょう。
これも1ビット右にシフトするのですが、
今度は符号を考慮しない論理シフトです。
符号を考慮しない場合、先頭には0が入ります。
今回の場合先頭に0が入り、末尾の0が一つ消えて01111111111111111111111111111100になります。
01111111111111111111111111111100を10進数にすると2147483644です。
これらの例を実際のプログラムで見てみましょう。
public class Shift3
{
public static void main( String[] args )
{
int a = -8; // 2進数(32ビット) :11111111111111111111111111111000
int n = 1;
System.out.println("10進数:" + (a >> n));
System.out.println("2進数:" + Integer.toBinaryString(a >> n));
System.out.println("10進数:" + (a >>> n));
System.out.println("2進数:" + String.format("%32s", Integer.toBinaryString(a >>> n)).replace(" ", "0"));
}
10進数:-4
2進数:11111111111111111111111111111100
10進数:2147483644
2進数:01111111111111111111111111111100
④「<<<」
この演算子は存在しません。
参考記事
参考記事
シフト演算子をまとめる上で大変参考になりました。
まだまだあるのですが、記事が長くなってきたので、
「JavaSilverの試験で多分問われない演算子②」に続きます
②では「&,^,|」といったビット演算子、三項演算子「?:」について紹介します。
③では「&=,|=,^=,<<=,>>=,>>>=」といった代入演算子について紹介します。