Scala - val在运行时是如何保证不变性的



当我们在java中创建final时,它保证即使在运行时也不能更改,因为JVM保证了这一点。

Java类:

public class JustATest {
    public final int x = 10;
}

Javap反编译:

编译自"JustATest.java"

public class JustATest {
  public final int x;
  public JustATest();
    Code:
       0: aload_0
       1: invokespecial #1                // Method java/lang/Object."<init>":()V
       4: aload_0
       5: bipush        10
       7: putfield      #2                  // Field x:I
      10: return
}

但是在scala中,如果我们声明一个val,它会被编译成一个普通的整数,var和val在反编译输出方面没有区别。

原始Scala类:

class AnTest {
  val x = 1
  var y = 2
}

反编译输出:

Compiled from "AnTest.scala"
public class AnTest {
  public int x();
    Code:
       0: aload_0
       1: getfield      #14                 // Field x:I
       4: ireturn
  public int y();
    Code:
       0: aload_0
       1: getfield      #18                 // Field y:I
       4: ireturn
  public void y_$eq(int);
    Code:
       0: aload_0
       1: iload_1
       2: putfield      #18                 // Field y:I
       5: return
  public AnTest();
    Code:
       0: aload_0
       1: invokespecial #25                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iconst_1
       6: putfield      #14                 // Field x:I
       9: aload_0
      10: iconst_2
      11: putfield      #18                 // Field y:I
      14: return
}

有了这些信息,val的不变性概念仅在编译时由scala编译器控制?在运行时如何保证这一点?

在Scala中,通过val传递不变性是一个编译时强制执行,它与发出的字节码无关。在Java中,当字段为final时,为了使其不被重新分配,您可以声明它,而在Scala中,用val声明变量只意味着它不能被重新分配,但它可以被覆盖。如果您想要一个字段为final,您需要像在Java中那样指定它:

class AnTest {
  final val x = 10
}

收益率:

public class testing.ReadingFile$AnTest$1 {
  private final int x;
  public final int x();
    Code:
       0: bipush        10
       2: ireturn
  public testing.ReadingFile$AnTest$1();
    Code:
       0: aload_0
       1: invokespecial #19                 // Method java/lang/Object."<init>":()V
       4: return
}

这相当于您在Java中看到的字节码,除了编译器为x发出了一个getter。

真正的简单的答案是:有些Scala特性可以用JVM字节码编码,而有些则不能。

特别是,有一些约束不能在JVM字节码中编码,例如sealedprivate[this]val。这意味着,如果您获得了Scala源文件的编译JVM字节码,那么您可以通过非Scala语言与代码交互来完成在Scala中无法完成的任务。

这不是JVM后端特有的,Scala.js也有类似的甚至更明显的问题,因为这里的编译目标(ECMAScript)提供的表达约束的方式比JVM字节码更少。

但实际上,这只是一个普遍的问题:我可以采用像Haskell这样安全纯净的语言,将其编译为本机代码,如果我得到编译后的二进制代码,所有的安全性都将失去。事实上,大多数Haskell编译器执行(几乎)完整的类型擦除,因此编译后实际上没有类型,也没有类型约束。

最新更新