hangscer

ScalaOOP杂记

2017/02/12

Application特质

自scala2.9版本以来,以App特质代替了Application
解释Application如何工作:

1
2
3
4
5
6
object hello extends Application{
println("asdasd")
}
trait Application{
def main(args:Array[String]):Unit={}
}

这样的代码真的能跑起来吗(main方法中没有代码)?可以的,在scala2.12.1中测试无误。该段编译得到三个文件:

  • interface Application
  • class hello$ implements Application
  • class hello

分别查看它们的代码如下:

1
2
3
4
5
6
7
public interface Application {// $FF: synthetic method
static default void main$(Application $this, String[] args) {
$this.main(args);
}
default void main(String[] args) {}
static default void $init$(Application $this) {}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public final class hello$ implements Application {
public static hello$ MODULE$;
static {
new hello$();
}
public void main(String[] args) {
Application.main$(this, args);
}
private hello$() {
MODULE$ = this;
Application.$init$(this);
.MODULE$.println("asdasd");
}
}
1
2
3
4
5
public final class hello {
public static void main(String[] var0) {
.MODULE$.main(var0);
}
}

这段过程复杂,可以看到,object hello中的代码块是在静态代码块中被构建。问题是静态代码块初始化自身无法被优化。

延迟构造

Scala2.9为构造器提供了新的机制。DelayedInit特质为编译器提供了标记性的特质
当实现了一个继承DelayedInit的类时,整个构造器包装成一个函数并传递给delayedInit方法。

1
2
3
trait DelayedInit{
def delayedInit(x: =>Unit):Unit
}

方法delayedInit接受一个函数对象,函数对象包含了全部的一般的构造器逻辑。这为解决了Application特质带来的问题提供了清晰的解决方法。我们来自己实现App特质:

1
2
3
4
5
6
7
8
trait App extends DelayedInit{
var x:Option[Function0[Unit]]=None
override def delatedInit(cons: =>Unit){
x=Some(()=>cons)
}
def main(args:Array[String]):Unit=
x.foreach(_())
}

DelayedInit特质可能是危险的,它推迟了对象的构造。那些期望完整初始化对象的方法可能在运行期发生难以查找错误。

多重继承

1
2
3
4
trait Property_1{
val name:String
override val toString="Property "+name
}

Property特质定义了抽象成员name用来存放当前属性的名字。覆盖toString方法用name成员创建了一个字段。猜想以下程序的输出结果:

1
2
3
val x=new Property_1{override val name="HI"}
println(x)
//Property null

那么,以下的Property_2测试结果如何?

1
2
3
4
5
6
trait Property_2{
val name:String
override def toString="Property "+name
}
val x=new Property_1{override val name="HI"}
// Property HI

val x定义了Property特质的匿名子类

为什么Property_1val toString会打印出null

这是因为初始化的顺序问题:基特质(Property)在构建过程中会被先初始化。当toString字段查找name的值时,name还没初始化,所以找了null。之后,匿名类才被构建。

解决办法两种:

  • toString字段声明为lazy,虽然可以推迟了toString字段查找name字段的时间,但不确保初始化顺序的正确。
  • 使用早期成员定义(early nmember definition)

scala2.8重写了特质的初始化顺序。重写的重点是创造了早期成员定义(在混入特质前创造一种看上去像是匿名类定义的东西来做到这一点),例:

1
2
3
class X extends {val name="HI"} with Property
//或者
new {val name="HI"} with Property