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?


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


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

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.