Reputation: 27425
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
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
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