Pourquoi «private val» et «private final val» sont-ils différents?

100

J'avais l'habitude de penser cela private valet private final valc'est pareil, jusqu'à ce que j'aie vu la section 4.1 dans Scala Reference:

Une définition de valeur constante est de la forme

final val x = e

où e est une expression constante (§6.24). Le modificateur final doit être présent et aucune annotation de type ne peut être donnée. Les références à la valeur constante x sont elles-mêmes traitées comme des expressions constantes; dans le code généré, ils sont remplacés par le côté droit de la définition e.

Et j'ai écrit un test:

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

javap -c production:

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
}

Le code d'octet est exactement comme l'a dit Scala Reference: private valn'est pas private final val.

Pourquoi le scalac ne traite- t-il pas simplement private valcomme private final val? Y a-t-il une raison sous-jacente?

Yang Bo
la source
28
En d'autres termes: comme a valest déjà immuable, pourquoi avons-nous besoin du finalmot - clé dans Scala? Pourquoi le compilateur ne peut-il pas traiter tous les vals de la même manière que les final vals?
Jesper
Notez que le privatemodificateur de portée a la même sémantique qu'en package privateJava. Vous voulez peut-être dire private[this].
Connor Doyle
5
@ConnorDoyle: En tant que package privé? Je ne pense pas: cela privatesignifie qu'il n'est visible que pour les instances de cette classe, private[this]seule cette instance - à l'exception des instances de la même classe , privatene permet à personne (inclure du même package) d'accéder à la valeur.
Make42

Réponses:

81

Donc, ce n'est qu'une supposition, mais c'était un ennui éternel en Java que les variables statiques finales avec un littéral sur le côté droit soient incorporées dans le bytecode en tant que constantes. Cela engendre un avantage de performance certain, mais cela provoque la rupture de la compatibilité binaire de la définition si la «constante» a changé. Lors de la définition d'une variable statique finale dont la valeur peut avoir besoin de changer, les programmeurs Java doivent recourir à des hacks comme l'initialisation de la valeur avec une méthode ou un constructeur.

Un val en Scala est déjà définitif au sens Java. Il semble que les concepteurs de Scala utilisent le modificateur redondant final pour signifier "l'autorisation d'insérer la valeur constante". Les programmeurs Scala ont donc un contrôle total sur ce comportement sans recourir à des hacks: s'ils veulent une constante inline, une valeur qui ne devrait jamais changer mais qui est rapide, ils écrivent "final val". s'ils veulent la flexibilité de changer la valeur sans casser la compatibilité binaire, juste "val".

Steve Waldman
la source
9
Oui, c'est la raison des vals non privés, mais les vals privés ne peuvent évidemment pas être incorporés dans d'autres classes et rompre la compatibilité de la même manière.
Alexey Romanov
3
Y a-t-il un problème de compatibilité binaire lorsque je passe private valà private final val?
Yang Bo
1
@ steve-waldman Excusez-moi, vouliez-vous dire valdans votre deuxième paragraphe?
Yang Bo
1
Voici les détails sur les variables statiques finales en Java concernant la compatibilité binaire - docs.oracle.com/javase/specs/jls/se7/html/…
Eran Medan
8

Je pense que la confusion provient ici de la fusion de l'immuabilité avec la sémantique de final. vals peuvent être surchargés dans les classes enfants et ne peuvent donc pas être traités comme définitifs à moins d'être marqués explicitement comme tels.

@Brian Le REPL fournit une portée de classe au niveau ligne. Voir:

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
la source
1
Je parle de private val. Peut-il être annulé?
Yang Bo du
Non, les vals privées ne peuvent pas être remplacées. Vous pouvez redéfinir une autre val privée avec le même nom dans une sous-classe, mais c'est une val complètement différente qui se trouve juste avoir le même nom. (Toutes les références à l'ancien feraient toujours référence à l'ancien.)
aij
1
cela ne semble pas être simplement ce comportement primordial, puisque je peux faire un val final (ou même un var final) à l'interpréteur sans être du tout dans le contexte d'une classe.
nairbv