Interfacce e Classi Astratte in Java: Guida Completa con Esempi

A clean, modern tech illustration showing Java interfaces and abstract classes concept. Dark background with blue and purple tones, showing code structure diagrams with interface and abstract class symbols, arrows showing implementation relationships, Java logo, professional programming/development style.

Interfacce e Classi Astratte in Java: guida completa

In Java, interfacce e classi astratte sono due strumenti fondamentali per progettare codice flessibile, riutilizzabile e facile da mantenere. In questa guida vedremo cosa sono, come si usano, le differenze principali e alcuni esempi pratici adatti a sviluppatori principianti e intermedi.

interfacce Java e classi astratte in Java: schema e gerarchia

Cosa sono le interfacce in Java

Un’interfaccia in Java è un contratto che specifica cosa una classe deve fare, senza dire come farlo. Contiene in genere firme di metodi (senza corpo) che le classi dovranno implementare.

In altre parole, un’interfaccia definisce un insieme di capacità (metodi) che possono essere condivise da classi diverse, anche non imparentate tra loro nella stessa gerarchia di ereditarietà.

Dichiarare un’interfaccia Java

La sintassi base per dichiarare un’interfaccia è:

public interface NomeInterfaccia {
    // Costanti (implicitamente public static final)

    // Metodi astratti (implicitamente public abstract)
    void metodo1();
    int metodo2(String parametro);
}

Caratteristiche principali delle interfacce (versioni moderne di Java):

  • I metodi senza corpo sono implicitamente public abstract.
  • Le variabili dichiarate in un’interfaccia sono implicitamente public static final (cioè costanti).
  • Da Java 8 in poi, un’interfaccia può contenere anche metodi di default e metodi statici con implementazione.

Implementare un’interfaccia Java

Una classe implementa un’interfaccia utilizzando la parola chiave implements. Deve fornire un’implementazione per tutti i metodi astratti dichiarati nell’interfaccia (a meno che la classe non sia astratta).

public interface Calcolabile {
    double calcolaArea();
}

public class Cerchio implements Calcolabile {

    private double raggio;

    public Cerchio(double raggio) {
        this.raggio = raggio;
    }

    // Implementazione del metodo definito nell'interfaccia
    @Override
    public double calcolaArea() {
        return Math.PI * raggio * raggio;
    }
}

Qui Cerchio si impegna a rispettare il contratto definito da Calcolabile, ovvero fornisce un metodo calcolaArea() che restituisce un double.

Metodi di default e statici nelle interfacce Java

Per consentire l’evoluzione delle API senza rompere il codice esistente, da Java 8 è possibile definire metodi con implementazione dentro un’interfaccia, usando le parole chiave default o static.

public interface Stampabile {

    void stampa(); // metodo astratto

    // Metodo di default con implementazione
    default void stampaConIntestazione() {
        System.out.println("=== Inizio stampa ===");
        stampa();
        System.out.println("=== Fine stampa ===");
    }

    // Metodo statico nell'interfaccia
    static void descrizioneGenerale() {
        System.out.println("Qualsiasi oggetto stampabile a schermo o su carta");
    }
}

Le classi che implementano Stampabile dovranno definire il metodo stampa(), ma potranno usare direttamente il metodo di default stampaConIntestazione() senza doverlo riscrivere.

Cosa sono le classi astratte in Java

Una classe astratta è una classe che non può essere istanziata direttamente, ma serve come base per altre classi. Può contenere sia metodi astratti (senza implementazione) sia metodi concreti (con corpo), oltre a campi di istanza.

Si dichiara con la parola chiave abstract:

public abstract class Forma {

    // Campo di istanza
    private String colore;

    public Forma(String colore) {
        this.colore = colore;
    }

    // Getter concreto
    public String getColore() {
        return colore;
    }

    // Metodo astratto: non ha implementazione qui
    public abstract double calcolaArea();
}

Le sottoclassi concrete dovranno fornire un’implementazione per i metodi astratti.

Estendere una classe astratta in Java

Per usare una classe astratta, la si estende con una sottoclasse che implementa i metodi astratti:

public class Rettangolo extends Forma {

    private double base;
    private double altezza;

    public Rettangolo(String colore, double base, double altezza) {
        super(colore); // chiama il costruttore della classe astratta
        this.base = base;
        this.altezza = altezza;
    }

    @Override
    public double calcolaArea() {
        return base * altezza;
    }
}

public class Triangolo extends Forma {

    private double base;
    private double altezza;

    public Triangolo(String colore, double base, double altezza) {
        super(colore);
        this.base = base;
        this.altezza = altezza;
    }

    @Override
    public double calcolaArea() {
        return (base * altezza) / 2;
    }
}

In questo esempio, Rettangolo e Triangolo condividono il campo colore e il relativo metodo getColore(), definiti una sola volta nella classe astratta Forma.

Differenze tra interfacce Java e classi astratte

Interfacce e classi astratte possono sembrare simili perché entrambe consentono di definire metodi senza implementazione, ma hanno scopi e regole diverse. Ecco le differenze principali (in una versione moderna di Java):

  • Ereditarietà multipla
    Una classe può implementare più interfacce, ma può estendere una sola classe (astratta o concreta).
  • Stato (campi di istanza)
    Una classe astratta può avere campi di istanza (stato) non costanti, mentre in un’interfaccia le variabili sono costanti (public static final).
  • Costruttori
    Le interfacce non possono avere costruttori; le classi astratte sì (anche se non possono essere istanziate direttamente, i costruttori vengono chiamati tramite le sottoclassi).
  • Finalità progettuale
    Un’interfaccia descrive un ruolo o una capacità che una classe può avere (es. Comparable, Runnable); una classe astratta descrive un tipo generico parzialmente implementato (es. Forma, Veicolo).
  • Compatibilità con codice esistente
    Le classi astratte possono cambiare nel tempo (aggiungendo metodi concreti) senza rompere le sottoclassi. Con le interfacce, l’aggiunta di nuovi metodi astratti romperebbe le implementazioni esistenti, motivo per cui si usano i metodi di default.

Quando usare un’interfaccia Java e quando una classe astratta

Linee guida pratiche:

  • Usa una interfaccia quando vuoi definire un contratto comune che può essere condiviso da classi non correlate tra loro, o quando prevedi la necessità di implementare più “ruoli” nella stessa classe.
  • Usa una classe astratta quando hai una gerarchia chiara di oggetti strettamente correlati che condividono stato comune e parte di implementazione, ma che devono comunque ridefinire alcuni comportamenti.

Esempio pratico: sistema di pagamento con interfacce Java e classi astratte

Immaginiamo un sistema di pagamenti che supporta diversi metodi: carta di credito, PayPal, bonifico. Possiamo definire una struttura che unisce interfacce e classi astratte.

1. Interfaccia per il pagamento

public interface Pagamento {
    void paga(double importo);
}

2. Classe astratta con logica comune

public abstract class PagamentoBase implements Pagamento {

    private String valuta;

    public PagamentoBase(String valuta) {
        this.valuta = valuta;
    }

    public String getValuta() {
        return valuta;
    }

    // Metodo di utilità comune a tutti i pagamenti
    protected void stampaRicevuta(double importo) {
        System.out.println("Pagamento di " + importo + " " + valuta + " effettuato.");
    }
}

3. Implementazioni concrete

public class PagamentoCartaCredito extends PagamentoBase {

    private String numeroCarta;

    public PagamentoCartaCredito(String valuta, String numeroCarta) {
        super(valuta);
        this.numeroCarta = numeroCarta;
    }

    @Override
    public void paga(double importo) {
        // Logica fittizia di pagamento con carta
        System.out.println("Addebito sulla carta " + numeroCarta + " per " + importo + " " + getValuta());
        stampaRicevuta(importo);
    }
}

public class PagamentoPayPal extends PagamentoBase {

    private String email;

    public PagamentoPayPal(String valuta, String email) {
        super(valuta);
        this.email = email;
    }

    @Override
    public void paga(double importo) {
        // Logica fittizia di pagamento con PayPal
        System.out.println("Addebito sull'account PayPal " + email + " per " + importo + " " + getValuta());
        stampaRicevuta(importo);
    }
}

4. Uso del polimorfismo

Dal punto di vista del codice client, possiamo lavorare solo con il tipo Pagamento, senza preoccuparci di quale implementazione concreta stiamo usando:

public class Cassa {

    public void effettuaPagamento(Pagamento pagamento, double importo) {
        pagamento.paga(importo);
    }

    public static void main(String[] args) {
        Cassa cassa = new Cassa();

        Pagamento p1 = new PagamentoCartaCredito("EUR", "1234-5678-9012-3456");
        Pagamento p2 = new PagamentoPayPal("EUR", "cliente@example.com");

        cassa.effettuaPagamento(p1, 100.0);
        cassa.effettuaPagamento(p2, 50.0);
    }
}

Qui la interfaccia Pagamento definisce il contratto comune, mentre la classe astratta PagamentoBase fornisce logica condivisa (gestione della valuta, stampa della ricevuta) tra tutte le implementazioni.

Casi d’uso reali delle interfacce Java e classi astratte

Esempi tipici di interfacce Java

  • java.lang.Comparable<T>: definisce il metodo compareTo() per ordinare oggetti.
  • java.lang.Runnable: definisce il metodo run() per eseguire codice in un thread.
  • java.util.List<E>, java.util.Set<E>: sono interfacce che rappresentano collezioni, implementate da classi come ArrayList, HashSet.

Esempi tipici di classi astratte in Java

  • java.util.AbstractList<E>, java.util.AbstractSet<E>: forniscono implementazioni parziali per collezioni, lasciando alle sottoclassi il compito di definire dettagli specifici.
  • java.io.InputStream, java.io.Reader: definiscono il comportamento generale degli stream di input, mentre le sottoclassi implementano la logica specifica (file, rete, memoria, ecc.).

Riepilogo: interfacce Java e classi astratte a confronto

Interfacce e classi astratte sono strumenti chiave della programmazione orientata agli oggetti in Java. Le interfacce definiscono contratti e ruoli che le classi possono assumere, favorendo il disaccoppiamento e la flessibilità. Le classi astratte, invece, permettono di condividere stato e comportamento comune tra gruppi di classi correlate. Saper scegliere lo strumento giusto in base al problema da risolvere è una competenza fondamentale per progettare software robusto ed estensibile.

Per approfondire, consulta la documentazione ufficiale Oracle sulle interfacce Java.

Commenti

Rispondi

Scopri di più da App Tutorial

Abbonati ora per continuare a leggere e avere accesso all'archivio completo.

Continua a leggere