I need to create a temporary Array[T | Null] where T is a type parameter with AnyRef as upper bound.
Given the arrays implementation will use an Array[AnyRef] after erasure for whatever T is, why should I relay on a ClassTag?
def method[T <: AnyRef](elements: Iterable[T])(using ctTorNull: ClassTag[T | Null]): T =
val array = elements.toArray[T | Null]
???
}
Is there a way to achieve the same without using ClassTag nor asInstanceOf in scala 3.4?
Edit: As Dmytro pointed out in his answer, the type information that the JVM has about Array[A] and of Array[B] is different even if both A and B extend AnyRef.
Evidently, arrays hold more information about the declared type of their elements than many collections do. So, to avoid the ClassTag in this scenario use a mutable collection that does not require a ClassTag. mutable.ArraySeq requires it but ArrayBuffer does not.
The example below uses the IndexedSeq factory which creates an ArrayBuffer.
def method[T <: AnyRef](elements: Iterable[T]): T =
val array = scala.collections.mutable.IndexedSeq.from[T | Null](elements)
???
}
If for efficiency reasons you need that the temporary collection be an Array, you may avoid the contextual ClassTag parameter if the received collection exposes the ClassTag of its backing Array. mutable.ArraySeq and mutable.ArrayStack do it in the elemTag member.
def method[T <: AnyRef](elements: mutable.ArraySeq[T]): T =
val array = elements.toArray[T | Null](using elements.elemTag.asInstanceOf[ClassTag[T | Null]])(elements)
???
}
Here I assumed that the erased type of T and T | Null is the same when T <: AnyRef.
Given the arrays implementation will use an
Array[AnyRef]after erasure for whateverTis
(where
Tis a type parameter withAnyRefas upper bound)
It's not true that erasure of Array[T] (T <: AnyRef) is Array[AnyRef]. See the spec:
The erasure of the parameterized type
scala.Array[T]isscala.Array[|T|].
https://www.scala-lang.org/files/archive/spec/3.4/03-types.html#type-erasure
(Here |T| denotes the erasure of T).
You can check that Array[Int] and Array[String] have different classes at runtime
classOf[Array[Int]]) // class [I
classOf[Array[String]] // class [Ljava.lang.String;
.asInstanceOf will throw if you specify wrong class
Array(1, 2, 3).asInstanceOf[Array[String]]
// java.lang.ClassCastException: class [I cannot be cast to class [Ljava.lang.String;
Similarly,
class SomeClass
class OtherClass
implicitly[SomeClass <:< AnyRef] // compiles
implicitly[OtherClass <:< AnyRef] // compiles
classOf[Array[SomeClass]]) // class [LSomeClass;
classOf[Array[OtherClass]] // class [LOtherClass;
Array(new SomeClass()).asInstanceOf[Array[OtherClass]]
// java.lang.ClassCastException: [LSomeClass; cannot be cast to [LOtherClass;
It's T in Iterable[T] that is erased. So you need ClassTag to preserve the information about T from compile time to runtime (in order to get correct Array[T] at runtime).