当我们在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编译器控制?如何在运行时保证这一点?
答案 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字节码编码,例如: sealed
或private[this]
或val
。这意味着如果你掌握了Scala源文件的已编译JVM字节码,那么你可以通过非Scala语言与代码进行交互来完成Scala无法做到的事情。
这不是特定于JVM后端的,因为这里的编译目标(ECMAScript)提供了比JVM字节码更少的表达约束的方法,因此Scala.js存在类似甚至更明显的问题。
但实际上,这只是一个普遍的问题:我可以使用像Haskell一样安全和纯粹的语言,将其编译为本机代码,如果我接受编译后的二进制文件,所有安全性都将丢失。实际上,大多数Haskell编译器执行(几乎)完全类型擦除,因此实际上没有类型,编译后也没有类型约束。