

お世話になっております． scalaの初心者です．以下のコード動作の理由が分からず悩んでいます．

[code]

    scala> case class MyVector[A]() {
      def test(init:A) = {
        println("type=" + init.getClass)
      }
    }
    scala> (new MyVector[Int]()).test(3)
    type=class java.lang.Integer
    
[/code]

Intを渡したのでIntegerじゃなくてIntになって欲しいのですが．．．

よろしくお願い致します．


CC BY-SA 3.0
2014-12-19T15:46:59.080
2158
2014-12-20T11:59:20.167
2014-12-19T15:54:31.683
33
4723
question
java scala
scalaで型パラメータをscalaのクラスで渡してもJavaのクラスになるのはなぜ？


JVM の仕組みに拠ります。

まず大前提として JVM では プリミティブ型 を型パラメータに指定できません。 そのため Java では型パラメータとして `int` を指定すると、
コンパイル時に自動的に、`int` に対応する非プリミティブ型である `Integer` に置き換えています。

JVM を用いる Scala においても、この前提は覆せません。 よって例示されたコードにおいて `A` (=`init.getClass`) は
`Int` ではなく `Integer` となります。


CC BY-SA 3.0
2014-12-19T16:34:06.557
2166
2014-12-19T16:51:06.713
2014-12-19T16:51:06.713
208
208
2158
answer


Java の仕様では、「 **ジェネリクスに使用される型（総称型）は Object クラスを継承していること**
」が求められます。よって、ジェネリクス型の変数に、Object クラスを継承していないプリミティブ型のデータを保持することはできません。 Scala
の場合は、総称型が Object型なのか プリミティブ型なのかという判断をコンパイル時にしています。

件の `test()` メソッドの場合、Scala のコンパイラは 型A が Object を継承した総称型と見なし、以下のような Java
コードとして見なせるバイトコードを生成します。

[code]

    public void test(Object init)
    {
        System.out.println(
            (new StringBuilder())
                .append("type=")
                .append(init.getClass())
                .toString());
    }
    
[/code]

`test()` メソッドの引数 `init` の型に注目してください。 `Object` 型ですね。 `Object` 型の入れ物に、 プリミティブ型の
`int` 値を入れたならば本来、エラーとなる ところなのですが、

  * `int` <=> `java.lang.Integer`
  * `short` <=> `java.lang.Short`
  * `float` <=> `java.lang.Float`
  * `double` <=> `java.lang.Double`

以上のような型変換が可能な場合、コンパイラは `int` 型を `Integer`型に 相互暗黙変換させるおせっかいなコードを組み込みます。今回は
`int` 型の値が `test()` メソッドに引数として渡される直前に `java.lang.Integer`
型の値に自動変換されることになりました。結果、 `getClass()` が、 `java.lang.Integer` を返すことになりました。

補足：

Scala の `Int` は、プリミティブ型 (Java の `int` ) であると対応付けしてしまいがちですが、そうではありません。Scala の
`Int` は、最適化のためにコンパイラに特別扱いされて扱いが煩雑になっています。Scala のコンパイラは、`scala.Int`
で記述されたものをできるだけJava の `int` 型として扱おうとしますが、やむ終えない場合は、`Integer`
としてもあつかえるよう、魔法の/保険のコードを付加したりします。

コンパイラは、大方のところ `int` 及び `java.lang.Integer`
を隠す良い仕事しますが、それは完璧ではないことがあります。例えば、今回の例に似た以下のコードをコンパイルしてみてください。（Scala 2.9.3
以前限定。）

[code]

    case class GenericsIntBug[A <: Int]() {
        def test(init:A) = {
              println("type=" + init.getClass)
        }
    }
    
[/code]

すると、コンパイルが通るものの、実行時に必ず例外の発生するメソッドが出来上がります。バイトコードを見ると、このメソッド内部では、プリミティブ型 `int`
のデータを扱いながら、そこに存在しないメソッド `getClass()` を呼び出そうとしています。 Java
であれは、以下のようなコンパイル不能のコードです。

[code]

    public void test(int init)
    {
        System.out.println(
            (new StringBuilder())
                .append("type=")
                .append(init.getClass()).toString());
    }
    
[/code]

面白いので、興味があれば scala.Int を使った処理を一度ディスアセンブルして見ることをお勧めしますよ。 (補足の例の詳細は
[このGist](https://gist.github.com/hyamamoto/ad177fe9233536bfc2ec) に張りました)


CC BY-SA 3.0
2014-12-19T17:13:03.040
2168
2014-12-20T11:59:20.167
2014-12-20T11:59:20.167
4978
4978
2158
answer