本題のとおりなので、早速コードを書いていく。
まず、 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 を出力するというブログを書けたので、そこそこ満足。