Generics (tipuri generice) în Java

Generics, lambda și stream-uri Java 5 (operatorul diamant <>: Java 7)

Generics parametrizează clase și metode cu tipuri (List<String>, Map<String, Integer>), ca erorile de tip să apară la compilare, nu la rulare.

Sintaxă
class Cutie<T> {                     // clasă generică
    private T continut;
    T get() { return continut; }
}

static <T extends Comparable<T>> T maxim(List<T> lista) { ... }   // metodă generică

List<String> nume = new ArrayList<>();
double suma(List<? extends Number> numere) { ... }               // wildcard

Ce face Generics (tipuri generice)

List<String> înseamnă „listă de șiruri”. Parametrul de tip între < > spune compilatorului ce conține colecția: add(42) nu compilează, iar get(0) returnează direct un String, fără conversie. Operatorul diamant new ArrayList<>() deduce tipul din stânga.

Poți scrie clase generice (class Cutie<T>) și metode generice (static <T> T primul(List<T> l)). Cu <T extends Comparable<T>> limitezi tipul la cele care se pot compara. Parametrii de tip se scriu de obicei cu o literă mare: T (tip), E (element), K/V (cheie/valoare).

Wildcard-ul ? face metodele mai flexibile: List<? extends Number> acceptă List<Integer> și List<Double> (doar citești din ea), iar List<? super Integer> acceptă liste în care poți adăuga întregi. Generics nu funcționează cu tipuri primitive: scrii List<Integer>, nu List<int>. La rulare informația de tip se șterge (type erasure).

Exemple

Exemplul 1: O clasă generică

Pereche<A, B> ține două valori de tipuri oarecare. Același cod servește pentru Pereche<String, Integer> și Pereche<String, Double>, iar getDoi() returnează tipul corect, fără conversie.

class Pereche<A, B> {
    private final A unu;
    private final B doi;

    Pereche(A unu, B doi) {
        this.unu = unu;
        this.doi = doi;
    }

    A getUnu() { return unu; }
    B getDoi() { return doi; }

    <C> Pereche<A, C> cuDoi(C nou) {
        return new Pereche<>(unu, nou);
    }

    @Override
    public String toString() {
        return "(" + unu + ", " + doi + ")";
    }
}

public class Main {
    public static void main(String[] args) {
        Pereche<String, Integer> varsta = new Pereche<>("Ana", 21);
        int ani = varsta.getDoi();          // fără cast
        System.out.println(varsta + " -> peste 5 ani: " + (ani + 5));

        Pereche<String, Double> medie = varsta.cuDoi(9.45);
        System.out.println(medie + " -> " + medie.getDoi().getClass().getSimpleName());
    }
}

Rezultat

(Ana, 21) -> peste 5 ani: 26
(Ana, 9.45) -> Double

Exemplul 2: Metode generice cu limită (extends)

maxim merge pentru orice tip comparabil: numere, texte, date. Tipul T este dedus din argument. Limita este Comparable<? super T>, nu Comparable<T>: LocalDate implementează Comparable<ChronoLocalDate> (un supertip), iar cu limita simplă apelul nu compilează (inference variable T has incompatible equality constraints).

import java.time.LocalDate;
import java.util.List;

public class Main {
    static <T extends Comparable<? super T>> T maxim(List<T> elemente) {
        T max = elemente.get(0);
        for (T e : elemente) {
            if (e.compareTo(max) > 0) {
                max = e;
            }
        }
        return max;
    }

    static <T> void afiseaza(String eticheta, List<T> lista) {
        System.out.println(eticheta + " (" + lista.size() + "): " + lista);
    }

    public static void main(String[] args) {
        List<Integer> note = List.of(7, 10, 8, 9);
        List<String> orase = List.of("Iași", "Arad", "Zalău", "Brăila");
        List<LocalDate> termene = List.of(LocalDate.of(2026, 11, 3), LocalDate.of(2026, 12, 20));

        afiseaza("note", note);
        System.out.println("nota maximă: " + maxim(note));
        System.out.println("ultimul oraș alfabetic: " + maxim(orase));
        System.out.println("ultimul termen: " + maxim(termene));
    }
}

Rezultat

note (4): [7, 10, 8, 9]
nota maximă: 10
ultimul oraș alfabetic: Zalău
ultimul termen: 2026-12-20

Exemplul 3: Wildcard: ? extends și ? super

suma citește din orice listă de numere. umple scrie întregi în orice listă care îi poate primi (Integer, Number, Object).

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

public class Main {
    static double suma(List<? extends Number> numere) {
        double s = 0;
        for (Number n : numere) {
            s += n.doubleValue();
        }
        return s;
    }

    static void umple(List<? super Integer> destinatie, int pana) {
        for (int i = 1; i <= pana; i++) {
            destinatie.add(i);
        }
    }

    public static void main(String[] args) {
        List<Integer> bucati = List.of(3, 5, 2);
        List<Double> preturi = List.of(12.5, 7.25);
        System.out.println("bucăți: " + suma(bucati));
        System.out.println("prețuri: " + suma(preturi));

        List<Number> amestec = new ArrayList<>();
        amestec.add(0.5);
        umple(amestec, 3);
        System.out.println("amestec: " + amestec + ", sumă: " + suma(amestec));
    }
}

Rezultat

bucăți: 10.0
prețuri: 19.75
amestec: [0.5, 1, 2, 3], sumă: 6.5

Greșeli frecvente

  • Folosești tipuri brute: List lista = new ArrayList();. Pierzi verificarea, iar -Xlint dă [rawtypes] și [unchecked]. Scrie mereu parametrul: List<String>.
  • Scrii List<int>: unexpected type; required: reference, found: int. Folosește clasa wrapper Integer.
  • Crezi că List<Integer> este un List<Number>. Nu este, deși Integer este Number; folosește List<? extends Number> ca parametru.
  • Încerci new T() sau new T[10]: din cauza ștergerii tipului, la rulare T nu mai este cunoscut și codul nu compilează.

Comenzi înrudite

  • 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ă.
  • Comparatori (Comparable și Comparator): Comparable dă ordinea naturală a unei clase (compareTo); Comparator descrie orice altă ordine și se combină cu comparing, thenComparing și reversed.
  • Tipuri wrapper și autoboxing: Fiecare primitiv are o clasă „înveliș” (Integer, Double, Character…); conversia automată între ele se numește autoboxing.
  • 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.
  • List: Interfața pentru liste ordonate: elementele au poziții (0, 1, 2…), se pot repeta și se pot citi după index.
  • Map: Interfața pentru dicționare: perechi cheie → valoare, în care găsești rapid valoarea după cheie. Cheile sunt unice.