transient în Java

transient marchează un câmp care nu trebuie salvat când obiectul este serializat; la citire primește valoarea implicită.

Sintaxă
class Utilizator implements Serializable {
    String email;
    transient String parola;   // nu se salvează la serializare
}

Ce face transient

Serializarea Java transformă un obiect într-un șir de octeți (pentru un fișier sau rețea), iar deserializarea îl reconstruiește. Funcționează pentru clasele care implementează Serializable și salvează implicit toate câmpurile.

Un câmp transient este sărit: nu ajunge în octeți, iar după deserializare are valoarea implicită (null, 0, false). Se folosește pentru date secrete (parole), date care se pot recalcula (cache-uri) sau obiecte care nu pot fi serializate (conexiuni, fire de execuție).

Cuvântul este rar în codul modern: serializarea Java nativă este considerată riscantă și a fost înlocuită în majoritatea aplicațiilor de JSON sau alte formate. Unele biblioteci JSON respectă însă și ele transient.

Exemple

Exemplul 1: Câmpul transient nu supraviețuiește serializării

Obiectul este scris într-un array de octeți și citit înapoi. Emailul se păstrează; parola și cache-ul (transient) revin la null și 0. Clasa declară serialVersionUID, cerut de -Xlint.

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

class Utilizator implements Serializable {
    private static final long serialVersionUID = 1L;

    final String email;
    transient String parola;
    transient int vizualizariCache;

    Utilizator(String email, String parola) {
        this.email = email;
        this.parola = parola;
        this.vizualizariCache = 42;
    }

    @Override
    public String toString() {
        return email + ", parola=" + parola + ", cache=" + vizualizariCache;
    }
}

public class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Utilizator u = new Utilizator("ana@exemplu.ro", "secret123");
        System.out.println("înainte: " + u);

        ByteArrayOutputStream octeti = new ByteArrayOutputStream();
        try (ObjectOutputStream out = new ObjectOutputStream(octeti)) {
            out.writeObject(u);
        }

        try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(octeti.toByteArray()))) {
            Utilizator citit = (Utilizator) in.readObject();
            System.out.println("după:    " + citit);
        }
    }
}

Rezultat

înainte: ana@exemplu.ro, parola=secret123, cache=42
după:    ana@exemplu.ro, parola=null, cache=0

Greșeli frecvente

  • Crezi că transient ascunde câmpul și în toString(), loguri sau JSON-ul produs de orice bibliotecă. Afectează doar serializarea (și bibliotecile care aleg să îl respecte).
  • Te aștepți ca un câmp transient să primească la deserializare valoarea de la declarare (transient int x = 5;). Constructorul nu rulează, deci câmpul este 0.
  • Uiți serialVersionUID într-o clasă Serializable: cu -Xlint:all -Werror compilarea eșuează ([serial]).

Comenzi înrudite

  • volatile: volatile pe un câmp garantează că fiecare fir vede imediat ultima valoare scrisă; nu face însă operațiile compuse (x++) atomice.
  • native: native declară o metodă fără corp, implementată în cod nativ (C, C++) și apelată prin JNI; nu se scrie aproape niciodată în aplicații obișnuite.
  • Interfețe (interface, implements): O interfață (interface) descrie ce metode trebuie să aibă o clasă; clasa o promite cu implements și poate implementa mai multe interfețe.
  • Modificatori de acces (public, protected, private): public, protected, private și lipsa modificatorului stabilesc cine poate vedea o clasă, un câmp, o metodă sau un constructor.