Introduzione alle Interfacce

Una interfaccia  (interface) in Java è come una classe astratta, ma può contenere SOLO metodi astratti e costanti (quindi niente costruttori, niente variabili, e solo l'intestazione dei metodi).
Si puo' dichiarare che una classe implementa (implements) una data interfaccia: deve allora fornire una implementazione per tutti i suoi metodi.

Non basterebbero le classi astratte?

Una classe può implementare più di una interfaccia: la relazione implements non deve rispettare la regola dell'ereditarietà singola!!!

 




Ruolo delle interfacce

Le interfacce in Java hanno almeno due ruoli fondamentali:
  • una interfaccia consente di fattorizzare informazioni comuni a più classi in modo indipendente dalla relazione di ereditarietà: compensa la mancanza di ereditarietà multipla;
  • una interfaccia può essere vista come una specifica astratta del comportamento di una collezione di oggetti (Tipo di dati astratto): una classe che implementa l'interfaccia fornisce una possibile realizzazione della specifica.
Introduciamo le interfacce sottolineandone il primo ruolo, ma successivamente le useremo come specifica di tipi di dati astratti (come code, pile, liste, insiemi, alberi binari).



Sintassi della dichiarazione di interfaccia

Si dichiara come una classe, ma con la parola chiave interface
 
public interface <IntName> [extends <IntName1>, <IntName2>, ...] {

   <tipo1> <nome-var1> = <val1>;

   ...

   <tipoN> <nome-varN> = <valN>;

   <tipo-res1> <metodo1> ( <arg1> );

   ...

   <tipo-resM> <metodoM> ( <argM> );

}

Si noti che:

  • Le variabili devono essere inizializzate e non possono essere modificate successivamente: anche se non sono dichiarate final di fatto sono delle costanti;
  • I metodi sono tutti astratti, anche se manca abstract: infatti al posto del corpo c'è solo un punto e virgola;
  • Una interfaccia può estendere una o più interfacce (non classi), indicate dopo la parola chiave extends. Per le interfacce non vale la restrizione di "ereditarietà singola" che vale per le classi.



Un classico esempio: Comparable


 

public interface Comparable {
    public int compareTo(Object o);
}

Nella API di Comparable  [locale, Medialab, Sun] si vede che al metodo compareTo() è associata una precisa interpretazione, descritta a parole, che le classi che lo implementano sono tenute a rispettare.





La relazione implements


E' simile alla relazione extends tra una classe concreta e una classe astratta:
 

public class <nomeClasse> implements <nomeInterfaccia> {

    ....
}
 

La classe <nomeClasse> deve implementare tutti i metodi dichiarati in <nomeInterfaccia>.

Esempio: creiamo sottoclassi di Tempo e Sfera che implementino Comparable
 


public class SferaOrdinabile extends Sfera implements Comparable{
      <costruttori>
    public int compareTo(Object o){
    Sfera sf = (Sfera) o;
        double delta = 0.000001;
        if (Math.abs(volume() - sf.volume()) < delta) return 0;
        else if (volume() < sf.volume()) return -1;
        else return 1;
    }
}


public class TempoOrdinabile extends Tempo
                             implements Comparable{

    public int compareTo(Object o){
    TempoOrdinabile  tempo =  (TempoOrdinabile) o;
        int totSecondi = ore * 3600 + minuti * 60  +  secondi;
        int totSecondi2 =  tempo.ore * 3600 + 
                  tempo.minuti * 60  + tempo.secondi; 
        return  (totSecondi - totSecondi2);
    }
}




Relazioni tra extends e implements

  • Ogni classe estende (extends) una sola altra classe (Object se non specificata);
  • Una interfaccia può estendere (extends) una o più interfacce:
public interface <Int> extends <Int-1>, <Int-2>, ... {
     ...
}
  • La gerarchia di ereditarietà singola delle classi e la gerarchia di ereditarietà multipla delle interfacce sono completamente disgiunte.
    • Una classe può implemetare (implements) una o più interfacce:

    public class <nomeClasse> extends <nomeSuperClasse>
           implements <Int-1>, <Int-2>, ..., <Int-n> {

        ....
    }
     

    • In questo caso <nomeClasse> deve fornire una realizzazione per tutti i metodi delle interfacce <Int-1>, <Int-2>, ... che implementa, nonché per i metodi di eventuali super-interfacce da cui queste ereditano.  
    • Se <nomeClasse> non fornisce un metodo di una interfaccia che implementa, concettualmente questo metodo rimane astratto in <nomeClasse>, e quindi la classe deve essere dichiarata astratta



    Interfacce come tipi

    Come le classi, anche le interfacce definiscono un tipo di dati in Java. In particolare:
    • Possiamo dichiarare una variabile indicando come tipo un'interfaccia.
        
    • Non possiamo istanziare un'interfaccia (come neanche una classe astratta).
        
    • Ad una variabile di tipo interfaccia possiamo assegnare solo istanze di classi che implementano l'interfaccia.
        
    • Su di una variabile di tipo interfaccia possiamo invocare solo metodi dichiarati nell'interfaccia (o nelle sue "super-interfacce").
    ...
    Comparable stuff, stuff1;            // OK
    stuff = new Comparable();              // NO!!
    stuff = new SferaOrdinabile(5, 10);  // OK
    stuff1 = new TempoOrdinabile();      // OK
    System.out.print(stuff.volume());    // NO!!
    System.out.print(stuff.compareTo(stuff1)); 
     //OK, ma produce un'eccezione. Perché? Proviamo.
    ...



    Esempi di uso di interfacce