我想编写重载函数如下:
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))=> A类型
到现在为止还挺好 . 但问题是同样的事情不适用于数组:
def f[T](t: T) = println("normal type")
def f[T](a: Array[T]) = println("Array type")
现在编译器抱怨:
double definition:方法f:[T](t:Array [T])单位和方法f:[T](t:T)第14行的单位在擦除后具有相同的类型:(t:java.lang.Object)单元
我认为类型擦除后第二个函数的签名应该是(a:Array [Object])Unit not(t:Object)Unit,所以它们不应该相互冲突 . 我在这里错过了什么?
如果我做错了什么,那么编写f的正确方法是什么,以便根据参数的类型调用正确的?
4 回答
这在Java中从来不是一个问题,因为它不支持泛型中的原始类型 . 因此,以下代码在Java中非常合法:
另一方面,Scala支持所有类型的泛型 . 尽管Scala语言没有原语,但结果字节码将它们用于Int,Float,Char和Boolean等类型 . 它区分了Java代码和Scala代码 . Java代码不接受
int[]
作为数组,因为int
不是java.lang.Object
. 因此,Java可以将这些方法参数类型擦除为Object
和Object[]
. (这意味着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:
此变体的工作方式与Java代码类似 . 这意味着,不支持基元数组 . 但这个小小的变化足以让它编译 . 另一方面,以下代码无法为类型擦除原因编译:
添加@specialized并不能解决问题,因为会生成泛型方法:
我希望@specialized可能已经解决了问题(在某些情况下),但编译器目前不支持它 . 但我不认为它会成为scalac的高优先级增强 .
擦除精确意味着您丢失了有关泛型类的类型参数的任何信息,并且只获取原始类型 . 所以
def f[T](a: Array[T])
的签名不能是def f[T](a: Array[Object])
,因为你仍然有一个类型参数(Object
) . 根据经验,你只需要删除类型参数以获得擦除类型,这将给我们def f[T](a: Array)
. 这适用于所有其他泛型类,但是数组在JVM上是特殊的,特别是它们的擦除只是Object(其中没有数组原始类型) . 因此擦除后f的签名确实是def f [T](a:Object) . [Updated, I was wrong] 实际上在检查了java规范后,看来我在这里完全错了 . 规范说其中
|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
,果然,现在它编译得很好:至于如何解决问题,一个常见的解决方案是添加一个虚拟参数 . 因为您当然不希望在每次调用时显式传递虚拟值,您可以为其指定一个虚拟默认值,或者使用始终由编译器隐式找到的隐式参数(例如
Predef
中的dummyImplicit
):[Scala 2.9]解决方案是使用隐式参数,这些参数自然地修改方法的签名,使它们不冲突 .
T : Manifest
是第二个参数列表(implicit mf: Manifest[T])
的语法糖 .不幸的是,我不知道为什么
Array[T]
会被删除到Object
而不是Array[Object]
.要在scala中克服类型擦除,您可以添加一个隐式参数,它将为您提供Manifest(scala 2.9 . *)或TypeTag(scala 2.10),然后您可以获得有关类型的所有信息,如:
您可以检查m是否是Array等的实例 .