Annotazioni di Tipo in Java
- Le annotazioni di tipo in Java consentono di specificare metadati sui tipi, migliorando la documentazione e la verifica del codice.
- Le annotazioni di tipo possono essere utilizzate su vari elementi, inclusi i metodi, i parametri e i tipi generici.
- È importante utilizzare le annotazioni di tipo in modo appropriato per garantire la qualità e la manutenibilità del codice.
Annotazioni di Tipo
Come menzionato nelle lezioni precedenti, le annotazioni erano originariamente consentite solo sulle dichiarazioni.
Tuttavia, per le versioni moderne di Java, le annotazioni possono anche essere specificate nella maggior parte dei casi in cui viene utilizzato un tipo.
Questa nuova funzionalità delle annotazioni è chiamata annotazione di tipo.
Ad esempio, è possibile annotare il tipo di ritorno di un metodo, il tipo di this
all'interno di un metodo, un cast, i livelli di array, una classe ereditata e una clausola throws
.
È inoltre possibile annotare tipi generici, inclusi i limiti dei parametri di tipo generico e gli argomenti di tipo generico. I tipi generici, o Generics, li affronteremo nelle prossime lezioni.
Le annotazioni di tipo sono importanti perché consentono agli strumenti di sviluppo (come IDE e editor) di eseguire controlli aggiuntivi sul codice per aiutare a prevenire errori. È importante capire che, come regola generale, il compilatore javac
non eseguirà questi controlli da solo. Per questo scopo viene utilizzato uno strumento separato, anche se tale strumento potrebbe operare come un plug-in del compilatore.
Un'annotazione di tipo deve includere ElementType.TYPE_USE
come target. Ricordiamo che i target di annotazione validi sono specificati utilizzando l'annotazione @Target
, come descritto nella lezione precedente.
Un'annotazione di tipo si applica al tipo che l'annotazione precede. Ad esempio, assumendo di aver implementato un'annotazione di tipo chiamata @AnnotazioneTipo
, la seguente riga di codice è legale:
void mioMetodo() throws @AnnotazioneTipo NullPointerException {
// ...
}
Qui, @AnnotazioneTipo
annota NullPointerException
nella clausola throws
.
È inoltre possibile annotare il tipo di this
(chiamato il ricevitore). Come sappiamo, this
è un argomento implicito a tutti i metodi di istanza e si riferisce all'oggetto invocante. Per annotare il suo tipo è necessario l'uso di un'altra caratteristica che non faceva originariamente parte di Java. A partire da JDK 8, è possibile dichiarare esplicitamente this
come primo parametro di un metodo. In questa dichiarazione, il tipo di this
deve essere il tipo della sua classe; ad esempio:
class UnaClasse {
// Dichiara esplicitamente this come primo parametro.
int mioMetodo(UnaClasse this, int i, int j) {
// ...
}
}
Qui, poiché mioMetodo()
è un metodo definito da UnaClasse
, il tipo di this
è UnaClasse
. Utilizzando questa dichiarazione, ora è possibile annotare il tipo di this
. Ad esempio, assumendo di nuovo che @AnnotazioneTipo
sia un'annotazione di tipo, la riga seguente è legale:
int mioMetodo(@AnnotazioneTipo UnaClasse this, int i, int j) {
// ...
}
È importante capire che non è necessario dichiarare this
a meno che non lo si stia annotando. Se this
non è dichiarato, viene comunque passato implicitamente, come è sempre stato.
Inoltre, dichiarare esplicitamente this
non cambia alcun aspetto della firma del metodo perché this
è dichiarato implicitamente, per impostazione predefinita. Ancora una volta, dichiareremo this
solo se vogliamo applicare un'annotazione di tipo ad esso. In ogni caso, se si dichiara this
, deve essere il primo parametro.
Il seguente programma mostra una serie di luoghi in cui può essere utilizzata un'annotazione di tipo. Definisce diverse annotazioni, di cui diverse sono per l'annotazione di tipo. I nomi e i target delle annotazioni sono mostrati qui:
Annotazione | Target |
---|---|
@AnnotazioneTipo |
ElementType.TYPE_USE |
@LunghezzaMax |
ElementType.TYPE_USE |
@NonLunghezzaZero |
ElementType.TYPE_USE |
@Unico |
ElementType.TYPE_USE |
@Cosa |
ElementType.TYPE_PARAMETER |
@VuotoOK |
ElementType.FIELD |
@Raccomandato |
ElementType.METHOD |
Notiamo che @VuotoOK
, @Raccomandato
e @Cosa
non sono annotazioni di tipo. Sono incluse per scopi di confronto. Di particolare interesse è @Cosa
, che viene utilizzata per annotare una dichiarazione di parametro di tipo generico. I commenti nel programma descrivono ogni uso.
// Dimostra diverse annotazioni di tipo.
import java.lang.annotation.*;
import java.lang.reflect.*;
// Un'annotazione marcatore che può essere applicata a un tipo.
@Target(ElementType.TYPE_USE)
@interface AnnotazioneTipo {
}
// Un'altra annotazione marcatore che può essere applicata a un tipo.
@Target(ElementType.TYPE_USE)
@interface NonLunghezzaZero {
}
// Ancora un'altra annotazione marcatore che può essere applicata a un tipo.
@Target(ElementType.TYPE_USE)
@interface Unico {
}
// Un'annotazione parametrizzata che può essere applicata a un tipo.
@Target(ElementType.TYPE_USE)
@interface LunghezzaMax {
int value();
}
// Un'annotazione che può essere applicata a un parametro di tipo.
@Target(ElementType.TYPE_PARAMETER)
@interface Cosa {
String description();
}
// Un'annotazione che può essere applicata a una dichiarazione di campo.
@Target(ElementType.FIELD)
@interface VuotoOK {
}
// Un'annotazione che può essere applicata a una dichiarazione di metodo.
@Target(ElementType.METHOD)
@interface Raccomandato {
}
// Usa un'annotazione su un parametro di tipo.
class DemoAnnotazioneTipo<@Cosa(description = "Tipo di dato generico") T> {
// Usa un'annotazione di tipo su un costruttore.
public @Unico DemoAnnotazioneTipo() {
}
// Annota il tipo (in questo caso String), non il campo.
@AnnotazioneTipo
String str;
// Questo annota il campo test.
@VuotoOK
String test;
// Usa un'annotazione di tipo per annotare this (il ricevitore).
public int f(@AnnotazioneTipo DemoAnnotazioneTipo<T> this, int x) {
return 10;
}
// Annota il tipo di ritorno.
public @AnnotazioneTipo Integer f2(int j, int k) {
return j + k;
}
// Annota la dichiarazione del metodo.
public @Raccomandato Integer f3(String str) {
return str.length() / 2;
}
// Usa un'annotazione di tipo con una clausola throws.
public void f4() throws @AnnotazioneTipo NullPointerException {
// ...
}
// Annota i livelli dell'array.
String @LunghezzaMax(10) [] @NonLunghezzaZero [] w;
// Annota il tipo dell'elemento dell'array.
@AnnotazioneTipo
Integer[] vec;
public static void mioMetodo(int i) {
// Usa un'annotazione di tipo su un argomento di tipo.
DemoAnnotazioneTipo<@AnnotazioneTipo Integer> ob =
new DemoAnnotazioneTipo<@AnnotazioneTipo Integer>();
// Usa un'annotazione di tipo con new.
@Unico
DemoAnnotazioneTipo<Integer> ob2 =
new @Unico DemoAnnotazioneTipo<Integer>();
Object x = Integer.valueOf(10);
Integer y;
// Usa un'annotazione di tipo su un cast.
y = (@AnnotazioneTipo Integer) x;
}
public static void main(String[] args) {
mioMetodo(10);
}
}
// Usa l'annotazione di tipo con la clausola di ereditarietà.
class UnaClasse extends @AnnotazioneTipo DemoAnnotazioneTipo<Boolean> {
}
Sebbene ciò a cui si riferiscono la maggior parte delle annotazioni nel programma precedente sia chiaro, quattro usi richiedono un po' di discussione.
Il primo è l'annotazione di un tipo di ritorno di un metodo rispetto all'annotazione di una dichiarazione di metodo. Nel programma, si presti particolare attenzione a queste due dichiarazioni di metodo:
// Annota il tipo di ritorno.
public @AnnotazioneTipo Integer f2(int j, int k) {
return j+k;
}
// Annota la dichiarazione del metodo.
public @Raccomandato Integer f3(String str) {
return str.length() / 2;
}
Notiamo che in entrambi i casi, un'annotazione precede il tipo di ritorno del metodo (che è Integer
). Tuttavia, le due annotazioni annotano due cose diverse. Nel primo caso, l'annotazione @AnnotazioneTipo
annota il tipo di ritorno di f2()
. Questo perché @AnnotazioneTipo
ha il suo target specificato come ElementType.TYPE_USE
, il che significa che può essere utilizzata per annotare usi di tipo.
Nel secondo caso, @Raccomandato
annota la dichiarazione del metodo stessa. Questo perché @Raccomandato
ha il suo target specificato come ElementType.METHOD
. Di conseguenza, @Raccomandato
si applica alla dichiarazione, non al tipo di ritorno. Pertanto, la specifica del target viene utilizzata per eliminare quella che, a prima vista, appare come ambiguità tra l'annotazione di una dichiarazione di metodo e l'annotazione del tipo di ritorno del metodo.
Un'altra cosa riguardo all'annotazione di un tipo di ritorno di un metodo: non è possibile annotare un tipo di ritorno di void
.
Il secondo punto di interesse sono le annotazioni applicate ai campi, mostrate qui:
// Annota il tipo (in questo caso String), non il campo.
@AnnotazioneTipo String str;
// Questo annota il campo test.
@VuotoOK String test;
Qui, @AnnotazioneTipo
annota il tipo String
, ma @VuotoOK
annota il campo test
. Anche se entrambe le annotazioni precedono l'intera dichiarazione, i loro target sono diversi, basati sul tipo di elemento target. Se l'annotazione ha il target ElementType.TYPE_USE
, allora il tipo è annotato. Se ha ElementType.FIELD
come target, allora il campo è annotato. Quindi, la situazione è simile a quella appena descritta per i metodi, e non esiste ambiguità. Lo stesso meccanismo disambigua anche le annotazioni su variabili locali.
Successivamente, notiamo come this
(il ricevitore) è annotato qui:
public int f(@AnnotazioneTipo DemoAnnotazioneTipo<T> this, int x) {
Qui, this
è specificato come primo parametro ed è di tipo DemoAnnotazioneTipo
(che è la classe di cui f()
è membro). Come spiegato, una dichiarazione di metodo di istanza può specificare esplicitamente il parametro this
allo scopo di applicare un'annotazione di tipo.
Infine, guardiamo come i livelli di array sono annotati dalla seguente istruzione:
String @LunghezzaMax(10) [] @NonLunghezzaZero [] w;
In questa dichiarazione, @LunghezzaMax
annota il tipo del primo livello e @NonLunghezzaZero
annota il tipo del secondo livello. In questa dichiarazione
@AnnotazioneTipo Integer[] vec;
il tipo dell'elemento Integer
è annotato.