ClassTag for parametrized Array

In following code, I need to make use of ClassTag.

def init[A:ClassTag](l: List[A]): Array[A] = {
def go(acc: Array[A], li: List[A]): Array[A] = {
li match {
case Cons(h, Cons(g, Nil)) => acc :+ h
case Cons(h, t) => go(acc :+ h, t)
}
}
go(Array.empty[A], l)
}

Why is ClassTag needed here? Not having it gives compilation error

No ClassTag available for A

For List, ClassTag is not needed.

This is caused by how Arrays are represented on the JVM. You don’t need a ClassTag for list, because it is a normal class / trait, with a generic parameter. When you have a List[String] and a List[Int], type erasure will cause them to have the same class at runtime. All type checks are done at compile time. In Java, you are allowed to leave generic parameters off, getting an unchecked “raw type”, which is what you’ll have at runtime.

Arrays on the other hand have different runtime classes on the JVM. Array types are not erased, so an Array[String] and and Array[Int] are different classes at runtime. For that reason, you need to know the type at runtime to instantiate the class. This is what the ClassTag does, it holds the type at runtime.

In Java, the difference is a bit more obvious, because of the different syntax for Arrays compared to generics, and because Arrays can have primitive types, which Java generics can not.

2 Likes

The uniform syntax for Array[A] and List[A] obscures that Array is special. For the JVM, array types are special builtin types of known element type (long predating other generic types), so if you have Array[String], the JVM will know that it is an Array[String], while for List[String], it will only know it is List.

That means the JVM can only create an Array if it knows the element type (although there are situations where an Array[A] gets turned into an Array[AnyRef]), but for other parametrized types, the parameter type will not be known anyway .

**scala> List(“yo”).getClass.getCanonicalName
res0: String = scala.collection.immutable.$colon$colon

Array(“yo”).getClass.getCanonicalName
res1: String = java.lang.String**

In Java, it is more obvious that Arrays are different, because Array[A] is written A, while List[A] is written List.

That means will both be List of Any at run time. How is it ensured at run time that we get a String and an Int when we access those lists? Wouldn’t they return 'Any’?

When you take an element out of a List[String] the compiler inserts a cast to String in the bytecode, which it can prove is safe as long as you didn’t do any unsafe casts yourself.

1 Like