Java 8: virtual extension method

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 {

	List<Point2D> getPoints();

	int getSize();
}

A questo punto aggiungiamo le classi Circle e Rectangle implementando i metodi di cui sopra.

public class Rectangle implements Geometry {
	private final List<Point2D> points;

	public Rectangle(Point2D ... vertex) {
    	  this.points = Arrays.asList(vertex);
	}

	@Override
	public List<Point2D> 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<Point2D> getPoints() {
    List<Point2D> 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 {

  List<Point2D> getPoints();

  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:

errore_compilazione2

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 post precedente 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.

Manuele Ventoruzzo

Software Architect Sono laureato in Informatica e lavoro come software architect e senior developer presso Over I.T. Progetto prevalentemente sistemi web based in Java con sistemi proprietari o stack JSF + Hibernate spesso con moduli GIS Open, basati su Geotools/Geoserver e client OpenLayers. Ho conosciuto cosenonjaviste.it tramite il mio collega Giampaolo Trapasso e lo trovo un mezzo molto interessante da usare come riferimento, sia per lavoro che per propria cultura personale e cercare di tenersi al passo con la tecnologia.