为什么针对.NET的Scala编译器忽略了val的含义?

7

我正在尝试使用Scala,发现了三个有趣的事情(标题是第三个)。

1. 声明为val的局部变量并不被解释为final。

class HowAreVarAndValImplementedInScala {
  var v1 = 123
  val v2 = 456

  def method1() = {
    var v3 = 123
    val v4 = 456
    println(v3 + v4)
  }
}

如果我将上述Scala代码编译为字节码,然后将其反编译为Java,它看起来是这样的:
public class HowAreVarAndValImplementedInScala
{
  private int v1 = 123;
  private final int v2 = 456;

  public int v1()
  {
    return this.v1;
  }

  public void v1_$eq(int x$1) { this.v1 = x$1; }

  public int v2() { return this.v2; }

  public void method1() {
    int v3 = 123;
    int v4 = 456;
    Predef..MODULE$.println(BoxesRunTime.boxToInteger(v3 + v4));
  }
}

我们可以看到v2是最终版本,但v4不是,为什么?
Scala编译器针对.NET在许多(如果不是全部)公共实例方法中添加了override关键字。
如果我们将上面的Scala代码编译成CIL,然后反编译成C#,就像这样:
public class HowAreVarAndValImplementedInScala : ScalaObject
{
  private int v1;
  private int v2;

  public override int v1()
  {
    return this.v1;
  }

  public override void v1_$eq(int x$1)
  {
    this.v1 = x$1;
  }

  public override int v2()
  {
    return this.v2;
  }

  public override void method1()
  {
    int v3 = 123;
    int v4 = 456;
    Predef$.MODULE$.println(v3 + v4);
  }

  public HowAreVarAndValImplementedInScala()
  {
    this.v1 = 123;
    this.v2 = 456;
  }
}

所有公共实例方法(不包括构造函数)都被标记为重写,为什么?这是必要的吗?

针对 .net 的 Scala 编译器丢失了 val 的含义。

在上面的 C# 代码中,我们可以看到 v2 只是一个普通字段,而在 Java 计数器部分中,v2 被标记为 final,难道 Scala 编译器为 .net 不应该将 v2 标记为 readonly 吗?(有 bug 吗?)


2
据我所知,final在局部变量中没有运行时影响,但强调你不会改变一个值。相比之下,final字段暗示它们在构造函数执行后被初始化。因此,我猜测对于机器来说这对于v4来说是无关紧要的,因此被省略了,而在Java中v2必须是final。然而我还不肯定到足以将其作为答案。 - Matthias Meid
9
三个问题应该是三个问题... - Austin Salonen
@MatthiasMeid 是的,那看起来是正确的。 - Cui Pengfei 崔鹏飞
@AustinSalonen 但我可以通过仅发布一次来重复使用代码 :) - Cui Pengfei 崔鹏飞
在没有父方法可覆盖的C#类中,public override void method1()将无法编译。编译器消息为“未找到适合的方法进行覆盖”。如果上述反编译代码能够通过C#编译器编译,则必须在ScalaObject或其他父级中定义了method1。 - Christian Maslen
1个回答

7
在字节码级别上,final对于局部变量不存在。实际上,局部变量本身的概念也不存在。将局部变量标记为final纯粹是为了编译时检查。由于该信息不在类文件中存在,反编译器无法猜测它。
至于后两个问题,我不太熟悉CIL字节码,但如果我必须猜测,我会说没有理由不添加override,而readonly可能有不同的语义。
编辑:在查看了CIL规范之后,我找到了以下内容。
Java中字段的final标志的CIL等效项是initonly,其语义似乎相同。不清楚Scala编译器为什么不发出此代码。也许他们只是还没有做到这一点?或者您使用的.net反编译器没有反映出来。如果您想查看编译器实际生成的内容,最好直接查看字节码。

在CIL中,它是这样的:.field private int32 v1 .field private int32 v2。 - Cui Pengfei 崔鹏飞
关于覆盖:它覆盖了什么?基类不包含任何这些成员。 - Cui Pengfei 崔鹏飞
字段在CIL字节码中非常常见,作为.field元数据元素存在。而CIL的initonly与C#的readonly是等价的。 - Daniel A.A. Pelsmaeker
5
Scala.NET实际上并没有将Scala编译成CIL,而是使用标准JVM实现将Scala编译成JVML,然后使用IKVM.NET将JVML编译成CIL。因此,字段未被标记为“initonly”的原因可能是因为在生成的.class文件中没有足够的信息供IKVM.NET推断。 - Jörg W Mittag

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接