Scalaでは32ビット符号付き整数を表すIntは特別な型ではなく、単にAnyのサブクラスであるAnyValのサブクラスであるIntクラスだという事になっているし、実際そのように扱われる。また、Intに対する+などの各種演算子も単なるIntクラスのメソッドであることになっているし、実際にそのように扱われる。では、Int型の+が単なるメソッドだとしたら、JavaのリフレクションAPIを経由して+メソッドを表すMethodオブジェクトを取得し、それを呼び出すことはできるのだろうか?
結論から言うと、無理である。まず、ScalaのInt型を使ったコードがクラスファイルにコンパイルされる際はJVMレベルでは可能な限りJVMのプリミティブ型であるint型を利用し、コレクションにInt型の値が格納される場合など、それが不可能な場合はjava.lang.Integer型に自動的にboxingされるようになっている。ここで、java.lang.Integer型に+メソッドなどというものがあれば良いのだが、Javaプログラマならわかるように、当然そんなメソッドは無いので、リフレクションAPIを経由して+メソッドを表すMethodオブジェクトを取得することはできない、ということになる。
念のため、REPLで
scala> 0.asInstanceOf[AnyRef].getClass.getMethods.filter(_.getName == "$plus")
のように入力して、$plusメソッド(Scalaの+メソッドはJVMレベルでは$plusという名前にmanglingされる)を探してみたが、当然、
res4: Array[java.lang.reflect.Method] = Array()
となり、そのようなメソッドは見つからなかった。というわけで、Int型の$plusメソッドをリフレクション経由で呼び出すことは無理なわけだが、だとすると一つ疑問がある。Scalaにはstructural typeという機能があり、「あるシグニチャを持ったメソッドだけを持った型」のようなものを表現できる。これを使うと、以下のようなコードを評価することができる。Any{ def +(x: Int): Int}というコードで、Anyのサブタイプでかつ「Int型を引数として受け取って、Int型を返す+メソッド」を持った任意のオブジェクトを受け取れるような型を表現している。
scala> {val i: Any{def +(x: Int): Int} = 1; i + 1}
res5: Int = 2このstructural typeという機能、知っている人ならわかると思うが、リフレクションAPIを使ってMethodオブジェクトを取得してそれを呼び出すことで実現されている。しかし、Int型に対して+メソッドを表すMethodオブジェクトを取得することはできないはず。一体どのようにして実現しているのだろうか…というわけで、
object I { def main(args: Array[String]) { val x: Any{ def +(i: Int): Int } = 10 println(x + 10) } }
というコードをscalacでコンパイルして、javap -cで逆アセンブルしてみた。以下が逆アセンブル結果である。
Compiled from "I.scala"
public final class I$ extends java.lang.Object implements scala.ScalaObject{
public static final I$ MODULE$;
public static {};
Code:
0: new #10; //class I$
3: invokespecial #13; //Method "<init>":()V
6: return
public void main(java.lang.String[]);
Code:
0: ldc #41; //int 10
2: invokestatic #47; //Method scala/runtime/BoxesRunTime.boxToInteger:(I
)Ljava/lang/Integer;
5: astore_2
6: getstatic #52; //Field scala/Predef$.MODULE$:Lscala/Predef$;
9: aload_2
10: astore_3
11: aload_2
12: instanceof #54; //class java/lang/Number
15: ifne 25
18: aload_2
19: instanceof #56; //class java/lang/Character
22: ifeq 40
25: aload_3
26: ldc #41; //int 10
28: invokestatic #47; //Method scala/runtime/BoxesRunTime.boxToInteger:(I
)Ljava/lang/Integer;
31: invokestatic #60; //Method scala/runtime/BoxesRunTime.add:(Ljava/lang
/Object;Ljava/lang/Object;)Ljava/lang/Object;
34: checkcast #22; //class java/lang/Integer
37: goto 73
40: aconst_null
41: astore 4
43: aload_3
44: invokevirtual #64; //Method java/lang/Object.getClass:()Ljava/lang/Cla
ss;
47: invokestatic #68; //Method reflMethod$Method1:(Ljava/lang/Class;)Ljav
a/lang/reflect/Method;
50: aload_3
51: iconst_1
52: anewarray #35; //class java/lang/Object
55: dup
56: iconst_0
57: ldc #41; //int 10
59: invokestatic #47; //Method scala/runtime/BoxesRunTime.boxToInteger:(I
)Ljava/lang/Integer;
62: aastore
63: invokevirtual #74; //Method java/lang/reflect/Method.invoke:(Ljava/lan
g/Object;[Ljava/lang/Object;)Ljava/lang/Object;
66: astore 4
68: aload 4
70: checkcast #22; //class java/lang/Integer
73: invokevirtual #78; //Method scala/Predef$.println:(Ljava/lang/Object;)
V
76: return
77: astore 5
79: aload 5
81: invokevirtual #84; //Method java/lang/reflect/InvocationTargetExceptio
n.getCause:()Ljava/lang/Throwable;
84: athrow
Exception table:
from to target type
43 68 77 Class java/lang/reflect/InvocationTargetException
public static java.lang.reflect.Method reflMethod$Method1(java.lang.Class);
Code:
0: getstatic #33; //Field reflPoly$Cache1:Lscala/runtime/MethodCache;
3: aload_0
4: invokevirtual #97; //Method scala/runtime/MethodCache.find:(Ljava/lang
/Class;)Ljava/lang/reflect/Method;
7: astore_1
8: aload_1
9: ifnull 14
12: aload_1
13: areturn
14: aload_0
15: ldc #99; //String $plus
17: getstatic #28; //Field reflParams$Cache1:[Ljava/lang/Class;
20: invokevirtual #103; //Method java/lang/Class.getMethod:(Ljava/lang/Str
ing;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
23: astore_1
24: getstatic #33; //Field reflPoly$Cache1:Lscala/runtime/MethodCache;
27: aload_0
28: aload_1
29: invokevirtual #106; //Method scala/runtime/MethodCache.add:(Ljava/lang
/Class;Ljava/lang/reflect/Method;)Lscala/runtime/MethodCache;
32: putstatic #33; //Field reflPoly$Cache1:Lscala/runtime/MethodCache;
35: aload_1
36: areturn
}若干長いが、重要なのは以下の部分だ。
12: instanceof #54; //class java/lang/Number 15: ifne 25 18: aload_2 19: instanceof #56; //class java/lang/Character 22: ifeq 40 25: aload_3 26: ldc #41; //int 10 28: invokestatic #47; //Method scala/runtime/BoxesRunTime.boxToInteger:(I )Ljava/lang/Integer; 31: invokestatic #60; //Method scala/runtime/BoxesRunTime.add:(Ljava/lang /Object;Ljava/lang/Object;)Ljava/lang/Object; 34: checkcast #22; //class java/lang/Integer 37: goto 73
このコードを見ると、レシーバがjava.lang.Numberのインスタンスまたjava.lang.Characterのインスタンスかをチェックして、もしそうならばBoxesRuntime.add(Object,Object)という専用のメソッドを呼び出していることがわかる。というわけで、structural typeの実現にあたって、プリミティブ型のラッパークラスに関しては特別扱いするようなコードをscalacが吐いているのであった。ちゃんちゃん。