38

As a test, I wrote this code:

object Ambig extends App {
  def f( x:Int    ) { println("Int"   ) }
  def f( x:String ) { println("String") }
  f( null.asInstanceOf[Int   ] )
  f( null.asInstanceOf[String] )
  f(null)
}

I was expecting to get an error on that last invocation of f(), saying that it was ambiguous. The compiler accepted it, and produced this output:

Int
String
String

Now I'm guessing that this has to do with the fact that Int is not an AnyRef, so the only version of f that works for f(null) is f(x:String). But then, if an Int can't be null, what does null.asInstanceOf[Int] mean? The repl says it's of type Int:

scala> :type null.asInstanceOf[Int]
Int

but I don't really see how that works. After all, if I try to cast a String to an Int, all hell breaks loose:

scala> "foo".asInstanceOf[Int]
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
    at scala.runtime.BoxesRunTime.unboxToInt(Unknown Source)
        ...

Of course that's to be expected -- "foo" can't be made into an Int. But neither can null, so why does casting null to an Int work? Presumably boxing in some form, but the type is still Int, which can't be null...

What am I missing?

AmigoNico
  • 6,652
  • 1
  • 35
  • 45
  • Oh, and how might I have gotten further understanding this on my own, using the REPL or scalap or some magic compiler flag or ... ? – AmigoNico May 25 '12 at 05:43
  • Another similar [question](http://stackoverflow.com/q/8285916/617996)... – PrimosK May 25 '12 at 05:49
  • Have a look at a recent spec update https://github.com/scala/scala-dist/pull/20 and issue https://issues.scala-lang.org/browse/SI-4437, which explains some of the background story. It is a bit comparable to `default(T)` in C#, afaik. – soc May 26 '12 at 00:24
  • 1
    https://github.com/scala/scala/pull/5176 – Kenji Yoshida Aug 10 '16 at 08:30

4 Answers4

54

The behaviour of casting null to an Int depends on the context in which it is done.

First of all, if you cast a null to an Int, it actually means a boxed integer, whose value is null. If you put the expression in a context where the expected type is Any (which is translated to Object behind the scene, because in the JVM bytecode, there is no way to refer to a primitive type and a reference type with the same reference), then this value is not converted further - that is why println(null.asInstanceOf[Int]) prints null.

However, if you use this same boxed integer value in a context where a primitive Int (Java int) is expected, it will be converted to a primitive, and null is (as a default value for reference types) converted to 0 (a default value for primitive types).

If a generic method does this cast, then, naturally, you get a null back.

However, if this method is specialized, then its return type is Int (which is a primitive integer in this case), so the null: Any value has to be converted to a primitive, as before.

Hence, running:

object Test extends App {
  println(null.asInstanceOf[Int])

  def printit(x: Int) = println(x)

  printit(null.asInstanceOf[Int])

  def nullint[T] = null.asInstanceOf[T]

  println(nullint[Int])

  def nullspecint[@specialized(Int) T] = null.asInstanceOf[T]

  println(nullspecint[Int])
}

produces:

null
0
null
0
axel22
  • 32,045
  • 9
  • 125
  • 137
21

Here's the thing: asInstanceOf doesn't have to make sense. What this method does is to tell the compiler to STOP MAKING SENSE, and trust what you are saying.

Now, if you want to know why it returns 0, that's because asInstanceOf works on AnyRef, not on AnyVal. When applied to an AnyVal, it will use the boxed version instead, and a boxed null has value 0.

Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
4

It looks like it's just automatically converting it to zero:

scala> null.asInstanceOf[Int]
res0: Int = 0

And, of course, 0, unlike null, can be an Int.

dhg
  • 52,383
  • 8
  • 123
  • 144
  • Very interesting. I'm embarrassed that while I asked the repl for the *type* of null.asInstanceOf[Int], it never occurred to me to ask for its *value*, which I foolishly assumed was null. Thanks! This conversion surprises me a bit, though -- hmmm. – AmigoNico May 25 '12 at 05:50
  • 1
    It surprises me too. I have no idea if it's the intended behavior, but it seems wrong to me. – dhg May 25 '12 at 05:52
  • 1
    This is the intended behavior--you get the same value from `var x: X = _` as you do from `var x = null.asInstanceOf[X]` for any `X`. – Rex Kerr May 25 '12 at 15:28
0

First, we all agree that we cannot assign null to scala.Int as documented in http://www.scala-lang.org/api/current/index.html#scala.Null

Second, why when we do println(null.asInstanceOf[Int]), it gives null?
This is because of the implementation of println. It eventually calls java String.valueOf method, which is

return (obj == null) ? "null" : obj.toString();

If you do a null.asInstanceOf[Int] == null in the shell, it will return true, but it gives an opposite warning that "comparing values of types Int and Null using `==' will always yield false". I think this might be a problem in scala's type erasure.

Doing a println only needs a scala.Any type, so the casting of null.asInstanceOf[Int] actually has not happen yet. So we just need to remember that when you assign null.asInstanceOf[Int] to an Int, the cast happens at runtime based on Scala's erasure semantics, and it assigns 0 to it.

By the way, you can still do a f(null) without any compilation error because scala is doing an implicit conversion for you

 null -> java.lang.Integer -> scala.Int

However, you will see it blows up at run time.

TFrost
  • 769
  • 2
  • 12
  • 31
GC001
  • 871
  • 8
  • 12