Le interfacce sono uno strumento utile ed interessante di Java: esse ci permettono di definire metodi comuni a classi, senza darne un’implementazione e permettono di standardizzare l’ iterazione fra oggetti definendone un comportamento limitato.

Le interfacce sono praticamente classi astratte al cento per cento, dove possiamo definire solo parametri di metodi, e delle costanti.

Nelle interfacce, è vietato dare un’implementazione dei metodi: a quelle dovranno pensarci le classe che implementano (implements) queste interfacce.

Anche se non lo definiamo, ed è infatti facoltativo, tutte le variabili sono automaticamente di tipo public final static mentre le classi sono di tipo public abstract.

Vediamo subito un esempio di interfaccia:

public interface LaMiaPrimaInterfaccia
{
   int TIME= 200;
   void apri();
   void chiudi();
}

Time sarà implicitamente di tipo public static final, mentre i metodi di cui ripetiamo è vietato darne l’implementazione, sono di tipo public abstract.

Implementare un’ interfaccia

Se scegliamo di implementare un’interfaccia, dobbiamo implementare tutti i metodi che possiede e dobbiamo specificare l’implementazione tramite la parola chiave implements NomeInterfaccia:

class LettoreDvd implements LaMiaPrimaInterfaccia
{
    public void apri()
    {
        apriCassetto(lettoreDvd.TIME);
    }
    public void chiudi()
    {
        chiudiCassetto(LettoreDvd.TIME);
    }
}

Oppure:

class Porta implements LaMiaPrimaInterfaccia
{   public void apri()
    {
        giraChiave(lettoreDvd.TIME);
        abbassaManiglia(lettoreDvd.TIME);
    }
    public void chiudi()
    {
        abbassaManiglia(LettoreDvd.TIME);
        giraChiave(LettoreDvd.TIME);
    }
}

Come abbiamo appena visto, anche se non sono in relazione gerarchica (is-a) possiamo definire comportamenti comuni a più oggetti.

Le interfacce iterabili

Alcuni tipi di dato particolare (gli array, le liste, le stringhe) possono essere iterate: per permettere ciò, la classe che li rende iterabili implementa questa interfaccia (ognuno a modo suo):

public interface Iterable
{
    boolean hasNext();
    Object next();
    void reset();
}

Vediamo un’ implementazione dell’ interfaccia per un’array di interi:

class IterableInteger implements Iterable
{
    private Integer[] array;
    private int k = 0;
    public IterableInteger(Integer[] array)
    {
		this.array=array;
	}
	@Override
	public boolean hasNext()
	{
		return k < array.length;
	}
	@Override
	public Object next()
	{
		return array[k++]
	}
	@Override
	public void reset() 
	{
		k=0; 
	}
}

Per usarlo invece:

public class InterfacciaIterabile
{
	public static void main (String[] args)
	{
		Iterable i1 = new IntegerIterable(new Integer[] {1,2,3,4});
		Iterable i2 = new IntegerIterable(new Integer[] {4,3,2,1});
		for (Iterable i: new Iterable[] {i1, i2})
		{
			while(i.hasNext())

				Syste.out.println(i.next());

		}
	}
}

Questo stamperà gli elementi di entrambi gli array, finchè il contatore k (contenuto in IntegerIterable) non avrà raggiunto la fine dell’ array.

Quando implementiamo un’  interfaccie, possiamo scegliere:

  • Di implementarne tutti i metodi;
  • Di implementarne alcuni;
  • Di non implementarne nessuno;

A patto che, negli ultimi due casi, definiamo la classe Abstract.

Come abbiamo detto all’ inizio, le interfaccie sono classi astratte: ma allora perchè’ non scegliamo semplicemente di creare classi astratte?

Con questa domanda, introduciamo il problema del diamante:

Il problema del diamante

In Java non è possibile estendere più di una classe, mentre è possibile implementare quante interfacce vogliamo.

Se avessimo la possibilità di estendere più di una classe, potremmo andare in contro al così chiamato problema del diamante:

Mettiamo che ho una classe Riproduttore musicale. Poi ho due classi, stereo e computer che estendono Riproduttore multimediale.

Infine, ho la classe smartphone che estende sia lettore cd, sia computer.

Avendo in Riproduttore musicale il metodo play();, ereditato ed implementato (in maniera differente, ovviamente) da Computer e Stereo, Smartphone quando estende queste due classi da chi deve ereditare il metodo play?

Nel linguaggio di programmazione C++ è possibile estendere più classi, a patto di definire ogni volta da chi è ereditato ogni metodo: in Java invece no, e come detto poco fa possiamo estendere al massimo una classe ma implementare quante interfacce vogliamo.

Altro sulle interfacce

La classe che implementa un’interfaccia, instaura un rapporto di tipo is-a ed è quindi legata alla possibilità del polimorfismo.

Riprendendo l’esempio visto poco fà:

LaMiaPrimaInterfaccia isa = new LettoreDvd();
LettoreDvd.apri();

In questo modo, come stabiliscono le regole del polimorfismo, abbiamo a disposizione solo i metodi specificati nell’ interfaccia con l’implementazione della sottoclasse LettoreDvd.