Astrazione e Contratti in Java
L'astrazione permette di definire "contratti" che le classi devono rispettare, separando il "cosa" dal "come". In Java questo si realizza con classi astratte e interfacce.
Cosa Imparerai
- Ereditarieta e la parola chiave extends
- Polimorfismo e override dei metodi
- Classi astratte e metodi astratti
- Interfacce e implementazione multipla
- Default methods nelle interfacce
- Quando usare classi astratte vs interfacce
Ereditarieta
L'ereditarieta permette a una classe di acquisire attributi e metodi da un'altra classe. La classe che eredita è detta sottoclasse, quella da cui eredita è la superclasse.
// Superclasse (classe padre)
public class Persona {
protected String nome;
protected String cognome;
protected int eta;
public Persona(String nome, String cognome, int eta) {
this.nome = nome;
this.cognome = cognome;
this.eta = eta;
}
public String getNomeCompleto() {
return nome + " " + cognome;
}
public void presentati() {
System.out.println("Mi chiamo " + getNomeCompleto());
}
}
// Sottoclasse (classe figlia) - eredita da Persona
public class Studente extends Persona {
private int matricola;
private double mediaVoti;
public Studente(String nome, String cognome, int eta, int matricola) {
super(nome, cognome, eta); // chiama costruttore della superclasse
this.matricola = matricola;
this.mediaVoti = 0;
}
// Metodo aggiuntivo specifico di Studente
public void studia(String materia) {
System.out.println(nome + " sta studiando " + materia);
}
// Getter
public int getMatricola() {
return matricola;
}
}
Polimorfismo e Override
Il polimorfismo permette di trattare oggetti di classi diverse attraverso un tipo comune. L'override permette di ridefinire un metodo ereditato.
public class Studente extends Persona {
private int matricola;
public Studente(String nome, String cognome, int eta, int matricola) {
super(nome, cognome, eta);
this.matricola = matricola;
}
// Override: ridefinisce il metodo della superclasse
@Override
public void presentati() {
System.out.println("Ciao! Sono " + getNomeCompleto());
System.out.println("Matricola: " + matricola);
}
}
public class Docente extends Persona {
private String materia;
public Docente(String nome, String cognome, int eta, String materia) {
super(nome, cognome, eta);
this.materia = materia;
}
@Override
public void presentati() {
System.out.println("Buongiorno, sono il Prof. " + cognome);
System.out.println("Insegno " + materia);
}
}
// Polimorfismo in azione
Persona[] persone = new Persona[3];
persone[0] = new Studente("Mario", "Rossi", 20, 123456);
persone[1] = new Docente("Luigi", "Verdi", 45, "Programmazione");
persone[2] = new Persona("Anna", "Bianchi", 30);
for (Persona p : persone) {
p.presentati(); // chiama il metodo corretto per ogni tipo
System.out.println("---");
}
Regole dell'Override
- L'annotazione
@Overrideè opzionale ma raccomandata - Il metodo deve avere la stessa firma (nome e parametri)
- Il tipo di ritorno deve essere lo stesso o un sottotipo
- Non si può ridurre la visibilità (da public a private)
- Non si possono override metodi
finalostatic
Classi Astratte
Una classe astratta non può essere istanziata direttamente e può contenere metodi astratti (senza implementazione) che le sottoclassi devono implementare.
// Classe astratta: non può essere istanziata
public abstract class FormaGeometrica {
protected String nome;
protected String colore;
public FormaGeometrica(String nome, String colore) {
this.nome = nome;
this.colore = colore;
}
// Metodo concreto: ha implementazione
public void descrivi() {
System.out.println("Sono un/una " + nome + " di colore " + colore);
}
// Metodi astratti: senza implementazione, le sottoclassi DEVONO implementarli
public abstract double calcolaArea();
public abstract double calcolaPerimetro();
}
// Sottoclasse concreta: DEVE implementare tutti i metodi astratti
public class Cerchio extends FormaGeometrica {
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 class Rettangolo extends FormaGeometrica {
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);
}
}
// Utilizzo
FormaGeometrica[] forme = {
new Cerchio("rosso", 5),
new Rettangolo("blu", 4, 3)
};
for (FormaGeometrica forma : forme) {
forma.descrivi();
System.out.printf("Area: %.2f, Perimetro: %.2f%n",
forma.calcolaArea(), forma.calcolaPerimetro());
}
Interfacce
Un'interfaccia definisce un contratto: un insieme di metodi che le classi implementatrici devono fornire. A differenza delle classi astratte, una classe può implementare multiple interfacce.
// Interfaccia: definisce un contratto
public interface Valutabile {
// Tutti i metodi sono implicitamente public abstract
double getVoto();
String getGiudizio();
}
public interface Stampabile {
void stampa();
}
public interface Esportabile {
String esportaJSON();
String esportaCSV();
}
// Una classe può implementare MULTIPLE interfacce
public class Esame implements Valutabile, Stampabile, Esportabile {
private String nomeCorso;
private int voto;
private boolean lode;
public Esame(String nomeCorso, int voto, boolean lode) {
this.nomeCorso = nomeCorso;
this.voto = voto;
this.lode = lode && voto == 30;
}
// Implementazione Valutabile
@Override
public double getVoto() {
return voto;
}
@Override
public String getGiudizio() {
if (voto >= 28) return "Ottimo";
if (voto >= 24) return "Buono";
if (voto >= 18) return "Sufficiente";
return "Insufficiente";
}
// Implementazione Stampabile
@Override
public void stampa() {
String votoStr = lode ? "30 e Lode" : String.valueOf(voto);
System.out.println(nomeCorso + ": " + votoStr + " (" + getGiudizio() + ")");
}
// Implementazione Esportabile
@Override
public String esportaJSON() {
return String.format("{\"corso\":\"%s\",\"voto\":%d,\"lode\":%b}",
nomeCorso, voto, lode);
}
@Override
public String esportaCSV() {
return String.format("%s,%d,%b", nomeCorso, voto, lode);
}
}
Default Methods (Java 8+)
public interface Calcolabile {
double calcola();
// Default method: ha un'implementazione di default
default String formattaRisultato() {
return String.format("%.2f", calcola());
}
// Static method nell'interfaccia
static double arrotonda(double valore, int decimali) {
double fattore = Math.pow(10, decimali);
return Math.round(valore * fattore) / fattore;
}
}
public class MediaVoti implements Calcolabile {
private int[] voti;
public MediaVoti(int... voti) {
this.voti = voti;
}
@Override
public double calcola() {
int somma = 0;
for (int v : voti) somma += v;
return (double) somma / voti.length;
}
// Può override il default method se vuole
@Override
public String formattaRisultato() {
return "Media: " + String.format("%.2f", calcola()) + "/30";
}
}
Classi Astratte vs Interfacce
Confronto
| Aspetto | Classe Astratta | Interfaccia |
|---|---|---|
| Ereditarieta | Singola (extends) | Multipla (implements) |
| Costruttori | Si | No |
| Stato (attributi) | Si (qualsiasi) | Solo costanti (static final) |
| Metodi concreti | Si | Solo default/static |
| Uso tipico | Base comune con stato | Contratto di comportamento |
Quando usare cosa
- Classe astratta: Quando hai codice comune da condividere, stato, o una relazione "e un tipo di"
- Interfaccia: Quando vuoi definire un comportamento che classi diverse possono implementare
- Entrambi: Puoi usare una classe astratta che implementa interfacce
Esempio Completo: Sistema Universitario
public interface Valutabile {
double getMedia();
String getGiudizio();
}
public interface Identificabile {
String getCodice();
String getTipo();
}
public abstract class MembroUniversita implements Identificabile {
protected String nome;
protected String cognome;
protected String codice;
public MembroUniversita(String nome, String cognome, String codice) {
this.nome = nome;
this.cognome = cognome;
this.codice = codice;
}
public String getNomeCompleto() {
return nome + " " + cognome;
}
@Override
public String getCodice() {
return codice;
}
// Metodo astratto: ogni sottoclasse definisce il suo comportamento
public abstract void svolgiFunzione();
}
public class StudenteUniversitario extends MembroUniversita implements Valutabile {
private int[] voti = new int[20];
private int numEsami = 0;
public StudenteUniversitario(String nome, String cognome, int matricola) {
super(nome, cognome, "S" + matricola);
}
public void registraEsame(int voto) {
if (numEsami < voti.length && voto >= 18 && voto <= 30) {
voti[numEsami++] = voto;
}
}
@Override
public String getTipo() {
return "Studente";
}
@Override
public void svolgiFunzione() {
System.out.println(getNomeCompleto() + " studia per gli esami");
}
@Override
public double getMedia() {
if (numEsami == 0) return 0;
int somma = 0;
for (int i = 0; i < numEsami; i++) somma += voti[i];
return (double) somma / numEsami;
}
@Override
public String getGiudizio() {
double media = getMedia();
if (media >= 28) return "Eccellente";
if (media >= 24) return "Buono";
if (media >= 18) return "Sufficiente";
return "In corso";
}
}
public class Professore extends MembroUniversita {
private String materia;
private int oreSettimanali;
public Professore(String nome, String cognome, String codice, String materia) {
super(nome, cognome, "P" + codice);
this.materia = materia;
this.oreSettimanali = 20;
}
@Override
public String getTipo() {
return "Professore";
}
@Override
public void svolgiFunzione() {
System.out.println("Prof. " + cognome + " insegna " + materia);
}
public void assegnaVoto(StudenteUniversitario studente, int voto) {
studente.registraEsame(voto);
System.out.printf("Il Prof. %s ha assegnato %d a %s%n",
cognome, voto, studente.getNomeCompleto());
}
}
public class TestUniversita {
public static void main(String[] args) {
// Creazione membri
StudenteUniversitario mario = new StudenteUniversitario("Mario", "Rossi", 123456);
StudenteUniversitario anna = new StudenteUniversitario("Anna", "Bianchi", 789012);
Professore profVerdi = new Professore("Luigi", "Verdi", "001", "Programmazione");
// Assegnazione voti
profVerdi.assegnaVoto(mario, 28);
profVerdi.assegnaVoto(mario, 30);
profVerdi.assegnaVoto(anna, 24);
profVerdi.assegnaVoto(anna, 26);
// Polimorfismo: array di tipo base
MembroUniversita[] membri = {mario, anna, profVerdi};
System.out.println("\n=== Membri Universita ===");
for (MembroUniversita m : membri) {
System.out.println(m.getTipo() + ": " + m.getNomeCompleto());
System.out.println("Codice: " + m.getCodice());
m.svolgiFunzione();
// Verifica se implementa Valutabile
if (m instanceof Valutabile) {
Valutabile v = (Valutabile) m;
System.out.printf("Media: %.2f - %s%n", v.getMedia(), v.getGiudizio());
}
System.out.println("---");
}
}
}
Conclusione
In questo articolo abbiamo esplorato ereditarieta, polimorfismo, classi astratte e interfacce - i pilastri avanzati della OOP che permettono di creare codice flessibile e riutilizzabile.
Nel prossimo articolo vedremo le API Core di Java: String, StringBuilder, Wrapper classes e la Date/Time API.
Punti Chiave da Ricordare
- extends: Ereditarieta singola da una classe
- implements: Implementazione di interfacce (multipla)
- @Override: Ridefinisce un metodo della superclasse
- abstract: Metodo senza implementazione, classe non istanziabile
- interface: Contratto di comportamento senza stato
- default: Metodo con implementazione nelle interfacce
Domande frequenti
Cos'è l'astrazione in Java?
L'astrazione in Java permette di definire "contratti" che le classi devono rispettare, separando il "cosa" dal "come".
Come si realizza l'astrazione in Java?
L'astrazione in Java si realizza con classi astratte e interfacce.
Cos'è l'ereditarietà in Java?
L'ereditarietà in Java permette a una classe di acquisire attributi e metodi da un'altra classe.
Che differenza c'è tra classi astratte e interfacce in Java?
Una classe astratta non può essere istanziata direttamente e può contenere metodi astratti, mentre un'interfaccia definisce un contratto che le classi implementatrici devono fornire.

Commenti
Caricamento commenti...
Accedi per commentare