Scala:数组和类型擦除

时间:2013-01-16 09:31:57

标签: scala type-erasure overloading

我想编写重载函数,如下所示:

case class A[T](t: T)
def f[T](t: T) = println("normal type")
def f[T](a: A[T]) = println("A type")

结果如我所料:

  

F(5)=>普通型
  F(A(5))=>一种类型

到目前为止一切顺利。但问题是同样的事情对阵列不起作用:

def f[T](t: T) = println("normal type")
def f[T](a: Array[T]) = println("Array type")

现在编译器抱怨:

  

双重定义:方法f:[T](t:数组[T])单位和方法f:[T](t:T)第14行的单位在擦除后具有相同的类型:(t:java.lang。对象)单位

我认为类型擦除后第二个函数的签名应该是(a:Array [Object])Unit not(t:Object)Unit,所以它们不应该相互冲突。我在这里缺少什么?

如果我做错了什么,编写f的正确方法是什么,以便根据参数的类型调用正确的?

4 个答案:

答案 0 :(得分:8)

  

我认为类型擦除后第二个函数的签名应该是(a:Array [Object])Unit not(t:Object)Unit,所以它们不应该相互冲突。我在这里缺少什么?

擦除精确意味着您丢失了有关泛型类的类型参数的任何信息,并且只获取原始类型。因此,def f[T](a: Array[T])的签名不能是def f[T](a: Array[Object]),因为您仍然有类型参数(Object)。根据经验,您只需要删除类型参数以获取擦除类型,这将为我们提供def f[T](a: Array)。这适用于所有其他泛型类,但是数组在JVM上是特殊的,,特别是它们的擦除只是Object(不是array原始类型)。因此,擦除后f的签名确实是def f[T](a: Object) [更新,我错了] 实际上在检查了java规范后,看来我在这里完全错了。规范说

  

数组类型T []的擦除是| T | []

其中|T|T的删除。因此,确实对数组进行了特殊处理,但奇怪的是,当类型参数确实被删除时,类型被标记为T的数组而不仅仅是T. 这意味着,Array[Int]在删除之后仍为Array[Int]。 但Array[T]不同:T是泛型方法f的类型参数。为了能够一般地处理任何类型的数组,scala除了将Array[T]转换为Object之外别无选择(我认为Java的方式也是如此)。 这是因为正如我上面所说的那样,没有原始类型Array,所以它必须是Object

我会试着用另一种方式。通常,在使用类型为MyGenericClass[T]的参数编译泛型方法时,擦除类型为MyGenericClass这一事实使得(在JVM级别)可以传递MyGenericClass的任何实例化,例如MyGenericClass[Int]MyGenericClass[Float],因为它们在运行时实际上都是相同的。但是,对于数组不是这样:Array[Int]是与Array[Float]完全无关的类型,它们不会擦除到常见的Array原始类型。它们最不常见的类型是Object,因此当数组被一般地处理时(这是编译器无法静态地知道元素的类型),这就是在引擎盖下操作的内容。

UPDATE 2 :v6ak的回答添加了一些有用的信息:Java不支持泛型中的原始类型。所以在Array[T]中,T必然(在Java中,但在Scala中不是)Object的子类,因此它对Array[Object]的擦除完全有意义,不像Scala其中T可以是原始类型Int,它绝对不是Object的子类(又名AnyRef)。为了与Java处于相同的情况,我们可以使用上限约束T,当然,现在它编译得很好:

def f[T](t: T) = println("normal type")
def f[T<:AnyRef](a: Array[T]) = println("Array type") // no conflict anymore

关于如何解决问题,一个常见的解决方案是添加一个虚拟参数。因为您当然不希望在每次调用时显式传递虚拟值,您可以为其指定一个虚拟默认值,或者使用一个始终由编译器隐式找到的隐式参数(例如找到dummyImplicitPredef):

def f[T](a: Array[T], dummy: Int = 0)
// or:
def f[T](a: Array[T])(implicit dummy: DummyImplicit)
// or:
def f[T:ClassManifest](a: Array[T])

答案 1 :(得分:8)

这在Java中从来都不是问题,因为它不支持泛型中的原始类型。因此,以下代码在Java中非常合法:

public static <T> void f(T t){out.println("normal type");}
public static <T> void f(T[] a){out.println("Array type");}

另一方面,Scala支持所有类型的泛型。尽管Scala语言没有原语,但结果字节码将它们用于Int,Float,Char和Boolean等类型。它区分了Java代码和Scala代码。 Java代码不接受int[]作为数组,因为int不是java.lang.Object。因此,Java可以将这些方法参数类型清除为ObjectObject[]。 (这意味着JVM上有Ljava/lang/Object;[Ljava/lang/Object;。)

另一方面,您的Scala代码处理所有数组,包括Array[Int]Array[Float]Array[Char]Array[Boolean]等。这些数组是(或可以是)基本类型的数组。它们无法在JVM级别上转换为Array[Object]Array[anything else]。只有Array[Int]Array[Char]的超类型:它是java.lang.Object。它是您可能希望拥有的更一般的超类型。

为了支持这些陈述,我编写了一个代码不太通用的代码f:

def f[T](t: T) = println("normal type")
def f[T <: AnyRef](a: Array[T]) = println("Array type")

此变体的工作方式与Java代码类似。这意味着,不支持基元数组。但这个小小的变化足以让它编译。另一方面,以下代码无法为类型擦除原因编译:

def f[T](t: T) = println("normal type")
def f[T <: AnyVal](a: Array[T]) = println("Array type")

添加@specialized并不能解决问题,因为会生成泛型方法:

def f[T](t: T) = println("normal type")
def f[@specialized T <: AnyVal](a: Array[T]) = println("Array type")

我希望@specialized可能已经解决了问题(在某些情况下),但编译器目前不支持它。但我不认为它会成为scalac的高优先级增强。

答案 2 :(得分:3)

[Scala 2.9]解决方案是使用隐式参数,这些参数自然地修改方法的签名,使它们不冲突。

case class A()

def f[T](t: T) = println("normal type")
def f[T : Manifest](a: Array[T]) = println("Array type")

f(A())        // normal type
f(Array(A())) // Array type

T : Manifest是第二个参数列表(implicit mf: Manifest[T])的语法糖。

不幸的是,我不知道为什么Array[T]会被删除为Object而不是Array[Object]

答案 3 :(得分:3)

要在scala中克服类型擦除,您可以添加一个隐式参数,该参数将为您提供Manifest(scala 2.9。*)或TypeTag(scala 2.10),然后您就可以获得所有信息你想要的类型如下:

  

def f [T](t:T)(隐式清单:清单[T])

您可以检查m是否是Array等的实例