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的正确方法是什么,以便根据参数的类型调用正确的?
我认为类型擦除后第二个函数的签名应该是(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上是特殊的,特别是它们的擦除很简单
[更新,我错了]实际上在检查了java规范后,看来我在这里完全错了.规范说Object
(不是array
原始类型).因此,f
擦除后的签名确实如此def f[T](a: Object)
.
数组类型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)
这在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可以将这些方法参数类型擦除到Object
和Object[]
.(这意味着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的高优先级增强.