1. 概要

このチュートリアルでは、Kotlinのコンパニオンオブジェクトと、いくつかの簡単な例を使用してJavaからそれらにアクセスする方法について学習します。 また、各例について、逆コンパイルされたKotlinバイトコード(コンパイラーが生成する同等のJavaコード)も表示されます。 IntelliJを使用している場合は、ツール→Kotlin→Kotlinバイトコードの表示→逆コンパイルから生成されたコードにアクセスできます。 この記事を最初に読んでいる間は、逆コンパイルされたコードをスキップしてもかまいません。

2. コンパニオンオブジェクトの宣言

Kotlinでは、オブジェクト宣言を使用してスレッドセーフなシングルトンを簡単に作成できます。クラス内で object を宣言する場合、としてマークするオプションがあります。コンパニオンオブジェクト。 Javaに関しては、コンパニオンオブジェクトのメンバーはクラスの静的メンバーとしてアクセスできます。 オブジェクトをコンパニオンとしてマークすると、そのメンバーを呼び出すときにオブジェクトの名前を省略できます。 Kotlinでコンパニオンオブジェクトを宣言する方法を示す簡単な例を見てみましょう。

class MyClass {
    companion object {
        val age: Int = 22
    }
}

上記のサンプルでは、コンパニオンオブジェクトを宣言しています。 名前を省略していることに注意してください。

3. Javaからコンパニオンオブジェクトのプロパティにアクセスする

KotlinはJavaと100% i相互運用可能であるため、コンパニオンオブジェクトのプロパティにJavaクラスの静的フィールドとしてアクセスするためのプロビジョニングもあります。

3.1. @JvmFieldアノテーション

Kotlinクラスのプロパティを@JvmFieldアノテーションでマークして、静的フィールドとして公開することをKotlinコンパイラに通知できます。

class FieldSample {
    companion object{
        @JvmField
        val age : Int = 22
    }
}

それでは、Javaクラスからアクセスしてみましょう。

public class Main {
    public static void main(String[] args) {
        System.out.println(FieldSample.age);
    }
} 

上記の例では、versionプロパティに@JvmFieldアノテーションを付けました。 その後、Javaクラスで直接アクセスすることができました。 Javaコードだけを見ると、AppDatabasestaticフィールドにアクセスしているように見えることに注意してください。

FieldSampleクラス逆コンパイルされたKotlinバイトコードを見てみましょう。

public final class FieldSample {
   @JvmField
   public static int age = 22;
   @NotNull
   public static final FieldSample.Companion Companion = new FieldSample.Companion((DefaultConstructorMarker)null);

   public static final class Companion {
      private Companion() {
      }
...
   }
}

上記のスニペットから、Kotlinコンパイラが@JvmFieldアノテーションでマークされたプロパティの静的フィールドを生成することがわかります。

3.2. 特殊なケース– lateinit修飾子

lateinit として定義された変数は、コンパニオン オブジェクトの一部として定義された場合、それぞれに静的バッキングフィールドが作成されるため、特別です。 この静的バッキングフィールドは、lateinit変数と同じ可視性を持っています。 したがって、パブリック lateinit 変数を定義すると、Javaコードでそれにアクセスできます。

private変数とpublic lateinit変数の両方を宣言する簡単なKotlinクラスを作成しましょう。

class LateInitSample {
    companion object{
        private lateinit var password : String
        lateinit var userName : String

        fun setData(pair: Pair<String,String>){
            password = pair.first
            userName = pair.second
        }
    }
}

次のJavaスニペットに示すように、publicとして宣言したlateinit変数にのみアクセスできます。

public class Main {
    static void callLateInit() {
        System.out.println(LateInitSample.userName);
        //System.out.println(LateInitSample.password); compilation error
    }
}

LateInitSampleクラスの逆コンパイルされたKotlinバイトコードの抜粋を見てみましょう。

public final class LateInitSample {
   private static String password;
   public static String userName;
   @NotNull
   public static final LateInitSample.Companion Companion = new LateInitSample.Companion((DefaultConstructorMarker)null);

   public static final class Companion {
      @NotNull
      public final String getUserName() {
         String var10000 = LateInitSample.userName;
         if (var10000 == null) {
            Intrinsics.throwUninitializedPropertyAccessException("userName");
         }

         return var10000;
      }

      public final void setUserName(@NotNull String var1) {
         Intrinsics.checkNotNullParameter(var1, "<set-?>");
         LateInitSample.userName = var1;
      }

      public final void setData(@NotNull Pair pair) {
         Intrinsics.checkNotNullParameter(pair, "pair");
         LateInitSample.password = (String)pair.getFirst();
         ((LateInitSample.Companion)this).setUserName((String)pair.getSecond());
      }

      private Companion() {
      }
   }
}

上記で生成されたコードから、コンパイラがlateinit変数の静的フィールドを生成していることがわかります。 視認性は実際のフィールドと同じです。

3.3. 特殊なケース– const修飾子

constキーワードを適用することで、Kotlinでコンパイル時定数を定義できます。 Javaで定数を定義した場合、そのようなフィールドはすべて次のようになります。 静的ファイナル。  Kotlinのconstは、Javaの静的最終フィールドに相当しますコンパニオンオブジェクトconst変数を定義するサンプルコードと、Javaでそれにアクセスする方法を見てみましょう。

class ConstSample {
    companion object{
        const val VERSION : Int = 100
    }
}
public class Main {
     static void callConst() {
        System.out.println(ConstSample.VERSION);
    }
}

ConstSampleクラスの逆コンパイルされたKotlinバイトコードを見てみましょう。

public final class ConstSample {
   public static final int VERSION = 100;
   @NotNull
   public static final ConstSample.Companion Companion = new ConstSample.Companion((DefaultConstructorMarker)null);

 
   public static final class Companion {
      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}

ご覧のとおり、コンパイラは内部でVERSIONフィールドをJavaのstaticfinalフィールドに変換します。

4. コンパニオンオブジェクトのメソッドへのアクセス

Javaからコンパニオンオブジェクトのメソッドにアクセスするには、@JvmStaticアノテーションでメソッドをマークする必要があります。例を見てみましょう。

class MethodSample {
    companion object {
        @JvmStatic
        fun increment(num: Int): Int {
            return num + 1
        }
    }
}
public class Main {
    public static void main(String[] args) {
        MethodSample.increment(1);
    }
}

このアノテーションは、Kotlinコンパイラに、囲んでいるクラスにstaticメソッドを生成するように指示します。 逆コンパイルされたKotlinバイトコードの抜粋を見てみましょう。

public final class MethodSample {
   @NotNull
   public static final MethodSample.Companion Companion = new MethodSample.Companion((DefaultConstructorMarker)null);

   @JvmStatic
   public static final int increment(int num) {
      return Companion.increment(num);
   }

  
   public static final class Companion {
      @JvmStatic
      public final int increment(int num) {
         return num + 1;
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}

上記のスニペットでは、内部で、Kotlinコンパイラが包含クラスの生成された静的インクリメントメソッドからCompanion.increment()メソッドを呼び出していることがわかります。

5. 結論

この記事では、JavaのKotlinコンパニオンオブジェクトのメンバーを使用する方法を説明しました。 また、これらの機能をよりよく理解するために、生成されたバイトコードも確認しました。

コードサンプルは、GitHubでも入手できます。