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.

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

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 {}


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow