St.Antario
St.Antario

Reputation: 27425

How to perform boxing in Scala?

I'm trying to understand how casting works in Scala. Here is the example:

object Main extends App {
    val ai: Array[Any] = Array(1, 2, 3, 4, 5, 6)
    val ar: Array[AnyRef] = ai.map(_.asInstanceOf[AnyRef])
}

And it works fine. https://ideone.com/6PerTR

Now let's rewrite it as follows:

object Main extends App {
    val ai: AnyRef = Array(1, 2, 3, 4, 5, 6)
    val ar: Array[AnyRef] = ai.asInstanceOf[Array[Any]].map(_.asInstanceOf[AnyRef])
}

It does not work. It fails with ClassCastException now https://ideone.com/JbOQbb. Why? I thought adding cast is enough here? Why does the first example work?

How to make it work in the second case if we are casting AnyRef to Array[Any] first? How to add boxing here?

UPD: I also tried:

object Main extends App {
    val ai: AnyRef = Array(1, 2, 3, 4, 5, 6)
    val aii: Array[Any] = ai.asInstanceOf[Array[Any]]
    val ar: Array[AnyRef] = aii.map(_.asInstanceOf[AnyRef])
}

But got ClassCastException https://ideone.com/ZcgT6x. It looks very similar to the first example though. How to cast aii to Array[AnyRef] in the case?

Upvotes: 2

Views: 1608

Answers (2)

Bubletan
Bubletan

Reputation: 3863

As there is no representation of Any type at bytecode level, both Array[Any] and Array[AnyRef] require the elements to be boxed. So, when trying to cast from Array[Int] to Array[Any], we're actually casting from [I to [java/lang/Object; which results as an exception.

The only general solution is specially handling arrays of each primitive type, which can be done quite easily using a WrappedArray:

val ai: AnyRef = Array(1, 2, 3, 4, 5, 6)
val ar: Array[AnyRef] = collection.mutable.WrappedArray.make(ai).toArray

Upvotes: 2

Andrey Tyukin
Andrey Tyukin

Reputation: 44957

TL;DR: The first snippet works because the autoboxing takes place already when the array is generated. The second snippet does not work, because the created array is an Array[Int].


Even though Int is subtype of Any, an Array[Int] is not a subtype of Array[Any]. Therefore, when you write

scala> val ai: Array[Any] = Array(1, 2, 3, 4, 5, 6)
ai: Array[Any] = Array(1, 2, 3, 4, 5, 6)

this is essentially equivalent to

val ai: Array[Any] = Array[Any](1, 2, 3, 4, 5, 6)

so that all integers are already boxed. The result is:

scala> ai.getClass
res0: Class[_ <: Array[Any]] = class [Ljava.lang.Object;

that is, your ai is essentially an Array[Object] right from the beginning.

The crucial difference in the second snippet is that even though Array[Int] is not subtype of Array[Any], it definitely is a subtype of AnyRef, so no autoboxing takes place:

scala> val ai: AnyRef = Array(1, 2, 3, 4, 5, 6)
ai: AnyRef = Array(1, 2, 3, 4, 5, 6)

scala> ai.getClass
res2: Class[_ <: AnyRef] = class [I

as you can see, the AnyRef is an int-array with unboxed ints.

If you now try to cast an array of unboxed ints into an Array[Any], you get a class cast exception.

You could fix your second example by enforcing immediate autoboxing like this:

object Main extends App {
    val ai: AnyRef = Array[Any](1, 2, 3, 4, 5, 6)
    val ar: Array[AnyRef] = 
      ai.asInstanceOf[Array[Any]].map(_.asInstanceOf[AnyRef])
}

or alternatively you could cast your array to the right type, namely Array[Int]:

object Main extends App {
    val ai: AnyRef = Array(1, 2, 3, 4, 5, 6)
    val ar: Array[AnyRef] =
      ai.asInstanceOf[Array[Int]].map(_.asInstanceOf[AnyRef])
}

Upvotes: 5

Related Questions