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.

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 metodocompareTo()per ordinare oggetti.java.lang.Runnable: definisce il metodorun()per eseguire codice in un thread.java.util.List<E>,java.util.Set<E>: sono interfacce che rappresentano collezioni, implementate da classi comeArrayList,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.

Rispondi