我想编写重载函数,如下所示:
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的正确方法是什么,以便根据参数的类型调用正确的?
答案 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上是特殊的,,特别是它们的擦除只是
[更新,我错了] 实际上在检查了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
,因此当数组被一般地处理时(这是编译器无法静态地知道元素的类型),这就是在引擎盖下操作的内容。
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
关于如何解决问题,一个常见的解决方案是添加一个虚拟参数。因为您当然不希望在每次调用时显式传递虚拟值,您可以为其指定一个虚拟默认值,或者使用一个始终由编译器隐式找到的隐式参数(例如找到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])
答案 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可以将这些方法参数类型清除为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:
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)