1. Panoramica dei modificatori di accesso
In Java esistono quattro livelli di accesso per classi, campi, metodi e costruttori:
| Modificatore | Dove è disponibile? |
|---|---|
|
Ovunque: all'interno della classe, in altre classi, in altri pacchetti |
|
All'interno della classe, nelle sottoclassi (ereditarie), nelle altre classi dello stesso pacchetto |
|
Solo all'interno del pacchetto (se il modificatore non è specificato esplicitamente) |
|
Solo all'interno della classe corrente |
Vediamo ciascuno più nel dettaglio — con esempi, qualche battuta e colpi di scena inaspettati.
public — accesso pubblico
public — è come un annuncio a tutto il mondo: «Ingresso consentito a tutti!». Se una classe, un campo, un metodo o un costruttore è dichiarato public, è accessibile da qualsiasi altra classe, anche da un altro pacchetto.
Esempio:
public class Cat {
public String name;
public void sayMeow() {
System.out.println("Miao!");
}
}
Si può accedere a questa classe e ai suoi campi/metodi da qualsiasi luogo. È comodo se vuoi che la tua classe sia disponibile a tutti: ad esempio, se stai scrivendo una libreria.
Ma! I campi pubblici non sono sempre una buona idea (vedi la lezione precedente). Di solito si rendono pubblici solo i metodi che devono essere accessibili dall'esterno, mentre i campi quasi sempre restano private.
private — accesso solo all'interno della classe
private — è come una cassaforte con combinazione: nessuno, tranne la stessa classe, può accedere a questi membri. Perfino le sottoclassi non vedono i campi e i metodi privati!
Esempio:
public class Cat {
private String secretName;
public void setSecretName(String name) {
secretName = name;
}
public String getSecretName() {
return secretName;
}
}
Qui secretName non può essere letto o modificato direttamente da un'altra classe. Solo Cat stesso può farlo (o i suoi metodi). Questa è la base dell'incapsulamento: nascondiamo i dettagli interni e forniamo l’accesso ad essi solo tramite metodi.
protected — accesso protetto
protected — è come un pass VIP: accesso consentito alla classe, alle sue sottoclassi (anche se si trovano in altri pacchetti) e a tutte le classi all'interno dello stesso pacchetto.
Esempio:
public class Animal {
protected int age;
protected void growOlder() {
age++;
}
}
Ora qualsiasi classe che estende Animal potrà accedere al campo age e al metodo growOlder().
public class Cat extends Animal {
public void haveBirthday() {
growOlder();
System.out.println("Il gatto ha compiuto " + age + " anni!");
}
}
Inoltre, tutte le classi dello stesso pacchetto hanno accesso ai membri protected.
(package-private) — accesso all'interno del pacchetto
Se non specifichi affatto un modificatore di accesso, il membro della classe è considerato package-private (o «accesso predefinito»). È come una porta senza serratura, ma solo per i tuoi: accesso consentito solo alle classi dello stesso pacchetto.
Esempio:
class Dog {
String name; // package-private
void bark() { // package-private
System.out.println("Bau!");
}
}
La classe Dog, il suo campo name e il metodo bark() sono accessibili solo all'interno dello stesso pacchetto. Un tentativo di accedervi da un altro pacchetto causerà un errore di compilazione.
2. Applicazione dei modificatori a campi e metodi
Perché i campi sono quasi sempre private
I campi di una classe rappresentano il suo stato interno. Se li lasci pubblici, qualsiasi codice esterno potrà modificarli in qualunque momento. È come se permettessi a bambini sconosciuti di giocare con i tuoi mobili come fossero Lego: un giorno ti svegli e il frigorifero è capovolto in bagno.
Esempio di incapsulamento sbagliato:
public class Person {
public String name;
public int age;
}
Esempio di buon incapsulamento:
public class Person {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
I metodi (getter e setter) consentono di controllare come i campi possono essere modificati dal codice esterno. Ad esempio, puoi vietare un'età negativa.
Quando rendere i metodi public, protected o package-private
- public — se il metodo deve essere accessibile a tutti. In genere è la funzionalità principale della classe.
- protected — se il metodo serve solo alle sottoclassi o all'interno del pacchetto (ad esempio, metodi di supporto che possono essere utili nelle classi figlie).
- package-private — se il metodo serve solo all'interno del pacchetto ma non deve essere accessibile dall'esterno (ad esempio, dettagli interni di implementazione).
- private — se il metodo è usato solo all'interno della stessa classe (ad esempio, metodi di supporto per la logica interna).
Esempio:
public class BankAccount {
private double balance;
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
protected void applyInterest() {
balance *= 1.05;
}
void internalAudit() {
// package-private: solo per le classi all'interno del pacchetto
}
private void logAction(String action) {
// solo per le esigenze interne della classe
}
}
3. Esempi di codice: una classe con diversi livelli di accesso
Creiamo una classe che ha di tutto: membri pubblici, privati, protetti e di pacchetto. Proviamo anche ad accedervi da altre classi e vediamo cosa succede.
package zoo;
public class Animal {
public String publicName = "Disponibile a tutti";
protected String protectedName = "Solo per le sottoclassi e il pacchetto";
String packageName = "Solo per il pacchetto";
private String privateName = "Solo per Animal";
public void publicMethod() {
System.out.println("Metodo pubblico");
}
protected void protectedMethod() {
System.out.println("Metodo protetto");
}
void packageMethod() {
System.out.println("Metodo di pacchetto");
}
private void privateMethod() {
System.out.println("Metodo privato");
}
}
Ora proviamo ad accedere a questi membri da un'altra classe nello stesso pacchetto:
package zoo;
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println(animal.publicName); // OK
System.out.println(animal.protectedName); // OK
System.out.println(animal.packageName); // OK
System.out.println(animal.privateName); // Errore: private
animal.publicMethod(); // OK
animal.protectedMethod(); // OK
animal.packageMethod(); // OK
animal.privateMethod(); // Errore: private
}
}
Ora proviamo ad accedere a questi membri da un altro pacchetto:
package other;
import zoo.Animal;
public class Test {
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println(animal.publicName); // OK
System.out.println(animal.protectedName); // Errore: protected
System.out.println(animal.packageName); // Errore: package-private
System.out.println(animal.privateName); // Errore: private
animal.publicMethod(); // OK
animal.protectedMethod(); // Errore: protected
animal.packageMethod(); // Errore: package-private
animal.privateMethod(); // Errore: private
}
}
Conclusione:
- public — disponibile ovunque.
- protected — disponibile all'interno del pacchetto e nelle sottoclassi (anche da altri pacchetti, se tramite ereditarietà).
- package-private — solo all'interno del pacchetto.
- private — solo all'interno della classe.
4. Best practice: come scegliere il modificatore di accesso
Minimizza la visibilità
Quanto meno codice può vedere il tuo campo o metodo, tanto meglio. Esponi solo ciò che serve davvero al codice esterno. Questo è il principio del privilegio minimo (principle of least privilege).
- I campi dovrebbero quasi sempre essere private. Eccezioni — solo per vere costanti (public static final), ma ne parleremo meglio nelle prossime lezioni.
- I metodi si rendono public solo se fanno parte dell'interfaccia esterna della classe.
- I metodi di supporto (logica interna) — private.
- Metodi per le sottoclassi — protected.
- Metodi di servizio interni al pacchetto — package-private.
Perché è importante?
- Qualsiasi modifica ai dettagli di implementazione può rompere il codice altrui, se i dettagli sono esposti.
- La classe diventa più difficile da testare e mantenere.
- Errori accidentali (ad esempio, modifiche scorrette a un campo) possono portare a bug.
A volte i principianti pensano: «Perché complicarsi la vita, rendiamo tutto public!» Ma poi, quando il progetto cresce, si ritrovano a riscrivere metà del programma solo perché qualcuno modificava direttamente i campi della classe.
5. Errori tipici nell'uso dei modificatori di accesso
Errore n. 1: Campi lasciati public o package-private per impostazione predefinita.
Se non specifichi un modificatore, il campo o il metodo sarà accessibile a tutte le classi all'interno del pacchetto. Questo può portare a situazioni inattese, se qualcuno inizia a cambiare direttamente i tuoi campi.
Errore n. 2: Tentativo di accedere a un membro private da un'altra classe.
Il compilatore non lo permette — otterrai un errore. Ma se decidi di aggirarlo tramite reflection (riflessione), benvenuto nel mondo di bug e crash inaspettati.
Errore n. 3: Troppo public.
Se dichiari tutto public, la classe assomiglia a una scatola aperta piena di cavi — chiunque può tirare quello sbagliato e rompere tutto.
Errore n. 4: Non usare protected per i metodi necessari solo alle sottoclassi.
Se un metodo serve solo per essere esteso nelle classi figlie, dichiaralo protected, non public.
Errore n. 5: Visibilità package-private implicita.
A volte ci si dimentica di specificare il modificatore e il metodo diventa accessibile a tutto il pacchetto. Può essere una sorpresa se pensavi che fosse private.
GO TO FULL VERSION