Чем отличаются `private val` и` private final val`?

100

Раньше я думал, что это одно private valи то private final valже, пока не увидел раздел 4.1 в Справочнике по Scala:

Определение постоянного значения имеет вид

final val x = e

где e - постоянное выражение (§6.24). Должен присутствовать последний модификатор, а аннотация типа не может быть дана. Ссылки на постоянное значение x сами по себе рассматриваются как постоянные выражения; в сгенерированном коде они заменяются правой частью определения e.

И я написал тест:

class PrivateVal {
  private val privateVal = 0
  def testPrivateVal = privateVal
  private final val privateFinalVal = 1
  def testPrivateFinalVal = privateFinalVal
}

javap -c вывод:

Compiled from "PrivateVal.scala"
public class PrivateVal {
  public int testPrivateVal();
    Code:
       0: aload_0       
       1: invokespecial #19                 // Method privateVal:()I
       4: ireturn       

  public int testPrivateFinalVal();
    Code:
       0: iconst_1      
       1: ireturn       

  public PrivateVal();
    Code:
       0: aload_0       
       1: invokespecial #24                 // Method java/lang/Object."<init>":()V
       4: aload_0       
       5: iconst_0      
       6: putfield      #14                 // Field privateVal:I
       9: return
}

Байт-код такой же, как и в Scala Reference: private valнет private final val.

Почему к скальаку не относятся просто private valтак private final val? Есть ли основная причина?

Ян Бо
источник
28
Другими словами: поскольку a valуже является неизменяемым, зачем нам вообще нужно finalключевое слово в Scala? Почему компилятор не может обрабатывать все так valже, как final vals?
Jesper
Обратите внимание, что privateмодификатор области имеет ту же семантику, что и package privateв Java. Вы можете сказать private[this].
Коннор Дойл
5
@ConnorDoyle: Как частный пакет? Я так не думаю: privateозначает, что он виден только экземплярам этого класса, private[this]только этот экземпляр - за исключением экземпляров того же класса , privateне позволяет никому (включая из того же пакета) получить доступ к значению.
Make42

Ответы:

81

Итак, это всего лишь предположение, но в Java было постоянное раздражение, что конечные статические переменные с литералом с правой стороны встраиваются в байт-код как константы. Это несомненно дает выигрыш в производительности, но приводит к нарушению двоичной совместимости определения, если "константа" когда-либо изменяется. При определении конечной статической переменной, значение которой может потребоваться изменить, Java-программисты должны прибегать к хитростям, таким как инициализация значения с помощью метода или конструктора.

Val в Scala уже является окончательным в смысле Java. Похоже, что дизайнеры Scala используют избыточный модификатор final для обозначения «разрешения встроить постоянное значение». Таким образом, программисты Scala имеют полный контроль над этим поведением, не прибегая к хитростям: если им нужна встроенная константа, значение, которое никогда не должно изменяться, но быстро, они пишут «final val». если они хотят гибкости для изменения значения без нарушения двоичной совместимости, просто «val».

Стив Уолдман
источник
9
Да, в этом причина использования не частных значений, но, очевидно, частные значения не могут быть встроены в другие классы и таким же образом нарушают совместимость.
Алексей Романов
3
Есть ли проблема с двоичной совместимостью при переходе private valна private final val?
Ян Бо
1
@ steve-waldman Простите, вы имели ввиду valво втором абзаце?
Ян Бо
1
Вот подробные сведения о конечных статических переменных в Java относительно двоичной совместимости - docs.oracle.com/javase/specs/jls/se7/html/…
Эран Медан
8

Я думаю, что путаница здесь возникает из-за смешения неизменяемости с семантикой final. vals могут быть переопределены в дочерних классах и, следовательно, не могут рассматриваться как final, если явно не отмечены как таковые.

@Brian REPL обеспечивает область видимости класса на уровне строки. Видеть:

scala> $iw.getClass.getPackage
res0: Package = package $line3

scala> private val x = 5
<console>:5: error: value x cannot be accessed in object $iw
  lazy val $result = `x`

scala> private val x = 5; println(x);
5
Коннор Дойл
источник
1
Я говорю о private val. Можно ли его переопределить?
Ян Бо
Нет, частные вальсы не могут быть отменены. Вы можете переопределить другой частный val с тем же именем в подклассе, но это совершенно другой val, у которого просто такое же имя. (Все ссылки на старый по-прежнему будут относиться к старому.)
aij
1
похоже, это не просто такое переопределяющее поведение, поскольку я могу сделать окончательный val (или даже конечный var) в интерпретаторе, вообще не находясь в контексте класса.
nairbv