Classi astratte

Una classe astratta che implementa un’interfaccia non deve necessariamente implementarne tutti i metodi, ma può delegarne l’implementazione alle sottoclassi impedendo l’istanziamento di oggetti del suo tipo.

Le interfacce diminuiscono leggermente le performance, però migliorano estremamente la generalità (che aiutano l’espandibilità ed evolvibilità del programma), quindi vale la pena di utilizzarle.

È possibile utilizzare le classi astratte anche per classi complete, ma che non ha senso che siano istanziate. Un buon esempio sono le classi utility della libreria standard di Java.

Classe utility della libreria standard di Java

Un esempio è Collections.shuffle(List<?> list) che accetta una lista omogenea di elementi e la mischia. Il tipo degli elementi è volutamente ignorato in quanto non è necessario conoscerlo per mischiarli.

Per l’ordinamento, invece, è necessario conoscere il tipo degli oggetti in quanto bisogna confrontarli tra loro per poterli ordinare. La responsabilità della comparazione è però delegata all’oggetto, che deve aderire all’interfaccia Comparable<T>.

Collections.sort(...) ha, infatti, la seguente signature:

public static <T extends Comparable<? super T>> void sort(List<T> list)

La notazione di generico aggiunge dei vincoli su T, ovvero il tipo degli elementi contenuti nella lista:

  • T extends Comparable<...> significa che T deve estendere - e quindi implementare - l’interfaccia Comparable<...>;
  • Comparable<? super T> significa che tale interfaccia può essere implementata su un antenato di T (o anche T stesso).

Comparable è un altro esempio di interface segregation: serve per specificare che un oggetto ha bisogno della caratteristica di essere comparabile.

Digressione: la classe Collections era l’unico modo per definire dei metodi sulle interfacce (es: dare la possibilità di avere dei metodi sulle collezioni, ovvero liste, mappe, ecc), ma ora si possono utilizzare i metodi di default.