Search code examples
scalaconcurrenthashmap

ConcurrentMap#putIfAbsent with Scala Int's


java.util.concurrent.ConcurrentMap's putIfAbsent docs say the following about the return type:

the previous value associated with the specified key, or null if there was no mapping for the key.

On Scala 2.10.4, I tried to call putIfAbsent(Int, Int), i.e. with Scala's Int type.

scala> import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentHashMap

scala> new ConcurrentHashMap[Int, Int]()
res0: java.util.concurrent.ConcurrentHashMap[Int,Int] = {}

scala> val x: Int = 1
x: Int = 1

Since 1 does not exist in the empty ConcurrentHashMap, I would expect null to return from a putIfAbsent(x, x) call.

scala> res0.putIfAbsent(x, x)
res1: Int = 0

But, 0 gets returned. I'm assuming that null gets converted to 0.

What exactly is going on here? It seems odd to me that it's compiling.


Solution

  • Int cannot be null, nor can any AnyVal. From the scaladoc:

    Null is a subtype of all reference types; its only instance is the null reference. Since Null is not a subtype of value types, null is not a member of any such type. For instance, it is not possible to assign null to a variable of type scala.Int.

    If we try it directly, we'll get an error:

    scala> val i: AnyVal = null
    <console>:9: error: type mismatch;
     found   : Null(null)
     required: AnyVal
    Note that implicit conversions are not applicable because they are ambiguous:
     both method RichException in object Predef of type (self: Throwable)RichException
     and method augmentString in object Predef of type (x: String)scala.collection.immutable.StringOps
     are possible conversion functions from Null(null) to AnyVal
           val i: AnyVal = null
    

    The compiler instead fills it with the default value for Int, which is 0.

    For example:

    scala> def getNull[A](i: A): A = null.asInstanceOf[A]
    getNull: [A](i: A)A
    
    scala> getNull(1)
    res6: Int = 0
    

    Behind the scenes, we can't actually cast null to an Int, but it can be cast to a boxed type. However, once we use the boxed type in a context where it should be like the primitive it contains, it's converted to that type, which requires a default value.