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.