2025/04/10

バイトコード入門 その5 オブジェクト生成、メソッドコール

このエントリーをはてなブックマークに追加

前回、スタックマシーンとしてのJVMの基本的な動作を紹介しました。

プリミティブ型の加算という簡単な処理ですが、これが分かればより複雑なバイトコードでも理解できるはずです。

そこで、今回はもう少し複雑な例としてオブジェクトの生成やメソッドコールについて紹介します。

  1. 準備編
  2. スタックマシン
  3. バイトコード処理の構成
  4. バイトコード処理の基礎
  5. オブジェクト生成、メソッドコール (今回)

 

命令セット

今までバイトコードのオペコード(命令、もしくはインストラクション)について簡単な説明しかしていなかったので、ここで紹介しましょう。

とはいってもすべてを紹介するのは大変ですし、おぼえても意味はないので、主に使われるオペコードについて見ていきます。

また、if文やfor文、switch式などの制御構造に関するバイトコードは次回紹介します。

 

オペコードの詳細な定義はJVMSのChapter 6にあります。

リンクはJava 24のJVMSです。JVMSも毎バージョンごとにアップデートされているので、なるべく最新を参照するようにしてください。

 

オペコードの構成

現在定義されているオペコードは200種類ほどあります。しかし、それらがすべて違う動作を表しているわけではありません。動作は同じだけど型が違う、動作は同じだけどローカル変数配列のインデックスが違うなどがあります。

型について接頭辞、インデックスなどは接尾辞で表します。接尾辞はインデックス以外にも繰り返し回数などがあり、オペコードによって意味が異なります。一方の接頭辞は常に型を表します。接頭辞の一覧を次に示します。

接頭辞
i int
l long
s short
b byte
c char
f float
d double
z boolean
a 参照

オペランドスタックやローカル変数配列にはプリミティブ型の値か参照しか入れることができません。このため、オブジェクトを扱う場合はヒープに存在するオブジェクトへの参照として扱われます。

もしかしたら、今後導入が予定されているValue Classであれば、直接オペランドスタックに積めるようになるかもしれません。

とはいうものの、バイトコードが増えるわけではなく、あくまでも最適化された場合に限るはずです。

 

たとえば、前回使用したオペランドのiload_1は、接頭辞がi、接尾辞が_1です。つまり、int型のloadで、ローカル変数配列のインデックス1からのロードということを表しています。

 

主なオペコード

オペコードが本体と接頭辞、接尾辞から構成されることが分かったところで、主なオペコードについて見ていきましょう。

 

スタック操作

オペランドスタックに対して何らかの操作を行うオペコードです。オペランドスタックと書くと長くなるので、以下では単にスタックと表記します。

  • ldc: コンスタントプールの定数をスタックに積む
  • const: インデックスで指定された値をスタックに積む (型、接尾辞あり)
  • bipush/sipush: byte値、short値をスタックに積む
  • pop: スタックから値を取り除く
  • dup: スタック末尾の要素を複製
  • swap: スタック末尾の2データを入れ替え

ldcはロードコンスタントのことですね。コンスタントプールは定数で書き換えることはないため、ストアはないです。

constは接尾辞で値を指定します。int型の1であれば iconst_1 となります。接尾辞は-1(m1)から5まであり、これを超える範囲の場合、次に紹介するbipush/sipushを使用します。

bipush/sipushもconstに似ていますが、byte値もしくはshort値を直接スタックに積みます。他の型はありません。

整数型の変数で、byteもしくはshortの範囲に収まるものはbipush/sipushが使われます。shortを超える範囲の場合、コンスタントプールにある定数を使用してldcが使われます。

 

ローカル変数

オペランドスタックとローカル変数配列とのやり取りに関するオペコードです。

  • load: ローカル変数配列の値をスタックに積む (型、接尾辞あり)
  • store: スタックから値を取り出し、ローカル変数配列に置く (型、接尾辞あり)

接尾辞のインデックスは0, 1, 2しかないため、それ以上の配列インデックスは iload 5 のようにオペコードの後に指定します。

 

算術演算

四則演算などの算術演算を行うオペコードです。

  • add: 加算 (型あり)
  • sub: 減算 (型あり)
  • mul: 乗算 (型あり)
  • div: 除算 (型あり)
  • rem: 除算の余り (型あり)
  • neg: 符号反転 (型あり)
  • iinc: インクリメント

インクリメントを行うiincはint型だけです。long型の場合はladdが使われます。

 

論理演算

AND/OR/XORとシフト演算を行うオペコードです。

  • iand/land: 論理積
  • ior/lor: 論理和
  • ixor/lxor: 排他論理和
  • ishl/lshl: 左シフト
  • ishr/lshr: 右シフト (符号は維持)
  • iushr/lushr: 右シフト

論理演算はint型とlong型に対してのみ存在します。byte/short/charに対してint型のオペコードが使われます。

 

オブジェクト関連

オブジェクトの生成などで使用するオペコードです。

  • new: インスタンス生成
  • getfield: インスタンス変数の値をスタックに積む
  • putfield: スタックから値を取り出し、インスタンス変数に代入
  • getstatic: クラス変数の値をスタックに積む
  • putstatic: スタックから値を取り出し、クラス変数に代入

newはインスタンスを生成するだけで、コンストラクターのコールは含まれていません。

コンストラクターのコールは、メソッドコールのバイトコードで行います。具体的な例は後述します。

getfield/putfieldは接頭辞による型の指定はありませんが、オペコードの後に指定します。getstatic/putstaticでも同じです。

 

配列関連

配列の生成や配列からの値の取り出し、代入などで使用するオペコードです。

  • newarray: プリミティブ型を要素にとる配列の生成
  • anewarray: 参照型を要素にとる配列の生成
  • multianewarray: 多次元配列の生成
  • aload: 配列のインデックスの要素をスタックに積む (型あり)
  • astore: スタックから値を取り出し、配列のインデックスの要素に代入 (型あり)
  • arraylength: 配列の長さをスタックに積む

Javaでは配列はオブジェクトの一種、つまり参照型として表されます。しかし、配列を扱うためのバイトコードも提供されています。

loadやstoreに接頭辞のaがついているのは、配列が参照型だからです。さらに要素の型をその前に接頭辞として付加します。たとえば、int型の配列から値を取り出すときはialoadになります。

 

メソッドコール

メソッドコールを行うバイトコードはメソッドの種類により5種類提供されています。

  • invokevirtual: インスタンスメソッド
  • invokeinterface: インタフェースメソッド
  • invokestatic: クラスメソッド
  • invokespecial: コンストラクター、プライベートメソッド、スーパークラスのインスタンスメソッド
  • invokedynamic: コールするメソッドを実行時に決定させるメソッドコール
  • return: メソッドから返る。(型あり)

メソッドの種類によってオペコードを変えますが、メソッド種類ごとにスタックに積んでおく値も異なります。たとえば、インスタンスメソッドであればコールするメソッドのオブジェクトが必要ですが、クラスメソッドであれば必要ありません。

特殊なのがinvokedynamicです。invokedynamicでは初回コール時にbootstrapという指定されたメソッドをコールし、ターゲットとなるメソッドを同定させてから、メソッドコールを行います。

2回目以降は同定したメソッドコールを直接行います。

invokedynamicに関しては、YujiSoftwareさんとJJUG CCCでセッションを行ったので、その資料をご参照ください。

 

メソッドから返るのがreturnです。int型の値を返すのであればがireturnになりますが、返り値がない場合はreturnのまま使用します。

 

残りは次回に回しましょう。

前回は算術演算を行うメソッドだけだったので、今回はその残りの部分の動作を見ていきます。

 

オブジェクト生成、メソッドコール

前章でオペコードにどのようなものがあるか紹介したので、実際にこれらのオペコードを使っていく様子を見てみましょう。

題材は前回と同じ足し算を行うだけのAdderクラスです。

public class Adder {
    public int add(int x, int y) {
        int z = x + y;
        return z;
    }

    public void static main(String... args) { 
        Adder adder = new Adder();
        int result = adder.add(2, 3);
        System.out.println(result);
    }
}

 

前回はaddメソッドの実行を見ましたが、今回はmainメソッドです。

Adderクラスをコンパイルオプションの-gを付加してコンパイルし、javap -vでmainメソッドを逆コンパイルしたのが以下です。

  public static void main(java.lang.String...);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0089) ACC_PUBLIC, ACC_STATIC, ACC_VARARGS
    Code:
      stack=3, locals=3, args_size=1
         0: new           #7    // class Adder
         3: dup
         4: invokespecial #9    // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: iconst_2
        10: iconst_3
        11: invokevirtual #10   // Method add:(II)I
        14: istore_2
        15: getstatic     #14   // Field java/lang/System.out:Ljava/io/PrintStream;
        18: iload_2
        19: invokevirtual #20   // Method java/io/PrintStream.println:(I)V
        22: return
      LineNumberTable:
        line 8: 0
        line 9: 8
        line 10: 15
        line 11: 22
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      23     0  args   [Ljava/lang/String;
            8      15     1 adder   LAdder;
           15       8     2 result   I
}

 

mainメソッドがコールされた時のオペランドスタックとローカル変数配列の初期状態は次のようになります。

 

addメソッドはインスタンスメソッドだったので、ローカル変数配列のインデックス0にはthisが格納されていました。しかし、mainメソッドはクラスメソッド(staticメソッド)なので、thisはありません。そのため、インデックス0にはmainメソッドの引数が格納されます。

argsは文字列の配列ですが、それがそのままローカル変数配列に格納されるわけではありません。文字列配列argsの実態はヒープに作成され、ローカル変数配列にはその参照が格納されます。

 

コンスタントプール

mainメソッドでは、最初にAdderオブジェクトを生成し、adder変数に代入しています。

まずはAdderオブジェクトの生成です。

0行のnew #7がオブジェクトを生成している部分です。この#7はコンスタントプールへの参照になります。

addメソッドではコンスタントプールを使用することがなかったので、ここで触れておきましょう。

javap -vの出力にはコンスタントプールが含まれています。その一部を以下に示します。

Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Class              #8             // Adder
   #8 = Utf8               Adder
   #9 = Methodref          #7.#3          // Adder."<init>":()V
  #10 = Methodref          #7.#11         // Adder.add:(II)I
  #11 = NameAndType        #12:#13        // add:(II)I
  #12 = Utf8               add
  #13 = Utf8               (II)I

 

コンスタントプールは番号付きでこのように並べられています。

さて、先ほどのnew #7の#7を見てみるとClass #8とあります。Classは文字通りクラスを示しています。そのクラス名が#8になります。

#8は、Utf8 Adderです。Utf8は文字列定数を示しています。実際にクラスファイルでは、文字列をUTF-8で表記しています。そして、その後の"Adder"がその文字列定数の実態です。

つまり、#7でクラスを示し、そのクラス名は#8で"Adder"であるということを示しています。

 

オブジェクト生成

では、mainメソッドに戻りましょう。

new #7では、Adderクラスのオブジェクト生成を行い、その結果、ヒープに生成したオブジェクトの参照をオペランドスタックに積みます。

 

ここまではオブジェクトの生成をしただけの状態で、コンストラクターはコールしていません。

mainメソッドの3行、4行がコンストラクターをコールする部分です。

まず、3行でdupを行います。dupはduplicateのコートで、スタックの値を複製します。

ここではAdderオブジェクトへの参照を複製します。

 

そして、その後のinvokesupecialでコンストラクターをコールします。

invokespecialはコンストラクター、プライベートメソッド、スーパークラスのインスタンスメソッドをコールするためのオペコードです。

コールするメソッドが、コンスタントプールの#9です。#9の部分を見てみると...

   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Class              #8             // Adder
   #8 = Utf8               Adder
   #9 = Methodref          #7.#3          // Adder."<init>":()V

 

#9はMethodrefで、メソッドへの参照を示しています。その値の#7は先ほど見たようにAdderクラスを指しています。

#3を見てみると、NameAndTypeとなっています。これはメソッド名とメソッドのシグネチャー(引数と戻り値の型)を示しています。

NameAndType #5:#6の#5がメソッド名、#6がシグネチャーです。

#5を見てみると、文字列定数で<init>です。

コンストラクター名は、Javaのコードではクラス名と同じですが、バイトコードではどのクラスでも<init>になります。

シグネチャーを表すのが、#6の文字列定数の()Vです。

()の中に引数の型が示されますが、Adderクラスのコンストラクターはデフォルトコンストラクターで引数なしなので、単に()となっています。

()の後に示されるのが、戻り値の型です。Vはvoid、つまり戻り値なしです。

コールするメソッドが分かったので、実際にコールするわけですが、その時にオペランドスタックの値が使われます。

ここでは、Adderオブジェクトへの参照が2つスタックに格納されていますが、1つがコールするメソッドのオブジェクト、そしてもう1つがそのメソッドへの暗黙の引数と考えることができます。

オブジェクト生成はnew, dup, invokespecialが常にセットになっているので、そういうものだと考えていただいて大丈夫です。

invokespecialを実行すると、mainメソッドはそのままにして、Adderクラスのコンストラクターを実行するフレームがJVMスタックに積まれます。

 

そして、コンストラクターの実行が完了すると、コンストラクターのフレームが取り除かれて、mainメソッドが再開します。

オペランドスタックには、コンストラクターをコールした後のAdderオブジェクトが積まれます。

 

さて、その後の7行のastore_1で、Adderオブジェクトの参照をローカル変数配列のインデックス1に格納します。

 

これで、Adder adder = new Adder();の処理が完了しました。

 

インスタンスメソッドコール

Adderオブジェクトが生成出来たので、次はaddメソッドをコールする部分です。

         8: aload_1
         9: iconst_2
        10: iconst_3
        11: invokevirtual #10   // Method add:(II)I
        14: istore_2

インスタンスメソッドをコールするには、コールするメソッドのオブジェクトとメソッドの引数をオペランドスタックに積んでおきます。

まず、aload_1でローカル変数のインデックス1にあるAdderオブジェクトを積みます。

 

9行と10行のiconstは、定数をスタックに積むオペコードです。接尾語の_2と_3がスタックに積む値を示しています。

 

constの接尾語は-1から5までなので、これを超える数にするとどうなるでしょう。

byte型の範囲であればbipush、short型の範囲であればsipush、それを超える場合はldcが使われます。

たとえば、adder(128, 1_000_000);とすると、バイトコードは次のように変化します。

         8: aload_1
         9: sipush        128
        12: ldc           #10                 // int 1000000
        14: invokevirtual #11                 // Method add:(II)I
        17: istore_2

 

128にはsipush、1,000,000にはldcでコンスタントプールの値が使われています。

 

メソッドのターゲットとなるオブジェクトと引数をオペランドスタックに積んだ後に、インスタンスメソッドをコールするinvokevirtualを実行します。

どのメソッドをコールするのかは、コンスタントプールの#10に記述されています。

Adderクラスのデフォルトコンストラクターと同じようにコンスタントプールをたどっていくと、Adder.add(II)Iになることが分かります。

(II)が引数が2つで両方ともint、最後のIで戻り値の型もintだということを示しています。

invokevirtualを実行すると、mainメソッドのフレームは一時停止し、新たにaddメソッドのフレームがJVMスタックに積まれ、addメソッドの実行が始まります。

 

addメソッドの実行は前回紹介したので、ここでは省略します。

addメソッドの実行が完了すると、addメソッドのフレームは取り除かれ、addメソッドの戻り値がmainメソッドのフレームのオペランドスタックに積まれます。

 

後は、その値をistore_2を使用してローカル変数配列のインデックス2に格納します。

 

ローカル変数配列のインデックス2はresult変数を表しているので、addメソッドをコールしてresultに代入するJavaのコードが完了したことになります。

 

最後のSystem.out.printlnメソッドもインスタンスメソッドなので、メソッドコールの方法はaddメソッドの場合と同じです。

少しだけ違うのが、printlnメソッドのターゲットとなるオブジェクトのSystem.outです。

System.outはSystemクラスのクラス変数(static変数)であるoutです。

このため、System.outを取得するために、オペコードのgetstaticを使用します。

15行のgetstatic #14がその部分です。

コンスタントプールの#14を見てみると、次のようになっています。

  #14 = Fieldref           #15.#16        // java/lang/System.out:Ljava/io/PrintStream;
  #15 = Class              #17            // java/lang/System
  #16 = NameAndType        #18:#19        // out:Ljava/io/PrintStream;
  #17 = Utf8               java/lang/System
  #18 = Utf8               out
  #19 = Utf8               Ljava/io/PrintStream;

 

#14がFieldrefでフィールドの参照を示していることが分かります。その値が#15.#16です。

#15の方がフィールドのクラス、#16がフィールド名と型を表します。

さらにたどっていくと、#15がjava.lang.Systemを指していることが分かります。

#16はNameAndTypeで、#18がフィールド名、#19が型を表しています。#18には文字列定数でout、#19も文字列定数でLjava/io/PrintStreamとなっています。

今まで、Iがint、voidがVだということは出てきました。プリミティブ型ではなく参照型の場合、接頭語としてLが使用され、その後にパッケージを含めたクラス名が続きます。

Aではなくて、Lになることにご注意ください。

これで、getstaticでSystem.outの参照がオペランドスタックに積まれることが分かりました。

後は、addメソッドと同じように引数もオペランドスタックに積んで、invokevirtualを実行します。

 

mainメソッドにはreturn文の記述はありませんが、実際には省略されているだけでreturn文は必ずあります。

25行のreturnがそれに相当します。mainメソッドに戻り値はないので、接頭辞はないreturnを使用します。

returnを実行すると、mainメソッドのフレームが破棄され、Adderクラスの実行が完了します。

 

まとめ

Javaだと高々数行のコードですが、バイトコードで見てみるとJavaのコードを細かく分解して実行されます。

ここで示したように、メソッド単位でフレームが作られ、その内部でオペランドスタックとローカル変数配列で処理を進めていくのです。

さて、次回はif文やforループなどの制御構造について紹介する予定です。

2025/04/01

バイトコード入門 その4 バイトコード処理の基礎

このエントリーをはてなブックマークに追加

Java 24のリリース関連エントリーが続きましたが、再びバイトコード入門に戻ってきました。

今回はJVMスタックに積まれたフレームでどのようにバイトコードを処理するのか紹介していきます。今回は動作を説明するため、個々の命令についての説明は必要最低限とさせていただきます。

  1. 準備編
  2. スタックマシン
  3. バイトコード処理の構成
  4. バイトコード処理の基礎 (今回)

 

バイトコード処理構成のおさらい

まず、バイトコードを処理する構成について簡単におさらいしておきましょう。

バイトコードを実行するために、JVMスタックが使用されるということを前回説明しました。

JVMスタックはスレッドごとに作成されます。ここでのスレッドはOSのスレッドと1対1に対応するPlatform Threadを指しています。Virtual ThreadはPlatform Thread上で動作するので、Virtual Thread用にJVMスタックが作られることはありません。

JVMスタックには、メソッドコールごとにフレームが積まれます。

たとえば、mainメソッドからfooメソッドがコールされ、fooメソッドからbarメソッドがコールされて、barメソッドを処理している場合、JVMスタックにはmainメソッドのフレーム、fooメソッドのフレーム、barメソッドのフレームという3つのフレームが積まれます。

barメソッドの処理が完了し、fooメソッドに戻ってきた時点でbarメソッドのフレームもJVMスタックから取り除かれます。

 

フレームは主にローカル変数用配列と、バイトコード処理中のデータを保持するオペランドスタックから構成されます。

いずれもそのサイズはjavacコンパイル時に決定します。

 

バイトコード処理

今回は動作だけを説明するので、とても簡単なアプリケーションを使用しましょう。

Adderクラスは整数の足し算をするメソッドaddメソッドを持つクラスです。

public class Adder {
    public int add(int x, int y) {
        int z = x + y;
        return z;
    }

    public void static main(String... args) { 
        Adder adder = new Adder();
        int result = adder.add(2, 3);
        System.out.println(result);
    }
}

 

このプログラムが実行されると、メインスレッドに対応するJVMスタックが生成されます。そして、mainメソッドがコールされて、JVMスタックにもmainメソッドに対応するフレームが積まれます。

mainメソッドの中でaddメソッドがコールされると、JVMスタックにもaddメソッドに対応するフレームが積まれます。

この状態を表したのが、以下の図です。

 

では、addメソッド実行の様子を追っていきましょう。

 

addメソッドのバイトコード

Adderクラスをデバッグオプションの-gを使用してコンパイルし、javapでaddメソッドを調べたのが以下です(-gオプションについては、前回を参照してください)。

  private int add(int, int);
    descriptor: (II)I
    flags: (0x0002) ACC_PRIVATE
    Code:
      stack=2, locals=4, args_size=3
         0: iload_1
         1: iload_2
         2: iadd
         3: istore_3
         4: iload_3
         5: ireturn
      LineNumberTable:
        line 3: 0
        line 4: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   LAdder;
            0       6     1     x   I
            0       6     2     y   I
            4       2     3     z   I

ここで使用されているオペコード(命令)を簡単に説明しておきましょう。

  • iload: int型の整数をローカル変数配列からオペランドスタックに積む。_の後の数字はローカル変数配列のインデックス
  • iadd: int型の加算
  • istore: オペランドスタックの先頭にあるint型整数を取り出し、ローカル変数配列に格納。_の後の数字はローカル変数配列のインデックス
  • ireturn: オペランドスタックの先頭にあるint型整数を戻り値としてメソッドを完了させる

この後に実際の動きを説明するので、ここではそんなものぐらいに思っておいてください。

 

addメソッドの動作

では、オペランドスタックとローカル変数配列を含めて、addメソッドの動作を追っていきます。

addメソッドがコールされると、引数がローカル変数配列に格納されます。また、インスタンスメソッドの場合、暗黙の引数としてthisが渡され、それも一緒にローカル変数配列に置かれます。

addメソッドを引数としてx=2, y = 3でコールされた直後の様子は次のようになります。

 

 

ローカル変数配列のインデックス0にthis、1にxの値である2、2にyの値である3が入ります。

上図では分かりやすさのため、ローカル変数配列にx、y、zを記述してありますが、実際にはローカル変数名はコンパイル時に削除され、インデックスだけで扱われます。

ただし、前回説明したようにコンパイル時に-gオプションをつければ、LocalVariableTableが付随し、インデックスと変数名の対応が付けられるようになります。

 

はじめのiload_1はローカル変数配列のインデックス1の値をオペランドスタックに積むということです。iloadのiはint型を表しています。i以外にも接頭辞はありますが、それは次回説明することにします。

この結果、オペランドスタックには2が積まれます。

 

同様に、iload_2でオペランドスタックに3が積まれます。

 

次のiaddはint型の加算です。

ここでは、「バイトコード入門その2」で示したHPの電卓のように、演算に必要な個数だけ値をスタックから取り出し、結果をスタックに積みます。

加算の場合、演算に必要な値は2つなので、オペランドスタックに積まれていた3と2を取り出し、加算をした結果の5をオペランドスタックに積みます。

 

加算の結果はオペランドスタックにしかないので、オペランドスタックから値を取り出し、ローカル変数配列に収めるのが、次の行のistore_3です。

istore_3なので、配列のインデックス3の場所に加算結果の5を格納します。

この操作が、ローカル変数zに5を代入していることに相当します。

 

残りはJavaのコードのreturn z;の部分です。

iload_3で再び5をオペランドスタックに積みます。

 

そして、オペランドスタックに積んだ値を戻り値として、メソッドを完了させるのがireturnです。

ireturnが完了すると、addメソッドに相当するフレームは削除されます。

 

これでaddメソッドが完了しました。

このaddメソッドはとても単純なメソッドですが、オペランドスタックとローカル変数配列の使い方は変わらないので、これが理解できれば後はそれほど難しいことはないはずです。

ちなみに、最後のストアとロードが不要のように見えるかもしれませんが、気にしない方がよいです。

もし、このメソッドが何度もコールされるようであればHotSpot VMによって最適化されます。

javacでコンパイルした時点では最適化はされずに、本当に必要であれば実行時に最適化されるのです。

 

次回はメソッドコールやオブジェクト生成など、もうちょっと複雑なバイトコードを紹介していきます。また、if文やループなども紹介する予定です。