1. Introduzione
Un'interfaccia funzionale — è un'interfaccia che contiene esattamente un solo metodo astratto (ossia un metodo senza implementazione). Proprio questo tipo di interfaccia può essere usato per una forma compatta di implementazione di un metodo — in Java ciò si fa con le espressioni lambda (le studieremo più avanti).
Perché solo un metodo?
Perché un'interfaccia funzionale descrive esattamente una sola operazione. Se i metodi fossero due o più, non sarebbe chiaro quale metodo dover implementare. Quindi la regola è semplice: una interfaccia — un metodo astratto.
Esempi dalla libreria standard
- Runnable — per attività nei thread (void run())
- Callable<V> — per attività che restituiscono un risultato (V call())
- Comparator<T> — per confrontare oggetti (int compare(T o1, T o2))
- Consumer<T> — “consumatore” del valore (void accept(T t))
- Supplier<T> — “fornitore” del valore (T get())
- Function<T, R> — funzione da T a R (R apply(T t))
- Predicate<T> — verifica di una condizione (boolean test(T t))
Ecco, per esempio, l'interfaccia Runnable:
public interface Runnable {
void run();
}
Ecco l'interfaccia Comparator:
public interface Comparator<T> {
int compare(T o1, T o2);
// ... ci sono anche metodi default e static, ma il metodo astratto è solo uno!
}
Punto importante: i metodi default e static non sono considerati astratti, quindi possono essercene quanti ne vuoi!
2. Annotazione @FunctionalInterface
Java è severa e rigorosa. Per evitare confusione, permette di contrassegnare esplicitamente un'interfaccia come funzionale con l'annotazione @FunctionalInterface. È come una targhetta “Funziona con un solo pulsante!” — così nessuno aggiunge qualcosa di superfluo.
@FunctionalInterface
public interface Operation {
int apply(int a, int b);
}
Ora, se per caso ti dimentichi e aggiungi un secondo metodo astratto, il compilatore segnalerà subito un errore:
@FunctionalInterface
public interface Oops {
void doIt();
void doSomethingElse(); // Errore! Due metodi astratti
}
L'annotazione è obbligatoria?
No, non è obbligatoria. L'interfaccia sarà funzionale anche senza di essa, se contiene esattamente un solo metodo astratto. Ma con l'annotazione manifesti esplicitamente il tuo intento e ti proteggi da errori accidentali.
Si possono aggiungere metodi default e static?
Sì, certo! L'importante è che ci sia solo un metodo astratto. Tutti gli altri metodi possono essere default o static, quanti ne vuoi.
Esempio:
@FunctionalInterface
public interface FancyOperation {
int apply(int a, int b);
default void printInfo() {
System.out.println("Sono un'operazione fancy!");
}
static void description() {
System.out.println("Interfaccia funzionale per l'aritmetica.");
}
}
3. Esempi di dichiarazione e utilizzo
Supponiamo che tu voglia descrivere un'operazione su due numeri. Ecco come si fa:
@FunctionalInterface
public interface Operation {
int apply(int a, int b);
}
Ora questa interfaccia può essere implementata in modi diversi.
Implementazione tramite una classe normale
public class SumOperation implements Operation {
@Override
public int apply(int a, int b) {
return a + b;
}
}
Uso:
Operation sum = new SumOperation();
System.out.println(sum.apply(2, 3)); // 5
Implementazione tramite classe anonima
Operation multiply = new Operation() {
@Override
public int apply(int a, int b) {
return a * b;
}
};
System.out.println(multiply.apply(2, 3)); // 6
Nota sulle lambda
A partire da Java 8, tali interfacce sono comode da implementare tramite espressioni lambda — una sintassi più breve. Studieremo le lambda tra un paio di lezioni, quindi per ora basta sapere: le interfacce funzionali sono state create proprio per lavorarci nel modo più comodo possibile.
4. Pratica: scrivi la tua interfaccia funzionale
Esercizio 1. Crea la tua Action!
Crea un'interfaccia Action che accetta una stringa e non restituisce nulla. Implementala tramite una classe anonima che stampa la stringa in maiuscolo.
@FunctionalInterface
interface Action {
void act(String s);
}
public class ActionDemo {
public static void main(String[] args) {
Action shout = new Action() {
@Override
public void act(String text) {
System.out.println(text.toUpperCase());
}
};
shout.act("imparo java!"); // IMPARO JAVA!
}
}
(Più avanti vedremo come scriverlo in modo più conciso con le espressioni lambda.)
Esercizio 2. Filtro di numeri
Crea un'interfaccia NumberPredicate con il metodo boolean test(int n). Implementa il controllo di parità con una classe anonima.
@FunctionalInterface
interface NumberPredicate {
boolean test(int n);
}
public class PredicateDemo {
public static void main(String[] args) {
NumberPredicate isEven = new NumberPredicate() {
@Override
public boolean test(int n) {
return n % 2 == 0;
}
};
System.out.println(isEven.test(4)); // true
System.out.println(isEven.test(7)); // false
}
}
Esercizio 3. Usiamo le interfacce standard
Invece di un'interfaccia propria puoi usare quella pronta Predicate<Integer>:
import java.util.function.Predicate;
Predicate<Integer> isPositive = new Predicate<Integer>() {
@Override
public boolean test(Integer x) {
return x > 0;
}
};
System.out.println(isPositive.test(10)); // true
System.out.println(isPositive.test(-5)); // false
Tabella: interfacce funzionali della libreria standard
| Interfaccia | Metodo | Descrizione | Esempio d'uso |
|---|---|---|---|
|
|
Attività senza argomenti e senza risultato | Thread, timer |
|
|
Attività con risultato | Thread, ExecutorService |
|
|
Confronto di due oggetti | Ordinamento di collezioni |
|
|
“Consumatore” del valore | Iterazione di una collezione |
|
|
“Fornitore” del valore | Inizializzazione lazy, generazione di dati |
|
|
Funzione da T a R | Trasformazione dei dati |
|
|
Verifica di una condizione | Filtraggio di collezioni |
5. Errori tipici nel lavoro con le interfacce funzionali
Errore n. 1: hai aggiunto un secondo metodo astratto. Se in un'interfaccia c'è più di un metodo astratto, smette di essere funzionale. Il compilatore (soprattutto con @FunctionalInterface) segnalerà subito l'errore.
Errore n. 2: ti sei scordato che i default- e static-metodi non sono considerati astratti. Puoi aggiungerli senza problemi a un'interfaccia funzionale — non infrange la regola “un solo metodo astratto”.
Errore n. 3: hai implementato in modo errato la firma del metodo. Per esempio, l'interfaccia richiede due argomenti, ma hai scritto un metodo con uno solo. Controlla sempre le firme.
Errore n. 4: non usi @FunctionalInterface e rovini accidentalmente l'interfaccia. Se non contrassegni l'interfaccia con l'annotazione, puoi aggiungere per sbaglio un metodo in più — e poi passare molto tempo a capire perché il codice non funziona. Meglio aggiungere sempre l'annotazione per chiarezza.
GO TO FULL VERSION