Java Language
Metodi predefiniti
Ricerca…
introduzione
Il metodo predefinito introdotto in Java 8 consente agli sviluppatori di aggiungere nuovi metodi a un'interfaccia senza rompere le implementazioni esistenti di questa interfaccia. Fornisce flessibilità per consentire all'interfaccia di definire un'implementazione che verrà utilizzata come impostazione predefinita quando una classe che implementa tale interfaccia non riesce a fornire un'implementazione di tale metodo.
Sintassi
- public default void methodName () {/ * metodo body * /}
Osservazioni
Metodi predefiniti
- Può essere utilizzato all'interno di un'interfaccia per introdurre un comportamento senza forzare le sottoclassi esistenti per implementarlo.
- Può essere sovrascritto da sottoclassi o da una sotto-interfaccia.
- Non è consentito sovrascrivere i metodi nella classe java.lang.Object.
- Se una classe implementa più di un'interfaccia, eredita metodi predefiniti con firme di metodo identiche da ciascuno degli intefacenti, quindi deve eseguire l'override e fornire la propria interfaccia come se non fossero metodi predefiniti (come parte della risoluzione dell'ereditarietà multipla).
- Sebbene siano intesi a introdurre un comportamento senza rompere le implementazioni esistenti, le sottoclassi esistenti con un metodo statico con la stessa firma del metodo del metodo predefinito introdotto di recente verranno comunque interrotte. Tuttavia questo è vero anche in caso di introduzione di un metodo di istanza in una superclasse.
Metodi statici
- Può essere utilizzato all'interno di un'interfaccia, destinata principalmente a essere utilizzata come metodo di utilità per i metodi predefiniti.
- Non può essere sovrascritto da sottoclassi o da una sotto-interfaccia (è nascosta a loro). Tuttavia, come nel caso dei metodi statici, anche ora ogni classe o interfaccia può avere il suo.
- Non è consentito sovrascrivere i metodi di istanza nella classe java.lang.Object (come nel caso attuale delle sottoclassi).
Di seguito una tabella che riassume l'interazione tra sottoclasse e super-classe.
- | SUPER_CLASS-GRADO-METODO | SUPER_CLASS-STATIC-METODO |
---|---|---|
SUB_CLASS-GRADO-METODO | le sostituzioni | genera-compiletime-errore |
SUB_CLASS-STATIC-METODO | genera-compiletime-errore | nasconde |
Di seguito è riportata una tabella che riassume l'interazione tra interfaccia e classe di implementazione.
- | INTERFACCIA-default-METODO | INTERFACCIA-STATIC-METODO |
---|---|---|
IMPL_CLASS-GRADO-METODO | le sostituzioni | nasconde |
IMPL_CLASS-STATIC-METODO | genera-compiletime-errore | nasconde |
Riferimenti :
- http://www.journaldev.com/2752/java-8-interface-changes-static-method-default-method
- https://docs.oracle.com/javase/tutorial/java/IandI/override.html
Utilizzo di base dei metodi predefiniti
/**
* Interface with default method
*/
public interface Printable {
default void printString() {
System.out.println( "default implementation" );
}
}
/**
* Class which falls back to default implementation of {@link #printString()}
*/
public class WithDefault
implements Printable
{
}
/**
* Custom implementation of {@link #printString()}
*/
public class OverrideDefault
implements Printable {
@Override
public void printString() {
System.out.println( "overridden implementation" );
}
}
Le seguenti dichiarazioni
new WithDefault().printString();
new OverrideDefault().printString();
Produrrà questa uscita:
default implementation
overridden implementation
Accesso ad altri metodi di interfaccia nel metodo predefinito
Puoi anche accedere ad altri metodi di interfaccia dal tuo metodo predefinito.
public interface Summable {
int getA();
int getB();
default int calculateSum() {
return getA() + getB();
}
}
public class Sum implements Summable {
@Override
public int getA() {
return 1;
}
@Override
public int getB() {
return 2;
}
}
La seguente dichiarazione stamperà 3 :
System.out.println(new Sum().calculateSum());
I metodi predefiniti potrebbero essere utilizzati insieme ai metodi statici di interfaccia:
public interface Summable {
static int getA() {
return 1;
}
static int getB() {
return 2;
}
default int calculateSum() {
return getA() + getB();
}
}
public class Sum implements Summable {}
La seguente dichiarazione stamperà anche 3:
System.out.println(new Sum().calculateSum());
Accesso ai metodi predefiniti sottoposti a override dalla classe di implementazione
Nelle classi, super.foo()
apparirà solo nelle superclassi. Se si desidera chiamare un'implementazione predefinita da una superinterfaccia, è necessario qualificarsi super
con il nome dell'interfaccia: Fooable.super.foo()
.
public interface Fooable {
default int foo() {return 3;}
}
public class A extends Object implements Fooable {
@Override
public int foo() {
//return super.foo() + 1; //error: no method foo() in java.lang.Object
return Fooable.super.foo() + 1; //okay, returns 4
}
}
Perché utilizzare i metodi predefiniti?
La semplice risposta è che ti consente di evolvere un'interfaccia esistente senza rompere le implementazioni esistenti.
Ad esempio, hai un'interfaccia Swim
che hai pubblicato 20 anni fa.
public interface Swim {
void backStroke();
}
Abbiamo fatto un ottimo lavoro, la nostra interfaccia è molto popolare, ci sono molte implementazioni in tutto il mondo e non hai il controllo sul loro codice sorgente.
public class FooSwimmer implements Swim {
public void backStroke() {
System.out.println("Do backstroke");
}
}
Dopo 20 anni, hai deciso di aggiungere nuove funzionalità all'interfaccia, ma sembra che la nostra interfaccia sia bloccata perché interromperà le implementazioni esistenti.
Fortunatamente Java 8 introduce una nuova funzione chiamata metodo Default.
Ora possiamo aggiungere un nuovo metodo all'interfaccia Swim
.
public interface Swim {
void backStroke();
default void sideStroke() {
System.out.println("Default sidestroke implementation. Can be overridden");
}
}
Ora tutte le implementazioni esistenti della nostra interfaccia possono ancora funzionare. Ma, soprattutto, possono implementare il metodo appena aggiunto nel loro tempo.
Uno dei principali motivi di questo cambiamento, e uno dei suoi maggiori usi, è nel framework delle collezioni Java. Oracle non ha potuto aggiungere un metodo foreach
all'interfaccia Iterable esistente senza rompere tutto il codice esistente che ha implementato Iterable. Aggiungendo metodi predefiniti, l'implementazione Iterable esistente erediterà l'implementazione predefinita.
Classe, classe astratta e precedenza del metodo di interfaccia
Le implementazioni in classi, incluse le dichiarazioni astratte, hanno la precedenza su tutte le impostazioni predefinite dell'interfaccia.
- Il metodo astratto della classe ha la precedenza sul metodo di default dell'interfaccia .
public interface Swim {
default void backStroke() {
System.out.println("Swim.backStroke");
}
}
public abstract class AbstractSwimmer implements Swim {
public void backStroke() {
System.out.println("AbstractSwimmer.backStroke");
}
}
public class FooSwimmer extends AbstractSwimmer {
}
La seguente dichiarazione
new FooSwimmer().backStroke();
Produrrà
AbstractSwimmer.backStroke
- Il metodo Class ha la precedenza su Interface Default Method
public interface Swim {
default void backStroke() {
System.out.println("Swim.backStroke");
}
}
public abstract class AbstractSwimmer implements Swim {
}
public class FooSwimmer extends AbstractSwimmer {
public void backStroke() {
System.out.println("FooSwimmer.backStroke");
}
}
La seguente dichiarazione
new FooSwimmer().backStroke();
Produrrà
FooSwimmer.backStroke
Metodo predefinito collisione di eredità multipla
Considera il prossimo esempio:
public interface A {
default void foo() { System.out.println("A.foo"); }
}
public interface B {
default void foo() { System.out.println("B.foo"); }
}
Qui ci sono due interfacce che dichiarano default
metodo di default
foo
con la stessa firma.
Se tenterai di extend
queste due interfacce nella nuova interfaccia dovrai scegliere tra due, perché Java ti obbliga a risolvere esplicitamente questa collisione.
Innanzitutto , è possibile dichiarare il metodo foo
con la stessa firma abstract
, che sovrascriverà il comportamento A
e B
public interface ABExtendsAbstract extends A, B {
@Override
void foo();
}
E quando implement
ABExtendsAbstract
nella class
dovrai fornire l'implementazione di foo
:
public class ABExtendsAbstractImpl implements ABExtendsAbstract {
@Override
public void foo() { System.out.println("ABImpl.foo"); }
}
In secondo luogo , è possibile fornire un'implementazione default
completamente nuova. È anche possibile riutilizzare il codice dei metodi A
e B
foo
accedendo ai metodi predefiniti sovrascritti dalla classe di implementazione .
public interface ABExtends extends A, B {
@Override
default void foo() { System.out.println("ABExtends.foo"); }
}
E quando implement
ABExtends
nella class
, not
dovrai fornire un'implementazione foo
:
public class ABExtendsImpl implements ABExtends {}