Concetti OOP in Java
Una panoramica della programmazione orientata agli oggetti in Java: incapsulamento, ereditarietà, polimorfismo e astrazione.
La programmazione orientata agli oggetti (OOP) è un modo di organizzare il codice attorno alle cose del programma anziché ai passi. Invece di un lungo script che opera su variabili grezze, si descrivono i tipi di oggetti del dominio — un User, un Order, un BankAccount — e si fornisce a ciascuno i dati e il comportamento necessari. Il resto del programma chiede poi a quegli oggetti di fare cose, invece di accedere direttamente ai loro dettagli interni.
Java è stato costruito attorno a questa idea. Ogni riga di codice Java vive all'interno di una classe, e una classe è il modello da cui vengono creati gli oggetti. Fino a questo punto hai scritto codice per lo più procedurale dentro main e alcuni helper statici; da questa parte del libro in poi, inizierai a progettare classi tue.
Classi e oggetti in una frase
Una classe descrive un tipo di cosa — quali dati contiene e cosa sa fare. Un oggetto è una specifica istanza di quella cosa.
public class Dog {
String name;
int age;
void bark() {
System.out.println(name + " says woof");
}
}
Dog d = new Dog(); // d is an object — one specific dog
d.name = "Rex";
d.bark(); // Rex says woofDog è la classe. d è un oggetto. Il cambiamento chiave rispetto al codice procedurale: un oggetto raggruppa stato (i campi name e age) e comportamento (il metodo bark()) in un'unica unità. Puoi creare quanti oggetti Dog vuoi; ognuno ottiene la propria copia di name e age, e ognuno può eseguire bark() autonomamente. Il capitolo su classi e oggetti approfondisce questo argomento nel dettaglio.
I quattro pilastri
L'OOP viene solitamente insegnato attorno a quattro concetti. Ognuno ha un capitolo dedicato più avanti in questa parte del libro — ciò che segue è una panoramica rapida per capire dove si arriverà.
Incapsulamento
Tieni i dati di un oggetto privati; esponi invece il comportamento. Il codice esterno chiama account.deposit(50), non account.balance += 50. Il vantaggio è che la classe controlla i propri invarianti — nessun altro può portarla in uno stato errato.
public class Account {
private int balance; // hidden
public void deposit(int amount) { // public behavior
if (amount <= 0) throw new IllegalArgumentException();
balance += amount;
}
}Vedi il capitolo sull'incapsulamento.
Ereditarietà
Una classe può estendere un'altra, ereditando i suoi campi e metodi e aggiungendone di nuovi. Cat extends Animal riutilizza tutto ciò che Animal fa già e specifica solo ciò che è diverso per i gatti.
public class Animal {
void breathe() { System.out.println("inhale, exhale"); }
}
public class Cat extends Animal {
void purr() { System.out.println("rrr"); }
}
Cat c = new Cat();
c.breathe(); // inherited
c.purr(); // ownVedi il capitolo sull'ereditarietà.
Polimorfismo
La stessa chiamata può fare cose diverse a seconda dell'oggetto effettivo che la riceve. Una variabile di tipo Animal potrebbe puntare a un Cat, un Dog o una Cow — chiamare speak() su di essa sceglie il comportamento corretto a runtime.
Animal a = new Cat();
a.speak(); // calls Cat's speak, even though a is typed as AnimalVedi il capitolo sul polimorfismo.
Astrazione
Definisci cosa fa qualcosa senza impegnarti su come. Un'interface Shape dice che ogni forma ha un metodo area(); ogni forma concreta — Circle, Square — fornisce la propria formula. Il codice che lavora con le forme non ha bisogno di sapere di quale tipo si tratta.
public interface Shape {
double area();
}Vedi il capitolo sull'astrazione.
I pilastri a colpo d'occhio
| Pilastro | Idea in una riga | Strumento Java |
|---|---|---|
| Incapsulamento | Nascondi i dati, esponi il comportamento | campi private + metodi pubblici |
| Ereditarietà | Riutilizza ed estendi un'altra classe | extends |
| Polimorfismo | Una chiamata, comportamento scelto a runtime | override + variabili di supertipo |
| Astrazione | Definisci cosa, non come | interfacce e classi astratte |
I pilastri si sovrappongono intenzionalmente: il polimorfismo si basa sull'ereditarietà, l'astrazione è applicata attraverso l'incapsulamento, e così via. Considerali come quattro angolazioni su un'unica idea — modella il tuo programma come oggetti che cooperano — piuttosto che quattro caratteristiche separate da memorizzare.
Perché farlo?
Per uno script di 20 righe, l'OOP è eccessivo. Il vantaggio emerge man mano che i programmi crescono:
- Ragionamento locale. Una classe
BankAccountpossiede le sue regole. Per capire o modificare i depositi, leggi il metododeposit— non devi cercare in tutto il codebasebalance +=. - Riuso senza copia-incolla. L'ereditarietà e la composizione consentono a
SavingsAccountdi basarsi suAccountinvece di duplicarla. - Sostituibilità. Una funzione che accetta una
Shapefunziona per ogni forma esistente oggi e per ogni nuova che aggiungi domani. - Testabilità. Oggetti piccoli con responsabilità chiare sono facili da istanziare, guidare e verificare.
Java è ben lungi dall'essere l'unico linguaggio orientato agli oggetti — Python, C#, Kotlin, Ruby e molti altri condividono le stesse idee con sintassi diverse. Ciò che impari in questa parte si trasferisce.
L'OOP non è l'unico paradigma
Anche all'interno di Java, hai già scritto codice che non è strettamente OO: metodi di utilità statici, aritmetica primitiva, semplice flusso di controllo. Il Java moderno mescola i paradigmi — pipeline funzionali con stream e lambda, dati immutabili con i record, lavoro dichiarativo con le annotazioni. L'OOP è la spina dorsale del linguaggio, non una gabbia. Usa una classe quando stai modellando una cosa con stato e comportamento; usa un metodo statico quando hai bisogno solo di un calcolo.
Un esempio pratico
Il codice completo degli snippet precedenti, messo insieme in modo da poterlo eseguire:
Cosa c'è dopo
Ora che conosci la struttura dell'OOP, il prossimo capitolo ne fissa i fondamenti: come una definizione di classe diventa un oggetto in memoria, cosa fa effettivamente new, e come i riferimenti agli oggetti differiscono dai tipi primitivi. Continua con classi e oggetti Java.