#言いたいこと
Javaにおいて、boolean型変数は1バイトではない!(結論)
...かもしれない。(ボソッ
では、確認していきます。
結論だけよこせ!というヒトは、
「終わりに」まで読み飛ばしちゃってください。
#環境
- Windows 10
- JDK
- 特に注釈が無い限り、OpenJDK 14 で確認しています。
- javacコマンドには適宜 -encoding オプションを付与してください。
- バイナリエディタ
#ローカル変数としてのbooleanのサイズ
次のコードを書きます:
public class Sandbox{
public static void main(String[] args){
boolean b = true;
//StackMapTable属性の為
if(b){
b=true;
}
}
}
これをいつも通りコンパイルし、出力された「.class」ファイルの中身を見ます。
> javac Sandbox.java
> javap -v Sandbox
(前略)
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=1
0: iconst_1
1: istore_1
2: iload_1
3: ifeq 8
6: iconst_1
7: istore_1
8: return
LineNumberTable:
line 3: 0
line 4: 2
line 5: 6
line 7: 8
StackMapTable: number_of_entries = 1
frame_type = 252 /* append */
offset_delta = 8
locals = [ int ]
}
SourceFile: "Sandbox.java"
この
locals = [ int ]
に注目してください。これはローカル変数がintであるということを表しています。(https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-4.html#jvms-4.7.4)
つまり
ローカル変数としてのboolean型変数はint型変数に変換される
ということです。
#クラスフィールドとしてのboolean
##型について
次のコードを書きます。
public class Sandbox{
boolean b;
public static void main(String[] args){
}
}
先ほどと同じようにやります:
> javac Sandbox.java
> javap -v Sandbox
(前略)
{
boolean b;
descriptor: Z
flags: (0x0000)
(中略)
}
SourceFile: "Sandbox.java"
この
descriptor: Z
というのは、「型がbooleanである」ということです。(https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-4.html#jvms-4.3.2)
ということで
クラスフィールドとしてのboolean型変数はboolean型変数として扱われる
事が示されました。(当たり前)
クラスフィールドとしてのbooleanのバイトサイズ
では、この変数のサイズはいくらなのでしょう。
次のコードを書きます:
public class Sandbox{
static int i;
static boolean b;
public static void main(String[] args){
b=true;
i=0x111111;
}
}
まずはこれをコンパイルします。
> javac Sandbox.java
出力された「.class」ファイルをバイナリエディタで開き、
B3 00 0E という並びのバイト列をみつけ、B3 00 07 と書き換えます。すなわち、
これを
こうします。
注釈:B3 00 0E の列以外にも0Eがある場合がありますが、それは書き換えないようにします。
そして、その中身をjavapで見ます。
> javap -v Sandbox
(...前略)
Code:
stack=1, locals=1, args_size=1
0: iconst_1
1: putstatic #7 // Field b:Z
4: ldc #13 // int 1118481
6: putstatic #7 // Field b:Z
9: return
(後略...)
こうなっていれば成功です。
注釈:この操作は、変数iに数値を代入する代わりに、変数bに数値を代入するように書き換えています。
この改造版「.class」を実行します。
> java Sandbox
特に何も表示されない、ということは正常に実行されました。
つまり、boolean変数に3バイトのデータを代入できる、ということであり、
boolean型のクラスフィールドはint型変数と同等の操作ができる
ということです。(https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-2.html#jvms-2.11.1)
#boolean配列について
##boolean配列の型
次のコードを書きます。
public class Sandbox{
static boolean b[];
public static void main(String[] args){
boolean[] lb = new boolean[]{true};
//StackMapTable属性の為
if(lb[0]){
b[0]=true;
}
}
}
これを、コンパイルして中身を見ます。
>javac Sandbox.java
>javap -v Sandbox
(...前略)
{
static boolean[] b;
descriptor: [Z
flags: (0x0008) ACC_STATIC
(...中略...)
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=2, args_size=1
0: iconst_1
1: newarray boolean
3: dup
4: iconst_0
5: iconst_1
6: bastore
7: astore_1
8: aload_1
9: iconst_0
10: baload
11: ifeq 20
14: getstatic #7 // Field b:[Z
17: iconst_0
18: iconst_1
19: bastore
20: return
LineNumberTable:
line 6: 0
line 8: 8
line 9: 14
line 11: 20
StackMapTable: number_of_entries = 1
frame_type = 252 /* append */
offset_delta = 20
locals = [ class "[Z" ]
}
SourceFile: "Sandbox.java"
今までのように、
static boolean[] b;
descriptor: [Z
は、「bは型がboolean配列である」ということです。また、
locals = [ class "[Z" ]
は、「ローカル変数の型がboolean配列である」ということです。
従って、
boolean配列型変数はローカル変数・クラスフィールドに限らずboolean配列型変数として扱われる
事が示されました。
##boolean配列の「一つの要素の」サイズ
boolean配列自体は通常のインスタンスと同等であり、(変数のみに限っては)それと同等のサイズを確保するのは明らかに見えるので、(https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-2.html#jvms-2.11.5)
一つの要素辺りのサイズを調査します。とはいえ、これは答えが書いてありました。
(https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-2.html#jvms-2.3.4)
In Oracle’s Java Virtual Machine implementation, boolean arrays in the Java programming language are encoded as Java Virtual Machine byte arrays, using 8 bits per boolean element.
すなわち、
Oracleによる実装では、一つの要素に8ビット(=1バイト)使われる
ということです。
#終わりに
- ローカル変数としてのboolean型変数はint型変数に変換される。
- クラスフィールドとしてのboolean型変数はboolean型変数として扱われ、int型変数と同等の操作ができる。
- boolean配列型の変数ではローカル変数・クラスフィールドのどちらでもboolean配列型変数として扱われ、Oracleによる実装では、その配列の一つの要素ごとに1バイトのサイズを使用する。
あまりドキュメントを読み込めておりませんので間違いがあれば教えてください。
以下、おまけです。
#番外編
※ここからは「.class」ファイルのオペコードが読める人のみが対象です。
ちなみに、先ほどクラスフィールドの調査において使用した変数bを、
java/lang/System.out と java/io/PrintStream.println:(I)V を用いて
出力してみると、1となっています。(引数がintであることに注意)。
>javap -v Sandbox
(...前略)
static boolean b;
descriptor: Z
flags: (0x0008) ACC_STATIC
(...中略...)
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: iconst_1
1: putstatic #7 // Field b:Z
4: ldc #13 // int 1118481
6: putstatic #7 // Field b:Z
9: getstatic #18 // Field java/lang/System.out:Ljava/io/PrintStream;
12: getstatic #7 // Field b:Z
15: invokevirtual #24 // Method java/io/PrintStream.println:(I)V
18: return
(後略...)
>java Sandbox
1
putstaticのドキュメントによると、
If the value is of type int and the field descriptor type is boolean, then the int value is narrowed by taking the bitwise AND of value and 1, resulting in value'.
とあります。したがって、booleanクラスフィールドの中身は0か1であることが保証されるようです。putfield・bastore も同様でした。
僕がbooleanのサイズを言いきらなかった理由はここにあります。
0か1が保証されるので、実際の変数サイズはintと同じでなくてもよくなります。
ドキュメントを探しましたがそれらしい記述は見当たりませんでした。
追記:古いバージョンでは動作が違うというコメントを頂きました。こちらで改めてOpenJDK8で同じことを行ってみたところ、出力は確かに17でした。少なくともOpenJDK8以前ではboolean変数は1バイトだったようです。
ドキュメントにないならやはりソースを読む必要があります。ということで2021/06/29日時点で最新のOpenJDKのソースにあたりました。
そのソースのこの部分に、次のコードが存在しています。
if (type == T_BOOLEAN || type == T_BYTE) {
data.load_byte_item();
} else {
data.load_item();
}
これによれば、どうやら(フィールドとしての)boolean変数はbyte変数と同じく、長さは1バイトであるということが言えそうです。
もっと直接的なコードがあれば教えてください。
以上です。
更新履歴
- JDKのソースコードにあたる部分を追記 (2021/06/29)
- JDK8以前の情報を追記 (2021/06/29)