Java Constantes (final)

Algumas informações de um programa não devem mudar nunca: o número de vagas de uma turma, a taxa de entrega da loja, o valor de π. Em Java, você marca essas variáveis com a palavra final, e o compilador passa a proibir qualquer tentativa de mudá-las. Nesta aula você vai criar constantes, ver o erro que o Java dá ao tentar alterá-las e aprender por que elas deixam o código mais fácil de entender e de manter.

Você vai aprender
  • Como declarar uma variável final e o que acontece ao tentar mudá-la.
  • Que uma variável final pode receber o valor depois, mas uma vez só.
  • As constantes da classe, com static final, e a convenção MAIÚSCULAS_COM_SUBLINHADO.
  • O que são "números mágicos" e por que trocá-los por constantes.

A ideia: escrito à caneta

Numa folha de cadastro, alguns campos você preenche a lápis, porque podem mudar (endereço, telefone), e outros à caneta, porque são definitivos (data de nascimento). Uma variável comum é o campo a lápis: você apaga e escreve de novo quantas vezes quiser. Uma variável final é o campo à caneta: depois de escrita, fica.

final quer dizer "final, definitivo": o valor que a variável recebe é o valor final dela. Uma variável que nunca muda é chamada de constante.


1. Variáveis final

Basta escrever final antes do tipo:

public class Main {
    public static void main(String[] args) {
        final int LIMITE_DE_ALUNOS = 30;
        System.out.println("Vagas: " + LIMITE_DE_ALUNOS);
        LIMITE_DE_ALUNOS = 35;
    }
}
final
Modificador: "esta variável recebe valor uma única vez".
int
O tipo, como em qualquer variável.
LIMITE_DE_ALUNOS
O nome. Constantes costumam ser escritas em maiúsculas, com _ entre as palavras (veja a convenção adiante).
= 30;
O valor, que nunca mais muda.
$ javac Main.java
Main.java:5: error: cannot assign a value to final variable LIMITE_DE_ALUNOS
        LIMITE_DE_ALUNOS = 35;
        ^
1 error

"Não é possível atribuir um valor à variável final LIMITE_DE_ALUNOS". O erro acontece na compilação: o programa nem chega a rodar. É exatamente o que queremos: se alguém (inclusive você, daqui a seis meses) tentar mudar o limite por engano, o Java avisa na hora.

Sem a linha 5, o programa compila e mostra Vagas: 30. Ler uma constante é igual a ler qualquer variável; só atribuir é proibido.

Missão (provoque o erro): a taxa de entrega é uma constante. Acrescente, depois do println, uma linha que tenta mudar TAXA_DE_ENTREGA para 9.0 e veja o javac recusar.

public class Main {
    public static void main(String[] args) {
        final double TAXA_DE_ENTREGA = 7.50;
        System.out.println("Entrega: " + TAXA_DE_ENTREGA);
    }
}
public class Main {
    public static void main(String[] args) {
        final double TAXA_DE_ENTREGA = 7.50;
        System.out.println("Entrega: " + TAXA_DE_ENTREGA);
        TAXA_DE_ENTREGA = 9.0;
    }
}

Atribuir depois, mas só uma vez

Uma variável final não precisa receber o valor na mesma linha. O que o Java garante é que ela recebe valor exatamente uma vez. Isso é útil quando o valor depende de uma decisão:

public class Main {
    public static void main(String[] args) {
        final double desconto;
        int idade = 70;
        if (idade >= 65) {
            desconto = 0.5;
        } else {
            desconto = 0.0;
        }
        System.out.println(desconto);
    }
}
$ javac Main.java && java Main
0.5

O if/else (assunto de if, else if, else) escolhe um dos dois caminhos; em qualquer deles, desconto recebe valor uma vez só, e o Java aceita. Repare que aqui o nome está em camelCase: desconto é final para não ser alterado por engano no resto do método, mas não é uma "constante do programa" como uma taxa fixa. Veja a diferença na convenção de nomes, mais adiante.

Se o Java perceber que a variável pode receber valor duas vezes, recusa:

public class Main {
    public static void main(String[] args) {
        final int x;
        x = 1;
        x = 2;
    }
}
$ javac Main.java
Main.java:5: error: variable x might already have been assigned
        x = 2;
        ^
1 error

"A variável x pode já ter sido atribuída". E, como qualquer variável local, uma final sem valor também não pode ser lida: final int TOTAL; seguido de println(TOTAL) dá variable TOTAL might not have been initialized, o mesmo erro visto em Variáveis.

final combina com var

final var cidade = "Recife"; é válido: o tipo é inferido (String) e a variável não pode mudar. Veja var.


2. Constantes da classe: static final

As constantes mais comuns não ficam dentro do main, e sim no topo da classe, fora de qualquer método. Assim, qualquer parte do programa pode usá-las, e quem abre o arquivo vê logo de cara os valores fixos. Para isso, elas levam também a palavra static:

public class Main {
    static final double TAXA_DE_ENTREGA = 7.50;
    static final int ESTOQUE_MINIMO = 5;

    public static void main(String[] args) {
        double compra = 42.0;
        System.out.println("Total: " + (compra + TAXA_DE_ENTREGA));
        System.out.println("Estoque mínimo: " + ESTOQUE_MINIMO);
        System.out.println(Math.PI);
        System.out.println(Integer.MAX_VALUE);
    }
}
$ javac Main.java && java Main
Total: 49.5
Estoque mínimo: 5
3.141592653589793
2147483647
static
"Da classe": existe uma só cópia, compartilhada por todo o programa. O static é assunto do Nível 2 (static); por enquanto, aceite a regra: constante no topo da classe leva static final.
final
Não pode mudar.
double
O tipo.
TAXA_DE_ENTREGA
O nome, em maiúsculas.
= 7.50;
O valor. Uma constante static final precisa receber o valor aqui mesmo.

Os parênteses em "Total: " + (compra + TAXA_DE_ENTREGA) fazem a soma acontecer antes de juntar com o texto; sem eles, o Java colaria os dois números no texto e mostraria Total: 42.07.5 (o porquê está em Precedência).

Você já usou constantes do Java

Math.PI (π) e Integer.MAX_VALUE, que apareceram na saída acima, são constantes static final declaradas pelo próprio Java, nas classes Math e Integer. É por isso que se escreve o nome da classe, um ponto e o nome da constante. Dentro da classe Main, as suas constantes podem ser usadas só pelo nome (TAXA_DE_ENTREGA); de fora, seria Main.TAXA_DE_ENTREGA.

Constante do JavaValorAula
Math.PI3.141592653589793Math
Math.E2.718281828459045 (número de Euler)Math
Integer.MAX_VALUE2147483647int, long, short, byte
Double.MAX_VALUE1.7976931348623157E308double e float

A convenção de nomes

O que éConvençãoExemplo
Constante static final (valor fixo do programa)MAIÚSCULAS_COM_SUBLINHADO (chamado SCREAMING_SNAKE_CASE, "cobra gritando")TAXA_DE_ENTREGA, MAX_VALUE, PI
Variável local final calculada no programacamelCase, como qualquer variávelfinal double desconto
Variável comumcamelCasetotalDaCompra

Quando você lê TAXA_DE_ENTREGA no meio de uma conta, as maiúsculas avisam: "isto é fixo, definido lá no topo". O Java não obriga a convenção (compila com qualquer nome), mas todo programador Java espera vê-la. Neste guia, constantes locais com valor fixo (como LIMITE_DE_ALUNOS no primeiro exemplo) também usam maiúsculas, para destacar que são valores de configuração.


3. Números mágicos

Olhe este cálculo de salário:

public class Main {
    public static void main(String[] args) {
        double salario = 3000;
        double desconto = salario * 0.075;
        double liquido = salario - desconto;
        System.out.println(liquido);
    }
}

O que é 0.075? Uma taxa de imposto? Uma comissão? Juros? Só quem escreveu sabe. Um número que aparece no código sem explicação se chama número mágico: funciona "por mágica", ninguém sabe de onde veio. Agora a mesma conta com uma constante:

public class Main {
    static final double ALIQUOTA_INSS = 0.075;

    public static void main(String[] args) {
        double salario = 3000;
        double desconto = salario * ALIQUOTA_INSS;
        double liquido = salario - desconto;
        System.out.println(liquido);
    }
}
$ javac Main.java && java Main
2775.0

As duas versões dão exatamente o mesmo resultado (2775.0), mas a segunda tem três vantagens:

  1. Explica a si mesma: salario * ALIQUOTA_INSS se lê como uma frase.
  2. Muda num lugar só: se a alíquota mudar, você troca uma linha no topo. Com o número mágico, teria de caçar todos os 0.075 do programa e torcer para não esquecer nenhum (nem trocar um 0.075 que era outra coisa).
  3. Evita erro de digitação: errar 0.075 como 0.75 compila e dá resultado errado em silêncio; errar ALIQUOTA_INSS dá cannot find symbol na hora.
Nem todo número é mágico

0, 1, 2 em contas óbvias (contador + 1, n / 2 para metade, n % 2 para par ou ímpar) não precisam de constante. A pergunta é: "alguém que leia esta linha sabe de onde veio o número?". Se não sabe, crie uma constante.

Missão: o 0.10 é um número mágico (o desconto da loja). Crie no topo da classe a constante static final double DESCONTO = 0.10; e use-a na conta. A saída deve continuar Com desconto: 180.0.

public class Main {

    public static void main(String[] args) {
        double compra = 200.0;
        double total = compra - compra * 0.10;
        System.out.println("Com desconto: " + total);
    }
}
public class Main {
    static final double DESCONTO = 0.10;

    public static void main(String[] args) {
        double compra = 200.0;
        double total = compra - compra * DESCONTO;
        System.out.println("Com desconto: " + total);
    }
}

Quando dá errado

"cannot assign a value to final variable"
$ javac Main.java
Main.java:5: error: cannot assign a value to final variable LIMITE_DE_ALUNOS
        LIMITE_DE_ALUNOS = 35;
        ^
1 error

O que quer dizer: você tentou mudar uma variável final. Para as constantes da classe, a mensagem diz cannot assign a value to static final variable TAXA.

Como consertar: pergunte-se se o valor realmente deve mudar. Se sim, ele não é constante: tire o final (e use um nome em camelCase). Se não, a linha que tenta mudar é que está errada.

"variable x might already have been assigned"

O que quer dizer: uma variável final declarada sem valor pode receber valor mais de uma vez no caminho do programa.

Como consertar: garanta uma única atribuição em cada caminho (como no exemplo do if/else) ou tire o final.

static final dentro do main: "illegal start of expression"
public class Main {
    public static void main(String[] args) {
        static final double TAXA = 7.50;
    }
}
$ javac Main.java
Main.java:3: error: illegal start of expression
        static final double TAXA = 7.50;
        ^
Main.java:5: error: class, interface, annotation type, enum, record, method or field expected
}
^
2 errors

O que quer dizer: "início de expressão ilegal". static só existe para coisas da classe; dentro de um método ele não faz sentido. O segundo erro é consequência do primeiro: o Java se perdeu e não reconheceu mais o fim das chaves.

Como consertar: dentro do método, use só final double TAXA = 7.50;. Para uma constante da classe, mova a linha para fora do main, logo abaixo de public class Main {.

Constante da classe sem valor: "variable MAX might not have been initialized"
$ javac Main.java
Main.java:2: error: variable MAX might not have been initialized
    static final int MAX;
                     ^
1 error

O que quer dizer: uma constante static final declarada sem valor nunca recebe um (não há if no topo da classe para decidir).

Como consertar: dê o valor na declaração: static final int MAX = 100;.


Referência

FormaOndeExemplo
final tipo NOME = valor;Dentro de um métodofinal int LIMITE_DE_ALUNOS = 30;
final tipo nome; + uma atribuiçãoDentro de um métodofinal double desconto; … desconto = 0.5;
final var nome = valor;Dentro de um métodofinal var cidade = "Recife";
static final tipo NOME = valor;No topo da classe, fora dos métodosstatic final double TAXA_DE_ENTREGA = 7.50;
Classe.CONSTANTEUsar a constante de outra classeMath.PI, Integer.MAX_VALUE

O final também se aplica a parâmetros, atributos, métodos e classes, cada um com um sentido próprio; isso é o assunto de final, no Nível 2.


Exercícios

1. Tornar constante

Complete para que VAGAS não possa mais ser alterada:

int VAGAS = 40;

2. Constante da classe

Complete a declaração da constante no topo da classe:

double FRETE = 12.0;

3. Convenção de nome

Escreva o nome da constante "taxa de juros" seguindo a convenção do Java:

static final double = 0.02;

4. O que acontece ao compilar final int X = 1; X = 2;?

5. Qual é a principal vantagem de trocar 0.075 por ALIQUOTA_INSS?

6. Onde se declara uma constante static final?


Resumo

  • final antes do tipo faz a variável receber valor uma vez só; mudar depois dá cannot assign a value to final variable.
  • A atribuição pode vir depois da declaração, desde que aconteça exatamente uma vez em cada caminho.
  • Constantes do programa ficam no topo da classe com static final e nomes em MAIÚSCULAS_COM_SUBLINHADO.
  • Math.PI e Integer.MAX_VALUE são constantes do próprio Java.
  • Troque números mágicos por constantes com nome: o código se explica e muda num lugar só.