CodeGym /Corsi /JAVA 25 SELF /Interfacce funzionali: @FunctionalInterface

Interfacce funzionali: @FunctionalInterface

JAVA 25 SELF
Livello 20 , Lezione 3
Disponibile

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
Runnable
void run()
Attività senza argomenti e senza risultato Thread, timer
Callable<V>
V call()
Attività con risultato Thread, ExecutorService
Comparator<T>
int compare(T, T)
Confronto di due oggetti Ordinamento di collezioni
Consumer<T>
void accept(T)
“Consumatore” del valore Iterazione di una collezione
Supplier<T>
T get()
“Fornitore” del valore Inizializzazione lazy, generazione di dati
Function<T, R>
R apply(T)
Funzione da T a R Trasformazione dei dati
Predicate<T>
boolean test(T)
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.

1
Compito
JAVA 25 SELF, livello 20, lezione 3
Bloccato
Il tuo Notificatore personale
Il tuo Notificatore personale
1
Compito
JAVA 25 SELF, livello 20, lezione 3
Bloccato
Verificatore di gioco: Punteggi alti
Verificatore di gioco: Punteggi alti
1
Compito
JAVA 25 SELF, livello 20, lezione 3
Bloccato
Validator di testo: verifica della lunghezza della stringa
Validator di testo: verifica della lunghezza della stringa
1
Compito
JAVA 25 SELF, livello 20, lezione 3
Bloccato
Convertitore di testo con output comodo
Convertitore di testo con output comodo
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION