实在找不到好的名词来翻译Type Specialization,不过从实际的情况来说的话,类型专门化还是可以很好的反映出这个新的特性。
新特性的起源
要说Type Specialization的起源,还是和泛型的类型擦除和自动装箱拆箱有关。我们知道,泛型类型擦除后,一般在内部会用它的上界类型来替代,通常是Object。然而,在Java中,基本类型与对象类型是不能相互引用的,这也是为什么泛型中不能使用基本类型的原因。所以,如果我们想在泛型中指定基本类型,就要用其对应的对象类型来替代,比如int类型,我们就需要用Integer来替代。这样就带来了更大的问题,看看下面的Java代码:ArrayList<Integer> list = new ArrayList<Integer>(); list.add(1); |
当我们想要将一个int类型1加入到list中时,因为list是默认ArrayList<Integer>类型的,所以就会发生自动装箱,将int类型1转换为new Integer(1)。要知道这个自动装箱和拆箱是要损耗性能的,一般是手动转换的十分之一。所以在有些时候迫使程序员去写手动的代码。
Type Specialization就是为了解决这个问题而产生的。除了生成泛型的一般版本之外,使用Type Specialization还可以让编译器为基本类型生成专门的版本。
语法
Type Specialization的语法很简单,就是在泛型类型前加上注解@specialized。一旦加上@specialized后,编译器除了生成普通的版本外,还会为每一个基本类型生成一个对应的版本。Scala的基本类型有Unit, Boolean, Byte, Short, Char, Int, Long, Float, Double九种,那么编译器就会生成九种不同的版本。当然,你还可以指定生成对应的版本。
class Vector[@specialized A] { def apply(i:Int): A = //... def map[@specialized(Int, Boolean) B](f: A=>B) = //... } |
上面的代码中Vector会将A为每一个基本类型生成一个专门的版本,而方法map仅仅会生成Int和Boolean的对应版本。
实现
Type Specialization是在定义阶段执行的。编译器为每一个基本类型或者用户指定的类型生成对应的版本。Type Specialization在定义阶段执行可以允许分块编译。如果基本类型的专门化在泛型类初始化的时候再执行的话,就有可能导致原始的类定义无法被同时编译并且编译器无法将方法专门化。
一个类定义可能生成多个专门化的版本和一个泛型版本(类型擦除的版本)。每一个专门化的类都继承于泛型的类并且将指定的类型与原始定义绑定。Type Specialization的关键点是在程序中同时包含了专门化和类型擦除的类。为了它们能够共存,专门化的类能够替代泛型的类就非常必要了。
让我们看一下下面的代码:
class RefCell[@specialized(Int) T] {
private var value: T = _
def get: T = x
def put(x: T) =
value = x
}
|
Scalac会生成一个额外的类继承于RefCell[Int],这个类里包含了所有RefCell成员的特殊化版本。它重写了所有的泛型成员定义来实现它们的专门化版本。这个代码路径不但包括了自动装箱和类型擦除,同时还必须保证程序在通过泛型接口调用它时,得到的结果是正确的。当在上下文中使用泛型代码并且有更多的类型信息可用,同时还存在专门化的版本时,编译器就会将类的实例化和方法调用重写成专门化的版本。这样的代码路径可以保证避免自动装箱:
class RefCell$mcI$sp extends RefCell/*[Int]*/ {
protected var value$mcI$sp: Int = _;
protected override def value: AnyRef =
value$mcI$ // boxing happens here
protected override def value_=(x$1: Int): Unit =
value$mcI$sp = x$1
override def get: AnyRef =
get$mcI$sp; // boxing happens here
override def get$mcI$sp: Int = value$mcI$sp;
override def put(x: AnyRef): Unit =
put$mcI$sp(x); // boxing happens here
override def put$mcI$sp(x: Int): Unit = value$mcI$sp = x
}
|
上面的这个类就是为类型Int专门化的RefCell类。它包含一个专门化后的整型字段来保存当前的值,重写了继承字段的访问方法,并且用新的访问方法代替原有的访问方法。除了字段以外,涉及到泛型的方法也会被重写。实现的方式就是利用专门化的类型来替代泛型T的引用并且尽可能的使用专门化后的字段和方法。
假设我们的代码需要使用RefCell来处理Int类型:
object Test extends Application {
val ref = new RefCell[Int]
ref.put(10)
println(ref.get)
}
|
Scala的编译器会使用专门化的版本将RefCell[Int]的实例替换掉,这样在put和get的时候就不会发生自动装箱拆箱了。
成员专门化
这里需要注意的是,并不是所有的成员都会被专门化。假设我们有一个成员变量m,通常情况下,m至少应该是一个纯的专门化类型,或者是专门化类型的数组,这样才会被编译器专门化。例如:
abstract class Foo[@specialized T, U] {
// the following members are specialized
def foo1(x: T): U
def foo2(x: Int): Array[T]
val a: Array[T]
// the following members are not specialized
def bar1(x: U): Unit
def bar2(x: List[T]): U
val b: List[T]
}
|
当前Type Specialization的状态
在Scala当前的实现中,默认是启用Type Specialization的。你可以通过参数-no-specialization来关闭这个特性。
标准库中的Type Specialization
在Scala的标准库中,以下的class使用了Type Specialization:- 两参数的FunctionN trait。参数的类型被专门化为Int, Long, Float和Double。结果的类型参数还加入了Unit和Boolean的专门化。
- AbstractFunctionN和上面的FunctionN一样也加入了相同的专门化。
- 两参数的Tuple被专门化为Int, Long和Double。
- 方法Range.foreach被专门化为Unit,这样可以加快整数的for循环:for(i<-0 until 100) //...
以上这些大部分翻译自
官方的PDF,有兴趣的人可以去看原文。