Kotlinの基本型(Primitive type)と参照型(Reference type)

基本型と参照型

Javaでは基本型(Primitive type)と参照型(Reference type)を区別して使用するが、KotlinではJavaとは異なり、IntLongBooleanなどのように見た目上は区別せずに使用する。

では、Kotlinには基本型と参照型が別々に存在しないのだろうか。nullがあることを考えると、次の2つを推測できる。

  • 型にnullがあるので、必ず参照型として宣言される。
  • 型がnullを許可するなら参照型、nullを許可しないなら基本型になる。

ここでの正解は後者である。型がnullを許可するなら参照型として宣言され、nullを許可しないなら基本型として宣言される。
つまりJVMでは、Kotlinでデータ型をどのように宣言したかによって、基本型と参照型に変換される。

ではサンプルコードで確認してみよう。
Kotlin

fun main() {
    val n1 = 10
    val n2: Int = 20
    val n3: Int? = 30

    println("n1=$n1, n2=$n2, n3=$n3")
}

上のコードでは、次の3つの場合で変数を宣言している。

  • Intが明示されていない場合
  • Intとして宣言され、nullを許可しない場合
  • Int?として宣言され、nullを許可する場合

これをバイトコードに変換して一部を確認すると、次のようになる。
Bytecode

   L0
    LINENUMBER 4 L0
    BIPUSH 10
    ISTORE 0
   L1
    LINENUMBER 5 L1
    BIPUSH 20
    ISTORE 1
   L2
    LINENUMBER 6 L2
    BIPUSH 30
    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
    ASTORE 2

L0、L1ではそれぞれ10、20が代入されていることが分かり、L2では30が代入され、Integer.valueOf(int i)が呼び出されて参照型として返されていることが分かる。

    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;

さらに分かりやすくJavaへデコンパイルしてコードを見てみよう。
Java

public final class DataTypePrimitiveReferenceTutorialKt {
   public static final void main() {
      int n1 = 10;
      int n2 = 20;
      Integer n3 = 30;
      String var3 = "n1=" + n1 + ", n2=" + n2 + ", n3=" + n3;
      boolean var4 = false;
      System.out.println(var3);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}

型がJavaでは次の表のように変換されていることが分かる。

データ型宣言 Kotlin型 Java型
var n1 = 10 Int int
var n2: Int = 20 Int int
var n3: Int? = 30 Int Integer

Kotlinの型で注意すべき点

Kotlinでは基本型と参照型の区別がないというより、隠されていると考える方がよさそうである。 一般的には型を区別せずに使用しても大きな問題はないが、Kotlinからリフレクションを活用したJavaライブラリを呼び出す場合には問題が起こる可能性がある。

次のコードは実際のJavaライブラリ(spring-retry)から抜粋したコードであり、ここではParameterとArgumentの型を比較している。

if (!parameterTypes[i].isAssignableFrom(argument.getClass())) {
    return false;
}

ParameterがintでArgumentがIntegerになるとfalseが返され、ユーザーが意図しない動作になる可能性がある。