为啥 .NET 的 Scala 编译器会忽略 val 的含义?

Posted

技术标签:

【中文标题】为啥 .NET 的 Scala 编译器会忽略 val 的含义?【英文标题】:Why does Scala compiler for .NET ignore the meaning of val?为什么 .NET 的 Scala 编译器会忽略 val 的含义? 【发布时间】:2013-05-02 20:26:20 【问题描述】:

我正在玩 Scala。 我发现了 3 件有趣的事情(标题是第三件)。

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 不是,为什么?

.net 的 2 scala 编译器为许多(如果不是全部)公共实例方法添加了 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;
  

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

3 .net 的 Scala 编译器失去了 val 的含义

在上面的c#代码中,我们可以看到v2只是一个普通的字段,而在java couter部分,v2被标记为final,v2不应该被Scala编译器标记为只读吗?(一个错误?)

【问题讨论】:

据我所知,final 在局部变量中没有运行时影响,只是强调您不会更改值。相比之下,final 字段意味着例如它们在构造函数执行后被初始化。因此,我 假设 v4 省略了它,因为它与机器无关,而 v2 在 Java 中必须是 final。但是,我不确定是否足以回答这个问题。 3个问题应该是3个问题... @MatthiasMeid 是的,这似乎是正确的 @AustinSalonen 但我可以通过只发布一次来重用代码:) public override void method1() 不会在没有要覆盖的父方法的 C# 类中编译。编译器消息是“找不到合适的方法来覆盖”。如果上面的反编译代码用 C# 编译器编译,那么在 ScalaObject 或其他父级中肯定已经定义了一个 method1。 【参考方案1】:

在字节码级别,局部变量不存在 final。事实上,局部变量的概念本身也不存在。将局部变量标记为 final 纯粹是为了编译时检查。由于类文件中不存在该信息,因此反编译器无法猜测它。

至于后两个问题,我对 CIL 字节码不太熟悉,但如果非要猜测的话,我会说没有理由不添加覆盖,readonly 可能有不同的语义。

编辑:查看 CIL 规范后,我发现了以下内容。

CIL 等效于 Java 的 final 字段标志是 initonly,它似乎具有相同的语义。目前尚不清楚为什么 Scala 编译器不发出这个。也许他们只是没有解决这个问题?或者您使用的 .net 反编译器可能没有反映这一点。如果您想查看编译器实际生成的内容,最好直接查看字节码。

【讨论】:

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

以上是关于为啥 .NET 的 Scala 编译器会忽略 val 的含义?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Scala 编译器会考虑一个类型,而分配给它的值的类型是不同的?

为啥 TypeScript 编译器会忽略 tsconfig.json?

为啥当我使用 JSON.NET 反序列化时会忽略我的默认值?

scala设计者为啥要提供package object

为啥重载的scala函数需要返回类型?

Scala:如何忽略“SSLHandshakeException”