Dlaczego „private val” i „private final val” są różne?

100

Kiedyś tak myślałem private vali tak private final valjest, dopóki nie zobaczyłem sekcji 4.1 w Scala Reference:

Definicja wartości stałej ma postać

final val x = e

gdzie e jest wyrażeniem stałym (§ 6.24). Ostateczny modyfikator musi być obecny i nie można podawać adnotacji typu. Odniesienia do stałej wartości x same są traktowane jako wyrażenia stałe; w generowanym kodzie są zastępowane prawą stroną definicji e.

Napisałem test:

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

javap -c wynik:

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
}

Kod bajtowy jest taki, jak powiedział Scala Reference: private valnie private final val.

Dlaczego scalac nie traktuje po prostu private valjako private final val? Czy jest jakiś podstawowy powód?

Yang Bo
źródło
28
Innymi słowy: skoro a valjest już niezmienne, dlaczego w ogóle potrzebujemy finalsłowa kluczowego w Scali? Dlaczego kompilator nie może traktować wszystkich valtak samo jak final vals?
Jesper
Zauważ, że privatemodyfikator zakresu ma taką samą semantykę jak package privatew Javie. Możesz chcieć powiedzieć private[this].
Connor Doyle
5
@ConnorDoyle: Czy pakiet jest prywatny? Nie sądzę: privatechodzi mi o to, że jest to widoczne tylko dla instancji tej klasy, private[this]tylko ta instancja - z wyjątkiem instancji tej samej klasy , privatenie pozwala nikomu (w tym z tego samego pakietu) na dostęp do wartości.
Make42

Odpowiedzi:

81

Tak więc to tylko przypuszczenie, ale w Javie była to wieczna irytacja, że ​​końcowe zmienne statyczne z literałem po prawej stronie są wstawiane do kodu bajtowego jako stałe. Z pewnością daje to korzyści w zakresie wydajności, ale powoduje zerwanie binarnej zgodności definicji, jeśli „stała” kiedykolwiek ulegnie zmianie. Definiując ostateczną zmienną statyczną, której wartość może wymagać zmiany, programiści Java muszą uciekać się do hacków, takich jak inicjowanie wartości za pomocą metody lub konstruktora.

Wartość w Scali jest już ostateczna w sensie Java. Wygląda na to, że projektanci Scali używają redundantnego modyfikatora final do oznaczenia „pozwolenia na wstawianie stałej wartości”. Tak więc programiści Scali mają pełną kontrolę nad tym zachowaniem bez uciekania się do hacków: jeśli chcą wbudowanej stałej, wartości, która nigdy nie powinna się zmieniać, ale jest szybka, piszą „wartość końcowa”. jeśli chcą elastyczności w zmianie wartości bez naruszania zgodności binarnej, po prostu „val”.

Steve Waldman
źródło
9
Tak, to jest powód dla wartości nieprywatnych, ale wartości prywatnych oczywiście nie można wstawić do innych klas i złamać kompatybilność w ten sam sposób.
Alexey Romanov
3
Czy jest jakiś problem ze zgodnością plików binarnych po zmianie private valna private final val?
Yang Bo
1
@ steve-waldman Przepraszam, czy miałeś na myśli valw drugim akapicie?
Yang Bo
1
Oto szczegóły dotyczące ostatecznych zmiennych statycznych w Javie dotyczących zgodności binarnej - docs.oracle.com/javase/specs/jls/se7/html/ ...
Eran Medan,
8

Myślę, że zamieszanie tutaj wynika z pomieszania niezmienności z semantyką finału. vals mogą być przesłonięte w klasach potomnych i dlatego nie mogą być traktowane jako ostateczne, chyba że są wyraźnie oznaczone jako takie.

@Brian REPL zapewnia zakres klas na poziomie liniowym. Widzieć:

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
Connor Doyle
źródło
1
Mówię o private val. Czy można to zastąpić?
Yang Bo
Nie, wartości prywatnych nie można zignorować. Możesz przedefiniować inną wartość prywatną o tej samej nazwie w podklasie, ale jest to zupełnie inna wartość, która po prostu ma taką samą nazwę. (Wszystkie odniesienia do starego nadal będą odnosić się do starego.)
aij
1
nie wydaje się to być jednak tylko tym nadrzędnym zachowaniem, ponieważ mogę wykonać końcowy val (lub nawet końcowy var) u tłumacza, nie będąc w ogóle w kontekście klasy.
nairbv