Reputation: 739
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)
Upvotes: 5
Views: 1585
Reputation: 7812
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.
Upvotes: 1
Reputation: 9705
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.
Upvotes: 5
Reputation: 24413
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.
Upvotes: 2