5
votes

Can anyone explain why the following snippet of Scala code:

def convertRefToVal(obj: Any): Int = {
  if (obj.isInstanceOf[java.lang.Integer]) obj.asInstanceOf[Int]
  else -1
}

convertRefToVal(42).getClass()

prints java.lang.Class[Int] = int, whereas this:

def convertRefToVal(obj: Any): AnyVal = {
  if (obj.isInstanceOf[java.lang.Integer]) obj.asInstanceOf[Int]
  else -1
}

convertRefToVal(42).getClass()

produces java.lang.Class[_] = class java.lang.Integer?

The methods are identical apart from the return type (Int vs. AnyVal).

So the first example returns an Int value type, whereas in the second case, I get a java.lang.Integer reference type as the result. It looks like auto-boxing is happening, but I wouldn't expect this seeing as the second version specifies AnyVal as its return type?

(I'm using Scala 2.10.2)

3
Very interesting question. Thanks for the excuse to dig into some language internals :).mikołak

3 Answers

5
votes

Actually, autoboxing is applied in both versions, at the function's entrypoint, probably because the obj needs to be Any. But the interesting thing is when you consider the types:

def convertRefToVal(obj: Any): Int = {
  println(obj.isInstanceOf[java.lang.Integer])
  println(obj.isInstanceOf[Int])
  println(obj.getClass()) 
  if (obj.isInstanceOf[java.lang.Integer]) obj.asInstanceOf[Int]
  else -1
}
convertRefToVal(42)

This prints:

true
true
class java.lang.Integer

So one problem is that java.lang.Integer is considered an instance of Int, in any case.

Regardless, it looks like Scala has specific support to "cast" from wrappers to primitives depending on the return type. I'll try to find an answer as to why and edit it in my question.

Edit: someone else will probably supply a historical reason, but here's a factual one. This is what javap prints for the two functions:

public int convertRefToVal(java.lang.Object); //first version
public java.lang.Object convertRefToVal1(java.lang.Object); //second version

So, as you can see, AnyVal maps to java.lang.Object in the long run. In fact, the difference between the functions is that while both unbox the previously autoboxed argument, the second boxes it again.

To demonstrate, here's an example class:

package stuff

object PrimTest {

  def convertRefToVal(obj: Any): Int = {
    if (obj.isInstanceOf[java.lang.Integer]) obj.asInstanceOf[Int]
    else -1
  }

  def convertRefToVal1(obj: Any): AnyVal = {
    if (obj.isInstanceOf[java.lang.Integer]) obj.asInstanceOf[Int]
    else -1
  }

  def main(args: Array[String]): Unit = {
    new java.lang.Integer(42).asInstanceOf[Int] //added for isolating the cast example
  }

}

and here's the java -p -v output:

  Compiled from "PrimTest.scala"
public final class stuff.PrimTest$
  SourceFile: "PrimTest.scala"
    Scala: length = 0x0

  minor version: 0
  major version: 50
  flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER
Constant pool:
   #1 = Utf8               stuff/PrimTest$
   #2 = Class              #1             //  stuff/PrimTest$
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             //  java/lang/Object
   #5 = Utf8               PrimTest.scala
   #6 = Utf8               MODULE$
   #7 = Utf8               Lstuff/PrimTest$;
   #8 = Utf8               <clinit>
   #9 = Utf8               ()V
  #10 = Utf8               <init>
  #11 = NameAndType        #10:#9         //  "<init>":()V
  #12 = Methodref          #2.#11         //  stuff/PrimTest$."<init>":()V
  #13 = Utf8               convertRefToVal
  #14 = Utf8               (Ljava/lang/Object;)I
  #15 = Utf8               java/lang/Integer
  #16 = Class              #15            //  java/lang/Integer
  #17 = Utf8               scala/runtime/BoxesRunTime
  #18 = Class              #17            //  scala/runtime/BoxesRunTime
  #19 = Utf8               unboxToInt
  #20 = NameAndType        #19:#14        //  unboxToInt:(Ljava/lang/Object;)I
  #21 = Methodref          #18.#20        //  scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
  #22 = Utf8               this
  #23 = Utf8               obj
  #24 = Utf8               Ljava/lang/Object;
  #25 = Utf8               convertRefToVal1
  #26 = Utf8               (Ljava/lang/Object;)Ljava/lang/Object;
  #27 = Utf8               boxToInteger
  #28 = Utf8               (I)Ljava/lang/Integer;
  #29 = NameAndType        #27:#28        //  boxToInteger:(I)Ljava/lang/Integer;
  #30 = Methodref          #18.#29        //  scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
  #31 = Utf8               main
  #32 = Utf8               ([Ljava/lang/String;)V
  #33 = Utf8               (I)V
  #34 = NameAndType        #10:#33        //  "<init>":(I)V
  #35 = Methodref          #16.#34        //  java/lang/Integer."<init>":(I)V
  #36 = Utf8               args
  #37 = Utf8               [Ljava/lang/String;
  #38 = Methodref          #4.#11         //  java/lang/Object."<init>":()V
  #39 = NameAndType        #6:#7          //  MODULE$:Lstuff/PrimTest$;
  #40 = Fieldref           #2.#39         //  stuff/PrimTest$.MODULE$:Lstuff/PrimTest$;
  #41 = Utf8               Code
  #42 = Utf8               LocalVariableTable
  #43 = Utf8               LineNumberTable
  #44 = Utf8               StackMapTable
  #45 = Utf8               SourceFile
  #46 = Utf8               Scala
{
  public static final stuff.PrimTest$ MODULE$;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL

  public static {};
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: new           #2                  // class stuff/PrimTest$
         3: invokespecial #12                 // Method "<init>":()V
         6: return        

  public int convertRefToVal(java.lang.Object);
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: aload_1       
         1: instanceof    #16                 // class java/lang/Integer
         4: ifeq          14
         7: aload_1       
         8: invokestatic  #21                 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
        11: goto          15
        14: iconst_m1     
        15: ireturn       
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      16     0  this   Lstuff/PrimTest$;
               0      16     1   obj   Ljava/lang/Object;
      LineNumberTable:
        line 6: 0
        line 7: 14
        line 6: 15
      StackMapTable: number_of_entries = 2
           frame_type = 14 /* same */
           frame_type = 64 /* same_locals_1_stack_item */
          stack = [ int ]


  public java.lang.Object convertRefToVal1(java.lang.Object);
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: aload_1       
         1: instanceof    #16                 // class java/lang/Integer
         4: ifeq          17
         7: aload_1       
         8: invokestatic  #21                 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
        11: invokestatic  #30                 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
        14: goto          21
        17: iconst_m1     
        18: invokestatic  #30                 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
        21: areturn       
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      22     0  this   Lstuff/PrimTest$;
               0      22     1   obj   Ljava/lang/Object;
      LineNumberTable:
        line 11: 0
        line 12: 17
        line 11: 21
      StackMapTable: number_of_entries = 2
           frame_type = 17 /* same */
           frame_type = 67 /* same_locals_1_stack_item */
          stack = [ class java/lang/Integer ]


  public void main(java.lang.String[]);
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=2, args_size=2
         0: new           #16                 // class java/lang/Integer
         3: dup           
         4: bipush        42
         6: invokespecial #35                 // Method java/lang/Integer."<init>":(I)V
         9: invokestatic  #21                 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
        12: pop           
        13: return        
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      14     0  this   Lstuff/PrimTest$;
               0      14     1  args   [Ljava/lang/String;
      LineNumberTable:
        line 16: 0

  private stuff.PrimTest$();
    flags: ACC_PRIVATE
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0       
         1: invokespecial #38                 // Method java/lang/Object."<init>":()V
         4: aload_0       
         5: putstatic     #40                 // Field MODULE$:Lstuff/PrimTest$;
         8: return        
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       9     0  this   Lstuff/PrimTest$;
      LineNumberTable:
        line 3: 0
}

Note the usage of the BoxesRunTime invocations, which is in fact a Java class BTW. This indicates there is probably some specific code in the compiler that adds those calls.

2
votes

Primitives as you know are not objects. For the value types to have a common supertype, they need to be objects, thus the boxing is applied to the second version.

1
votes

When you define the function return type to Int there is an implicit conversion taking place: implicit def Integer2int(x: jl.Integer): Int:

You can list the implicit conversions in sbt console:

scala> :implicits -v

...
implicit def Integer2int(x: jl.Integer): Int
...

Here's also a sample console script:

scala> val ji: java.lang.Integer = 1
ji: java.lang.Integer = 1

scala> val si: Int = ji  // here the implicit conversion is taking place
si: Int = 1

scala> ji.getClass()
res4: java.lang.Class[_ <: java.lang.Integer] = class java.lang.Integer

scala> si.getClass()
res5: java.lang.Class[Int] = int

scala> Integer2int(ji)
res6: Int = 1

scala> Integer2int(ji).getClass // calling it explicitly
res7: java.lang.Class[Int] = int

See more about implicit conversions here on docs.scala-lang.org.