Questo articolo fa parte della serie "Stranger things in Java", dedicata agli approfondimenti del linguaggio che ci permetteranno di padroneggiare anche gli scenari più strani che si possono presentare quando programmiamo. Tutti gli articoli sono ispirati dal contenuto dal libro "Java for Aliens" (in inglese) e dal libro "Il nuovo Java".
Il modificatore protected è un specificatore d'accesso utilizzato alquanto
raramente, applicabile a variabili, metodi e costruttori, ma non a tipi Java (classi,
interfacce, enumerazioni, annotazioni e record), a meno che essi non siano innestati in
altri tipi. In questo articolo eviteremo di parlare dei tipi innestati e ci limiteremo a
parlare di tale modificatore riferendoci soprattutto ai membri (variabili e metodi)
delle classi per semplificare il discorso. In particolare faremo alcune osservazioni
importanti riguardo tale modificatore, che è spesso usato senza la necessaria
consapevolezza. Inoltre faremo una digressione anche sul pattern Singleton per
aggiungere ad esso l'estensibilità.
Definizione
Tutti i programmatori Java conoscono le proprietà dei modificatori public
e private, giacché utilizzati quotidianamente nella programmazione. Il
modificatore protected invece, viene usato molto meno frequentemente, e
quando viene impiegato non è raro che si utilizzi in maniera inopportuna, a volte
perché se ne si ignora la corretta definizione.
Quando applicato ad un membro di una classe, il modificatore protected
definisce il grado di accessibilità più alto dopo quello definito dal modificatore
public. Un membro protetto infatti, sarà
accessibile all'interno dello stesso package (tramite l'operatore dot), ma verrà
anche ereditato in tutte le sottoclassi della classe in cui è definito, anche se non
appartenenti allo stesso package.
Visibilità di package
In termini di visibilità di un membro quindi, usare il modificatore
protected o non usare alcun modificatore è la stessa cosa. Infatti, quando
non anteponiamo modificatori ad un membro di una classe, si parla di visibilità di package (o visibilità di default), e tale membro risulterà
accessibile in tutti i tipi definiti nello stesso package.
Per esempio consideriamo la classe ProtectedClass che dichiara una
variabile e un metodo protetti:
package com.claudiodesio.blog.staj; public class ProtectedClass { protected int protectedVariable; protected void protectedMethod() { System.out.println("Protected method invoked"); } }
Inoltre consideriamo la seguente classe ProtectedClassSamePackage
appartenente allo stesso package:
package com.claudiodesio.blog.staj; public class ProtectedClassSamePackage { public void methodUsingProtectedMembers() { var protectedClass = new ProtectedClass(); protectedClass.protectedMethod(); System.out.println(protectedClass.protectedVariable); } }
Tale classe compilerà senza problemi, anche se utilizza esplicitamente i membri
dichiarati protetti nella superclasse tramite l'operatore dot come se fossero dichiarati
pubblici.
Invece, se proviamo ad utilizzare i membri protected allo stesso modo in
una classe appartenente ad un diverso package come la seguente:
package com.claudiodesio.blog.staj.other; import com.claudiodesio.blog.staj.ProtectedClass; public class ProtectedClassOtherPackage { public void methodUsingProtectedMembers() { var protectedClass = new ProtectedClass(); protectedClass.protectedMethod(); System.out.println(protectedClass.protectedVariable); } }
otterremo i seguenti errori in compilazione:
ProtectedClassOtherPackage.java:9: error: protectedMethod() has protected access in ProtectedClass protectedClass.protectedMethod(); ^ ProtectedClassOtherPackage.java:10: error: protectedVariable has protected access in ProtectedClass System.out.println(protectedClass.protectedVariable); ^ ProtectedClassOtherPackage.java uses preview language features. 2 errors
proprio perché ci troviamo in un package differente.
Avremmo ottenuto lo stesso risultato anche se i membri della classe
ProtectedClassli avessimo dichiarati con visibilità di package, ovvero non
avessimo utilizzato nessun modificatore.
Ereditarietà
Dichiarare un membro protected però, è importante quando oltre a voler
limitare la visibilità al package di appartenenza, vogliamo anche che esso sia
ereditato nelle sottoclassi anche se esterne al package. Utilizzare
protected quindi, ha senso solo quando vogliamo utilizzare l'ereditarietà.
Infatti le sottoclassi ereditano membri dichiarati protetti nella superclasse, anche se
tali sottoclassi appartengono a package differenti dal package a cui appartiene la
superclasse.
Per esempio la seguente sottoclasse della classe ProtectedClass:
package com.claudiodesio.blog.staj; public class ProtectedSubclassSamePackage extends ProtectedClass { public void methodUsingProtectedMembers() { var protectedClass = new ProtectedClass(); protectedMethod(); System.out.println(protectedVariable); } }
appartiene allo stesso package della superclasse, e viene compilata senza errori.
Ma lo stesso discorso vale anche per una sottoclasse che appartiene ad un package
diverso come la seguente:
package com.claudiodesio.blog.staj.other; import com.claudiodesio.blog.staj.ProtectedClass; public class ProtectedSubclassOtherPackage extends ProtectedClass { public void methodUsingProtectedMembers() { var protectedClass = new ProtectedClass(); protectedMethod(); System.out.println(protectedVariable); } }
The stranger thing
Purtroppo, un'altra classe che si trova in un package diverso da
com.claudiodesio.blog.staj, non potrà accedere ai membri protetti ereditati
dalla superclasse ProtectedClass, perché la visibilità di tali metodi è
circoscritta al package dove sono stati dichiarati. Per esempio, la compilazione della
seguente classe:
package com.claudiodesio.blog.staj.other; public class StrangerThingsAboutProtectedTest { public static void main(String args[]) { ProtectedSubclassOtherPackage psop = new ProtectedSubclassOtherPackage(); psop.metodoProtected(); } }
darà luogo ad un errore la cui descrizione non lascia dubbi:
StrangerThingsAboutProtectedTest.java:7: error: protectedMethod() has protected access in ProtectedClass psop.protectedMethod(); ^ 1 error
Quindi, nonostante StrangerThingsAboutProtectedTest si trovi nello stesso
package della classe ProtectedSubclassOtherPackage, non può invocarne il
metodo protetto, perché la visibilità di tale metodo è limitata al package
com.claudiodesio.blog.staj dove è stato dichiarato.
StrangerThingsAboutProtectedTest appartenesse al
package com.claudiodesio.blog.staj e importasse correttamente la classe
ProtectedSubclassOtherPackage, la compilazione sarebbe andata a buon fine.
Workaround
In questo caso, per risolvere il problema possiamo fare override del metodo protetto
protectedMethod nella sottoclasse
ProtectedSubclassOtherPackage, per esempio nel seguente modo:
package com.claudiodesio.blog.staj.other; import com.claudiodesio.blog.staj.ProtectedClass; public class ProtectedSubclassOtherPackage extends ProtectedClass { public void methodUsingProtectedMembers() { var protectedClass = new ProtectedClass(); protectedMethod(); System.out.println(protectedVariable); } @Override protected void protectedMethod() { super.protectedMethod(); } }
In questo modo la classe StrangerThingsAboutProtectedTest si può compilare
ed eseguire correttamente. Infatti, il metodo protectedMethod è ridefinito
dalla classe ProtectedSubclassOtherPackage, appartenente al package
com.claudiodesio.blog.staj.other dove si trova anche la classe
StrangerThingsAboutProtectedTest.
protectedMethod
nella sottoclasse ProtectedSubclassOtherPackage, portebbe anche essere
dichiarato public.
E le variabili?
Lo stesso problema si pone anche con le variabili protette. Infatti, se il metodo della
classe StrangerThingsAboutProtectedTest provasse ad accedere alla
variabile protectedVariable:
public static void main(String args[]) { ProtectedSubclassOtherPackage psop = new ProtectedSubclassOtherPackage(); psop.protectedVariable = 1; }
otterremmo il seguente errore:
StrangerThingsAboutProtectedTest.java:8: error: protectedVariable has protected access in ProtectedClass psop.protectedVariable = 1; ^ 1 error
Anche in questo caso, per poter compilare correttamente, potremmo riscrivere la variabile nella sottoclasse:
// dichiarazione di package e di import omessi public class ProtectedSubclassOtherPackage extends ProtectedClass { protected int protectedVariable; // resto del codice omesso
Una soluzione assolutamente non elegante.
D'altronde, come vedremo nelle prossime sezioni, il modificatore protected
può essere molto utile quando applicato a metodi, a costruttori e in alcuni casi a
costanti. È molto meno utile quando applicato alle variabili.
Costruttori protected
Dichiarando un costruttore protected, possiamo rendere una classe
istanziabile solo all'interno di un certo package, ma contemporaneamente estendibile con
sottoclassi appartenenti anche a package diversi. Un esempio interessante è quello
relativo al famoso design pattern Singleton.
Una sua tipica implementazione in Java, prevede la dichiarazione del costruttore
privato. In realtà, nello storico libro che per primo ha formalizzato questo pattern:
"Design Patterns: Elements of Reusable
Object-Oriented Software", si proponeva di dichiarare il costruttore
protected, per permettere l'estensione della classe Singleton (vedi figura
1).

Figura 1: l'esempio di codice scritto in C++ proposto nel libro Design Patterns
L'esempio proposto era scritto in C++ (Java all'epoca non esisteva ancora), dove il
modificatore protected è definito in maniera più semplice e naturale. Non
esistendo il concetto di package infatti, l'utilizzo di protected in C++ garantisce una
visibilità privata ed il supporto
all'ereditarietà.
Per supportare l'ereditarietà di una classe Singleton in Java invece, oltre a dichiarare
il costruttore protected, occorre anche includere tale classe all'interno
di un package dedicato, ovvero che non contiene
altre classi. Se così non fosse, le altre classi potrebbero istanziare la classe
singleton violando la filosofia del pattern. Tenendo presente quanto appena detto, il
seguente esempio rappresenta un singleton estendibile:
package com.claudiodesio.blog.staj.singleton; public class SingletonExample { private static SingletonExample instance; protected SingletonExample () { } public static SingletonExample getInstance() { if (instance == null) { synchronized (SingletonExample.class) { instance = new SingletonExample(); } } return instance; } //altro codice omesso }
A patto che il package com.claudiodesio.blog.staj.singleton non contenga
altre classi.
Variabili protected
Ma se l'utilizzo di protected è destinato soprattutto a metodi e
costruttori, dichiarare una variabile protected, la maggior parte delle
volte non è necessario o addirittura sbagliato.
Un errore comune tra i neofiti infatti, è quello di pensare che per poter utilizzare
una certa variabile in una sottoclasse, sia necessario dichiararla
protected, quando è già sufficiente avere a disposizione i metodi mutator
(set) ed accessor (get) nelle sottoclassi. Inoltre, dichiarare protetta una variabile
d'istanza significa renderla pubblica a tutte le classi incluse nello stesso package.
Questo significa che la variabile protected è accessibile direttamente a
tutte le classi appartenenti allo stesso package, e tranne in rari casi non è questo
il livello di incapsulamento desiderato. Quindi se consideriamo la classe
Articolo:
public class Articolo { private int id; public void setId(int id) { this.id = id; } public int getId() { return id; } }
Nella sua sottoclasse Libro, possiamo settare e leggere tranquillamente la
variabile id. Infatti, anche non avendola ereditata, abbiamo comunque
ereditato i metodi setId e getId. Per esempio potremmo
implementare la sottoclasse Libro nel seguente modo:
public class Libro extends Articolo { public Libro (int id, String titolo){ setId(id); setTitolo(titolo); } private String titolo; public void setTitolo(String titolo) { this.titolo = titolo; } public String getTitolo() { return titolo; } }
Notare che abbiamo usato il metodo setId all'interno del costruttore.
Quindi non è necessario dichiarare nella superclasse Articolo la variabile
id come protected, e rompere così il contratto
dell'incapsulamento.
Conclusioni
In questo articolo abbiamo approfondito un argomento di base, come il modificatore
protected, ed in particolare il suo rapporto con l'ereditarietà. Tale
modificatore può essere utile se applicato a metodi e costruttori, molto meno per le
variabili incapsulate. Siccome è uno di quegli argomenti che vengono imparati nelle
prime fasi di apprendimento del linguaggio, e trattandosi di un modificatore di uso
abbastanza raro, alcuni programmatori ne trascurano le sottigliezze della sua
definizione, ed in alcuni casi viene utilizzato in maniera inopportuna.
