Scala — как гарантируется неизменность значений во время выполнения

#java #scala #jvm

#java #scala #jvm

Вопрос:

Когда мы создаем финал в java, гарантируется, что он не может быть изменен даже во время выполнения, потому что 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, если мы объявляем a 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
}
  

С этой информацией концепция неизменности a val контролируется компилятором scala только во время компиляции? Как это гарантируется во время выполнения?

Комментарии:

1. Как вы сказали, неизменность значения гарантируется только во время компиляции. И вы действительно можете изменить значение, используя отражение во время выполнения.

2. Единственный способ изменить значение во время выполнения — это если вы выполняете отражение в своем коде. Какие «гарантии времени выполнения» вы ищете?

3. @YuvalItzchakov: Концепция неизменности значения отсутствует в java, т.е. Значение int может изменяться, но в случае scala значение val гарантирует, что оно не может быть изменено. В таком случае, как это сообщается с jvm, поскольку значение val не может вести себя как final

4. Это не отражается в байт-коде JVM, поскольку оно применяется во время компиляции . Если вы пытаетесь изменить val , ваш код не будет компилироваться. Зачем вам нужно передавать его в байт-код?

5. К вашему сведению final , в Java это также просто ограничение времени компиляции. Вы можете изменять final поля.

Ответ №1:

В 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 .

Ответ №2:

Действительно простой ответ: есть некоторые функции Scala, которые могут быть закодированы в байт-коде JVM, а некоторые — нет.

В частности, существуют некоторые ограничения, которые не могут быть закодированы в байт-коде JVM, например sealed , или private[this] , или val . Это означает, что если вы получите в свои руки скомпилированный байт-код JVM исходного файла Scala, то вы сможете делать то, что вы не можете сделать из Scala, взаимодействуя с кодом через язык, который не является Scala.

Это не относится к серверной части JVM, у вас похожие и даже более выраженные проблемы с Scala.js , поскольку цель компиляции здесь (ECMAScript) предлагает еще меньше способов выражения ограничений, чем байт-код JVM.

Но на самом деле это всего лишь общая проблема: я могу взять такой безопасный и чистый язык, как Haskell, скомпилировать его в машинный код, и если я получу в свои руки скомпилированный двоичный файл, вся безопасность будет потеряна. Фактически, большинство компиляторов Haskell выполняют (почти) полное удаление типов, поэтому после компиляции буквально не остается типов и ограничений типов.