W3docs

Annotazioni Built-in di Java

Le principali annotazioni built-in di Java: @Override, @Deprecated, @SuppressWarnings, @SafeVarargs, @FunctionalInterface.

La libreria standard include un piccolo insieme di annotazioni nel pacchetto java.lang che il compilatore tratta in modo speciale. Nessuna di esse aggiunge comportamento al codice a runtime; sono tutte indicazioni per javac (o, nel caso di @Deprecated, per l'intera toolchain). Capire cosa promette ciascuna — e cosa non promette — è la parte pratica del lavoro quotidiano con le annotazioni.

Le cinque che incontrerai più spesso:

  • @Override — "questo metodo sovrascrive uno di un supertipo."
  • @Deprecated — "non usare questo; verrà rimosso."
  • @SuppressWarnings — "silenzia queste specifiche avvertenze in questo scope."
  • @SafeVarargs — "il mio metodo varargs non inquina l'heap."
  • @FunctionalInterface — "questa interfaccia ha esattamente un metodo astratto."

@Override

Inserire @Override su un metodo dice al compilatore che il metodo intende sovrascrivere un metodo della superclasse o implementare un metodo di un'interfaccia. Se non sovrascrive effettivamente nulla (perché il nome è scritto male, la firma è errata, o il tipo padre ha rimosso il metodo), javac interrompe la compilazione.

class Animal {
  public String speak() { return "?"; }
}

class Dog extends Animal {
  @Override
  public String speak() { return "woof"; }                 // OK

  @Override
  public String spaek() { return "oops"; }                 // compile error: nothing to override
}

Intercetta il tipo di bug molto difficile da trovare a runtime: un metodo che sovrascrive la cui firma si discosta da quella della superclasse. Scrivi sempre @Override sui metodi che intendi sovrascrivere. Ha retention SOURCE, quindi scompare nel momento in cui la compilazione termina.

@Deprecated

@Deprecated contrassegna qualcosa — classe, metodo, campo, costruttore — come sconsigliato. Il compilatore avverte in ogni punto di utilizzo. Da Java 9 l'annotazione accetta due elementi:

@Deprecated(since = "1.4", forRemoval = true)
public void oldApi() { ... }
  • since documenta quando è iniziata la deprecazione.
  • forRemoval = true è un segnale più forte: una versione futura prevede di eliminare l'API. Il compilatore emette un removal warning (tipicamente più evidente di un semplice avviso di deprecazione) e lo strumento Javadoc lo segnala in modo diverso.

A differenza di @Override, @Deprecated ha retention RUNTIME — strumenti bytecode, IDE e reflection possono vederla. Il tag Javadoc associato @deprecated (minuscolo, in un commento doc) porta la spiegazione; l'annotazione attiva i meccanismi degli strumenti.

@SuppressWarnings

Quando il compilatore ha ragione quasi sempre ma sbaglia qui, @SuppressWarnings silenzia una categoria di avvertenze all'interno dell'elemento annotato:

@SuppressWarnings("unchecked")
List<String> strings = (List<String>) raw;               // raw cast intentional

@SuppressWarnings({"unchecked", "rawtypes"})
public void uglyButNeeded() { ... }

L'elemento string indica una categoria di avvertenza; le più comuni sono unchecked, rawtypes, deprecation, serial, unused, removal. I compilatori possono accettarne altre. Due regole per mantenere pulito l'uso:

  1. Annota lo scope più piccolo possibile. Sopprimi su una variabile locale o su un singolo metodo, mai su una classe, a meno che ogni riga non ne abbia davvero bisogno.
  2. Abbinala a un commento che spieghi il perché. Un @SuppressWarnings("unchecked") nudo accanto a un cast lascia il prossimo lettore a chiedersi se il cast sia effettivamente sicuro.

@SafeVarargs

Un metodo varargs il cui tipo di parametro contiene un parametro di tipo ha un problema sottile: nel punto di chiamata, il compilatore potrebbe dover creare un array di un tipo generico, il che non è sicuro. Il compilatore avverte con il messaggio "possible heap pollution". Se l'autore ha verificato che il corpo non fa trapelare l'array né scrive tipi errati al suo interno, @SafeVarargs silenzia l'avviso:

@SafeVarargs
public final <T> List<T> listOf(T... items) {
  return java.util.List.of(items);                        // only reads the items, never stores other types
}

Regole:

  • Valida solo su metodi che non possono essere sovrascritti — static, final, o private, più i costruttori in stile record.
  • L'annotazione è una promessa. Se il corpo scrive effettivamente nell'array varargs con un tipo errato, il cast nel punto di chiamata può fallire in seguito con un ClassCastException confuso.

Ha retention SOURCE.

@FunctionalInterface

Un'interfaccia funzionale è un'interfaccia con esattamente un metodo astratto — la forma condivisa da Runnable, Callable, Comparator e Function. Le espressioni lambda e i riferimenti a metodi si rivolgono alle interfacce funzionali. L'annotazione rende esplicita l'intenzione e chiede al compilatore di applicare la regola del singolo metodo astratto:

@FunctionalInterface
public interface StringMapper {
  String map(String input);                              // the single abstract method

  default StringMapper andThen(StringMapper next) {       // default methods are allowed
    return s -> next.map(map(s));
  }
}

Se in seguito aggiungi un secondo metodo astratto, la compilazione fallisce immediatamente. Senza l'annotazione, l'interfaccia smetterebbe silenziosamente di essere utilizzabile come target di una lambda — cosa che un utente noterebbe solo nel punto di chiamata.

Come @Override, ha retention SOURCE.

Un esempio pratico: vedere l'applicazione del compilatore

Questo programma dimostra le quattro annotazioni applicate e mostra cosa il runtime può vedere in seguito. Le parti interessanti: il metodo @SafeVarargs compila effettivamente dove quello non annotato stampa un avviso; @FunctionalInterface si riflette tramite le regole di conteggio SAM; i valori dell'elemento @Deprecated arrivano a runtime.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • @FunctionalInterface ha svolto il suo lavoro a compile-time garantendo che StringMapper sia un tipo SAM: sia String::toUpperCase che la lambda s -> s + \"!\" si sono legate correttamente ad essa. Se qualcuno avesse aggiunto un secondo metodo astratto all'interfaccia, la compilazione sarebbe fallita e queste espressioni avrebbero smesso di risolversi.
  • La riga @Override è stata il meccanismo contabile che ha garantito che Child.describe() sovrascrivesse effettivamente Parent.describe(). La chiamata polimorfica che atterra su \"child\" lo conferma; se la firma si fosse discostata (nome diverso, tipo di ritorno diverso), la compilazione avrebbe fallito invece di produrre un comportamento errato a runtime.
  • @Deprecated è l'unica annotazione qui che sopravvive alla compilazione. La reflection ha estratto con successo since=1.4 e forRemoval=true dal file class. Il metodo stesso ha continuato a funzionare — @Deprecated avverte, non disabilita.
  • @SafeVarargs ha rimosso l'avviso "possible heap pollution" a compile-time mantenendo la chiamata type-safe. Nota che il metodo è static, quindi soddisfa la regola "non può essere sovrascritto". Rimuovere l'annotazione compilerebbe ma genererebbe avvisi durante javac; aggiungerla su un metodo non-static, non-final, non-private sarebbe un errore di compilazione.
  • @SuppressWarnings non ha lasciato tracce a runtime — l'array di annotazioni stampato per parseOrZero è vuoto. Questo è l'intero scopo della retention SOURCE: l'annotazione svolge il suo lavoro durante la compilazione e poi scompare, mantenendo il file class non sovraccaricato.

Altre annotazioni built-in da conoscere

Un gruppo di annotazioni meno comuni dalla libreria standard, brevemente:

  • @SuppressWarnings("preview") — per codice che usa funzionalità linguistiche in anteprima (Java 14+).
  • @Native (java.lang.annotation.Native) — contrassegna una costante che potrebbe essere referenziata dal codice nativo; usata da strumenti che generano header JNI.
  • @Generated (javax.annotation.processing.Generated, Java 9+) — aggiunta dai generatori di codice sui file che emettono.
  • @Documented, @Retention, @Target, @Inherited, @Repeatable — queste sono meta-annotazioni; trattate nel prossimo capitolo.

Raramente scriverai le prime tre a mano. Le meta-annotazioni sono la porta d'accesso alla scrittura di tipi di annotazione personalizzati, e la reflection è il modo in cui le annotazioni con retention runtime come @Deprecated vengono rilette — come mostra l'esempio pratico sopra.

Esercitazione

Pratica
Un metodo è dichiarato `public <T> T[] toArray(T... values)` e il compilatore avverte di 'possible heap pollution from parameterized vararg type'. L'autore esamina il corpo, conferma che scrive solo elementi di tipo T nell'array, e aggiunge `@SafeVarargs`. Perché il compilatore rifiuta l'annotazione?
Un metodo è dichiarato `public <T> T[] toArray(T... values)` e il compilatore avverte di 'possible heap pollution from parameterized vararg type'. L'autore esamina il corpo, conferma che scrive solo elementi di tipo T nell'array, e aggiunge `@SafeVarargs`. Perché il compilatore rifiuta l'annotazione?
Was this page helpful?