Зачем нужен ClassManifest с массивом, а не с списком? - PullRequest
12 голосов
/ 31 января 2011

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

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?

Ответы [ 4 ]

11 голосов
/ 31 января 2011

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

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

10 голосов
/ 31 января 2011

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

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

2 голосов
/ 31 января 2011

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

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

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

2 голосов
/ 31 января 2011

Короткий ответ: потому что методы определены в API :

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

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

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

val result = new Array[B](size)

Этот конструктор Array требует ClassManifest. См. Ответ Easy Angel для документации этого. Вот пример, демонстрирующий это в 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 для создания нового массива.

...