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)
if - statements as Functions In Scala if-statements can be used as functions. That is, they can return a value. Here is an example: var myInt : Int = 1; var myText : String = if(myInt == 0) "myInt == 0"; else "myInt !=
AnyVal is the root class of all value types, which describe values not implemented as objects in the underlying host system. Value classes are specified in Scala Language Specification, section 12.2. The standard implementation includes nine AnyVal subtypes: scala. Double, scala.
Scala Type Hierarchy Any is the supertype of all types, also called the top type. It defines certain universal methods such as equals , hashCode , and toString . Any has two direct subclasses: AnyVal and AnyRef . AnyVal represents value types.
Value classes are a new mechanism which help to avoid allocating run time objects. AnyVal define value classes. Value classes are predefined, they coincide to the primitive kind of Java-like languages. There are nine predefined value types : Double, Float, Long, Int, Short, Byte, Char, Unit, and Boolean.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With