4
votes

When we create a final in java it is guaranteed that it cannot be changed even at run time because the JVM guarantees it.

Java class:

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

Javap decompiled:

Compiled from "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
}

But in scala, if we declare a val, it compiles into a normal integer and there is no difference between var and val in terms of decompilation output.

Original Scala class:

class AnTest {

  val x = 1
  var y = 2
}

Decompiled output:

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
}

With that information, the concept of immutability of a val is controlled only at compile time by the scala compiler? How is this guaranteed at run time?

2
As you said the val immutability is guaranteed at compile time only. And you can actually mutate a val using runtime reflection. - sarveshseri
The only way a run-time value can change is if you're doing reflection in your code. What kind of "runtime guarantees" are you looking for? - Yuval Itzchakov
@YuvalItzchakov : The concept of a value immutability is not present in java i.e an int can change , but in the case of scala a val guarantees that it cannot be changed. In that case, how is this communicated with the jvm since a val cannot behave like a final - Greedy Coder
It isn't reflected in the JVM bytecode, since it's enforced at compile time. If you're trying to alter a val your code isn't going to compile. Why would you need to trickle it to the bytecode? - Yuval Itzchakov
FYI final in Java is also just a compile-time constraint. You can mutate final fields. - Marko Topolnik

2 Answers

11
votes

In Scala, conveying immutability via val is a compile time enforcement which has nothing to do with the emitted byte code. In Java, you state that when the field is final in order for it not to be reassigned, where in Scala, declaring a variable with val only means it can't be reassigned, but it can be overridden. If you want a field to be final, you'll need to specify it as you do in Java:

class AnTest {
  final val x = 10
}

Which yields:

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
}

Which is equivalent to the byte code you see in Java, except the compiler has emitted a getter for x.

1
votes

The really simple answer is: there are some Scala features which can be encoded in JVM bytecode, and some which can't.

In particular, there are some constraints which cannot be encoded in JVM bytecode, e.g. sealed or private[this], or val. Which means that if you get your hands on the compiled JVM bytecode of a Scala source file, then you can do stuff that you can't do from Scala by interacting with the code through a language that is not Scala.

This is not specific to the JVM backend, you have similar, and even more pronounced problems with Scala.js, since the compilation target here (ECMAScript) offers even less ways of expressing constraints than JVM bytecode does.

But really, this is just a general problem: I can take a language as safe and pure as Haskell, compile it to native code, and if I get my hands on the compiled binary, all safety will be lost. In fact, most Haskell compilers perform (almost) complete type erasure, so there are literally no types, and no type constraints left after compilation.