W3docs

Palavra-chave final em Java

Torne variáveis Java constantes, impeça a sobrescrita de métodos e proíba a herança de classes com a palavra-chave final.

final significa "defina isso uma vez e não altere mais." Aplica-se a quatro coisas, e o efeito depende de qual delas:

  • Em uma variável, o vínculo não pode ser reatribuído.
  • Em um método, o método não pode ser sobrescrito por uma subclasse.
  • Em uma classe, a classe não pode ser estendida.
  • Em um parâmetro, o parâmetro não pode ser reatribuído dentro do método.

O fio condutor é "nenhuma modificação posterior ao vínculo inicial." O que exatamente isso proíbe depende do que está sendo marcado.

Variáveis locais final

A forma mais simples: uma variável local final não pode ser reatribuída após a primeira atribuição.

void demo() {
  final int max = 100;
  max = 50;          // ERROR: cannot assign a value to final variable max
}

Não é obrigatório inicializá-la na declaração — você pode adiar a primeira atribuição, desde que atribua exatamente uma vez antes de qualquer leitura:

final int x;
if (condition) x = 1;
else           x = 2;
System.out.println(x);   // ok — x was definitely assigned

final em uma variável local é principalmente uma ferramenta de documentação — ela informa a um leitor futuro (e ao compilador) "isso não mudará a partir deste ponto." Capturas por lambda e por classe interna exigem isso: qualquer variável local usada dentro de uma deve ser final ou efetivamente final (nunca reatribuída).

Parâmetros final

Um parâmetro final não pode ser reatribuído dentro do corpo do método:

void greet(final String name) {
  name = name.toUpperCase();    // ERROR
}

Algumas equipes exigem final em cada parâmetro; outras acham que é ruído visual. Ambas as abordagens são válidas — o que importa é consistência dentro de um código-base. O hábito de mutar parâmetros que isso desencoraja é uma fonte real de bugs.

Campos final

Um campo final é atribuído exatamente uma vez — seja na declaração, em um inicializador de instância, ou no construtor — e nunca mais:

public class Point {
  private final int x, y;

  public Point(int x, int y) {
    this.x = x;        // assigned once
    this.y = y;
  }

  // no setX or setY — there's no way to change the values
}

Este é o núcleo de uma classe imutável. Cada campo é final; nada pode mutar o objeto após a construção.

O compilador verifica que cada campo final é atribuído exatamente uma vez em cada caminho do construtor. Se um caminho for ignorado, ocorre um erro de compilação. Se atribuído duas vezes, também ocorre um erro de compilação.

static final — constantes

A forma padrão de uma constante é public static final seguido de um nome em UPPER_SNAKE:

public static final double PI       = 3.141592653589793;
public static final int    MAX_RETRY = 3;

Static, porque não há razão para manter uma cópia por instância; final, porque é uma constante. Para constantes primitivas e String, o compilador incorpora o valor em cada ponto de chamada. Uma consequência: se você alterar o valor de tal constante, as classes que a referenciavam mantêm o valor antigo até serem recompiladas — o literal foi incorporado ao seu bytecode no momento da compilação.

final e referências

Um equívoco comum: final congela a referência, não o objeto para o qual ela aponta. Com um array ou lista final, ainda é possível mutar o conteúdo:

final int[] nums = {1, 2, 3};
nums[0] = 99;        // ok — mutating the array, not reassigning the reference
nums = new int[5];   // ERROR — reassigning the reference

final List<String> names = new ArrayList<>();
names.add("Rex");    // ok — mutates the list
names = List.of();   // ERROR

Se você quiser uma lista cujo conteúdo também esteja congelado, envolva-a com List.copyOf(...) ou Collections.unmodifiableList(...).

Métodos final

final em um método significa nenhuma subclasse pode sobrescrevê-lo:

public class Shape {
  public final String describe() {       // can never be overridden
    return getClass().getSimpleName() + " area=" + area();
  }
  public double area() { return 0; }
}

public class Circle extends Shape {
  public String describe() { return "circle"; }   // ERROR
}

Você marcaria um método como final quando sobrescrevê-lo quebraria invariantes dos quais a classe depende. É uma escolha de design opinativa — a maioria dos métodos não é final em código-bases reais — mas para templates que não devem ser alterados, é a ferramenta certa.

Classes final

final em uma classe significa nenhuma classe pode estendê-la:

public final class Money { ... }

public class CryptoMoney extends Money { }    // ERROR

Use isso quando a extensão frustraria o design. A biblioteca padrão faz isso rotineiramente: String, Integer, Long, Double e os demais wrappers de tipos primitivos são todos final — estendê-los permitiria inserir comportamento mutável em um tipo que a linguagem trata como imutável.

final não é imutabilidade

Um campo final sozinho não torna um objeto imutável; ele apenas impede a mudança da referência. A imutabilidade real requer:

  1. Todos os campos final.
  2. A própria classe final, ou construída com cuidado para evitar que subclasses adicionem estado mutável.
  3. Nenhum método que exponha um objeto interno mutável (cópias defensivas na saída).
  4. Nenhum método que permita ao chamador mutar o estado através dele.

Records (abordados em records) agrupam a maior parte disso automaticamente.

Um exemplo prático

java— editable, runs on the server

O que vem a seguir

Campos private e imutabilidade com final são os blocos de construção da próxima ideia: ocultar o estado completamente por trás dos métodos de uma classe. O capítulo de encapsulamento une esses fios.

Prática

Prática
Um campo declarado como final int[] xs = {1, 2, 3}; — qual atribuição é rejeitada pelo compilador?
Um campo declarado como final int[] xs = {1, 2, 3}; — qual atribuição é rejeitada pelo compilador?
Was this page helpful?