Vai al contenuto

Classi astratte

In questa lezione impari a

  • comprendere cosa sono le classi astratte
  • capire quando e perché usarle
  • creare classi e metodi astratti
  • differenze tra classi concrete e astratte
  • esempi pratici di progettazione

Cos'è una classe astratta?

Una classe astratta è una classe che non può essere istanziata direttamente.

Serve come modello per le sottoclassi, definendo metodi che DEVONO essere implementati dalle classi figlie.

Si usa la parola chiave abstract.

Quando usare le classi astratte?.

  • Quando vuoi definire un comportamento comune, ma l'implementazione varia
  • Quando alcune parti della logica sono condivise, altre specifiche
  • Per creare una "famiglia" di classi correlate
  • Per forzare le sottoclassi a implementare certi metodi

Sintassi base

// Classe astratta
public abstract class Forma {
    protected String colore;
    
    // Costruttore (le classi astratte possono avere costruttori)
    public Forma(String colore) {
        this.colore = colore;
    }
    
    // Metodo concreto (con implementazione)
    public void mostraColore() {
        System.out.println("Colore: " + colore);
    }
    
    // Metodo astratto (SENZA implementazione)
    public abstract double calcolaArea();
    public abstract double calcolaPerimetro();
}

// ERRORE! Non puoi fare questo:
// Forma f = new Forma("rosso");  // Classe astratta non istanziabile!

// Devi creare una sottoclasse concreta
public class Cerchio extends Forma {
    private double raggio;
    
    public Cerchio(String colore, double raggio) {
        super(colore);
        this.raggio = raggio;
    }
    
    // DEVI implementare i metodi astratti
    @Override
    public double calcolaArea() {
        return Math.PI * raggio * raggio;
    }
    
    @Override
    public double calcolaPerimetro() {
        return 2 * Math.PI * raggio;
    }
}

// Ora puoi istanziare il Cerchio
Cerchio c = new Cerchio("rosso", 5);
c.calcolaArea();  // OK!

Attenzione: regole importanti.

  • Una classe astratta NON può essere istanziata
  • Può contenere sia metodi astratti che concreti
  • Una sottoclasse DEVE implementare TUTTI i metodi astratti (o essere anch'essa astratta)
  • Può avere costruttori, attributi, metodi normali
  • Un metodo astratto non ha corpo (no {...})

Esempio completo: forme geometriche

// Classe astratta base
public abstract class Forma {
    protected String colore;
    protected String nome;
    
    public Forma(String nome, String colore) {
        this.nome = nome;
        this.colore = colore;
    }
    
    // Metodi concreti
    public void mostraInfo() {
        System.out.println("--- " + nome + " ---");
        System.out.println("Colore: " + colore);
    }
    
    public String getColore() {
        return colore;
    }
    
    // Metodi astratti - ogni sottoclasse decide come calcolarli
    public abstract double calcolaArea();
    public abstract double calcolaPerimetro();
}

// Sottoclasse: Rettangolo
public class Rettangolo extends Forma {
    private double base;
    private double altezza;
    
    public Rettangolo(String colore, double base, double altezza) {
        super("Rettangolo", colore);
        this.base = base;
        this.altezza = altezza;
    }
    
    @Override
    public double calcolaArea() {
        return base * altezza;
    }
    
    @Override
    public double calcolaPerimetro() {
        return 2 * (base + altezza);
    }
    
    public boolean eQuadrato() {
        return base == altezza;
    }
}

// Sottoclasse: Cerchio
public class Cerchio extends Forma {
    private double raggio;
    
    public Cerchio(String colore, double raggio) {
        super("Cerchio", colore);
        this.raggio = raggio;
    }
    
    @Override
    public double calcolaArea() {
        return Math.PI * raggio * raggio;
    }
    
    @Override
    public double calcolaPerimetro() {
        return 2 * Math.PI * raggio;
    }
    
    public double getDiametro() {
        return 2 * raggio;
    }
}

// Sottoclasse: Triangolo
public class Triangolo extends Forma {
    private double lato1, lato2, lato3;
    
    public Triangolo(String colore, double lato1, double lato2, double lato3) {
        super("Triangolo", colore);
        this.lato1 = lato1;
        this.lato2 = lato2;
        this.lato3 = lato3;
    }
    
    @Override
    public double calcolaArea() {
        // Formula di Erone
        double s = calcolaPerimetro() / 2;
        return Math.sqrt(s * (s - lato1) * (s - lato2) * (s - lato3));
    }
    
    @Override
    public double calcolaPerimetro() {
        return lato1 + lato2 + lato3;
    }
    
    public boolean eEquilatero() {
        return lato1 == lato2 && lato2 == lato3;
    }
}

Test delle forme

public class TestForme {
    public static void main(String[] args) {
        // Array polimorfico di Forma (anche se Forma è astratta!)
        Forma[] forme = new Forma[4];
        
        forme[0] = new Rettangolo("rosso", 5, 3);
        forme[1] = new Cerchio("blu", 4);
        forme[2] = new Triangolo("verde", 3, 4, 5);
        forme[3] = new Rettangolo("giallo", 4, 4);  // Quadrato
        
        // Elaboro tutte le forme allo stesso modo!
        double areaTotle = 0;
        
        for (Forma forma : forme) {
            forma.mostraInfo();
            System.out.println("Area: " + forma.calcolaArea());
            System.out.println("Perimetro: " + forma.calcolaPerimetro());
            System.out.println();
            
            areaTotale += forma.calcolaArea();
        }
        
        System.out.println("=== Riepilogo ===");
        System.out.println("Numero forme: " + forme.length);
        System.out.println("Area totale: " + areaTotale);
        
        // Usando instanceof e cast per metodi specifici
        System.out.println("\n=== Controlli specifici ===");
        for (Forma forma : forme) {
            if (forma instanceof Rettangolo) {
                Rettangolo r = (Rettangolo) forma;
                if (r.eQuadrato()) {
                    System.out.println("Trovato un quadrato " + r.getColore() + "!");
                }
            } else if (forma instanceof Triangolo) {
                Triangolo t = (Triangolo) forma;
                if (t.eEquilatero()) {
                    System.out.println("Trovato un triangolo equilatero!");
                }
            }
        }
    }
}
--- Rettangolo ---
Colore: rosso
Area: 15.0
Perimetro: 16.0

--- Cerchio ---
Colore: blu
Area: 50.26548245743669
Perimetro: 25.132741228718345

--- Triangolo ---
Colore: verde
Area: 6.0
Perimetro: 12.0

--- Rettangolo ---
Colore: giallo
Area: 16.0
Perimetro: 16.0

=== Riepilogo ===
Numero forme: 4
Area totale: 87.26548245743669

=== Controlli specifici ===
Trovato un quadrato giallo!

Esempio: sistema bancario

// Classe astratta per conti bancari
public abstract class ContoBancario {
    protected String numeroConto;
    protected String intestatario;
    protected double saldo;
    
    public ContoBancario(String numeroConto, String intestatario, double saldoIniziale) {
        this.numeroConto = numeroConto;
        this.intestatario = intestatario;
        this.saldo = saldoIniziale;
    }
    
    // Metodi concreti comuni
    public void deposita(double importo) {
        if (importo > 0) {
            saldo += importo;
            System.out.println("Depositati €" + importo);
        }
    }
    
    public double getSaldo() {
        return saldo;
    }
    
    public void mostraInfo() {
        System.out.println("=== Conto " + numeroConto + " ===");
        System.out.println("Intestatario: " + intestatario);
        System.out.println("Saldo: €" + saldo);
    }
    
    // Metodi astratti - implementazione diversa per ogni tipo di conto
    public abstract boolean preleva(double importo);
    public abstract void calcolaInteressi();
    public abstract String getTipoConto();
}

// Conto Corrente
public class ContoCorrente extends ContoBancario {
    private double fido;  // Scoperto permesso
    
    public ContoCorrente(String numeroConto, String intestatario, 
                         double saldoIniziale, double fido) {
        super(numeroConto, intestatario, saldoIniziale);
        this.fido = fido;
    }
    
    @Override
    public boolean preleva(double importo) {
        // Può andare in negativo fino al fido
        if (saldo - importo >= -fido) {
            saldo -= importo;
            System.out.println("Prelevati €" + importo);
            return true;
        } else {
            System.out.println("Fondo insufficiente! Fido: €" + fido);
            return false;
        }
    }
    
    @Override
    public void calcolaInteressi() {
        // Interessi solo se in positivo
        if (saldo > 0) {
            double interessi = saldo * 0.01;  // 1%
            saldo += interessi;
            System.out.println("Interessi maturati: €" + interessi);
        }
    }
    
    @Override
    public String getTipoConto() {
        return "Conto Corrente";
    }
}

// Conto Risparmio
public class ContoRisparmio extends ContoBancario {
    private static final double TASSO_INTERESSE = 0.03;  // 3%
    private int numPrelievi;
    
    public ContoRisparmio(String numeroConto, String intestatario, double saldoIniziale) {
        super(numeroConto, intestatario, saldoIniziale);
        this.numPrelievi = 0;
    }
    
    @Override
    public boolean preleva(double importo) {
        // Non può andare in negativo
        if (saldo >= importo) {
            saldo -= importo;
            numPrelievi++;
            System.out.println("Prelevati €" + importo + " (prelievo n." + numPrelievi + ")");
            
            // Penale dopo 3 prelievi
            if (numPrelievi > 3) {
                double penale = 2.0;
                saldo -= penale;
                System.out.println("Applicata penale: €" + penale);
            }
            return true;
        } else {
            System.out.println("Saldo insufficiente!");
            return false;
        }
    }
    
    @Override
    public void calcolaInteressi() {
        double interessi = saldo * TASSO_INTERESSE;
        saldo += interessi;
        System.out.println("Interessi maturati: €" + interessi);
        System.out.println("Nuovo saldo: €" + saldo);
    }
    
    @Override
    public String getTipoConto() {
        return "Conto Risparmio";
    }
}
public class TestBanca {
    public static void main(String[] args) {
        ContoBancario[] conti = new ContoBancario[2];
        
        conti[0] = new ContoCorrente("CC001", "Mario Rossi", 1000, 500);
        conti[1] = new ContoRisparmio("CR001", "Giulia Bianchi", 5000);
        
        for (ContoBancario conto : conti) {
            System.out.println("\n=== " + conto.getTipoConto() + " ===");
            conto.mostraInfo();
            
            conto.deposita(200);
            conto.preleva(300);
            conto.calcolaInteressi();
            
            System.out.println("Saldo finale: €" + conto.getSaldo());
        }
    }
}

Classi astratte vs classi concrete

Caratteristica Classe Astratta Classe Concreta
Istanziabile No Sì
Metodi astratti Può averne No
Metodi concreti Può averne Sì
Costruttori Sì Sì
Attributi Sì Sì
Uso Modello per sottoclassi Oggetti utilizzabili

Esercizi guidati

⭐⭐ Medio

Esercizio 1 – veicoli astratti.

Obiettivo: Creare una gerarchia di veicoli con classe astratta.

Requisiti:

  • Classe astratta Veicolo con marca, modello
  • Metodo astratto: calcolaCostoViaggio(double km)
  • Metodo concreto: mostraInfo()
  • Sottoclassi: Auto (0.20€/km), Moto (0.10€/km), Camion (0.50€/km)
  • Test con array di veicoli

⭐⭐ Medio

Esercizio 2 – dipendenti e stipendi.

Obiettivo: Sistema di calcolo stipendi.

Requisiti:

  • Classe astratta Dipendente (nome, cognome)
  • Metodo astratto: calcolaStipendio()
  • Impiegato: stipendio fisso
  • Manager: stipendio + bonus
  • Consulente: tariffa oraria * ore lavorate

Esercizi da svolgere

⭐ Facile

Esercizio 3 – strumenti di disegno.

Crea classe astratta Strumento con metodo disegna():

  • Matita, Pennello, Gomma
  • Ogni strumento disegna in modo diverso

⭐⭐ Medio

Esercizio 4 – elettrodomestici.

Crea classe astratta Elettrodomestico con:

  • Metodo astratto: calcolaConsumo()
  • Frigorifero, Lavatrice, TV
  • Calcola consumo totale

⭐⭐⭐ Difficile

Esercizio 5 – sistema di gioco.

Crea classe astratta Personaggio con:

  • Attributi: nome, salute, forza
  • Metodo astratto: attacca(Personaggio nemico)
  • Guerriero, Mago, Arciere
  • Simula combattimenti

Concetti chiave da ricordare

  • Classe astratta: abstract class NomeClasse
  • Metodo astratto: public abstract tipo metodo();
  • NON può essere istanziata direttamente
  • Sottoclassi DEVONO implementare metodi astratti
  • Può avere sia metodi astratti che concreti
  • Usa per definire un contratto che le sottoclassi devono rispettare
  • Perfetta con polimorfismo per codice flessibile