Cos’è un virtual extension method
Con Java 8 è stata introdotta la possibilità di indicare in una interfaccia una sua implementazione “di default”, quindi evitando la scrittura di classi astratte intermedie. Questo costrutto viene chiamato virtual extension method o defender method. Facciamo un esempio, vogliamo definire tramite interfacce alcuni tipi geometrici.
public interface Geometry { ListgetPoints(); int getSize(); }
A questo punto aggiungiamo le classi Circle e Rectangle implementando i metodi di cui sopra.
public class Rectangle implements Geometry { private final Listpoints; public Rectangle(Point2D ... vertex) { this.points = Arrays.asList(vertex); } @Override public List getPoints() { return this.points; } @Override public int getSize() { return this.getPoints()).size(); } } public class Circle implements Geometry { private final Point2D center; private final double radius; private static final int NUMPOINTS = 100; public Circle(Point2D center, double radius) { this.center = center; this.radius = radius; } @Override public List getPoints() { List ris = new ArrayList<>(); for (int i = 0; i < NUMPOINTS; i++) { double ang = i * (2 * Math.PI / NUMPOINTS); double x = radius * Math.cos(ang) + center.getX(); double y = radius * Math.sin(ang) + center.getY(); ris.add(new Point2D.Double(x, y)); } return ris; } @Override public int getSize() { return this.getPoints().size(); } }
Notiamo che i due metodi getSize
hanno la stessa implementazione. Supponiamo di fare una classe per ogni tipo di poligono regolare: dovremmo ripetere lo stesso codice ogni volta oppure definire una classe astratta intermedia che lo implementi una volta per tutte lasciando astratto solo il metodo getPoints
. Java 8 rende questo compito più veloce:
public interface Geometry { ListgetPoints(); default int getSize() { return this.getPoints().size(); } }
A questo punto si possono rimuovere i metodi getSize
dalle due implementazioni.
E se, invece, si tenta implementare due interfacce con la stessa firma? Ovviamente non si possono ereditare due comportamenti diversi, per cui viene segnalato da Java con un errore di compilazione, come si vede dall’immagine sotto:
Virtual extension method e Lambda Expression
Quanto visto sopra è particolarmente utile quando si aggiunge un metodo ad un’interfaccia. Supponiamo di voler aggiungere un metodo toString
che restituisca la lista dei vertici in JSON. Viene da se che anche questa può essere un’implementazione comune, visto che può dipendere dal solo getPoints
. Con Java 7 saremmo stati costretti a modificare tutte le implementazioni, invece con Java 8 l’impatto di una modifica del genere può essere trascurabile, in quanto l’implementazione di default “difende” il codice da un potenziale problema di compilazione in tutte le classi che implementano quell’interfaccia.
L’esigenza di avere i virtual extension method si è fatta pressante con l’arrivo delle Lambda Expression. Infatti in un abbiamo visto che dipendono dalla definizione di una nuova interfaccia: Consumer
. Proviamo ad immaginare cosa sarebbe successo introducendo il metodo foreach
senza i defender methods.
Per introdurre un costrutto del genere sarebbe stato necessario aggiungere all’interfaccia Iterable
il seguente metodo astratto:
void forEach(Consumer super T> action;
e scriverne l’implementazione in tutte le classi derivate, comprese quelle esterne alle API Java, comportando modifiche per rendere le varie librerie compatibili con Java 8. Invece i virtual extension method risolvono il problema elegantemente.
Ereditarietà multipla?
Si potrebbe pensare che i defenders siano il modo con cui Java 8 implementi l’ereditarietà multipla. In realtà Java contiene un tipo di ereditarietà multipla fin dalle sue origini, ovvero quella che si ottiene con le normali interfacce (ereditarietà dei tipi). I virtual extension method aggiungono un nuovo tipo di ereditarietà multipla a Java: quella di comportamento. Infatti possiamo derivare metodi da “interfacce” diverse, ma non si arriva a definire un concetto simile a quello presente in C++, infatti non c’è ereditarietà di stato.
Ciò è dovuto al fatto che questo costrutto è costruito sopra le classiche interfacce, che, quindi non contengono attributi. Allo stato dell’arte questa è l’unica differenza sostanziale tra classi astratte e virtual extension method. Quindi l’ereditarietà di stato resta confinata alle sole classi in modalità singola.
Conclusioni
L’introduzione dei virtual extension method rappresenta una delle caratteristiche principali di Java 8, forse con un impatto sul linguaggio maggiore alle lambda expression. Infatti cambia il modo in cui si progetta l’ereditarietà nel linguaggio, spingendo in parecchi casi a favore delle interfacce a dispetto delle classi astratte che restano, a mio avviso, necessarie quasi unicamente quando si desidera condividere lo stato della classe stessa.
Pingback: ()
Pingback: ()