7
votes

The following code can be compiled without errors.

val a: Int = 1
val b = a.asInstanceOf[AnyRef]

That makes me confused, because Int extends AnyVal which is not a subclass but a sibling of AnyRef.

However, if an ascription is used as following:

val a: Int = 1
val b: AnyRef = a

It doesn't work.

error: type mismatch;
 found   : Int
 required: AnyRef
Note: an implicit exists from scala.Int => java.lang.Integer, but
methods inherited from Object are rendered ambiguous.  This is to avoid
a blanket implicit which would convert any scala.Int to any AnyRef.
You may wish to use a type ascription: `x: java.lang.Integer`.
   val b: AnyRef = a

What I am understanding :

asInstanceOf is executed at run time, it forces compiler to believe val a is an AnyRef. However, an ascription is at compile time, the conversion can not pass the type check, so we have a "type mismatch" error.

My questions:

  1. Basically, why does the conversion work at run time ?
  2. If AnyRef is considered as java.lang.Object in JVM, how about AnyVal ? Is it an Object at run time ?
    • (If yes, what's its type ? java.lang.Object ? But AnyVal is sibling of AnyRef, isn't it ?)
    • (If not, how can we use some AnyVal's subclass as Object, like Int, Double)
    • Are there some tricks played by scala compiler ?
2

2 Answers

8
votes

This is because of autoboxing :

scala>val a: Int = 1
a: Int = 1
scala> a.getClass
res2: Class[Int] = int
scala> val b = a.asInstanceOf[AnyRef]
b: AnyRef = 1
scala> b.getClass
res1: Class[_ <: AnyRef] = class java.lang.Integer

by forcing the conversion to AnyRef (java.lang.Object) you trigger autoboxing from int to java.lang.Integer

If AnyRef is considered as java.lang.Object in JVM, how about AnyVal ? Is it an Object at run time ?

AnyRef is indeed an alias for java.lang.Object AnyVal is a "virtual" type, it only exists at compile time for the sake of the type system completeness.

At run time instances extending AnyVal are converted to the corresponding native type (int, double, etc) except for String which goes to java.lang.String which itself extends java.lang.Object but has special handling in the JVM.

But AnyVal is sibling of AnyRef, isn't it ?)

Both AnyVal and AnyRef extend the type Any but they don't extend each other.

Are there some tricks played by scala compiler ?

Loads :)

For a more complete explanation of the Scala type hierarcy, I suggest you start by reading : http://docs.scala-lang.org/tutorials/tour/unified-types.html

4
votes

Supplementary to "because of autoboxing", you can observe what magic is used.

-Xprint:all will show which compiler phase has done the magic.

-Ytyper-debug shows what decisions were made by typer.

For instance, given val a: Int, then val b = a.isInstanceOf[AnyRef] is changed to Int.box(a).$asInstanceOf[Object] in erasure, where $asInstanceOf is the nominal member of Object.

There's a nice comment about these transforms in erasure:

/**  Replace member references as follows:
 *
 *   - `x == y` for == in class Any becomes `x equals y` with equals in class Object.
 *   - `x != y` for != in class Any becomes `!(x equals y)` with equals in class Object.
 *   - x.asInstanceOf[T] becomes x.$asInstanceOf[T]
 *   - x.isInstanceOf[T] becomes x.$isInstanceOf[T]
 *   - x.isInstanceOf[ErasedValueType(tref)] becomes x.isInstanceOf[tref.sym.tpe]
 *   - x.m where m is some other member of Any becomes x.m where m is a member of class Object.
 *   - x.m where x has unboxed value type T and m is not a directly translated member of T becomes T.box(x).m
 *   - x.m where x is a reference type and m is a directly translated member of value type T becomes x.TValue().m
 *   - All forms of x.m where x is a boxed type and m is a member of an unboxed class become
 *     x.m where m is the corresponding member of the boxed class.
 */

By contrast, the conversion due to the ascription mentioned in the OP's error message happens because of an implicit in Predef:

scala> val I: java.lang.Integer = a
[[syntax trees at end of                     typer]] // <console>

private[this] val I: Integer = scala.this.Predef.int2Integer($line3.$read.$iw.$iw.a);