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.