Le incertezze circa l'utilità della ereditarietà multipla nei programmi Object Oriented hanno portato alla definizione di una strada alternativa ma sicuramente più efficiente. Infatti, con l'avvento di Java è stato introdotto il concetto di interfaccia.
In generale, un interfaccia rappresenta una sorta di "promessa" che una classe si impegna a mantenere. La promessa è quella di implementare determinati metodi di cui viene resa nota soltanto la definizione (un po' come si è già visto per le classi ed i metodi astratti). Ciò che è importante non è tanto come verranno implementati tali metodi all'interno della classe ma, piuttosto, che la denominazione ed i parametri richiesti siano assolutamente rispettati.
Si supponga, ad esempio, di voler creare un'interfaccia denominata StackInterface che debba essere utilizzata da ogni classe che desideri definire la classica struttura dati a pila (LIFO). Una siffatta interfaccia sarà definita nel seguente modo (sintassi Java):
public interface StackInterface
{
public void push(int i);
public int pop();
}
Come si vede, sono stati definiti due metodi (push e pop), corrispondenti rispettivamente alle due operazioni di inserimento ed eliminazione di elementi dalla pila ma non ne viene in alcun modo fornita l'implementazione.
Supponiamo, ora, di voler definire una classe Stack che contenga il codice necessario per la corretta gestione di una pila. Tale classe sarà definita nel seguente modo:
public class Stack implements StackInterface
{
public void push(int i)
{
... ....
}
public int pop()
{
... ....
}
}
Come si potrà osservare, all'interno della classe Stack sono implementati i metodi che sono stati definiti all'interno dell'interfaccia StackInterface. Poiché la classe Stack promette di fare uso dell'interfaccia StackInterface (lo si può notare dalla istruzione "implements StackInterface"), qualora il programmatore omettesse di definire e implementare uno o entrambi questi metodi, si otterrebbe un errore di compilazione.
Sebbene le interfacce non vengano istanziate, come avviene per le classi, esse conservano determinate caratteristiche che sono simili a quelle viste nelle classi ordinarie. Ad esempio, una volta definita un'interfaccia, è possibile dichiarare un oggetto come se fosse del tipo dichiarato dall'interfaccia stessa utilizzando la medesima notazione utilizzata per la dichiarazione di variabili.
Inoltre, allo stesso modo delle classi, è possibile utilizzare l'ereditarietà anche per le interfacce, ovvero definire una interfaccia che estenda le caratteristiche di un'altra, aggiungendo altri metodi all'interfaccia padre.
Infine, una classe può implementare più di una interfaccia. Ovvero, è possibile obbligare una classe ad implementare tutti i metodi definiti nelle interfacce con le quali essa è legata. Questa ultima caratteristica fornisce, indiscutibilmente, la massima flessibilità nella definizione del comportamento che si desidera attribuire ad una classe.