【问题标题】:Why is ClassManifest needed with Array but not List?为什么 Array 需要 ClassManifest 而 List 不需要?
【发布时间】:2011-06-18 13:39:45
【问题描述】:

定义如下代码:

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

请注意,testFunctestFunc1 的最终输出是相同的。

为什么List 版本不需要ClassManifest

【问题讨论】:

    标签: scala scala-2.8 scala-collections


    【解决方案1】:

    Java 中的数组不会被类型擦除,特别是,Array[Int] 与 JVM 级别的 Array[Object] 不同。

    对于任何其他类,类型参数被擦除为Object,因此List[Int]List[Object] 在JVM 级别具有相同的表示。

    【讨论】:

    • 你的答案和天使的答案结合在一起形成正确答案:)
    【解决方案2】:

    方法toArray 使用T 类型的元素创建新数组。还有according to documentation

    因此,根据 T 的实际类型参数,它可以是 Array[Int] 或 Array[Boolean],或 Java 中一些其他基本类型的数组,或某种引用类型的数组。但是这些类型都有不同的运行时表示,那么 Scala 运行时如何选择正确的呢?事实上,它不能根据它给出的信息来做到这一点,因为与类型参数 T 对应的实际类型在运行时被擦除了。

    【讨论】:

      【解决方案3】:

      简短的回答是因为方法就是defined in the API

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

      如果您在代码中省略了 def test[T:ClassManifest] 中的 :ClassManifest,那么编译器所知道的只是它有一些未知类型 T,因此编译器无法找到该类型的 ClassManifest .

      为什么代码需要ClassManifest?如果你look at the source for 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 才能创建新数组。

      【讨论】:

        【解决方案4】:

        Scala 使用 JVM 原生数组作为 Array 的实现。这样只能用已知类型创建。

        自从 Sun 决定创建遗留工件以来,泛型类型被删除并且不再存在于字节码中。这意味着在运行时,Array[T] 不再知道 T 的值,因此 VM 无法创建数组。当您尝试与 Java 1.4 兼容并在数组上引入泛型时,您会遇到一些更复杂的陷阱(我也不了解全部深度),但底线是:JVM/Java 的数组不是泛型的; Scala 帮助我们进行泛型抽象。

        因此,您提到的类清单本质上是一个 hack。当通过要求这个隐式参数来创建数组时,它静态地确保类型信息可用。在实践中,任何创建类型参数数组的方法/函数都必须要求一个隐式清单,并且它的所有被调用者等等直到类型是已知的(静态)。

        【讨论】:

        • "可能是因为对象实际上是按顺序放在堆上的" 不,它们不是。对象数组按顺序存储引用,而不是对象本身。 (当然,这不适用于基元数组。)
        猜你喜欢
        • 2018-03-18
        • 2018-07-30
        • 2021-04-13
        • 1970-01-01
        • 1970-01-01
        • 2016-01-10
        • 2023-03-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多