Como funciona a palavra-chave “final” em Java? (Ainda posso modificar um object)

Em Java, usamos final palavra-chave final com variables ​​para especificar que seus valores não devem ser alterados. Mas vejo que você pode alterar o valor no construtor / methods da class. Novamente, se a variável é static então é um erro de compilation.

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 compilation. Como esta final realmente funciona?

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

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

Java não tem nenhum conceito de imutabilidade do object; isto é conseguido através do desenho cuidadoso do object, e é um esforço longe de ser trivial.

Esta é uma pergunta favorita da entrevista . Com essas perguntas, o entrevistador tenta descobrir o quão bem você entende o comportamento dos objects em relação aos construtores, methods, variables ​​de class (variables ​​estáticas) e variables ​​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, nós definimos um construtor para ‘Test’ e fornecemos um método ‘setFoo’.

Sobre o construtor: O construtor pode ser chamado apenas uma vez por criação de object usando a new palavra-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ê quiser (mesmo nunca) e o compilador sabe disso.

Cenário 1

 private final List foo; // 1 

foo é uma variável de instância . Quando criamos Test object da class Test , a variável da instância foo será copiada dentro do object da class Test . Se nós atribuímos foo dentro do construtor, então o compilador sabe que o construtor será invocado apenas uma vez, então não há nenhum problema em atribuí-lo dentro do construtor.

Se nós atribuirmos foo dentro de um método, o compilador sabe 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 variável final . Então 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(); 

foo é agora uma variável estática . Quando criamos uma instância da class Test , foo não será copiado para o object porque foo é estático. Agora foo não é uma propriedade independente de cada object. Esta é uma propriedade da class Test . Mas foo pode ser visto por vários objects e se todo object que é criado usando a new palavra-chave que acabará invocando o construtor de Test que altera o valor no momento da criação de múltiplos objects (Lembre-se static foo não é copiado em cada object, mas é compartilhado entre vários objects.)

Cenário 3

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

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

classs finais não podem ser subclassificadas
os methods finais não podem ser substituídos. (Este método está na superclass)
methods finais podem replace. (Leia isto de maneira gramatical. Este método está em uma subclass)

A palavra-chave final tem uma maneira numerosa de usar:

  • Uma class final não pode ser subclassificada.
  • Um método final não pode ser substituído por subclasss
  • Uma variável final só pode ser inicializada uma vez

Outro uso:

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

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

A palavra-chave final pode ser interpretada de duas maneiras diferentes, dependendo do que é usado:

Tipos de valor: para int s, double s etc, ele irá garantir que o valor não pode mudar,

Tipos de referência: Para referências a objects, final garante que a referência nunca será alterada, o que significa que sempre se referirá ao mesmo object. Não garante, em absoluto, que os valores dentro do object sejam referenciados para permanecerem iguais.

Como tal, final List foo; garante que foo sempre se refira à mesma lista, mas o conteúdo da lista pode mudar com o tempo.

Se você fizer foo estático, você deve inicializá-lo no construtor de class (ou inline onde você o define) como os exemplos a seguir.

Construtor de class (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 modificador final funciona, mas sim como o modificador static funciona.

O modificador final impõe uma boot de sua referência no momento em que a chamada ao seu construtor é concluída (ou seja, você deve inicializá-lo no construtor).

Quando você inicializa um atributo in-line, ele é inicializado antes que o código definido para o construtor seja executado, portanto, você obtém os seguintes resultados:

  • se foo for static , foo = new ArrayList() será executado antes que o construtor static{} definido para sua class seja executado
  • Se foo não for static , foo = new ArrayList() será executado antes que seu construtor seja executado

Quando você não inicia um atributo in-line, o modificador final impõe que você o inicialize e que você deve fazê-lo no construtor. Se você também tiver um modificador static , o construtor no qual você terá que inicializar o atributo é o bloco de boot da class: static{} .

O erro que você recebe no seu código é do fato de que o static{} é executado quando a class é carregada, antes da hora em que você instancia um object dessa class. Portanto, você não inicializou foo quando a class é criada.

Pense no bloco static{} como um construtor para um object do tipo Class . É aqui que você deve fazer a boot de seus atributos de class static final (se não for feito inline).

Nota:

O modificador final assegura a constância apenas para tipos primitivos e referências.

Quando você declara um object final , o que obtém é uma referência final a esse object, mas o object em si não é constante.

O que você está realmente conseguindo ao declarar um atributo final é que, quando você declara um object para seu propósito específico (como a final List que você declarou), esse e somente aquele object será usado para aquele propósito: você não será capaz para alterar List foo para outra List , mas você ainda pode alterar sua List adicionando / removendo itens (a List você está usando será a mesma, apenas com seu conteúdo alterado).

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

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

2) Um object 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"; 

ref variável x pode ser alterado para apontar uma string diferente, mas o valor de “abc” não pode ser alterado.

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

4) “Mas vejo que você pode alterar o valor no construtor / methods da class”. – Você não pode alterá-lo dentro de um método.

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

Vale mencionar algumas definições diretas:

Classes / Métodos

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

Variáveis

Uma vez que uma variável final tenha sido inicializada, ela sempre contém o mesmo valor.

final basicamente evitar replace / sobrescrever por qualquer coisa (subclasss, variável “reatribuir”), dependendo do caso.

Suponha que você tenha dois moneyboxes, vermelho e branco. Você atribui a esses moneyboxes apenas dois filhos e eles não podem trocar suas checkboxs. Então você tem checkboxs de dinheiro vermelhas ou brancas (final) você não pode modificar a checkbox, mas você pode colocar dinheiro em sua checkbox. Ninguém se importa (Modificação-2).

final é uma palavra-chave reservada em Java para restringir o usuário e pode ser aplicada a variables ​​de membros, methods, classs e variables ​​locais. As variables ​​finais são frequentemente declaradas com a palavra-chave static em Java e são tratadas como constantes. Por exemplo:

 public static final String hello = "Hello"; 

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

Por exemplo:

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

Nota : Uma class declarada como final não pode ser estendida ou herdada (ou seja, não pode haver uma subclass da superclass). Também é bom notar que os methods declarados como final não podem ser substituídos por subclasss.

Benefícios de usar a palavra-chave final são abordados neste segmento .

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

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

A palavra-chave final pode ser aplicada com as variables, uma variável final que não tem valor, é chamada de variável final branco ou variável final não inicializada. Pode ser inicializado apenas no construtor. A variável final branco também pode ser static que será inicializada apenas no bloco static .

Variável final do Java:

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

Exemplo de variável final

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, 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 de Java:

Se você fizer uma aula como final , não poderá estendê- 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 de Java:

Se você fizer qualquer método como final, você não poderá sobrescrevê- lo.

Exemplo de método final (run () no Honda não pode replace 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 de: http://www.javatpoint.com/final-keyword

Quando você faz a final estática, ela deve ser inicializada em um bloco de boot estática

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

A palavra final indica que uma variável só pode ser inicializada uma vez. Em seu código, você está executando apenas uma boot final para que os termos sejam satisfeitos. Esta declaração executa a boot solitária de foo . Note que final ! = Imutável, significa apenas que a referência não pode mudar.

 foo = new ArrayList(); 

Quando você declara foo como static final a variável deve ser inicializada quando a class é carregada e não pode confiar na instanciação (também chamada de construtor) para inicializar foo pois os campos estáticos devem estar disponíveis sem uma instância de uma class. 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 cenário static final , a class Test é carregada antes de instanciar t neste momento, não há nenhuma instância de foo o que significa que ele não foi inicializado, então foo é definido como padrão para todos os objects, que é null . Neste ponto, presumo que seu código lança um NullPointerException quando você tenta adicionar um item à lista.

  1. Como a variável final é não-estática, ela pode ser inicializada no construtor. Mas se você torná-lo estático, não pode ser inicializado pelo construtor (porque os construtores não são estáticos).
  2. Não se espera que a adição à lista pare de fazer a lista final. final apenas liga a referência a um object específico. Você é livre para mudar o “estado” desse object, mas não o object em si.

Leia todas as respostas.

Existe outro caso de usuário em que final palavra-chave final pode ser usada, isto é, 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 variables ​​que não devem ser alteradas.

Pensei em escrever uma resposta atualizada e detalhada aqui.

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

  1. classs

Uma final class significa que nenhuma outra class pode estender essa class final. Quando o Java Run Time ( JRE ) sabe que uma referência de object está no tipo de uma class final (digamos F), ele sabe que o valor dessa referência pode estar apenas no tipo de 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 object, esse método não precisa ser resolvido no tempo de execução usando uma tabela virtual . isto é, o polymorphism em tempo de execução não pode ser aplicado. Então o tempo de execução não se incomoda com isso. O que significa que economiza tempo de processamento, o que melhorará o desempenho.

  1. methods

Um final method de qualquer class significa que qualquer class filha que estenda essa class não pode sobrescrever esse (s) método (s) final (is). Portanto, o comportamento do tempo de execução nesse cenário também é bem o mesmo com o comportamento anterior que mencionei para as classs.

  1. campos, variables ​​locais, parâmetros do método

Se alguém especificou qualquer tipo de acima como final , isso 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 do método

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

Isso significa simplesmente que o valor do valor de referência final não pode ser alterado. ou seja, apenas uma boot é permitida. Nesse cenário, no 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 não precisa carregar de novo e de novo da memory principal . Caso contrário, ele é carregado para o cache L2 e faz o tempo para carregar a partir da memory principal. Por isso, também é uma melhoria de desempenho.

Portanto, em todos os três cenários acima, quando não tivermos especificado a palavra-chave final em lugares que podemos usar, não precisamos nos preocupar, pois as otimizações do compilador farão isso para nós. Há também muitas outras coisas que as otimizações do compilador fazem por nós. 🙂

Acima de tudo estão corretas. Além disso, se você não quiser que outras pessoas criem subclasss de sua turma, declare sua turma como final. Então, torna-se o nível de folha da hierarquia da sua tree de classs, que ninguém pode estender ainda mais. É uma boa prática evitar uma enorme hierarquia de classs.

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

 foo = new ArrayList(); 

foo é um object (com tipo List), portanto, é um tipo de referência , não um tipo de valor (como int). Como tal, ele contém uma referência a um local de memory (por exemplo, 0xA7D2A834) onde seus elementos de Lista sã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 memory). Em vez disso, eles apenas adicionam elementos ao local de memory referenciado. Para violar a palavra-chave final , você teria que tentar reatribuir o foo da seguinte forma novamente:

 foo = new ArrayList(); 

Isso lhe daria um erro de compilation.


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

Quando você não tem a palavra-chave estática, cada object que instancia a class tem sua própria cópia do foo. Portanto, o construtor atribui um valor a uma cópia nova e vazia da variável foo, que é perfeitamente adequada.

No entanto, quando você tem a palavra-chave static, existe apenas um foo na memory que está associada à class. Se você fosse criar dois ou mais objects, o construtor estaria tentando reatribuir esse foo a cada vez, violando a palavra-chave final .

A seguir estão contextos diferentes onde final é usado.

Variáveis finais Uma variável final só pode ser atribuída uma vez. Se a variável é uma referência, isso significa que a variável não pode ser recontratada para fazer referência a outro object.

 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 posterior (não obrigatório para um valor atribuído quando declarado), mas apenas uma vez.

Classes finais Uma class 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 subclasss.

 //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(); } }