University Logo

Università
di Padova

Diploma in Ingegneria Informatica
Corso di Fondamenti di Informatica 2
2 Moduli

A.A. 2001/2002

Versione 1.0 27/09/2001

Argomenti supplementari


Introduzione a Java
Classi interne
Strutture lineari
Ordinamento
Sentinelle
Tabella di confronto
Interfaccia grafica
Gestione eventi

Pagina principale Torna alla pagina principale


  1. Introduzione a Java
  2. Classi interne
    (gli esempi sono ricavati da [Fla])

    Le classi interne di Java, ovvero classi definite all'interno di altre classi, sono state introdotte a partire dal JDK 1.1.x per consentire un maggior controllo nella visibilità di classi ad uso molto ristretto (semplici helper o adattatori), inserendo queste ultime nello specifico contesto in cui debbono operare, spesso attraverso una singola istanza (oggetto). Limitando la visibilità della classe interna si ottiene un codice più chiaro, si riduce la quantità di nomi esportati dal package di appartenenza, e se ne fa un uso particolarmente vantaggioso nel caso dell'interfaccia grafica (ad esempio per difinire gli ascoltatori di eventi).

    Esistono 4 tipi di classi interne:

    classe o interfaccia innestata di primo livello (nested top-level)
    si tratta di un membro statico di una classe o di una interfaccia al primo livello; ha l'attributo static e agisce come una classe o interfaccia 'normale' a parte che il nome che la identifica include la classe contenente (ad es., se nella classe 'normale' A del package P è definita la classe top-level B, il suo nome completo è P.A.B).
    classe membro (member)
    si tratta di un membro non statico di una classe che la include; può accedere a tutti i membri, anche privati, della classe includente; ogni istanza della classe membro è associata implicitamente ad una stabilita istanza della classe includente (cosa che ha richiesto alcune estensioni anche alla sintassi del linguaggio).
    classe locale (local)
    si tratta di una classe definita all'interno di un blocco di codice, entro il quale è limitata la visibilità della classe stessa (similmente ad una variabile locale); può accedere ai membri della classe includente ed anche ad ogni varibile o parametro locale di tipo final visibile nel blocco; è utile come classe adattatore (adapter), in particolare per gli ascoltatori di eventi (listener o callback).
    classe anonima (anonymous)
    si tratta di una classe locale priva di nome che viene istanziata una sola volta; si preferisce alla definizione di una classe locale quando la classe è molto semplice.

    La JVM non ha subito variazioni con l'introduzione delle classi interne: ciò comporta, per esempio, che, mentre la classe A di primo livello produce il compilato A.class, la classe A.B produca A$B.class, e similmente per la trasformazione di altri nomi dovuta al nesting. In qualche caso il compilatore deve anche generare codice aggiuntivo per consentire alcune particolari regole di visibilità. Comunque il programmatore non è tenuto a preoccuparsi di queste trasformazioni automaticamente operate dal compilatore.

    Classi e interfacce interne di primo livello
    Queste classi e interfacce vengono definite in altre classi (le interfacce anche in altre interfacce) di primo livello, con un nesting di profondità arbitraria (anche se un nesting profondo rende piuttosto illegibile la varietà di nomi composti che si devono usare). Le classi di questo tipo hanno sempre l'attributo static (per le interfacce, è implicito). Possono essere richiamate sia dalla classe contenente che da altre classi esterne, eventualmente con l'uso dell'istruzione import se da un file diverso, cosa che rende non più obbligatoria la qualificazione con il nome della classe contenente, similmente a quanto accade con i package.

    Esempio di interfaccia interna top-level:

    public class LinkedList
    {
        // interfaccia (statica) locale
        public interface Linkable
        {
            public Linkable getNext();
            public void setNext(Linkable node);
        }
    
        // altri membri della classe contenente
        Linkable head;  // testa della lista
    
        // metodi
        public void insert(Linkable node)
          { ... }
    } //{c} LinkedList
    
    
    // in altro file
    import LinkedList.*;
    
    class LinkableFloat implements Linkable
    {
        float f;
    
        public LinkableFloat(float f)
        {
            this.f = f;
        }
    
        // implementazione dell'interfaccia
        Linkable next;
        public Linkable getNext() { return next; }
        ....
    } //{c} LinkableFloat
    
    Si noti che con l'uso di import si può citare solo Linkable anziché LinkedList.Linkable.
    Classi membro

    Non sono dichiarate con l'attributo static e la loro introduzione aggiunge effettivamente una semantica nuova. Infatti ogni oggetto istanza della classe interna è associato implicitamente ad uno specifico oggetto della classe contenente, con una relazione che è in generale molti-a-uno. Quando chiamando il metodo a.m() sull'oggetto a di classe A al suo interno viene creato l'oggetto b della classe B membro interno di A, l'oggetto b è implicitamente associato ad a. Ogni successiva chiamata a.m() crea oggetti di B che sono associati ad a. L'oggetto b attraverso i suoi metodi può quindi accedere ai membri anche privati dell'oggetto associato a (oltre naturalmente ai propri membri). Questa associazione automatica, che evita di passare esplicitamente ai metodi dell'oggetto 'incluso' un riferimento all'oggetto 'includente', rende elegante il codice e pratica la definizione di classi helper o adattatori.

    L'accesso, da parte dell'oggetto incluso, ai membri dell'oggetto includente può dare qualche problema di sintassi. Anche se è consentito che avvenga con la semplice citazione del nome del membro, così come avviene normalmente con i membri propri, in caso di conflitto (lo stesso nome per un membro proprio e uno dell'oggetto includente) non è possibile utilizzare la qualificazione con this che vale per i membri propri. A tale scopo, la sintassi viene estesa: nell'esempio sopra, se entrambe le classi A e B hanno un membro x, nel metodo b.f() un riferimento a quello in B è dato da x oppure da this.x, mentre uno a quello di A è dato da A.this.x, cioè anteponendo anche il nome della classe includente.

    Un'altra estensione della sintassi riguarda la possibilità di specificare esplicitamente l'associazione oggetto incluso-oggetto includente. Nel caso della creazione di un oggetto incluso non da un metodo di un oggetto includente, l'operatore new di creazione va corredato dell'indicazione dell'oggetto includente. Per l'esempio sopra la sintassi è a.new B() (si noti, non a.new A.B()). All'interno di un metodo di B è invece possibile usare solo new oppure, coerentemente, this.new. Se il nesting è multiplo, ci si riferisce sempre all'oggetto immediatamente contenente. Se ad esempio la classe membro C è definita in B, si può scrivere:

    A     a = new   A();
    A.B   b = a.new B();
    A.B.C c = b.new C();
    
    Vi sono anche alcuni vincoli: la classi membro non possono avere lo stesso nome di altre classi o package, e non possono contenere membri statici (come anche quelle locali ed anonime), questi ultimi avendo senso solo in classi di primo livello.

    Esempio di enumeratore come classe membro:

    public class LinkedList
    {
        ....
        // metodi
        public void insert(Linkable node)
          { ... }
        public Enumeration enums() { return new Enum(); }
          // creatore dell'enumeratore
    
        private class Enum implements Enumeration
        {
            Linkable current;
    
            public Enum() { current = head; }
            // equivalente a:
            // this.current = LinkedList.this.head;
    
            .....
        } //{c} Enum
    
    
    Si noti che nell'esempio la classe membro Enum è dichiarata private e pertanto non è possibile creare enumeratori al di fuori della classe LinkedList se non chiamando il metodo enums(). Se invece la visibilità fosse package, all'interno di quest'ultimo ma fuori da LinkedList si potrebbero utilizzare le istruzioni:
    LinkedList li = new LinkedList();
    Enumeration e = li.new Enum();
    
    Classi locali

    Una classe locale viene definita all'interno di un metodo, di un inizializzatore statico e di un inizializzatore d'istanza di classe (vedi). Esiste un principio di associazione oggetto incluso-oggetto includente del tutto analogo ad una classe membro. Una classe locale è però utilizzabile sono nel blocco di codice ove è definita. Per motivi di implementazione, oltre ai membri della classe contenente, può utilizzare anche variabili e parametri di chiamata locali ma solo se hanno l'attributo final, quest'ultima essendo un'ulteriore estensione di Java 1.1. La regola di visibilità porta ad utilizzare questo tipo di classi quando sono usate in un solo metodo o blocco di codice. Una classe locale può utilizzare la sintassi di this vista sopra per le classi membro ma non quella di new. Non puņ includere membri statici né interfacce interne (che sono come visto implicitamente statiche) e non possono avere alcun modificatore (public, protected, private) esattamente come accade per le variabili locali.

    Esempio di ascoltatore di un evento come classe locale:

    public class GUI extends Frame
    {
        SomeApplication someObject;
    
        ....
        MenuItem createMItem(final int command, ...)
        {
            MenuItem m = new MenuItem(...);
    
            // classe locale ascoltatore
            class MenuItemListener implements ActionListener
            {
                // implementa il metodo d'interfaccia
                public void actionPerformed(ActionEvent e)
                {
                    someObject.someAction(command);
                }
            } //{c} MenuItemListener
    
            // registra l'ascoltatore
            m.addActionListener(new MenuItemListener());
            return m;
        } //[m] createMItem
    } //{c} GUI
    
    
    Si noti che command è un parametro final del metodo all'interno del quale la classe locale è definita, mentre someObject, essendo un membro della classe includente, può non essere final.
    Classi anonime

    Una classe anonima differisce da una classe locale solo perché non c'è una dichiarazione con nome della classe ma, in pratica, solo un'istruzione con l'operatore new seguito dal corpo della classe anonima (tra parentesi graffe), eventualmente preceduto da un nome di classe o di interfaccia, seguito da parentesi tonde, se rispettivamente la classe anonima estende quella classe o implementa quell'interfaccia. Ad esempio la scrittura new A(5) { ... } indica la creazione di un oggetto di classe anonima che estende la classe A, passando come parametro di un suo costruttore il valore 5; il corpo della classe anonima è in parentesi graffe. Non potendo esplicitare le parole chiave extends o implements, se la classe anonima implementa un'interfaccia, può discendere solo da Object, come per esempio nell'istruzione new Sequence() { ... }

    L'assenza di un nome per la classe ha come svantaggio quello di non poter definire per la classe alcun costruttore; inoltre costringe il compilatore a nominare il compilato di una classe anonima definita nella classe B come B$n ove n è un numero progressivo assegnato alle classi anonime. È conveniente che la scrittura di una classe anonima segua semplici regole di indentazione utili alla leggibilità del codice.

    Esempio di ascoltatore di un evento come classe anonima:

    public class GUI extends Frame
    {
        SomeApplication someObject;
    
        ....
        MenuItem createMItem(final int command, ...)
        {
            MenuItem m = new MenuItem(...);
            // registra l'ascoltatore
            m.addActionListener(new ActionListener() {
                // classe anonima ascoltatore
                // implementa il metodo d'interfaccia
                public void actionPerformed(ActionEvent e)
                {
                    someObject.someAction(command);
                }
            } /*{c} classe anonima ascoltatore */ );
    
            return m;
        } //[m] createMItem
    } //{c} GUI
    
    
    L'assenza di costruttori nelle classi anonime viene compensata da una estensione di Java 1.1, utilizzabile in qualsiasi tipo di classe, data dall'inizializzatore d'istanza. Si tratta di un blocco di codice, inserito tra graffe, posto nel corpo di una classe e che viene eseguito alla creazione di ogni istanza della classe, dopo aver chiamato il costruttore della superclasse ma prima di eseguire quello della classe. Una classe può definire un numero arbitrario di questi blocchi, che vengono eseguiti nell'ordine in cui compaiono.

    Esempio di uso di inizializzatori d'istanza:

    public class Indici
    {
        // variabile d'istanza
        public int[] a1;
    
        // inizializzatore di istanza
        {
            a1 = new int[10];
            for (int i=0; i<10; i++)
                a1[i] = i;
        }
    
        // variabile d'istanza
        public int[] a2;
    
        // inizializzatore di istanza
        {
            a2 = new int[10];
            for (int i=0; i<10; i++)
                a2[i] = (i<<1); // i*2
        }
        ....
    } //{c} Indici
    

    Altre estensioni di Java 1.1 che vengono qui solo brevemente citate sono:

    blank final
    Poiché ora i parametri di chiamata a metodi e catch possono avere l'attributo final, il vincolo di inserire per le variabili final l'inizializzatore assieme alla dichiarazione è stato rimosso e sostituito dal vincolo che la variabile final deve essere inizializzata prima del suo primo utilizzo, ad esempio in un costruttore, e da allora non è più modificabile.
    array anonimo
    Si tratta di un'estensione dell'operatore new che consente di esplicitare una lista di espressioni di inizializzazione degli elementi di un array non contestualmente alla dichiarazione dell'array. Ad esempio è ora consentito scrivere entrambe queste forme:
    int[] a = {1, 2, 3};
    int[] b; b = new int[] {1, 2, 3}; // array anonimo
    
    class literals
    Con riferimento alla riflessione in Java 1.1, con la parola chiave class si può ottenere un oggetto Class che fornisce la descrizione della classe a cui l'operatore è applicato: per esempio, String.class fornisce un oggetto che descrive la classe String. In Java 1.1 questo è possibile anche per i tipi primigenî, ad esempio int.class. Poiché per questi tipi le descrizioni sono predefinite, vengono rese disponibili alcune classi literal che rappresentano queste descrizioni; ad esempio nel caso di int la classe literal di descrizione è data da Integer.TYPE, nel caso float da float.TYPE, nel caso void da Void.TYPE.

    Ulteriori informazioni sono reperibili nella documentazione allagata al JDK ed in particolare in docs/guide/innerclasses/index.html.

    Indice Torna all'indice


  3. Strutture lineari
  4. Indice Torna all'indice


  5. Ordinamento
  6. Sentinelle

    In alcuni algoritmi di ordinamento si ottiene un miglioramento, più o meno sensibile, che si traduce normalmente nella eliminazione di un confronto ripetuto, inserendo ad uno dei capi della lista da ordinare un elemento aggiuntivo denominato sentinella. La caratteristica di questo elemento è che il confronto della sua chiave con quella di qualsiasi altro elemento della lista fa terminare il ciclo che è basato su tale confronto. Ad esempio, nell'algoritmo di insertion sort (vedi FI2.Sorting.ArraySort.insertionSort()) nel ciclo più interno:

    while (i >= 0 && vec[i].greater(key))
    

    il confronto i>=0 potrebbe essere eliminato se l'elemento vec[0] fosse una sentinella la cui chiave si sa per certo essere <= di quella di ogni altro elemento effettivo, in quanto ciò farebbe terminare il ciclo. Se il numero di elementi è elevato e il confronto tra chiavi di complessità confrontabile con quello tra interi (i>=0), l'eleminazione di quest'ultimo può comportare un miglioramento anche sensibile. Può però non essere agevole imporre la presenza della sentinella: intanto, deve occupare una posizione estrema dell'array; poi, per verificare la condizione attesa, o la sua chiave è posta ad ogni ciclo pari alla chiave di key, oppure è sempre pari ad un valore maggiore di qualsiasi chiave effettiva, valore che non è sempre possibile determinare.

    Tabella di confronto

    La seguente tabella è è ricavata dal libro di N.Wilt "Classical Algorithms in C++", John Wiley & Sons, New York, 1995, ed è stata calcolata utilizzando un processore Digital Alpha con sistema operativo Windows NT e un real-time process cycle counter della stessa risoluzione del clock del processore (233 MHz corripondente a 4.3 ns) e fornisce il valore medio calcolato su un ampio ventaglio di casi (da 10 a 100000 elementi, inizialmente ordinati a caso) senza uso di memoria virtuale.

    Durate degli algoritmi di sorting
    Algoritmo Durata (in cicli di clock)
    Insertion Sort 1.93 N²
    Selection Sort 4.84 N²
    Shell Sort 38.1 N lg² N º
    Recursive Merge Sort 38.2 N lg N
    Iterative Merge Sort 44.4 N lg N
    Recursive Quicksort 20.2 N lg N
    Full-blown Quicksort (cutoff=10) 17.8 N lg N
    Heapsort 65.4 N lg N

    º Il comportamento asintotico di shell sort non è noto ma la curva N lg² N sembra approssimare meglio i dati piuttosto che N1.25


  7. Interfaccia grafica
  8. Gestione degli eventi

    In Java 1.1 il modello di gestione degli eventi è detto 'a delegazione' poiché si basa sull'uso di 'classi ascoltatore' che, attarverso uno o più dei loro metodi, fanno la funzione del meccanismo di callback presente in altri ambienti grafici e facilmente realizzabile in C/C++ con l'uso dei puntatori a funzione. In estrema sintesi l'oggetto che riceve l'evento, e che è tipicamente l'immagine di un elemento dell'interfaccia grafica (bottone, area di testo, ecc.), ha pre-registrato presso di sè un certo numero di oggetti ascoltatore: se fra questi ve n'è uno adatto al tipo di evento prodottosi, quest'ultimo viene passato all'ascoltatore attivando l'apposito metodo definito nell'interfaccia dell'ascoltatore stesso, altrimenti l'evento viene ignorato.

    Nella seguente tabella sono elencati per ogni classe dell'interfaccia grafica gli eventi che possono essere generati e i relativi ascoltatori con la lista dei metodi da implementare; per alcuni ascoltatori sono anche citate le classi adattatore che consentono di non dover implementare tutti i metodi dell'interfaccia dell'ascoltatore se si interessa gestire solo un sottoinsieme degli eventi 'ascoltabili'. Gli eventi sono divisi tra eventi a basso livello (low-level) e eventi semantici.

    Gestione eventi di basso livello
    Componente Evento Ascoltatore Adattatore Metodi Commento
    Component ComponentEvent ComponentListener ComponentAdapter componentHidden() comp. nascosto
    componentMoved() comp. mosso
    componentResized() comp. modificato
    componentShown() comp. mostrato
    FocusEvent FocusListener FocusAdapter focusGained() comp. con focus
    focusLost() comp. senza focus
    KeyEvent KeyListener KeyAdapter keyPressed() tasto premuto
    keyReleased() tasto rilasciato
    keyTyped() tasto premuto e rilasciato
    MouseEvent MouseListener MouseAdapter mouseClicked() tasto mouse premuto e rilasciato
    mouseEntered() mouse entro un comp.
    mouseExited() mouse fuori di un comp.
    mousePressed() tasto mouse premuto
    mouseReleased() tasto mouse rilasciato
    MouseMotionListener
    [1.07 99/00]
    MouseMotionAdapter mouseDragged() mouse trascinato
    mouseMoved() mouse mosso su un comp.
    Container ContainerEvent ContainerListener ContainerAdapter componentAdded() comp. aggiunto
    componentRemoved() comp. eliminato
    Window (Dialog and Frame) WindowEvent WindowListener WindowAdapter windowActivated() finestra attivata
    windowClosed() finestra chiusa
    windowClosing() finestra in chiusura
    windowDeactivated() finestra disattivata
    windowDeiconified() finestra riaperta da icona
    windowIconified() finestra iconizzata
    windowOpened() finestra aperta

    Gestione eventi semantici
    Componente Evento Ascoltatore Adattatore Metodi Commento
    Button ActionEvent ActionListener --- actionPerformed() azione eseguita
    Choice ItemEvent ItemListener --- itemStateChanged() variazione su una voce
    Checkbox ItemEvent ItemListener --- itemStateChanged() variazione su una voce
    CheckboxMenuItem ItemEvent ItemListener --- itemStateChanged() variazione su una voce
    List ActionEvent ActionListener --- actionPerformed() azione eseguita
    ItemEvent ItemListener --- itemStateChanged() variazione su una voce
    MenuItem ActionEvent ActionListener --- actionPerformed() azione eseguita
    ScrollBar AdjustmentEvent AdjustmentListener --- adjustmentValueChanged() modifica valore di impostazione
    TextComponent (TextArea e TextField) TextEvent TextListener --- textValueChanged() modifica contenuto di testo
    TextField ActionEvent ActionListener --- actionPerformed() azione eseguita

    Indice Torna all'indice


    Pagina principale Torna alla pagina principale