Controllo di Accesso in Java

Concetti Chiave
  • Il controllo degli accessi in Java consente di limitare l'accesso ai membri di una classe.
  • I modificatori di accesso public, private e protected determinano come e da dove i membri di una classe possono essere accessibili.
  • L'incapsulamento permette di proteggere i dati e i metodi di una classe, creando una sorta di scatola nera che può essere utilizzata senza esporre i dettagli interni.

Introduzione al Controllo degli Accessi

Come è noto dalle lezioni precedenti sulla programmazione ad oggetti, l'incapsulamento collega i dati con il codice che li manipola.

Tuttavia, l'incapsulamento fornisce un'altra importante caratteristica: il controllo degli accessi.

Tramite l'incapsulamento, è possibile controllare quali parti di un programma possono accedere ai membri di una classe. Controllando l'accesso, è possibile prevenire usi impropri.

Ad esempio, consentire l'accesso ai dati solo attraverso un insieme ben definito di metodi permette di prevenire l'uso improprio di tali dati. Pertanto, se implementato correttamente, una classe crea una sorta di scatola nera che può essere usata, ma i meccanismi interni non sono accessibili alla manomissione.

Tuttavia, le classi presentate in precedenza non soddisfano completamente questo obiettivo. Ad esempio, si consideri la classe Stack mostrata di seguito:

/*
 * Questa classe definisce una pila di 
 * interi che può contenere 10 valori.
 */
class Stack {

    // Array che contiene la pila
    int[] stck = new int[10];
    // Indice della cima della pila
    // (top-of-stack)
    int tos;

    // Inizializza la cima della pila
    Stack() {
        tos = -1;
    }

    // Effettua il push di un elemento nella pila
    // (aggiunge l'elemento alla cima della pila)
    void push(int item) {
        if(tos==9)
            System.out.println("Lo Stack è pieno.");
        else
            stck[++tos] = item;
    }

    // Effettua il pop di un elemento dalla pila
    // (rimuove l'elemento dalla cima della pila)
    int pop() {
        if(tos < 0) {
            System.out.println("Stack vuoto.");
            return 0;
        }
        else
            return stck[tos--];
    }

}

Anche se è vero che i metodi push() e pop() forniscono un'interfaccia controllata alla pila, questa interfaccia non è imposta. Cioè, è possibile per un'altra parte del programma bypassare questi metodi e accedere direttamente alla pila: in particolare, si può accedere all'array stck e all'indice tos. Ciò significa che un'altra parte del programma potrebbe, ad esempio, impostare tos a un valore che va oltre la fine dell'array stck, causando un errore di esecuzione. Inoltre, l'array stck potrebbe essere modificato in modi che non sono previsti dalla classe Stack.

Naturalmente, nelle mani sbagliate, ciò potrebbe causare problemi. In questa sezione, verrà introdotto il meccanismo tramite il quale è possibile controllare con precisione l'accesso ai vari membri di una classe.

Modificatori di Accesso

Come un membro può essere accessibile è determinato dal modificatore di accesso associato alla sua dichiarazione.

Java fornisce un ricco insieme di modificatori di accesso. Alcuni aspetti del controllo degli accessi sono correlati principalmente all'ereditarietà o ai package. (Un package è, essenzialmente, un raggruppamento di classi.) Queste parti del meccanismo di controllo degli accessi di Java saranno discusse nelle prossime lezioni.

Qui, si inizia esaminando il controllo degli accessi come si applica a una singola classe. Una volta compresi i fondamenti del controllo degli accessi, il resto sarà facile.

Consiglio

Moduli e accessibilità

La funzionalità dei moduli introdotta con JDK 9 può anche influenzare l'accessibilità. I moduli sono descritti nelle prossime lezioni.

I modificatori di accesso di Java sono:

  1. public
  2. private
  3. protected.

Java definisce anche un livello di accesso predefinito. protected si applica solo quando è coinvolta l'ereditarietà. Gli altri modificatori di accesso sono descritti di seguito.

Iniziamo con la definizione di public e private:

  1. Quando un membro di una classe è modificato come public, quel membro può essere accessibile da qualsiasi altro codice.
  2. Quando un membro di una classe è specificato come private, allora tale membro può essere accessibile solo da altri membri della sua classe.

Ora si può comprendere perché il metodo main() è sempre stato preceduto dal modificatore public. È chiamato da codice che si trova all'esterno del programma — cioè, dal sistema di esecuzione Java.

Quando non viene utilizzato alcun modificatore di accesso, per impostazione predefinita il membro di una classe è public solo all'interno del proprio package, ma non può essere accessibile al di fuori di esso. (I package saranno discussi in dettaglio nelle prossime lezioni.)

Nelle classi sviluppate finora, tutti i membri di una classe hanno utilizzato la modalità di accesso predefinita. Tuttavia, questo non è ciò che generalmente si vorrà fare. Di solito, si vorrà limitare l'accesso ai membri di dati di una classe — consentendo l'accesso solo tramite metodi. Inoltre, ci saranno momenti in cui si vorrà definire metodi che sono privati alla classe.

Un modificatore di accesso precede il resto della specifica del tipo di un membro. Cioè, deve precedere l'intera dichiarazione del membro. Ecco un esempio:

public int i;
private double j;

private int mioMetodo(int a, char b) { //...

Per comprendere gli effetti dell'accesso public e private, si consideri il seguente programma:

/* Questo programma dimostra la differenza tra
   pubblico e `private`.
*/
class Test {
    int a;           // accesso predefinito
    public int b;    // accesso pubblico
    private int c;   // accesso `private`

    // metodi per accedere a c
    void setC(int i) { // imposta il valore di c
        c = i;
    }

    int getC() { // restituisce il valore di c
        return c;
    }
}
class ProvaAccesso {
    public static void main(String[] args) {
        Test ob = new Test();

        // Questi vanno bene, a e b possono essere accessibili direttamente
        ob.a = 10;
        ob.b = 20;

        // Questo non va bene e genererà un errore
        // ob.c = 100; // Errore!

        // È necessario accedere a c tramite i suoi metodi
        ob.setC(100); // OK
        System.out.println("a, b e c: " + ob.a + " " +
                           ob.b + " " + ob.getC());
    }
}

Come si può vedere, all'interno della classe Test, a utilizza l'accesso predefinito, che per questo esempio è lo stesso che specificare public.

b è esplicitamente specificato come public.

Il membro c è dato come private. Questo significa che non può essere accessibile da codice al di fuori della sua classe. Quindi, all'interno della classe ProvaAccesso, c non può essere usato direttamente. Deve essere accessibile tramite i suoi metodi pubblici: setC() e getC(). Se si rimuovesse il simbolo di commento dalla riga seguente,

// ob.c = 100; // Errore!

non sarebbe possibile compilare questo programma a causa della violazione di accesso.

Esempio

Per vedere come il controllo degli accessi può essere applicato a un esempio più pratico, si consideri la seguente versione migliorata della classe Stack mostrata sopra. Questa versione utilizza i modificatori di accesso per proteggere i membri della classe:

/*
 * Questa classe definisce una pila di 
 * interi che può contenere 10 valori.
 */
class Stack {

    /* Ora, sia stck che tos sono privati. Questo significa
       che non possono essere accidentalmente o maliziosamente
       alterati in un modo che sarebbe dannoso per la pila.
    */

    // Array che contiene la pila
    int[] stck = new int[10];
    // Indice della cima della pila
    // (top-of-stack)
    int tos;

    // Inizializza la cima della pila
    Stack() {
        tos = -1;
    }

    // Effettua il push di un elemento nella pila
    // (aggiunge l'elemento alla cima della pila)
    void push(int item) {
        if(tos==9)
            System.out.println("Lo Stack è pieno.");
        else
            stck[++tos] = item;
    }

    // Effettua il pop di un elemento dalla pila
    // (rimuove l'elemento dalla cima della pila)
    int pop() {
        if(tos < 0) {
            System.out.println("Stack vuoto.");
            return 0;
        }
        else
            return stck[tos--];
    }

}

Come si può vedere, ora sia stck, che contiene la pila, sia tos, che è l'indice della cima della pila, sono specificati come private.

Questo significa che non possono essere accessibili o modificati se non tramite i metodi push() e pop(). Rendere tos private, ad esempio, impedisce ad altre parti del programma di impostarlo accidentalmente a un valore che va oltre la fine dell'array stck.

Il seguente programma dimostra l'uso della classe Stack migliorata. Si provi a rimuovere i commenti alle righe per verificare che i membri stck e tos siano effettivamente inaccessibili.

class ProvaStack {
    public static void main(String[] args) {
        Stack mioStack1 = new Stack();
        Stack mioStack2 = new Stack();

        // inserisce alcuni numeri nella pila
        for(int i = 0; i < 10; i++) mioStack1.push(i);
        for(int i = 10; i < 20; i++) mioStack2.push(i);

        // estrae quei numeri dalla pila
        System.out.println("Stack in mioStack1:");
        for(int i = 0; i < 10; i++)
            System.out.println(mioStack1.pop());

        System.out.println("Stack in mioStack2:");

        for(int i = 0; i < 10; i++)
            System.out.println(mioStack2.pop());

        // queste istruzioni non sono legali
        // mioStack1.tos = -2;
        // mioStack2.stck[3] = 100;
    }
}

Sebbene i metodi di solito forniscano l'accesso ai dati definiti da una classe, ciò non deve necessariamente essere sempre il caso. È perfettamente corretto consentire a una variabile di istanza di essere pubblica quando esiste una valida ragione per farlo.

Ad esempio, la maggior parte delle classi semplici in questa guida è stata creata con poco interesse per il controllo dell'accesso alle variabili di istanza per semplicità.

Tuttavia, nel mondo reale, sarà necessario consentire operazioni su dati solo tramite metodi. Questo è il motivo per cui è importante conoscere il controllo degli accessi. Come si può vedere, si tratta di uno strumento potente e utile.