Java 8 ha introdotto cambiamenti nell’interfaccia, inclusi metodi statici e metodi di default. Prima di Java 8, potevamo avere solo dichiarazioni di metodi nelle interfacce. Ma a partire da Java 8, possiamo avere metodi di default e metodi statici nelle interfacce.
Interfaccia Java 8
Progettare interfacce è sempre stato un compito difficile perché se vogliamo aggiungere metodi aggiuntivi nelle interfacce, sarà necessario apportare modifiche a tutte le classi che le implementano. Man mano che un’interfaccia invecchia, il numero di classi che la implementano potrebbe crescere a tal punto che non è possibile estendere le interfacce. Ecco perché, durante la progettazione di un’applicazione, la maggior parte dei framework fornisce una classe di implementazione di base e poi la estendiamo e sovrascriviamo i metodi applicabili per la nostra applicazione. Esaminiamo i metodi di default delle interfacce e i metodi statici delle interfacce, e la ragione della loro introduzione nei cambiamenti dell’interfaccia Java 8.
Metodo di Default dell’Interfaccia Java
Per creare un metodo predefinito nell’interfaccia Java, è necessario utilizzare la parola chiave “default” con la firma del metodo. Ad esempio,
package com.journaldev.java8.defaultmethod;
public interface Interface1 {
void method1(String str);
default void log(String str){
System.out.println("I1 logging::"+str);
}
}
Nota che log(String str) è il metodo predefinito nell’interfaccia Interface1
. Ora, quando una classe implementerà l’interfaccia Interface1, non sarà obbligatorio fornire un’implementazione per i metodi predefiniti dell’interfaccia. Questa caratteristica ci aiuterà nell’estendere le interfacce con metodi aggiuntivi; tutto ciò che serve è fornire un’implementazione predefinita. Supponiamo di avere un’altra interfaccia con i seguenti metodi:
package com.journaldev.java8.defaultmethod;
public interface Interface2 {
void method2();
default void log(String str){
System.out.println("I2 logging::"+str);
}
}
Sappiamo che Java non ci permette di estendere più classi perché ciò comporterebbe il “Problema del Diamante”, in cui il compilatore non può decidere quale metodo della superclasse utilizzare. Con i metodi predefiniti, il problema del diamante sorgerebbe anche per le interfacce. Poiché se una classe sta implementando sia Interface1
che Interface2
e non implementa il metodo predefinito comune, il compilatore non può decidere quale scegliere. L’estensione di interfacce multiple è parte integrante di Java, la troverai nelle classi di base di Java così come nella maggior parte delle applicazioni enterprise e dei framework. Quindi, per assicurarci che questo problema non si verifichi nelle interfacce, è obbligatorio fornire un’implementazione per i metodi predefiniti comuni delle interfacce. Quindi, se una classe sta implementando entrambe le interfacce sopra, dovrà fornire un’implementazione per il metodo log()
, altrimenti il compilatore genererà un errore di compilazione. Una semplice classe che implementa sia Interface1
che Interface2
sarà:
package com.journaldev.java8.defaultmethod;
public class MyClass implements Interface1, Interface2 {
@Override
public void method2() {
}
@Override
public void method1(String str) {
}
@Override
public void log(String str){
System.out.println("MyClass logging::"+str);
Interface1.print("abc");
}
}
Punti importanti sui metodi predefiniti delle interfacce Java:
- I metodi predefiniti dell’interfaccia Java ci aiuteranno ad estendere le interfacce senza il timore di rompere le classi di implementazione.
- I metodi predefiniti dell’interfaccia Java hanno abbattuto le differenze tra le interfacce e le classi astratte.
- I metodi predefiniti dell’interfaccia Java 8 ci aiuteranno ad evitare classi di utilità, poiché tutti i metodi delle classi di Collections possono essere forniti direttamente nelle interfacce stesse.
- I metodi predefiniti dell’interfaccia Java ci aiuteranno a rimuovere le classi di implementazione di base, possiamo fornire un’implementazione predefinita e le classi di implementazione possono scegliere quale sovrascrivere.
- Una delle principali ragioni per introdurre i metodi predefiniti nelle interfacce è migliorare l’API delle Collections in Java 8 per supportare le espressioni lambda.
- Se una classe nella gerarchia ha un metodo con la stessa firma, allora i metodi predefiniti diventano irrilevanti. Un metodo predefinito non può sovrascrivere un metodo da
java.lang.Object
. Il ragionamento è molto semplice, è perché Object è la classe di base per tutte le classi Java. Quindi, anche se abbiamo metodi di classe Object definiti come metodi predefiniti nelle interfacce, saranno inutili perché il metodo di classe Object verrà sempre utilizzato. Ecco perché, per evitare confusione, non possiamo avere metodi predefiniti che sovrascrivono i metodi di classe Object. - I metodi predefiniti dell’interfaccia Java sono anche chiamati metodi Defender o metodi di estensione virtuale.
Metodo Statico dell’Interfaccia Java
Il metodo statico dell’interfaccia Java è simile al metodo predefinito tranne che non possiamo sovrascriverli nelle classi di implementazione. Questa caratteristica ci aiuta a evitare risultati indesiderati in caso di implementazione non adeguata nelle classi di implementazione. Vediamo questo con un semplice esempio.
package com.journaldev.java8.staticmethod;
public interface MyData {
default void print(String str) {
if (!isNull(str))
System.out.println("MyData Print::" + str);
}
static boolean isNull(String str) {
System.out.println("Interface Null Check");
return str == null ? true : "".equals(str) ? true : false;
}
}
Ora vediamo una classe di implementazione che ha il metodo isNull() con un’implementazione non adeguata.
package com.journaldev.java8.staticmethod;
public class MyDataImpl implements MyData {
public boolean isNull(String str) {
System.out.println("Impl Null Check");
return str == null ? true : false;
}
public static void main(String args[]){
MyDataImpl obj = new MyDataImpl();
obj.print("");
obj.isNull("abc");
}
}
Nota che isNull(String str)
è un metodo di classe semplice, non sta sovrascrivendo il metodo dell’interfaccia. Ad esempio, se aggiungiamo l’annotazione @Override al metodo isNull(), otterremo un errore del compilatore. Ora quando eseguiamo l’applicazione, otteniamo il seguente output.
Interface Null Check
Impl Null Check
Se rendiamo il metodo dell’interfaccia da statico a predefinito, otterremo il seguente output.
Impl Null Check
MyData Print::
Impl Null Check
Il metodo statico dell’interfaccia Java è visibile solo ai metodi dell’interfaccia, se rimuoviamo il metodo isNull() dalla classe MyDataImpl
, non saremo in grado di usarlo per l’oggetto MyDataImpl
. Tuttavia, come altri metodi statici, possiamo utilizzare i metodi statici dell’interfaccia utilizzando il nome della classe. Ad esempio, una dichiarazione valida sarà:
boolean result = MyData.isNull("abc");
Punti importanti sul metodo statico dell’interfaccia Java:
- Il metodo statico dell’interfaccia Java fa parte dell’interfaccia, non possiamo utilizzarlo per gli oggetti delle classi di implementazione.
- I metodi statici dell’interfaccia Java sono utili per fornire metodi di utilità, ad esempio il controllo di nullità, l’ordinamento delle collezioni ecc.
- Interfaccia statica di Java aiuta nella fornitura di sicurezza impedendo alle classi di implementazione di sovrascriverle.
- Non possiamo definire il metodo statico dell’interfaccia per i metodi della classe Object, altrimenti otterremmo un errore del compilatore come “Questo metodo statico non può nascondere il metodo di istanza di Object”. Questo perché non è consentito in Java, poiché Object è la classe di base per tutte le classi e non possiamo avere un metodo statico di livello di classe e un altro metodo di istanza con la stessa firma.
- Possiamo utilizzare i metodi statici dell’interfaccia di Java per rimuovere classi di utilità come Collections e spostare tutti i suoi metodi statici nell’interfaccia corrispondente, rendendoli facili da trovare e utilizzare.
Interfacce funzionali di Java
Prima di concludere il post, vorrei fornire una breve introduzione alle interfacce funzionali. Un’interfaccia con esattamente un metodo astratto è conosciuta come Interfaccia Funzionale. È stata introdotta una nuova annotazione @FunctionalInterface per contrassegnare un’interfaccia come Interfaccia Funzionale. L’annotazione @FunctionalInterface è un’opzione per evitare l’aggiunta accidentale di metodi astratti nelle interfacce funzionali. È facoltativa ma è una buona pratica utilizzarla. Le interfacce funzionali sono una caratteristica molto attesa e ricercata di Java 8 perché ci consente di utilizzare espressioni lambda per istanziarle. È stato aggiunto un nuovo pacchetto java.util.function
con un insieme di interfacce funzionali per fornire tipi di destinazione per le espressioni lambda e i riferimenti ai metodi. Esamineremo le interfacce funzionali e le espressioni lambda nei futuri post.