Menu


Trascrizione diapositive

1. Modificatori, classi ed interfacce

  • Complessità: MEDIA.


2. Modificatori fondamentali

  • Nell’elenco mostrato a seguire, vengono presentati i modificatori fondamentali ed i relativi componenti cui si possono applicare:

  • Modificatore

    Classe

    Costruttore

    Attributo

    Metodo

    public

    Si
    Si
    Si
    Si

    protected

    No
    Si
    Si
    Si

    (default)

    Si
    Si
    Si
    Si

    private

    No
    Si
    Si
    Si

    abstract

    Si
    No
    No
    Si

    final

    Si
    No
    Si
    Si

    static

    Si
    No
    Si
    Si

    strictfp

    Si
    No
    No
    Si

    Si noti che per modificatore «default», si intende indicare la situazione in cui non anteponiamo alcun modificatore alla dichiarazione di un componente.


    3. Modificatori fondamentali

    • Volutamente sono stati trascurati i seguenti modificatori:

      - native (perché sfrutta la tecnologia JNI che rende Java dipendente dal sistema operativo).
      - synchronized (riguarda i processi in Java).
      - transient (riguarda i processi in Java).
      - volatile (riguarda i processi in Java).


    4. Modificatori d’accesso

    • Di seguito vengono definiti i principali modificatori d’accesso che forniscono al compilatore informazioni sulla visibilità del codice:

    Modificatore di visibilità

    Effetto

    public

    visibile da qualsiasi parte del programma.

    protected

    visibile solo dalle classi dello stesso package e dalle sottoclassi.

    (default)

    visibile dallo stesso package e dalle sottoclassi se sono nello stesso pacchetto.

    private

    visibile solo dall'interno della classe stessa.


    Non è possibile utilizzare più di un modificatore d’accesso per singolo componente.


    5. Il modificatore final

    • Questo modificatore è fondamentale per rendere di sola lettura interi blocchi di codice, precisamente final permette:

    • - Di trasformare la variabile cui è dichiarato in costante.
    • - Di impedire ad un metodo di essere riscritto (non è possibile applicare l’override).
    • - Di impedire ad una classe di essere estesa (non è possibile applicare l’ereditarietà).

    • Il modificatore final può essere utilizzato anche per le variabili locali e per le variabili parametro.


    6. Il modificatore static

    • Il modificatore static rappresenta una tra le nozioni più importanti di Java, esso permette ad un componente (attributo o metodo) di essere totalmente indipendente dall’oggetto, questo significa che si potrà accedere ad un qualsiasi membro statico anche con una sintassi del tipo «NomeClasse.NomeMembro» e non solo con «NomeOggetto.NomeMembro».
    • Quindi se applichiamo il modificatore static ad un metodo, esso si trasforma in funzione, invece se lo applichiamo ad una variabile, essa non sarà più legata agli oggetti cui appartiene.
    • I programmatori C++ troveranno familiare questo tipo di approccio, in quanto il modificatore static è un concetto molto più vicino alla programmazione procedurale.


    7. Le variabili e il modificatore static

    • Scriviamo a semplice titolo di esempio una classe che dichiara una variabile di tipo static:

    [Download]

    class Variabile {
    public static int variabileDiTipoStatic;
    }

    • Adesso invochiamola istanziando oggetti:

    [Download]

    public class Programma {
    public static void main(String [] args) {
    Variabile oggetto = new Variabile();
    Variabile altroOggetto = new Variabile();
    oggetto.variabileDiTipoStatic = 5;
    System.out.println (oggetto.variabileDiTipoStatic+"-"+ altroOggetto.variabileDiTipoStatic);
    altroOggetto.variabileDiTipoStatic = 8;
    System.out.println (oggetto.variabileDiTipoStatic+"-"+ altroOggetto.variabileDiTipoStatic);
    }
    }

    • In output otterremo:

    5-5
    8-8


    e non…

    5-0
    5-8



    8. I metodi e il modificatore static

    • Un metodo di tipo static non dipende dall’oggetto che lo invoca, quindi se usiamo this o super, dobbiamo fare sempre riferimento a membri di tipo static, esempio:

    [Download]

    class Variabile {
    public int variabile;
    public static void  mostraVariabile() {
    System.out.println(this.variabile);
    }
    }

    Si invoca così...

    [Download]

    public class Programma {
    public static void main(String [] args) {
    Variabile oggetto = new Variabile();
    oggetto.variabile = 2;
    oggetto.mostraVariabile();
    }
    }

    • Produrrà un errore in compilazione, mentre:

    [Download]

    class Variabile {
    public static int variabile;
    public static void mostraVariabile() {
    System.out.println(this.variabile);
    }
    }

    Si invoca così...

    [Download]

    public class Programma {
    public static void main(String [] args) {
    Variabile oggetto = new Variabile();
    oggetto.variabile = 2;
    oggetto.mostraVariabile();
    }
    }

    • Sarà compilato ed eseguito regolarmente.


    9. Inizializzatori statici

    • Se il modificatore static viene impiegato per contrassegnare un semplice blocco di codice, significa che siamo in presenza di un inizializzatore statico.

    [Download]

    class Automobile {
    static { // Inizializzatore statico
    System.out.println(“Motore acceso”);
    }
    }

    • Un inizializzatore statico viene invocato in automatico quando si istanzia un oggetto della classe in cui è presente, prima del metodo costruttore.
    • Da un inizializzatore statico si potranno invocare solo dei metodi dichiarati di tipo static, inoltre si potrà agire solo su variabili statiche.
    • È possibile inserire in una classe anche più di un inizializzatore statico, ovviamente, ogni inizializzatore verrà eseguito in maniera sequenziale (dall’alto verso il basso).


    10. Inizializzatori d’istanza

    • È anche possibile contrassegnare un semplice blocco di codice senza usare il modificatore static, in questo specifico caso siamo in presenza di un inizializzatore d’istanza:

    [Download]

    class Automobile {
    { // Inizializzatore d’istanza
    System.out.println(“Motore acceso”);
    }
    }

    • Un inizializzatore d’istanza viene invocato in automatico quando si istanzia un oggetto della classe in cui è presente, prima del metodo costruttore ma sempre dopo un eventuale inizializzatore statico.
    • In un inizializzatore d’istanza si possono eseguire le stesse istruzioni di un semplice metodo costruttore, l’unica differenza è che in un inizializzatore d’istanza è impossibile passare parametri, come si potrebbe eventualmente fare in un costruttore.


    11. Classi innestate

    • Fino ad ora abbiamo applicato il modificatore static ad un attributo, ad un metodo e ad un semplice blocco di codice, ma è importante sapere che è anche possibile applicare questo modificatore per istanziare una classe innestata da una classe esterna, ad esempio:

    [Download]

    class Persona { // Classe principale
    static class NomeAnagrafico { // Classe innestata
    private String nome;
    private String cognome;
    public NomeAnagrafico (String n, String c) {
    this.nome = n;
    this.cognome = c;
    }
    public String getNome() {
    return this.nome;
    }
    public String getCognome() {
    return this.cognome;
    }
    }
    }

    Si invoca così...

    [Download]

    public class Programma { // Classe esterna
    public static void main(String[] args) {
    Persona.NomeAnagrafico i = new Persona.NomeAnagrafico ("Mario", "Rossi"); // Operazione possibile perché la classe innestata è di tipo static.
    System.out.println(i.getNome());
    System.out.println(i.getCognome());
    }
    }

    • Dall’esempio mostrato si evince che una classe innestata non è altro che una classe definita all’interno di un’altra classe.


    12. Classi innestate

    • La classe innestata proposta nell’esempio soffre di alcune limitazioni dovute al modificatore di tipo static, se da un certo punto di vista l’attributo in questione permette di istanziare direttamente la classe (usando «new Persona.NomeAnagrafico»), di contro diverrà impossibile fare uso nello stesso oggetto di variabili e/o metodi della classe principale (soluzione nella prossima slide…).
    • Non è detto che si possa sempre istanziare una classe innestata di tipo static da una classe esterna. Come per le variabili ed i metodi, infatti, le classi statiche innestate possono fare uso dei quattro modificatori d'accesso validi in Java (public, private, protected e default).
    • Notiamo che la compilazione del precedente codice sorgente «Programma.java», genera tre distinti bytecode anziché due:

      - Programma.class
      - Persona.class
      - Persona$NomeAnagrafico.class


    13. Classi innestate

    Nel metodo main() viene istanziata la classe innestata tramite un metodo della classe principale, in questo caso è possibile evitare di inserire il modificatore static.

    [Download]

    class Persona {
    class NomeAnagrafico {
    private String nome;
    private String cognome;

    public NomeAnagrafico(String n, String c) {
    this.nome = n;
    this.cognome = c;
    }
    public String getNome() {
    return this.nome;
    }
    public String getCognome() {
    return this.cognome;
    }
    }

    private NomeAnagrafico nomeAnagrafico;

    public Persona(String n, String c) {
    nomeAnagrafico = new NomeAnagrafico(n, c);
    }
    public NomeAnagrafico getNomeAnagrafico() {
    return nomeAnagrafico;
    }
    }

    Si invoca così...

    [Download]

    public class Programma {
    public static void main(String[] args) {
    Persona p = new Persona("Mario", "Rossi");
    Persona.NomeAnagrafico nom = p.getNomeAnagrafico();
    System.out.println(nom.getNome());
    System.out.println(nom.getCognome());
    }
    }

    Notiamo che quando viene creato un oggetto di tipo «Persona», è istanziato automaticamente dal metodo costruttore un altro oggetto di tipo «NomeAnagrafico» chiamato «nomeAnagrafico».
    Come sappiamo l’oggetto «nomeAnagrafico» non è altro che una variabile non primitiva che contiene un indirizzo di memoria, il metodo che permette di restituire questo indirizzo si chiama «getNomeAnagrafico()», che può essere invocato dal metodo main() a partire da un oggetto di tipo «Persona».
    Con questa tecnica siamo in grado di poter associare in assoluta sicurezza una classe innestata, con una classe principale chiamata anche top-level class.



    14. Classi innestate

    • Dal punto di vista concettuale, le classi innestate sono da considerarsi un «inutile complicazione» se usate a sproposito. Diventano indispensabili quando usate propriamente. Che siano indispensabili è dimostrato dall'esistenza di una relazione object-oriented che si affronta raramente.
    • Durante la narrazione del concetto di ereditarietà si era affermato che si riteneva importante effettuare un test chiamato «is-a» relationship (è-un), dobbiamo sapere adesso, che per quanto riguarda l’implementazione di una classe innestata, bisogna effettuare un altro tipo di test chiamato «part-of» relationship (è-parte-di).
    • Ad esempio per correlare l’elemento «Veicolo» con l’elemento «Automobile» e «Motorino» bisogna usare l’ereditarietà, in quanto un «Automobile» (è-un) «Veicolo», come è anche vero che un «Motorino» (è-un) «Veicolo», mentre nel caso dovessimo mettere in relazione l’elemento «Motore» con l’elemento «Automobile» e «Motorino», dobbiamo usare le classi innestate, in quanto un «Automobile» ha un «Motore» concettualmente ben diverso da quello di un «Motorino» (è improprio utilizzare is-a), mentre è giusto affermare che «Motore» (è-parte-di) «Automobile», come è anche vero che «Motore» (è-parte-di) «Motorino».


    15. Modificatore abstract

    • Il modificatore abstract può essere applicato a classi e metodi.
    • Una classe astratta non può essere istanziata, ma tuttavia sarà possibile ereditare da essa (sia variabili che metodi) da parte di una (o più) sottoclassi. Ecco un semplice esempio di classe astratta:

    abstract class Veicolo {
    }

    • Invece, un metodo astratto è un metodo che non è fornito di proprio blocco di codice, ma termina con un semplice punto e virgola. Ecco un esempio di metodo astratto:

    abstract class Veicolo {
    public abstract void accellera();
    public abstract void decellera();
    }

    • Anche i metodi astratti potranno essere ridefiniti (tramite override) nelle classi derivate.
    • Inoltre, se definiamo un metodo astratto è obbligatorio applicare il modificatore abstract anche alla classe.


    16. Modificatore abstract

    • In particolare i metodi astratti sono molto utili in fase di progettazione, in quanto nell’ambito del polimorfismo per dati, agevolano l’invocazione virtuale di metodi senza compromettere la configurazione della superclasse.
    • Il modificatore abstract per quanto riguarda l’ambito dichiarativo può essere considerato l’opposto del modificatore final, in quanto il modificatore final inibisce la riscrittura di un metodo e l’estensione di una classe, mentre il modificatore abstract lo permette.


    17. Interfacce

    • Quando parliamo di un'interfaccia in Java, intendiamo definire un semplice step evolutivo del concetto di classe astratta, infatti, un'interfaccia proprio come una classe astratta, non può essere istanziata, inoltre, non può essere neanche estesa.
    • In un'interfaccia tutti i metodi sono dichiarati implicitamente come public e abstract, mentre tutte le variabili sono dichiarate implicitamente come public, static e final.
    • Chiaramente se non possiamo neanche applicare la tecnica dell’ereditarietà su di un'interfaccia, inizialmente è normale pensare che non possiamo adoperarla, invece, è possibile fare un qualcosa di più e cioè è possibile implementarla.
    • L’implementazione di un'interfaccia può essere eseguita grazie ad una nuova parola chiave di Java chiamata: implements.


    18. Interfacce

    • Ecco un esempio completo di interfaccia:

    [Download]

    interface SpiaBenzina {
    String ACCESA = "Fare rifornimento";
    void stato();
    }

    • Chiaramente al codice summenzionato verranno aggiunti in fase di compilazione i seguenti modificatori:

    interface SpiaBenzina {
    public static final String ACCESA = "Fare rifornimento";
    public abstract void stato();
    }

    • Come abbiamo precedentemente affermato, un'interfaccia deve essere implementata da una classe, ad esempio in questo modo:

    [Download]

    class Automobile implements SpiaBenzina {
    private int benzina;
    @Override
    public void stato() {
    if (benzina < 20) {
    System.out.println(ACCESA);
    }
    }
    }

    Una classe può implementare un'interfaccia, ma un'interfaccia può solo estendere un’altra interfaccia.


    19. Ereditarietà multipla

    • Come avevamo in precedenza affermato, in Java non esiste la cosiddetta ereditarietà multipla, però grazie alle interfacce si riesce a simulare questo meccanismo.
    • Quindi è possibile scrivere ad esempio:

    class Automobile extends Veicolo implements SpiaBenzina, SpiaMotore {
    ...
    }

    • Chiaramente sia «SpiaBenzina» come anche «SpiaMotore» dovranno essere delle interfacce, con tutte le limitazioni che ne derivano.


    20. Differenza tra interfacce e classi astratte

    • Una classe astratta può dichiarare variabili e metodi concreti, a differenza di una interfaccia che dichiara implicitamente tutti e metodi di tipo abstract e tutte le variabili di tipo static e final.
    • Di contro sappiamo che in una classe astratta non è possibile simulare il meccanismo di ereditarietà multipla, in quanto una classe astratta (come qualsiasi altro tipo di classe), può essere soltanto estesa ma non implementata.


    21. Enumerazioni

    • Le enumerazioni sono strutture dati molto simili alle classi, ma a differenza di quest’ultime non si possono estendere.
    • L’uso pratico di un’enumerazione è quello di comporre un insieme di valori predefiniti senza ricorrere alle costanti. Di seguito un esempio di enumerazione:

    enum Mesi {
    GENNAIO, FEBBRAIO, MARZO, APRILE, MAGGIO, GIUGNO, LUGLIO, AGOSTO, SETTEMBRE, OTTOBRE, NOVEMBRE, DICEMBRE;
    }

    • Un’enumerazione non può essere istanziata come una classe, ma i suoi elementi sono tutti istanziabili e sono dichiarati implicitamente di tipo public, final e static. È possibile istanziare un elemento di un’enumerazione in questo modo:

    Mesi m = Mesi.GENNAIO;

    • In questa ipotesi la variabile non primitiva «m», conterrà un indirizzo di memoria che punta al valore «GENNAIO».


    22. Enumerazioni

    • Di seguito un esempio completo di enumerazione, è stato importante aver definito dei valori immutabili in modo tale da evitare possibili errori in fase di esecuzione:

    [Download]

    enum Mesi {
    GENNAIO, FEBBRAIO, MARZO, APRILE, MAGGIO, GIUGNO, LUGLIO, AGOSTO, SETTEMBRE, OTTOBRE, NOVEMBRE, DICEMBRE;
    }

    [Download]

    class Calendario {
    public void corrente(Mesi mese) {
    switch(mese) {
    case GENNAIO:
    System.out.println("Mese di Gennaio");
    break;
    ...
    }
    }
    }

    Si invoca così...

    [Download]

    public class Programma {
    public static void main(String[] args) {
    Mesi m = Mesi.GENNAIO;
    Calendario mese = new Calendario();
    mese.corrente(m);
    }
    }

    È importante precisare che è possibile implementare un eventuale interfaccia ad un’enumerazione, chiaramente considerando che un’enumerazione non si può estendere non sarà possibile fare eventuale uso del modificatore abstract.


    23. Classi anonime

    • Per completare il ragionamento focalizzato sui possibili tipi di classe, è corretto introdurre anche il concetto di classe anonima.
    • Una classe anonima non è altro che una classe senza metodo costruttore, quindi priva di nome. Ecco un esempio:

    [Download]

    abstract class Animale { // Classe principale
    abstract void faiverso();
    void mangia() {
    System.out.println("GNAM!");
    }
    }

    Si invoca così...

    [Download]

    public class Programma {
    public static void main(String[] args) {
    Animale gatto = new Animale() {
    // Classe anonima
    @Override
    void faiverso() {
    System.out.println("MIAO!");
    }
    };
    Animale cane = new Animale() {
    // Classe anonima
    @Override
    void faiverso() {
    System.out.println("BAU!");
    }
    };
    gatto.faiverso();
    cane.faiverso();
    cane.mangia();
    }
    }

    Dal codice si evince che una classe anonima deve essere definita ogni volta che viene istanziato un oggetto del tipo della superclasse.
    Quindi è possibile utilizzare una classe anonima solo per estendere un’altra classe, riscrivendone uno o più metodi; ma è anche vero che una classe anonima può implementare un'interfaccia anziché estendere una superclasse.
    Inoltre, considerando che una classe anonima istanzia necessariamente uno specifico oggetto, non sarà possibile fare uso interno del modificatore static.



    24. Modificatore strictfp

    • Il modificatore strictfp modifica il comportamento di tutte le espressioni in virgola mobile contenute nel componente cui è applicato.
    • Java usa i formati in virgola mobile specificati dallo ANSI/IEEE Standard 754-1985. In alcuni casi però viene usato internamente un formato a esponente esteso per garantire la correttezza dei calcoli. In generale ciò non comporta problemi e migliora le prestazioni dei programmi, ma in casi particolari può accadere che un programma si aspetti esattamente il comportamento previsto nello Standard IEEE 754 anche se i risultati ottenuti sono meno accurati. In tale caso l’utilizzo del modificatore strictfp garantisce l’utilizzo nei calcoli del solo formato Standard IEEE 754.