前些天在scala中国的QQ群里,看到有人问为什么scala允许这样定义一个变量:
val x:Int = x + 1
这种在java里不允许,因为java里初始化一个变量(赋值)时不允许引用自身。java同样不允许下面的形式:
public class A {
int x = y + 1; //java中不行
int y = 2;
}
必须把y定义在x前面,才可以在x初始化时使用y。而scala里可以:
scala> class A {
val x = y + 1 //scala里可以
val y = 2
}
编译时只是给出警告,但会通过,运行也通过,只是x结果可能不符合你的预期;-)。
我们看一下为什么scala里会允许这种直觉上不合理的方式。看看scala在定义了一个val变量时,是怎样转成java字节码的
class A {
val x:Int = 1
println(x) //看看访问x变量会被翻译成什么
}
对上面的代码通过 scalac -Xprint:jvm 可以看到:
class A extends Object {
private[this] val x: Int = _;
<stable> <accessor> def x(): Int = A.this.x;
def <init>(): A = {
A.super.<init>();
A.this.x = 1;
scala.this.Predef.println(scala.Int.box(A.this.x())); //调用的是x()方法而不是x变量
()
}
}
它除了定义了一个 Int 类型的x变量,还定义了一个同名的 x()
方法,并且在其他地方访问x变量时实际都是调用的x()
方法,它很像C#里的“属性”,C#里访问一个属性,都是调用的它的get方法。
所以scala里访问变量时,都是访问其同名的get方法;另参考这篇:scala中的无参方法与统一访问原则
现在清楚了,val x = y + 1
里面的y实际上是调用的 y()
方法,val x:Int = x + 1
也是同理。不过这里面还有初始化的问题,scala的初始化比java的花样多一些,比如一个类在混入trait时的初始化顺序,以及引入了early definition的情况;后边再单独说。
val x: Int = x + 1能通过编译,但val x = x + 1 会提示error: recursive value x needs type,看来指定类型是找到默认初值。
是的,这种情况下不指定类型的话,无法推断出它的类型。
类型的初始值和代码执行的顺序决定的
“val x:Int = x + 1” 在scala 2.12.6里不能通过编译了
2.12.8里是可以的,并且可能默认初始化0结果返回x: Int = 1.