Scala:数组和类型擦除

K J*_*K J 10 scala overloading type-erasure

我想编写重载函数如下:

case class A[T](t: T)
def f[T](t: T) = println("normal type")
def f[T](a: A[T]) = println("A type")
Run Code Online (Sandbox Code Playgroud)

结果如我所料:

f(5)=>正常类型
f(A(5))=> A类型

到现在为止还挺好.但问题是同样的事情不适用于数组:

def f[T](t: T) = println("normal type")
def f[T](a: Array[T]) = println("Array type")
Run Code Online (Sandbox Code Playgroud)

现在编译器抱怨:

double definition:方法f:[T](t:Array [T])单位和方法f:[T](t:T)第14行的单位在擦除后具有相同的类型:(t:java.lang.Object)Unit

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

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

Rég*_*les 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,因此当数组被一般地处理时,这是在引擎盖下操作的(每个编译器都不能静态地知道元素的类型).

更新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
Run Code Online (Sandbox Code Playgroud)

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

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])
Run Code Online (Sandbox Code Playgroud)

  • +1因为它解释了问题而不是简单地减轻它 (2认同)

v6a*_*6ak 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");}
Run Code Online (Sandbox Code Playgroud)

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

在另一方面,你的Scala代码处理所有的阵列,其中包括Array[Int],Array[Float],Array[Char],Array[Boolean]等等.这些数组是(或可以是)基本类型的数组.它们无法转换到JVM级别Array[Object]Array[anything else]在JVM级别上.恰好有一个超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")
Run Code Online (Sandbox Code Playgroud)

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

def f[T](t: T) = println("normal type")
def f[T <: AnyVal](a: Array[T]) = println("Array type")
Run Code Online (Sandbox Code Playgroud)

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

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

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