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

时间:2016-10-06 10:31:20

标签: java scala jvm

当我们在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编译器控制?如何在运行时保证这一点?

2 个答案:

答案 0 :(得分:11)

在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。

答案 1 :(得分:1)

真正的简单答案是:有一些Scala功能可以用JVM字节码编码,有些可以编码。

特别是,有一些约束不能用JVM字节码编码,例如: sealedprivate[this]val。这意味着如果你掌握了Scala源文件的已编译JVM字节码,那么你可以通过非Scala语言与代码进行交互来完成Scala无法做到的事情。

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

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