Scala 2.8.1:更改延迟val的导入方式会影响其评估时间。这是一个错误吗?

时间:2012-07-16 10:41:54

标签: scala

这是在scala.2.8.1

我有一个对象配置。在它是一个懒惰的val database

样品A:

import Config.database

 trait Dao {  
   protected val database = database  
}

样本B:

import Config

trait Dao {
  protected val database = Config.database
}

在配置中,相关代码为:

lazy val database = 
   somethingFromAFile match {
      case "a" => databaseA
      case "b" => databaseB
      case "c" => databaseC
   }

lazy val databaseA = makeDB("a")
lazy val databaseB = makeDB("b")
lazy val databaseC = makeDB("c")

var changes = throw new Exception ("Not yet initialised")
private def makeDB(db: String) = {
    db match {
      case "a" => var changes = x => 2*x; ... //database making stuff
      case "a" => var changes = x => 3*x; ...
      case "a" => var changes = x => 4*x; ...
    }
}

评估数据库的顺序在样本A和样本B中是不同的。
我不认为应该是正确的行为。当然这不直观。如果这不是一个错误,有人可以解释为什么选择这种行为吗?

具体行为是在lazy val中,var设置为某个值。在样本A中,var的设置晚于样本B中的。

编辑所以我才意识到我有val database = database。所以改变导入导致了变量的阴影,虽然我期望自引用val应该发出编译器警告,或者溢出堆栈?

2 个答案:

答案 0 :(得分:4)

我编译了两个示例,然后用javap -c分析了字节码。 (在我的示例中,Config.database是一个惰性字段,其值为""; database的类型应该没有影响。

第一个示例创建以下字节码:

Compiled from "foo.scala"
public class foo extends java.lang.Object implements scala.ScalaObject{
public java.lang.String db();
  Code:
   0:   aload_0
   1:   getfield        #11; //Field db:Ljava/lang/String;
   4:   areturn

public foo();
  Code:
   0:   aload_0
   1:   invokespecial   #17; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   getstatic       #23; //Field Config$.MODULE$:LConfig$;
   8:   invokevirtual   #26; //Method Config$.database:()Ljava/lang/String;
   11:  putfield        #11; //Field db:Ljava/lang/String;
   14:  return

}

第二个示例创建以下相同的字节码:

Compiled from "foo.scala"
public class foo extends java.lang.Object implements scala.ScalaObject{
public java.lang.String db();
  Code:
   0:   aload_0
   1:   getfield        #11; //Field db:Ljava/lang/String;
   4:   areturn

public foo();
  Code:
   0:   aload_0
   1:   invokespecial   #17; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   getstatic       #23; //Field Config$.MODULE$:LConfig$;
   8:   invokevirtual   #26; //Method Config$.database:()Ljava/lang/String;
   11:  putfield        #11; //Field db:Ljava/lang/String;
   14:  return

}

我甚至看不出两者之间的肤浅差异。


这与进口实际上是一致的 - 它们只是一种防止你必须完全限定所有参考资料的方法。 import Config._不会任何事情;它只是将Config中的值带入顶级作用域,因此在编译期间,解析器会认为它们没有前缀有效。

按顺序说,

import A.b
...
b

完全相同
...
A.b

导入只会保存您的输入,它们不会更改代码的含义。如果您看到差异,那么我相信只有在您的某个导入隐藏了另一个定义时才能实现这一点,因此根据导入的措辞,对未加前缀的声明进行不同的解释。

答案 1 :(得分:1)

def可能导致堆栈溢出,但不会导致valval由getter和setter组成,因此赋值通过setter完成,读取通过getter完成,不存在循环。存储的值与JVM使用null初始化的值相同。

自引用变量没有警告,因为有适用的用例。例如,请参阅人们如此喜爱的常见“素数”或“斐波那契”流示例。