Kotlinのデフォルト引数を調べた

こっとりーん!(挨拶)

Kotlinはこんな感じでデフォルト引数が使えます。

class Calc {
  fun add(x: Int = 1, y: Int = 2): Int = x + y
  fun add2() = add(8, 16)
  fun add3() = add(4)
  fun add4() = add()
}

addの引数にデフォルト値を設定しているのでadd3やadd4で引数を省略できています。

で、これをjavapしました。

public final int add(int, int);
  descriptor: (II)I
  flags: ACC_PUBLIC, ACC_FINAL
  Code:
    stack=2, locals=3, args_size=3
       0: iload_1
       1: iload_2
       2: iadd
       3: ireturn
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       4     0  this   LCalc;
          0       4     1     x   I
          0       4     2     y   I
    LineNumberTable:
      line 2: 0
  RuntimeVisibleParameterAnnotations:
    parameter 0:
      0: #22(#23=s#24)
    parameter 1:
      0: #22(#23=s#25)

public static int add$default(Calc, int, int, int);
  descriptor: (LCalc;III)I
  flags: ACC_PUBLIC, ACC_STATIC
  Code:
    stack=4, locals=4, args_size=4
       0: aload_0
       1: iload_3
       2: iconst_1
       3: iand
       4: ifeq          9
       7: iconst_1
       8: istore_1
       9: iload_1
      10: iload_3
      11: iconst_2
      12: iand
      13: ifeq          18
      16: iconst_2
      17: istore_2
      18: iload_2
      19: invokevirtual #32                 // Method add:(II)I
      22: ireturn
    LineNumberTable:
      line 2: 7
    StackMapTable: number_of_entries = 2
         frame_type = 73 /* same_locals_1_stack_item */
        stack = [ class Calc ]
         frame_type = 255 /* full_frame */
        offset_delta = 8
        locals = [ class Calc, int, int, int ]
        stack = [ class Calc, int ]


public final int add2();
  descriptor: ()I
  flags: ACC_PUBLIC, ACC_FINAL
  Code:
    stack=3, locals=1, args_size=1
       0: aload_0
       1: bipush        8
       3: bipush        16
       5: invokevirtual #32                 // Method add:(II)I
       8: ireturn
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       9     0  this   LCalc;
    LineNumberTable:
      line 3: 0

public final int add3();
  descriptor: ()I
  flags: ACC_PUBLIC, ACC_FINAL
  Code:
    stack=4, locals=1, args_size=1
       0: aload_0
       1: iconst_4
       2: iconst_0
       3: iconst_2
       4: invokestatic  #37                 // Method add$default:(LCalc;III)I
       7: ireturn
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       8     0  this   LCalc;
    LineNumberTable:
      line 4: 0

public final int add4();
  descriptor: ()I
  flags: ACC_PUBLIC, ACC_FINAL
  Code:
    stack=4, locals=1, args_size=1
       0: aload_0
       1: iconst_0
       2: iconst_0
       3: iconst_3
       4: invokestatic  #37                 // Method add$default:(LCalc;III)I
       7: ireturn
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       8     0  this   LCalc;
    LineNumberTable:
      line 5: 0

public Calc();
  descriptor: ()V
  flags: ACC_PUBLIC
  Code:
    stack=1, locals=1, args_size=1
       0: aload_0
       1: invokespecial #41                 // Method java/lang/Object."<init>":()V
       4: return
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       5     0  this   LCalc;

コンスタントプールやコンストラクタは省略しました。

注目すべきはadd$default(Calc, int, int, int)というstaticメソッドですね。 第2,3引数はaddメソッドの第1,2引数に対応しています。

そして第4引数がデフォルト値が必要かどうかを判断するためのビット演算を利用したフラグです。 判断の方法ですが、例えば第4引数と1との論理積が0の場合は第1引数にデフォルト値を、第4引数と2との論理積が0の場合は第2引数にデフォルト値を使用する、といった具合です。 次に示す箇所がそれに当たります。

1: iload_3
2: iconst_1
3: iand
4: ifeq          9
7: iconst_1
8: istore_1
9: iload_1

intは32ビットなので33以上の引数にはどう対応してるのだろうと思って調べてみたところ第33引数では一周回って1との論理積で判断していました。 ということは第1引数に明示的に値を指定すると第33引数に何も指定していなくてもデフォルト値は使用されないということになりますね。 試してはいないですが。

javapの抜粋を掲載しますがインデックス416以降がそれに当たります。

390: iload         34
392: ldc           #77                 // int 1073741824
394: iand
395: ifeq          401
398: iconst_1
399: istore        31
401: iload         31
403: iload         34
405: ldc           #78                 // int -2147483648
407: iand
408: ifeq          414
411: iconst_1
412: istore        32
414: iload         32
416: iload         34
418: iconst_1
419: iand
420: ifeq          426
423: iconst_1
424: istore        33
426: iload         33
428: invokevirtual #80                 // Method x:(IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII)I
431: ireturn

ただしKotlinはまだM8なので改善されるかも知れませんね。 ていうか33も引数使うんじゃねえ、って話ですが。

そんなこんなでKotlinのデフォルト引数を調べてみました。 あとやっぱりjavapは楽しいです。

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