Como a palavra-chave "final" em Java funciona? (Ainda posso modificar um objeto.)

480

Em Java, usamos finalpalavras-chave com variáveis ​​para especificar que seus valores não devem ser alterados. Mas vejo que você pode alterar o valor no construtor / métodos da classe. Novamente, se a variável for static, será um erro de compilação.

Aqui está o código:

import java.util.ArrayList;
import java.util.List;

class Test {
  private final List foo;

  public Test()
  {
      foo = new ArrayList();
      foo.add("foo"); // Modification-1
  }
  public static void main(String[] args) 
  {
      Test t = new Test();
      t.foo.add("bar"); // Modification-2
      System.out.println("print - " + t.foo);
  }
}

O código acima funciona bem e sem erros.

Agora mude a variável como static:

private static final List foo;

Agora é um erro de compilação. Como isso finalrealmente funciona?

GS
fonte
como foo não é visível - como ele pode ser compilado?
Björn Hallström
5
@therealprashant isso não é verdade. Variáveis ​​estáticas privadas são válidas, são acessíveis a partir de métodos estáticos dentro da classe em que são definidas. Uma variável estática significa que a variável existe uma vez e não está vinculada a uma instância de uma classe.
Mbdavis
3
@mbdavis Oh Yes! obrigado. Ainda assim, não excluirei o comentário para ajudar as pessoas que estão pensando como eu e, em seguida, seu comentário fará com que elas pensem na direção correta.
Therealprashant
@therealprashant ok, não se preocupe!
Mbdavis 11/11

Respostas:

518

Você sempre tem permissão para inicializar uma finalvariável. O compilador garante que você possa fazer isso apenas uma vez.

Observe que chamar métodos em um objeto armazenado em uma finalvariável não tem nada a ver com a semântica de final. Em outras palavras: finalé apenas sobre a própria referência, e não sobre o conteúdo do objeto referenciado.

Java não tem conceito de imutabilidade de objetos; isso é conseguido com o design cuidadoso do objeto e é um empreendimento longe de trivial.

Marko Topolnik
fonte
12
tente fazer t.foo = new ArrayList (); no método principal, e você obterá erro de compilação ... o foo de referência é binded a apenas um objeto final de ArrayList ... ele não pode apontar para qualquer outra ArrayList
Code2Interface
50
hmmm. É tudo sobre referência, não valor. Obrigado!
GS
2
Eu tenho uma pergunta. Alguém que eu conheço reivindicado como "final" também faz com que a variável seja armazenada na pilha. Isso está correto? Pesquisei em todos os lugares e não encontrei nenhuma referência que possa aprovar ou desaprovar esta reivindicação. Eu pesquisei na documentação Java e Android. Também procurou por "modelo de memória Java". Talvez funcione dessa maneira em C / C ++, mas não acho que funcione dessa maneira em Java. Estou correcto?
desenvolvedor android
4
@androiddeveloper Nada em Java pode controlar explicitamente o posicionamento da pilha / heap. Mais especificamente, o posicionamento da pilha, conforme decidido pelo compilador HotSpot JIT, está sujeito a uma análise de escape , que é muito mais envolvente do que verificar se uma variável é final. Objetos mutáveis ​​também podem ser alocados à pilha. finalcampos podem ajudar na análise de escape, porém, mas essa é uma rota bastante indireta. Observe também que variáveis efetivamente finais têm tratamento idêntico ao marcado finalno código-fonte.
Marko Topolnik
5
finalestá presente no arquivo de classe e tem consequências semânticas significativas para otimizar o tempo de execução. Também pode incorrer em custos, porque o JLS tem uma forte garantia sobre a consistência dos finalcampos de um objeto. Por exemplo, o processador ARM deve usar uma instrução explícita de barreira à memória no final de cada construtor de uma classe que possui finalcampos. Em outros processadores, isso não é necessário, no entanto.
Marko Topolnik
574

Esta é uma pergunta de entrevista favorita . Com essas perguntas, o entrevistador tenta descobrir até que ponto você entende o comportamento dos objetos em relação aos construtores, métodos, variáveis ​​de classe (variáveis ​​estáticas) e variáveis ​​de instância.

import java.util.ArrayList;
import java.util.List;

class Test {
    private final List foo;

    public Test() {
        foo = new ArrayList();
        foo.add("foo"); // Modification-1
    }

    public void setFoo(List foo) {
       //this.foo = foo; Results in compile time error.
    }
}

No caso acima, definimos um construtor para 'Test' e atribuímos a ele um método 'setFoo'.

Sobre o construtor: O construtor pode ser chamado apenas uma vez por criação de objeto usando a newpalavra - chave Você não pode chamar o construtor várias vezes, porque o construtor não foi projetado para fazer isso.

Sobre o método: Um método pode ser chamado quantas vezes você desejar (Mesmo nunca) e o compilador sabe disso.

Cenário 1

private final List foo;  // 1

fooé uma variável de instância . Quando criamos o Testobjeto de classe, a variável de instância fooserá copiada dentro do objeto de Testclasse. Se atribuirmos foodentro do construtor, o compilador saberá que o construtor será invocado apenas uma vez; portanto, não haverá problema em atribuí-lo dentro do construtor.

Se atribuirmos foodentro de um método, o compilador saberá que um método pode ser chamado várias vezes, o que significa que o valor terá que ser alterado várias vezes, o que não é permitido para uma finalvariável. Portanto, o compilador decide que o construtor é uma boa escolha! Você pode atribuir um valor a uma variável final apenas uma vez.

Cenário 2

private static final List foo = new ArrayList();

fooagora é uma variável estática . Quando criamos uma instância da Testclasse, foonão serão copiados para o objeto porque fooé estático. Agora foonão é uma propriedade independente de cada objeto. Esta é uma propriedade de Testclasse. Mas foopode ser visto por vários objetos e se todos os objetos criados com a newpalavra - chave invocarem o Testconstrutor que altera o valor no momento da criação de vários objetos (Lembre- static foose de que não é copiado em todos os objetos, mas é compartilhado entre vários objetos). .)

Cenário 3

t.foo.add("bar"); // Modification-2

Acima Modification-2é da sua pergunta. No caso acima, você não está alterando o primeiro objeto referenciado, mas está adicionando conteúdo dentro do fooqual é permitido. O compilador reclama se você tentar atribuir um new ArrayList()à foovariável de referência.
Regra Se você inicializou uma finalvariável, não poderá alterá-la para se referir a um objeto diferente. (Neste caso ArrayList)

classes finais não podem ser subclassificadas métodos
finais não podem ser substituídos. (Este método está na superclasse)
os métodos finais podem substituir. (Leia isso de maneira gramatical. Este método está em uma subclasse)

AmitG
fonte
1
Só para ficar claro. No cenário 2, você está dizendo que fooseria definido várias vezes, apesar da designação final, se foofor definido na classe Test e várias instâncias do teste forem criadas?
Rawr
Não entendi a última linha do cenário 2: mas foopode haver vários objetos.) Isso significa que, se eu criar vários objetos de cada vez, qual objeto está inicializando a variável final depende da execução?
Saumya Suhagiya 5/09/16
1
Eu acho que uma maneira útil de pensar no cenário 3 é você estar atribuindo finalao endereço de memória referenciado pelo fooqual é um ArrayList. Você não está atribuindo finalao endereço de memória referenciado pelo primeiro elemento de foo(ou por qualquer elemento). Portanto, você não pode mudar, foomas pode mudar foo[0].
Pinkerton
@Rawr Como está, o cenário 2 causaria um erro em tempo de compilação por causa de foo = new ArrayList();- foorefere-se à variável estática porque estamos dentro da mesma classe.
flow2k
Sou desenvolvedor C ++ aprendendo Java. É seguro pensar finalem uma variável como a mesma constpalavra - chave em C ++?
Doug Barbieri
213

A palavra-chave final tem várias maneiras de usar:

  • Uma classe final não pode ser subclassificada.
  • Um método final não pode ser substituído por subclasses
  • Uma variável final pode ser inicializada apenas uma vez

Outro uso:

  • Quando uma classe interna anônima é definida no corpo de um método, todas as variáveis ​​declaradas finais no escopo desse método são acessíveis de dentro da classe interna

Uma variável de classe estática existirá desde o início da JVM e deve ser inicializada na classe. A mensagem de erro não aparecerá se você fizer isso.

czupe
fonte
24
Esta é de longe a minha resposta favorita. Simples e direto, é isso que eu esperaria ler em documentos on-line sobre java.
RAnders00
Então, em variáveis ​​estáticas, podemos inicializar quantas vezes quisermos?
Jorge Saraiva
1
@jorgesaraiva sim, variáveis ​​estáticas não são constantes.
precisa saber é
1
@jorgesaraiva Você pode atribuir (não inicializar ) staticcampos (contanto que não sejam final) quantas vezes quiser. Veja este wiki para a diferença entre atribuição e inicialização .
56

A finalpalavra-chave pode ser interpretada de duas maneiras diferentes, dependendo do que é usada:

Tipos de valor: para ints, doubles etc., garantirá que o valor não possa ser alterado,

Tipos de referência: para referências a objetos, finalgarante que a referência nunca seja alterada, o que significa que sempre se referirá ao mesmo objeto. Não garante, de forma alguma, que os valores dentro do objeto sejam mantidos iguais.

Como tal, final List<Whatever> foo;garante que foosempre se refira à mesma lista, mas o conteúdo dessa lista pode mudar com o tempo.

Smallhacker
fonte
23

Se você tornar fooestático, deverá inicializá-lo no construtor da classe (ou na linha em que o define) como nos exemplos a seguir.

Construtor de classe (não instância):

private static final List foo;

static
{
   foo = new ArrayList();
}

Na linha:

private static final List foo = new ArrayList();

O problema aqui não é como o finalmodificador funciona, mas como ostatic funciona.

O finalmodificador impõe uma inicialização de sua referência no momento em que a chamada ao construtor é concluída (ou seja, você deve inicializá-la no construtor).

Quando você inicializa um atributo em linha, ele é inicializado antes da execução do código que você definiu para o construtor, para que você obtenha os seguintes resultados:

  • se foofor static, foo = new ArrayList()será executado antes da execução do static{}construtor que você definiu para sua classe
  • caso foocontrário static, foo = new ArrayList()será executado antes da execução do construtor

Quando você não inicia um atributo em linha, o finalmodificador impõe que você o inicialize e que você deve fazê-lo no construtor. Se você também tem umstatic modificador, o construtor no qual você terá que inicializar o atributo é o bloco de inicialização da classe:static{} .

O erro que você obtém no seu código é o fato de static{}ser executado quando a classe é carregada, antes da instanciação de um objeto dessa classe. Assim, você não inicializoufoo quando a classe for criada.

Pense no static{}bloco como um construtor para um objeto do tipo Class. É aqui que você deve fazer a inicialização do seustatic final atributos de classe (se não for feito em linha).

Nota:

o final modificador assegura a consistência apenas para tipos e referências primitivas.

Quando você declara um finalobjeto, o que você recebe é uma final referência a esse objeto, mas o objeto em si não é constante.

O que você realmente está alcançando ao declarar um finalatributo é que, depois de declarar um objeto para sua finalidade específica (como a final Listque você declarou), esse e somente esse objeto serão usados ​​para essa finalidade: você não poderá mudar List foopara outro List, mas você ainda pode alterá-lo Listadicionando / removendo itens (o que Listvocê está usando será o mesmo, apenas com o conteúdo alterado).

lucian.pantelimon
fonte
8

Esta é uma pergunta de entrevista muito boa. Às vezes, eles podem até perguntar qual é a diferença entre um objeto final e um objeto imutável.

1) Quando alguém menciona um objeto final, significa que a referência não pode ser alterada, mas seu estado (variáveis ​​de instância) pode ser alterado.

2) Um objeto imutável é aquele cujo estado não pode ser alterado, mas sua referência pode ser alterada. Ex:

    String x = new String("abc"); 
    x = "BCG";

A variável ref x pode ser alterada para apontar uma sequência diferente, mas o valor "abc" não pode ser alterado.

3) Variáveis ​​de instância (campos não estáticos) são inicializadas quando um construtor é chamado. Assim, você pode inicializar valores para suas variáveis ​​dentro de um construtor.

4) "Mas vejo que você pode alterar o valor no construtor / métodos da classe". - Você não pode alterá-lo dentro de um método.

5) Uma variável estática é inicializada durante o carregamento da classe. Portanto, você não pode inicializar dentro de um construtor, isso deve ser feito antes mesmo dele. Portanto, você precisa atribuir valores a uma variável estática durante a própria declaração.

user892871
fonte
7

A finalpalavra-chave em java é usada para restringir o usuário. A finalpalavra-chave java pode ser usada em muitos contextos. Final pode ser:

  1. variável
  2. método
  3. classe

A finalpalavra-chave pode ser aplicada com as variáveis, uma finalvariável que não tem valor, é chamada de finalvariável em branco ou finalvariável não inicializada . Pode ser inicializado apenas no construtor. A finalvariável em branco statictambém pode ser qual será inicializada nostatic bloco.

Variável final Java:

Se você fizer qualquer variável como final, não poderá alterar o valor definal variável (será constante).

Exemplo de final variável

Existe uma variável final speedlimit, vamos alterar o valor dessa variável, mas ela não pode ser alterada porque a variável final, uma vez atribuída a um valor, nunca pode ser alterada.

class Bike9{  
    final int speedlimit=90;//final variable  
    void run(){  
        speedlimit=400;  // this will make error
    }  

    public static void main(String args[]){  
    Bike9 obj=new  Bike9();  
    obj.run();  
    }  
}//end of class  

Classe final Java:

Se você formar uma classe como final, não poderá estender la.

Exemplo de aula final

final class Bike{}  

class Honda1 extends Bike{    //cannot inherit from final Bike,this will make error
  void run(){
      System.out.println("running safely with 100kmph");
   }  

  public static void main(String args[]){  
      Honda1 honda= new Honda();  
      honda.run();  
      }  
  }  

Método final Java:

Se você fizer um método final, não poderá substituí- lo.

Exemplo de finalmétodo (run () na Honda não pode substituir run () em Bike)

class Bike{  
  final void run(){System.out.println("running");}  
}  

class Honda extends Bike{  
   void run(){System.out.println("running safely with 100kmph");}  

   public static void main(String args[]){  
   Honda honda= new Honda();  
   honda.run();  
   }  
}  

compartilhado em: http://www.javatpoint.com/final-keyword

Ali Ziaee
fonte
7

Vale mencionar algumas definições diretas:

Classes / Métodos

Você pode declarar alguns ou todos os métodos de uma classe como final, para indicar que o método não pode ser substituído por subclasses.

Variáveis

Depois que uma finalvariável é inicializada, ela sempre contém o mesmo valor.

final basicamente evite substituir / substituir por qualquer coisa (subclasses, variável "reatribuir"), dependendo do caso.

ivanleoncz
fonte
1
Eu acho que a definição final sobre variáveis ​​é um pouco curta; "Em Java, quando a palavra-chave final é usada com uma variável de tipos de dados primitivos (int, float, etc. etc), o valor da variável não pode ser alterado, mas Quando final é usado com variáveis ​​não primitivas (Observe que variáveis ​​não primitivas sempre são referências a objetos em Java), os membros do objeto referido podem ser alterados. final para variáveis ​​não primitivas significa apenas que elas não podem ser alteradas para se referir a qualquer outro objeto ". geeksforgeeks.org/g-fact-48
ceyun em 23/04
Também válido, especialmente para citar como casos primitivos e não primitivos. Tks.
ivanleoncz 23/04
4

finalé uma palavra-chave reservada em Java para restringir o usuário e pode ser aplicada a variáveis ​​de membro, métodos, classe e variáveis ​​locais. Variáveis ​​finais são frequentemente declaradas com a staticpalavra - chave em Java e são tratadas como constantes. Por exemplo:

public static final String hello = "Hello";

Quando usamos a finalpalavra-chave com uma declaração de variável, o valor armazenado nessa variável não pode ser alterado posteriormente.

Por exemplo:

public class ClassDemo {
  private final int var1 = 3;
  public ClassDemo() {
    ...
  }
}

Nota : Uma classe declarada como final não pode ser estendida ou herdada (ou seja, não pode haver uma subclasse da superclasse). Também é bom observar que os métodos declarados como finais não podem ser substituídos pelas subclasses.

Os benefícios do uso da palavra-chave final são abordados neste tópico .

Desta Haileselassie Hagos
fonte
2
the value stored inside that variable cannot be changed latteré parcialmente verdade. É verdade apenas para tipos de dados primitivos. No caso de qualquer objeto ser criado como final, como arraylist, seu valor pode mudar, mas não a referência. Obrigado!
GS
3

Suponha que você tenha duas caixas de dinheiro, vermelho e branco. Você atribui a essas caixas apenas dois filhos e eles não têm permissão para trocar suas caixas. Portanto, você tem caixas de dinheiro vermelhas ou brancas (final), não é possível modificar a caixa, mas você pode colocar dinheiro na caixa.Ninguém se importa (Modificação-2).

huseyin
fonte
2

Leia todas as respostas.

Há outro caso de usuário em que a finalpalavra-chave pode ser usada, ou seja, em um argumento de método:

public void showCaseFinalArgumentVariable(final int someFinalInt){

   someFinalInt = 9; // won't compile as the argument is final

}

Pode ser usado para variáveis ​​que não devem ser alteradas.

Pritam Banerjee
fonte
1

Quando você o torna estático final, ele deve ser inicializado em um bloco de inicialização estático

    private static final List foo;

    static {
        foo = new ArrayList();
    }

    public Test()
    {
//      foo = new ArrayList();
        foo.add("foo"); // Modification-1
    }
Evgeniy Dorofeev
fonte
1

A finalpalavra-chave indica que uma variável pode ser inicializada apenas uma vez. No seu código, você está executando apenas uma inicialização do final para que os termos sejam satisfeitos. Esta declaração executa a inicialização solitária de foo. Observe que final! = Imutável, significa apenas que a referência não pode ser alterada.

foo = new ArrayList();

Quando você declara foocomo static finala variável deve ser inicializada quando a classe é carregada e não pode confiar na instanciação (também chamada de construtor) para inicializarfoo pois os campos estáticos devem estar disponíveis sem uma instância de uma classe. Não há garantia de que o construtor tenha sido chamado antes de usar o campo estático.

Quando você executa seu método no static finalcenário, a Testclasse é carregada antes da instanciação tno momento. Não há instanciação de foosignificado que ela não foi inicializada; portanto, fooé definida como o padrão para todos os objetos que são null. Neste ponto, suponho que seu código lança um NullPointerExceptionquando você tenta adicionar um item à lista.

Kevin Bowersox
fonte
1

Primeiro de tudo, o lugar no seu código em que você está inicializando (ou seja, atribuindo pela primeira vez) foo está aqui:

foo = new ArrayList();

foo é um objeto (com o tipo Lista), portanto, é um tipo de referência , não um tipo de valor (como int). Como tal, ele mantém uma referência a um local de memória (por exemplo, 0xA7D2A834) onde seus elementos da Lista estão armazenados. Linhas como esta

foo.add("foo"); // Modification-1

não altere o valor de foo (que, novamente, é apenas uma referência a um local de memória). Em vez disso, eles apenas adicionam elementos nesse local de memória referenciado. Para violar a palavra-chave final , você teria que tentar atribuir novamente o foo da seguinte forma:

foo = new ArrayList();

Isso iria dar-lhe um erro de compilação.


Agora, com isso fora do caminho, pense no que acontece quando você adiciona a palavra-chave estática .

Quando você NÃO possui a palavra-chave estática, cada objeto que instancia a classe possui sua própria cópia de foo. Portanto, o construtor atribui um valor a uma cópia nova e em branco da variável foo, o que é perfeitamente aceitável.

No entanto, quando você tem a palavra-chave estática, existe apenas um foo na memória associado à classe. Se você fosse criar dois ou mais objetos, o construtor tentaria reatribuir esse nome de cada vez, violando a palavra-chave final .

Niko Bellic
fonte
1
  1. Como a variável final é não estática, ela pode ser inicializada no construtor. Mas se você o tornar estático, ele não poderá ser inicializado pelo construtor (porque os construtores não são estáticos).
  2. Não se espera que a adição à lista pare, tornando a lista final. finalapenas vincula a referência a um objeto específico. Você é livre para alterar o 'estado' desse objeto, mas não o próprio objeto.
Ankit
fonte
1

A seguir, são apresentados diferentes contextos em que final é usado.

Variáveis finais Uma variável final pode ser atribuída apenas uma vez. Se a variável for uma referência, isso significa que a variável não pode ser ligada novamente para fazer referência a outro objeto.

class Main {
   public static void main(String args[]){
      final int i = 20;
      i = 30; //Compiler Error:cannot assign a value to final variable i twice
   }
}

A variável final pode receber um valor posteriormente (não é obrigatório atribuir um valor quando declarado), mas apenas uma vez.

Classes finais Uma classe final não pode ser estendida (herdada)

final class Base { }
class Derived extends Base { } //Compiler Error:cannot inherit from final Base

public class Main {
   public static void main(String args[]) {
   }
}

Métodos finais Um método final não pode ser substituído por subclasses.

//Error in following program as we are trying to override a final method.
class Base {
  public final void show() {
       System.out.println("Base::show() called");
    }
}     
class Derived extends Base {
    public void show() {  //Compiler Error: show() in Derived cannot override
       System.out.println("Derived::show() called");
    }
}     
public class Main {
    public static void main(String[] args) {
        Base b = new Derived();;
        b.show();
    }
}
roottraveller
fonte
1

Pensei em escrever uma resposta atualizada e aprofundada aqui.

final A palavra-chave pode ser usada em vários lugares.

  1. Aulas

A final classsignifica que nenhuma outra classe pode estender essa classe final. Quando o Java Run Time ( JRE ) sabe que uma referência a objeto está no tipo de uma classe final (digamos F), ele sabe que o valor dessa referência pode estar apenas no tipo F.

Ex:

F myF;
myF = new F();    //ok
myF = someOther;  //someOther cannot be in type of a child class of F.
                  //because F cannot be extended.

Portanto, quando ele executa qualquer método desse objeto, esse método não precisa ser resolvido em tempo de execução usando uma tabela virtual . ou seja, o polimorfismo em tempo de execução não pode ser aplicado. Portanto, o tempo de execução não se preocupa com isso. O que significa que economiza tempo de processamento, o que melhora o desempenho.

  1. métodos

A final methodde qualquer classe significa que qualquer classe filha que estenda essa classe não poderá substituir o (s) método (s) final (is). Portanto, o comportamento em tempo de execução nesse cenário também é o mesmo com o comportamento anterior que mencionei para as classes.

  1. campos, variáveis ​​locais, parâmetros de método

Se alguém especificou qualquer tipo acima final, significa que o valor já está finalizado, portanto, o valor não pode ser alterado .

Ex:

Para campos, parâmetros locais

final FinalClass fc = someFC; //need to assign straight away. otherwise compile error.
final FinalClass fc; //compile error, need assignment (initialization inside a constructor Ok, constructor can be called only once)
final FinalClass fc = new FinalClass(); //ok
fc = someOtherFC; //compile error
fc.someMethod(); //no problem
someOtherFC.someMethod(); //no problem

Para parâmetros de método

void someMethod(final String s){
    s = someOtherString; //compile error
}

Isso significa simplesmente que o valor do valor de finalreferência não pode ser alterado. ou seja, apenas uma inicialização é permitida. Nesse cenário, em tempo de execução, como o JRE sabe que os valores não podem ser alterados, ele carrega todos esses valores finalizados (de referências finais) no cache L1 . Porque ele não precisa a carga de volta novamente e novamente de memória principal . Caso contrário, ele será carregado no cache L2 e será carregado periodicamente da memória principal. Portanto, é também uma melhoria de desempenho.

Portanto, em todos os três cenários acima, quando não especificamos a finalpalavra - chave nos locais que podemos usar, não precisamos nos preocupar, as otimizações do compilador farão isso por nós. Também existem muitas outras coisas que as otimizações do compilador fazem por nós. :)

Supun Wijerathne
fonte
0

Acima de tudo, estão corretos. Além disso, se você não quiser que outras pessoas criem subclasses a partir de sua classe, declare-a como final. Então, torna-se o nível da folha da hierarquia da árvore de classes que ninguém pode estender ainda mais. É uma boa prática evitar uma hierarquia enorme de classes.

Shehan Simen
fonte