mike-neckのブログ

Java or Groovy or Swift or Golang

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.PrintStreamprintln(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 メソッドの本体の動作は、

  1. System.outPrintStream をスタックする
  2. 出力する文字列をスタックする
  3. println メソッドを呼び出す
  4. return する

であるから、これをそのまま記述する。

                    codeBuilder -> codeBuilder
                            .getstatic(systemOutPrintStream)
                            .ldc(helloWorldConstant)
                            .invokevirtual(printStreamPrintln)
                            .return_()

最後にこれらを ClassBuilderwithMethodBody にわたす。

            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 を実行

気になるところと言えば、 HelloWorldClassjavap がどのような感じになっているのかを確認してみる。

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年の年の瀬に JavaHello World を出力するというブログを書けたので、そこそこ満足。