final (și static final) în Java

final interzice schimbarea: o variabilă primește valoare o singură dată, o metodă nu poate fi rescrisă, o clasă nu poate fi extinsă.

Sintaxă
final int x = 10;                         // variabilă: o singură atribuire
static final double TVA = 0.21;           // constantă de clasă
final void metoda() { ... }               // nu poate fi rescrisă
final class Utilitar { ... }              // nu poate fi extinsă

Ce face final (și static final)

Pe o variabilă (locală, parametru sau câmp), final înseamnă că poate primi valoare o singură dată. Un câmp final trebuie inițializat la declarare sau în fiecare constructor. Atenție: final blochează referința, nu obiectul: într-o listă final poți adăuga elemente, dar nu poți face variabila să arate spre altă listă.

Combinația static final creează o constantă de clasă, scrisă după convenție cu majuscule și liniuță jos: static final double TVA = 0.21;. Există o singură copie, care nu se schimbă niciodată.

Pe o metodă, final interzice rescrierea în subclase. Pe o clasă, interzice extinderea: String, Integer și Math sunt final. Variabilele locale folosite într-o lambda sau într-o clasă anonimă trebuie să fie final sau „efectiv final” (să nu fie modificate după inițializare).

Exemple

Exemplul 1: Constante static final și câmpuri final

TVA și MONEDA sunt constante comune. cod și pret sunt final, setate o singură dată în constructor; produsul este imutabil.

class Produs {
    static final double TVA = 0.21;
    static final String MONEDA = "lei";

    final String cod;
    final double pret;

    Produs(String cod, double pret) {
        this.cod = cod;
        this.pret = pret;
    }

    double pretCuTva() {
        return pret * (1 + TVA);
    }
}

public class Main {
    public static void main(String[] args) {
        Produs p = new Produs("RO-123", 200);
        System.out.printf("%s: %.2f %s fără TVA, %.2f %s cu TVA%n",
            p.cod, p.pret, Produs.MONEDA, p.pretCuTva(), Produs.MONEDA);
        // p.pret = 150;        // eroare: cannot assign a value to final variable pret
        // Produs.TVA = 0.19;   // eroare: cannot assign a value to final variable TVA
    }
}

Rezultat

RO-123: 200.00 lei fără TVA, 242.00 lei cu TVA

Exemplul 2: final blochează referința, nu obiectul

Lista orase este final, dar conținutul ei se poate schimba. Pentru o listă care nu se poate modifica deloc folosește List.of (imutabilă); modificarea ei aruncă excepție.

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

public class Main {
    public static void main(String[] args) {
        final List<String> orase = new ArrayList<>();
        orase.add("Iași");
        orase.add("Vaslui");
        System.out.println(orase);
        // orase = new ArrayList<>();  // eroare: cannot assign a value to final variable orase

        final List<String> fixe = List.of("Iași", "Vaslui");
        try {
            fixe.add("Bacău");
        } catch (UnsupportedOperationException e) {
            System.out.println("List.of nu se poate modifica: " + e.getClass().getSimpleName());
        }
    }
}

Rezultat

[Iași, Vaslui]
List.of nu se poate modifica: UnsupportedOperationException

Exemplul 3: Metodă finală și variabilă efectiv finală

calculeaza este final, deci subclasele nu o pot schimba. Variabila prag nu e declarată final, dar nu se mai modifică, deci este „efectiv finală” și poate fi folosită în lambda.

import java.util.List;

class Nota {
    final double valoare;
    Nota(double valoare) { this.valoare = valoare; }

    final String calculeaza() {
        return valoare >= 5 ? "promovat" : "nepromovat";
    }
}

public class Main {
    public static void main(String[] args) {
        double prag = 9.0;   // efectiv finală
        List<Double> note = List.of(9.5, 7.0, 10.0, 4.5);
        long excelente = note.stream().filter(n -> n >= prag).count();
        System.out.println("note de cel puțin " + prag + ": " + excelente);

        for (double n : note) {
            System.out.println(n + " -> " + new Nota(n).calculeaza());
        }
    }
}

Rezultat

note de cel puțin 9.0: 2
9.5 -> promovat
7.0 -> promovat
10.0 -> promovat
4.5 -> nepromovat

Greșeli frecvente

  • Crezi că final List face lista nemodificabilă. Doar variabila nu se poate reatribui; pentru conținut fix folosește List.of sau List.copyOf.
  • Lași un câmp final neinițializat pe o ramură a constructorului: variable pret might not have been initialized.
  • Modifici o variabilă locală folosită într-o lambda: local variables referenced from a lambda expression must be final or effectively final.
  • Încerci să extinzi o clasă final, de exemplu class TextulMeu extends String: cannot inherit from final String.

Comenzi înrudite

  • static: static leagă un câmp sau o metodă de clasă, nu de un obiect: există o singură copie, accesată prin numele clasei.
  • var: Declară o variabilă locală al cărei tip este dedus de compilator din valoarea inițială; tipul rămâne fix.
  • Moștenire (extends): Cu extends o clasă moștenește câmpurile și metodele altei clase și le poate extinde sau rescrie; Java permite o singură superclasă.
  • sealed, permits și non-sealed: O clasă sau interfață sealed își alege explicit subtipurile (permits); fiecare subtip este final, sealed sau non-sealed.
  • record: record declară o clasă imutabilă pentru date: constructorul, accesorii, equals, hashCode și toString sunt generați automat.
  • Expresii lambda: O lambda este o funcție scurtă, fără nume, scrisă ca parametri -> corp, folosită oriunde se așteaptă o interfață funcțională.