我最近开始学习Scala并且很失望(但并不感到惊讶)他们的泛型也是通过类型擦除来实现的。
我的问题是,Scala是否有可能使用具体的泛型,或者JVM是否需要以某种方式进行更改?如果确实需要更改JVM,究竟需要更改什么?
答案 0 :(得分:21)
否 - 如果该字节码不支持具体化的泛型,则Scala不可能作为Java等效字节码运行。
当您询问“需要更改的内容是什么?”时,答案是:字节码规范。目前,字节码不允许定义变量的参数化类型。已经决定,作为字节码的修改,以支持具体化的泛型break backwards compatibility,即generics would have to be implemented via type erasure。
为了解决这个问题,Scala利用其implicit
机制的强大功能来定义 Manifest
,可以在任何范围内导入,以发现类型信息运行。清单是实验性的,很大程度上没有记录,但它们are coming as part of the library in 2.8。这是Scala reified generics / Manifests
答案 1 :(得分:5)
为了补充oxbow_lakes,关于how to get around type erasure in Scala的Stack Overflow存在一个问题。
答案 2 :(得分:3)
“隐式Manifest”是一个Scala编译器技巧,它不会使Scala中的泛型具体化。 Scala编译器,当它看到一个带有“隐式m:Manifest [A]”参数的函数,并且它知道调用站点的A 的泛型类型时,它将包装A的类及其泛型将参数输入Manifest并使其在函数内可用。但是,如果它无法找出A的真实类型,那么就无法创建Manifest。换句话说,如果内部函数需要Manifest,则必须传递函数调用链。
scala> def typeName[A](a: A)(implicit m: reflect.Manifest[A]) = m.toString
typeName: [A](a: A)(implicit m: scala.reflect.Manifest[A])java.lang.String
scala> typeName(List(1))
res6: java.lang.String = scala.collection.immutable.List[int]
scala> def foo[A](a: A) = typeName(a)
<console>:5: error: could not find implicit value for parameter m:scala.reflect.Manifest[A].
def foo[A](a: A) = typeName(a)
^
scala> def foo[A](a: A)(implicit m: reflect.Manifest[A]) = typeName(a)
foo: [A](a: A)(implicit m: scala.reflect.Manifest[A])java.lang.String
scala> foo(Set("hello"))
res8: java.lang.String = scala.collection.immutable.Set[java.lang.String]
答案 3 :(得分:3)
补充oxbow_lakes回答:这是不可能的,似乎永远不会发生(至少很快)。
JVM不支持具体化泛型的(可反驳)原因似乎是:
参考文献:
Odersky comment in 2010:“我更喜欢带有类型擦除的更简单的虚拟机架构”
在scala-internals列表中(2013年2月)Grzegorz Kossakowski said:
您可以轻松地对其进行基准测试,并确定性能影响非常大 显。特别是内存消耗增加了很多。
我相信要走的路是按照我们的方式进行可选的具体化 在Scala中用Manifest / TypeTags开始做。
如果可以并将其与运行时专业化相结合,那么您可以瞄准 高性能和通用代码。但是,这可能是目标 Scala 2.12或2.13。
答案 4 :(得分:1)
一旦 scalac 是一个编译器,它就有可能修饰生成的代码,无论使用什么数据结构来实现具体化的泛型。
我的意思是 scalac 能够看到......
// definition
class Klass[T] {
value : T
}
//calls
floats = Klass[float]
doubles = Klass[double]
......并“扩展”到类似的东西:
// definition
class Klass_float {
value : float
}
class Klass_double {
value : double
}
// calls
floats = Klass_float
doubles = Klass_double
修改强>
重点是:编译器能够创建所有必要的数据结构,证明在运行时提供其他类型信息是必要的。一旦此类型信息可用,Scala运行时将利用它并可以执行我们可以想象的所有类型感知操作。 JVM是否为已知的泛型提供字节码并不重要。这项工作不是由JVM完成的,而是由Scala库完成的。
如果您已经编写了一个符号调试器(我做过!),您知道基本上可以将编译器在编译时具有的所有信息“转储”到生成的二进制文件中,采用任何数据组织演示为更方便进一步处理。这是完全相同的想法:'转储'Scala编译器具有的所有类型信息。
简而言之,我不明白为什么它不可能,无论JVM是否为具体化的泛型提供本机操作都无关紧要。 JVM字节码与具体化的泛型无关。这类事情是语言规范,编译器功能和运行时库支持的问题。
其他编辑
IBM X10展示了我所说的能力:它将X10代码编译到Java代码上,在Java平台上利用已知的泛型。正如我之前提到的:它可以完成(和IBM X10一样!)但这种功能涉及语言规范,编译器支持(或编译器插件)以及运行时库中的足够支持。更多信息:http://x10.sourceforge.net/documentation/papers/X10Workshop2012/slides/Takeuchi.pdf