CodeGym /Corsi /JAVA 25 SELF /Differenza tra interfacce e classi astratte

Differenza tra interfacce e classi astratte

JAVA 25 SELF
Livello 20 , Lezione 1
Disponibile

1. Classe astratta: ripasso delle basi

Prima di passare al confronto, ricordiamo che cos'è una classe astratta.

Una classe astratta — è una classe che non può essere istanziata direttamente (non si può scrivere new Animal()), ma può contenere sia metodi normali (con implementazione) sia metodi astratti (senza implementazione). Una classe astratta è spesso usata come base per altre classi, che ne ereditano il comportamento e/o sono tenute a implementare alcuni metodi.

Per esempio, se nella nostra applicazione ci sono diversi tipi di trasporto, si può creare una classe astratta Transport:

public abstract class Transport {
    private String model;

    public Transport(String model) {
        this.model = model;
    }

    public String getModel() {
        return model;
    }

    // Metodo astratto — nessuna implementazione, solo dichiarazione
    public abstract void move();

    // Metodo normale — c'è un'implementazione
    public void printInfo() {
        System.out.println("Modello del trasporto: " + model);
    }
}

Caratteristiche di una classe astratta:

  • Può contenere campi (stato).
  • Può contenere metodi implementati.
  • Può contenere metodi astratti (obbligatori da implementare nelle sottoclassi).
  • Non si può creare un'istanza direttamente.
  • Usata per comportamento e stato comuni.

2. Interfaccia: ripasso delle basi

Un'interfaccia è un insieme di metodi che una classe deve implementare. Un'interfaccia non descrive lo stato (non può avere campi normali, solo costanti) e non contiene implementazioni dei metodi (fino a Java 8). Un'interfaccia è un contratto puro: “Se mi implementi, devi saper fare questo”.

Esempio di interfaccia:

public interface Movable {
    void move(int x, int y);
}

Caratteristiche di un'interfaccia:

  • Non contiene stato (solo costanti public static final).
  • Fino a Java 8 — solo metodi astratti (da Java 8 sono arrivati i metodi default e static, ma ne parleremo più avanti).
  • I metodi sono sempre public abstract per impostazione predefinita.
  • Una classe può implementare più interfacce.
  • Usata per descrivere le capacità, “che cosa sa fare” una classe.

3. Tabella di confronto: classe astratta vs interfaccia

È ora di confrontare questi due strumenti faccia a faccia! Ecco una tabella riassuntiva:

Caratteristica Classe astratta Interfaccia
Sintassi
abstract class
interface
Si può creare un'istanza? No No
Può contenere metodi normali? Fino a Java 8 — no; da Java 8 — solo default/static
Può contenere metodi astratti? Sì (tutti i metodi fino a Java 8 sono astratti)
Può contenere campi (stato)? Sì (qualsiasi campo) Solo public static final (costanti)
Può contenere costruttori? No
Ereditarietà Solo una classe (astratta o normale) Si possono implementare più interfacce
Modificatori dei metodi Qualsiasi (public, protected, private) I metodi sono per impostazione predefinita public abstract. Da Java 9 è possibile aggiungere metodi private da usare all'interno dell'interfaccia
Ereditarietà tramite
extends
implements
Uso tipico Implementazione e stato comuni Descrizione di capacità e ruoli
Esempi dalla JDK
AbstractList, AbstractMap
Comparable, Runnable

4. Quando usare un'interfaccia e quando una classe astratta?

Usa un'interfaccia quando:

  • vuoi descrivere “che cosa sa fare” una classe senza preoccuparti di come lo fa.
  • hai bisogno che una classe possa implementare più capacità indipendenti.
  • Esempio: Comparable (si può confrontare), Serializable (si può serializzare), Runnable (si può eseguire in un thread).

Usa una classe astratta quando:

  • vuoi fornire un'implementazione e uno stato comuni per tutte le sottoclassi.
  • ti serve che tutte le sottoclassi abbiano determinati campi o metodi con implementazione.
  • L'ereditarietà è strettamente “uno a uno”: una classe può estendere una sola classe (concreta o astratta).

Analogie dal mondo reale

  • Un'interfaccia è come una “patente di guida”: se ce l'hai, puoi guidare un'auto, ma nessuno specifica con quale auto e in che modo lo fai.
  • Una classe astratta è come un “progetto generale di un'auto”: tutte le auto hanno volante, pedali, motore, ma ogni marca implementa i dettagli a modo suo.

5. Esempi dalla libreria standard di Java

Interfaccia: Comparable

public interface Comparable<T> {
    int compareTo(T o);
}

Qualsiasi classe che implementa questa interfaccia deve implementare il metodo compareTo. Per esempio, String, Integer, LocalDate e molte altre.

Classe astratta: AbstractList

public abstract class AbstractList<E> implements List<E> {
    // Implementazione predefinita di alcuni metodi di List
    // Alcuni metodi sono lasciati astratti
}

AbstractList implementa già parte del comportamento delle collezioni (per esempio, i metodi di aggiunta/rimozione), ma lascia alcuni metodi astratti affinché le sottoclassi possano implementarli a modo loro.

6. Esempi di codice: confronto nella pratica

Interfaccia

Creiamo un'interfaccia e una classe che la implementa.

public interface Printable {
    void print();
}

public class Document implements Printable {
    @Override
    public void print() {
        System.out.println("Sto stampando il documento...");
    }
}

Classe astratta

Ora una classe astratta e la sua sottoclasse.

public abstract class Machine {
    public void turnOn() {
        System.out.println("La macchina è accesa.");
    }

    public abstract void work();
}

public class Printer extends Machine {
    @Override
    public void work() {
        System.out.println("La stampante stampa...");
    }
}

Una classe che combina entrambi: interfaccia e classe astratta

public class SmartPrinter extends Machine implements Printable {
    @Override
    public void work() {
        System.out.println("La stampante intelligente è in funzione...");
    }

    @Override
    public void print() {
        System.out.println("La stampante intelligente stampa...");
    }
}

7. Implementazione multipla di interfacce: perché è potente

In Java una classe può estendere una sola classe (astratta o concreta), ma può implementare quante interfacce vuole! Questo consente di creare architetture flessibili ed estensibili.

public interface Scannable {
    void scan();
}

public class MultiFunctionPrinter extends Machine implements Printable, Scannable {
    @Override
    public void work() {
        System.out.println("Il multifunzione è in funzione...");
    }

    @Override
    public void print() {
        System.out.println("Il multifunzione stampa...");
    }

    @Override
    public void scan() {
        System.out.println("Il multifunzione scansiona...");
    }
}

Cosa scegliere e quando?

  • Se stai progettando funzionalità di base con stato condiviso (per esempio, campi), usa una classe astratta.
  • Se vuoi aggiungere agli oggetti delle “etichette di capacità” (per esempio, “sa stampare”, “sa confrontarsi”, “sa essere serializzato”) — usa le interfacce.
  • Se non sei sicuro — inizia da un'interfaccia. In Java è considerata una buona pratica: le interfacce offrono maggiore flessibilità ed estensibilità.

8. Errori tipici e insidie

Errore n. 1: tentare di estendere più classi — Java non lo permette!
Una classe può estendere solo un'unica classe, ma può implementare molte interfacce. Per esempio, class A extends B, C — errore, mentre class A extends B implements X, Y, Z — va benissimo.

Errore n. 2: confondere i campi di un'interfaccia e quelli di una classe.
In un'interfaccia si possono dichiarare solo costanti (public static final). Non si può dichiarare uno stato normale, per esempio, private int count; — il compilatore ti fermerà subito.

Errore n. 3: non hai implementato tutti i metodi dell'interfaccia.
Se una classe non implementa anche solo un metodo dell'interfaccia — deve essere dichiarata abstract, altrimenti il compilatore segnalerà un errore.

Errore n. 4: tentare di istanziare un'interfaccia o una classe astratta.
Entrambi questi tipi sono “semilavorati”. Si possono solo estendere, non istanziare direttamente:

Printable p = new Printable(); // Errore!
Machine m = new Machine();     // Errore!

Errore n. 5: pensare che un'interfaccia possa avere un costruttore.
Le interfacce non possono avere costruttori, perché non descrivono lo stato degli oggetti. Solo le classi (concrete e astratte).

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION