Por que `private val` e` private final val` são diferentes?

100

Eu costumava pensar assim private vale private final valsão os mesmos, até que vi a seção 4.1 na Referência de Scala:

Uma definição de valor constante está na forma

final val x = e

onde e é uma expressão constante (§6.24). O modificador final deve estar presente e nenhuma anotação de tipo pode ser fornecida. As referências ao valor constante x são tratadas como expressões constantes; no código gerado, eles são substituídos pelo lado direito da definição e.

E eu escrevi um teste:

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

javap -c resultado:

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
}

O código de byte é exatamente como o Scala Reference disse: private valnão é private final val.

Por que o scalac não trata apenas private valcomo private final val? Existe alguma razão subjacente?

Yang Bo
fonte
28
Em outras palavras: já que a valjá é imutável, por que precisamos da finalpalavra-chave em Scala? Por que o compilador não pode tratar todos os vals da mesma maneira que final vals?
Jesper
Observe que o privatemodificador de escopo tem a mesma semântica package privatedo Java. Você pode querer dizer private[this].
Connor Doyle
5
@ConnorDoyle: Como pacote privado? Acho que não: privatequer dizer que só é visível para instâncias desta classe, private[this]apenas esta instância - exceto para instâncias da mesma classe , privatenão permite que ninguém (incluir do mesmo pacote) acesse o valor.
Make42

Respostas:

81

Portanto, isso é apenas uma suposição, mas era um aborrecimento perene em Java que as variáveis ​​estáticas finais com um literal no lado direito fossem embutidas no bytecode como constantes. Isso gera um benefício de desempenho com certeza, mas faz com que a compatibilidade binária da definição seja interrompida se a "constante" for alterada. Ao definir uma variável estática final cujo valor pode precisar ser alterado, os programadores Java precisam recorrer a hacks, como inicializar o valor com um método ou construtor.

Um val em Scala já é final no sentido de Java. Parece que os designers do Scala estão usando o modificador redundante final para significar "permissão para embutir o valor constante". Assim, os programadores de Scala têm controle total sobre esse comportamento sem recorrer a hacks: se quiserem uma constante embutida, um valor que nunca deve mudar, mas é rápido, eles escrevem "valor final". se eles querem flexibilidade para alterar o valor sem quebrar a compatibilidade binária, apenas "val".

Steve Waldman
fonte
9
Sim, essa é a razão para vals não privados, mas vals privados obviamente não podem ser embutidos em outras classes e quebrar a compatibilidade da mesma maneira.
Alexey Romanov
3
Existe algum problema de compatibilidade binária quando eu mudo private valpara private final val?
Yang Bo
1
@steve-waldman Com licença, você quis dizer valno seu segundo parágrafo?
Yang Bo
1
Aqui estão os detalhes sobre as variáveis ​​estáticas finais em Java com relação à compatibilidade binária - docs.oracle.com/javase/specs/jls/se7/html/…
Eran Medan
8

Acho que a confusão aqui surge de confundir imutabilidade com a semântica do final. vals podem ser substituídos em classes filhas e, portanto, não podem ser tratados como finais, a menos que sejam marcados como tal explicitamente.

@Brian O REPL fornece escopo de classe no nível de linha. Vejo:

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
fonte
1
Estou a falar private val. Pode ser anulado?
Yang Bo
Não, vals privados não podem ser substituídos. Você pode redefinir outro val privado com o mesmo nome em uma subclasse, mas é um val completamente diferente que por acaso tem o mesmo nome. (Todas as referências ao antigo ainda se referem ao antigo.)
aij
1
entretanto, não parece ser apenas esse comportamento de substituição, já que posso fazer um val final (ou mesmo uma var final) no interpretador sem estar no contexto de uma classe.
nairbv de