Почему ClassManifest нужен с массивом, но не списком?

Определите следующий код:

import scala.collection.JavaConversions._  
val iter:java.util.Iterator[Any] = Array[Any](1, 2, 3).iterator
def func(a:Any):String = a.toString

def test[T:ClassManifest](iter:java.util.Iterator[Any], func:Any=>T):Array[T] =  
  iter.map(i=>func(i)).toArray

def testFunc = test(iter, func)

Здесь мне нужно использовать ClassManifest для его правильной компиляции, иначе я получаю ошибку:

scala> def test[T](iter:java.util.Iterator[Any], func:Any=>T):Array[T] = 
     |   iter.map(i=>func(i)).toArray         

<console>:11: error: could not find implicit value for evidence parameter of 
type ClassManifest[T]
     iter.map(i=>func(i)).toArray
                          ^

С другой стороны, альтернативный код ниже, используя List, не требует этого и компилирует штраф.

import scala.collection.JavaConversions._  
val iter:java.util.Iterator[Any] = Array[Any](1, 2, 3).iterator
def func(a:Any):String = a.toString 

def test1[T](iter:java.util.Iterator[Any], func:Any=>T):List[T] = 
  iter.map(i=>func(i)).toList   


def testFunc1 = test1(iter, func).toArray

Обратите внимание, что конечный вывод testFunc и testFunc1 идентичен.

Почему версия List не требует ClassManifest?

Ответ 1

Массивы в Java не стираются, и, в частности, Array[Int] отличается от Array[Object] на уровне JVM.

Для любого другого класса параметры типа стираются до Object, поэтому List[Int] и List[Object] имеют одинаковое представление на уровне JVM.

Ответ 2

Метод toArray создает новый массив с элементами типа T. И в соответствии с документацией:

Таким образом, в зависимости от фактического параметра типа для T это может быть Array [Int] или Array [Boolean] или массив некоторых других примитивных типов в Java или массив некоторого ссылочного типа. Но эти типы имеют все разные представления времени выполнения, так как будет Scala время выполнения выбрать правильный? Фактически, он не может сделать это на основе информации, которую он задает, потому что фактический тип, который соответствует параметру типа T, удаляется во время выполнения.

Ответ 3

Короткий ответ заключается в том, что методы определенные в API:

def toArray [B >: A] (implicit arg0: ClassManifest[B]) : Array[B]
def toList : List[A]

Если вы оставите :ClassManifest в def test[T:ClassManifest] в своем коде, то весь компилятор знает, что у него есть неизвестный тип T, и поэтому компилятор не может найти ClassManifest для этого типа.

Зачем нужен код ClassManifest? Если вы посмотрите на источник forArray, вы увидите:

val result = new Array[B](size)

Для этого конструктора Array требуется ClassManifest. См. Ответ "Легкий ангел" для документации по этому вопросу. Вот пример, демонстрирующий это в REPL:

scala> def createArray[T] = new Array[T](10)
<console>:5: error: cannot find class manifest for element type T
       def createArray[T] = new Array[T](10)

Таким образом, вы должны написать T: ClassManifest, потому что Scala нужен ClassManifest для создания нового массива.

Ответ 4

Scala использует собственные массивы JVM как реализацию для Array. Такой может быть создан только с известным типом.

Поскольку Sun решила создать устаревшие артефакты, общие типы стираются и больше не присутствуют в байтовом коде. Это означает, что во время выполнения Array[T] больше не знает значения T, и поэтому VM не может создать массив. Есть несколько более сложных ошибок, которые вы получаете, когда пытаетесь быть совместимыми с Java 1.4 и вводите generics в массив (я тоже не получаю всю глубину), но в нижней строке: массивы JVM/Java не являются общими; Scala помогает нам с общей абстракцией.

В манифесте класса, о котором вы говорите, есть, по сути, хак. Он обеспечивает статически, что информация о типе доступна, когда массив создается, требуя этого неявного параметра. На практике любой метод/функция, создающая массив параметра типа, должен требовать неявного манифеста, и все его вызовы и т.д. Вплоть до точки были известны типу (статически).