2023年も終わりだしそろそろ Java で Hello World でもしてみようと思う
本題のとおりなので、早速コードを書いていく。
まず、 static void main を書いておく
public interface HelloWorldClassAPI { static void main(String[] args) { // この中に Hello World を書いていく } }
そうしたら、パッケージ名とクラス名を決める。
ついでにファイルのルートの出力先も決めてしまえば、パッケージ名とクラス名からファイルのパスも決定される。
また、パッケージ名とクラス名が決まれば、クラスの定義もできあがる。
public interface HelloWorldClassAPI { static void main(String[] args) { String packageName = "com.example.helloworld"; // パッケージ名 String className = "HelloWorldClass"; // クラス名 Path helloWorldClassClassFile = Path.of( // ファイルの出力先 "outputs", "classes", packageName.replace('.', '/'), STR."\{className}.class" ); ClassDesc classDesc = ClassDesc.of(packageName, className); // クラスの定義 } }
次にクラスの中身を記述していく。
特にクラスの出力詳細を決めずにクラスファイルを作る。
そして、クラスの内容を書き出していく。
なお、ファイルの書き出し先のディレクトリーが存在しないと例外が発生するので、あらかじめディレクトリーを作っておく。
if (!Files.exists(helloWorldClassClassFile.getParent())) { // クラスファイルのディレクトリーをあらかじめ作っておく Files.createDirectories(helloWorldClassClassFile.getParent()); } ClassFile classFile = ClassFile.of(); // クラスファイル classFile.buildTo(helloWorldClassClassFile, classDesc, classBuilder -> { // ここに Hello World の本体を書いていく });
Hello World といえば、文字列「Hello World」 という文字列を出力するプログラムである。
そこで「Hello World」 をコンスタントプールに作っておく。
ConstantPoolBuilder constantPool = classBuilder.constantPool();
StringEntry helloWorldConstant = constantPool.stringEntry("Hello World");
つづいて、Java での Hello World といえば、 System.out.println である。
これは、 java.lang.System クラスのスタティックでパブリックなフィールドで java.io.PrintStream のインスタンスである out に対して、
println(String) というメソッドを呼び出している。
まず java.lang.System にある out フィールドを取得して、次に java.io.PrintStream の println(String) メソッドの参照を取得する。
なお、 println(String) の戻り値は void であるが、 ClaassDesc.of("void") とすると、 void というクラスを探しにいこうとして、 ClassNoDefFoundError が出てくるので、 void の正しい形(V)を用いて、 ClassDesc.ofDescriptor("V") と記述する必要があるので注意が必要。
ClassDesc javaIoPrintStream = ClassDesc.of("java.io.PrintStream"); FieldRefEntry systemOutPrintStream = constantPool.fieldRefEntry( ClassDesc.of("java.lang.System"), "out", javaIoPrintStream ); MethodRefEntry printStreamPrintln = constantPool .methodRefEntry( javaIoPrintStream, "println", MethodTypeDesc.of( ClassDesc.ofDescriptor("V"), ConstantDescs.CD_String));
これで、コンスタントプールへの値の設定が完了した。
Java での Hello World の実際の表示処理は多くの場合 public static void main(String[] args) に書いてある。
そこで、これからは public static void main(String[] args) を書いていく。
main メソッドののパラメーターと戻り値は String[] と void である。
MethodTypeDesc mainMethodTypeDesc = MethodTypeDesc.of(
ClassDesc.ofDescriptor("V"), // 戻り値 void
ConstantDescs.CD_String.arrayType() // パラメーターは String の配列
);
main メソッドのアクセスフラグは public static である。
int accessFlag = AccessFlag.PUBLIC | AccessFlag.STATIC;
main メソッドの本体の動作は、
System.outのPrintStreamをスタックする- 出力する文字列をスタックする
printlnメソッドを呼び出すreturnする
であるから、これをそのまま記述する。
codeBuilder -> codeBuilder
.getstatic(systemOutPrintStream)
.ldc(helloWorldConstant)
.invokevirtual(printStreamPrintln)
.return_()
最後にこれらを ClassBuilder の withMethodBody にわたす。
MethodTypeDesc mainMethodTypeDesc = MethodTypeDesc.of(
ClassDesc.ofDescriptor("V"), // 戻り値 void
ConstantDescs.CD_String.arrayType() // パラメーターは String の配列
);
int accessFlag = AccessFlag.PUBLIC | AccessFlag.STATIC;
classBuilder.withMethodBody(
"main",
mainMethodTypeDesc,
accessFlag,
codeBuilder -> codeBuilder
.getstatic(systemOutPrintStream)
.ldc(helloWorldConstant)
.invokevirtual(printStreamPrintln)
.return_()
);
これで完成である。全体はこの様になる。
public interface HelloWorldClassAPI { static void main(String[] args) { // この中に Hello World を書いていく String packageName = "com.example.helloworld"; // パッケージ名 String className = "HelloWorldClass"; // クラス名 Path helloWorldClassClassFile = Path.of( // ファイルの出力先 "outputs", "classes", packageName.replace('.', '/'), STR."\{className}.class" ); ClassDesc classDesc = ClassDesc.of(packageName, className); // クラスの定義 if (!Files.exists(helloWorldClassClassFile.getParent())) { // クラスファイルのディレクトリーをあらかじめ作っておく Files.createDirectories(helloWorldClassClassFile.getParent()); } ClassFile classFile = ClassFile.of(); // クラスファイル classFile.buildTo(helloWorldClassClassFile, classDesc, classBuilder -> { // ここに Hello World の本体を書いていく ConstantPoolBuilder constantPool = classBuilder.constantPool(); StringEntry helloWorldConstant = constantPool.stringEntry("Hello World"); ClassDesc javaIoPrintStream = ClassDesc.of("java.io.PrintStream"); FieldRefEntry systemOutPrintStream = constantPool.fieldRefEntry( ClassDesc.of("java.lang.System"), "out", javaIoPrintStream ); MethodRefEntry printStreamPrintln = constantPool .methodRefEntry( javaIoPrintStream, "println", MethodTypeDesc.of( ClassDesc.ofDescriptor("V"), ConstantDescs.CD_String)); MethodTypeDesc mainMethodTypeDesc = MethodTypeDesc.of( ClassDesc.ofDescriptor("V"), // 戻り値 void ConstantDescs.CD_String.arrayType() // パラメーターは String の配列 ); int accessFlag = AccessFlag.PUBLIC | AccessFlag.STATIC; classBuilder.withMethodBody( "main", mainMethodTypeDesc, accessFlag, codeBuilder -> codeBuilder .getstatic(systemOutPrintStream) .ldc(helloWorldConstant) .invokevirtual(printStreamPrintln) .return_() ); }); } }
ここから、プログラムを実行していきたいのだが、前提として以下の通り Java22 以上がインストールされていることを確認する

以下実行結果
HelloWorldClassAPI を実行

HelloWorldClass を実行

気になるところと言えば、 HelloWorldClass を javap がどのような感じになっているのかを確認してみる。
Classfile /Users/mike/tmp/java/projects/java-22-ea-project/outputs/classes/com/example/helloworld/HelloWorldClass.class
Last modified 2023/12/30; size 387 bytes
SHA-256 checksum 5b43b4fde75a59a9af10fa884588cb0ba1147c69fb89acbe3118ec4ffb533028
public class com.example.helloworld.HelloWorldClass
minor version: 0
major version: 66
flags: (0x0001) ACC_PUBLIC
this_class: #2 // com/example/helloworld/HelloWorldClass
super_class: #24 // java/lang/Object
interfaces: 0, fields: 1, methods: 1, attributes: 0
Constant pool:
#1 = Utf8 com/example/helloworld/HelloWorldClass
#2 = Class #1 // com/example/helloworld/HelloWorldClass
#3 = Utf8 Hello World
#4 = String #3 // Hello World
#5 = Utf8 MY_FIELD
#6 = Utf8 Ljava/lang/String;
#7 = Utf8 java/lang/System
#8 = Class #7 // java/lang/System
#9 = Utf8 out
#10 = Utf8 Ljava/io/PrintStream;
#11 = NameAndType #9:#10 // out:Ljava/io/PrintStream;
#12 = Fieldref #8.#11 // java/lang/System.out:Ljava/io/PrintStream;
#13 = Utf8 java/io/PrintStream
#14 = Class #13 // java/io/PrintStream
#15 = Utf8 println
#16 = Utf8 (Ljava/lang/String;)V
#17 = NameAndType #15:#16 // println:(Ljava/lang/String;)V
#18 = Methodref #14.#17 // java/io/PrintStream.println:(Ljava/lang/String;)V
#19 = Utf8 main
#20 = Utf8 ([Ljava/lang/String;)V
#21 = NameAndType #5:#6 // MY_FIELD:Ljava/lang/String;
#22 = Fieldref #2.#21 // com/example/helloworld/HelloWorldClass.MY_FIELD:Ljava/lang/String;
#23 = Utf8 java/lang/Object
#24 = Class #23 // java/lang/Object
#25 = Utf8 ConstantValue
#26 = Utf8 Code
{
public static final java.lang.String MY_FIELD;
descriptor: Ljava/lang/String;
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: String Hello World
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: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
3: getstatic #22 // Field MY_FIELD:Ljava/lang/String;
6: invokevirtual #18 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
9: return
}
微妙に javac で作ったものとは異なっているものの、妥当だと思う(偉そう)
2023年の年の瀬に Java で Hello World を出力するというブログを書けたので、そこそこ満足。