Recherche…


Introduction

La méthode par défaut introduite dans Java 8 permet aux développeurs d'ajouter de nouvelles méthodes à une interface sans casser les implémentations existantes de cette interface. Il offre une certaine souplesse pour permettre à l'interface de définir une implémentation qui sera utilisée par défaut lorsqu'une classe qui implémente cette interface ne parvient pas à fournir une implémentation de cette méthode.

Syntaxe

  • public par défaut void methodName () {/ * method body * /}

Remarques

Méthodes par défaut

  • Peut être utilisé dans une interface pour introduire un comportement sans forcer les sous-classes existantes à l'implémenter.
  • Peut être remplacé par des sous-classes ou par une sous-interface.
  • Ne sont pas autorisés à remplacer les méthodes dans la classe java.lang.Object.
  • Si une classe implémentant plusieurs interfaces hérite des méthodes par défaut avec des signatures de méthode identiques pour chacune des interfaces, elle doit alors remplacer et fournir sa propre interface comme si elles n'étaient pas des méthodes par défaut (dans le cadre de la résolution de l'héritage multiple).
  • Bien que soient destinés à introduire un comportement sans casser les implémentations existantes, les sous-classes existantes avec une méthode statique avec la même signature de méthode que la méthode par défaut nouvellement introduite seront toujours interrompues. Cependant, cela est vrai même en cas d'introduction d'une méthode d'instance dans une super-classe.



Méthodes statiques

  • Peut être utilisé dans une interface, principalement destinée à être utilisée comme méthode utilitaire pour les méthodes par défaut.
  • Ne peut pas être remplacé par des sous-classes ou par une sous-interface (qui leur est masquée). Cependant, comme c'est déjà le cas avec les méthodes statiques, chaque classe ou interface peut avoir son propre nom.
  • Ne sont pas autorisés à remplacer les méthodes d'instance dans la classe java.lang.Object (comme c'est également le cas actuellement pour les sous-classes).



Vous trouverez ci-dessous un tableau résumant l'interaction entre la sous-classe et la super-classe.

- SUPER_CLASS-INSTANCE-METHOD MÉTHODE SUPER_CLASS-STATIC
SUB_CLASS-INSTANCE-METHOD annule génère-compiletime-error
SUB_CLASS-STATIC-METHOD génère-compiletime-error se cache



Vous trouverez ci-dessous un tableau résumant l'interaction entre l'interface et la classe d'implémentation.

- METHODE INTERFACE-DEFAULT METHODE INTERFACE STATIQUE
IMPL_CLASS-INSTANCE-METHOD annule se cache
IMPL_CLASS-STATIC-METHOD génère-compiletime-error se cache

Les références :

  • http://www.journaldev.com/2752/java-8-interface-changes-static-method-default-method
  • https://docs.oracle.com/javase/tutorial/java/IandI/override.html

Utilisation basique des méthodes par défaut

/**
 * 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" );
    }
}

Les déclarations suivantes

    new WithDefault().printString();
    new OverrideDefault().printString();

Produira cette sortie:

default implementation
overridden implementation

Accéder à d'autres méthodes d'interface avec la méthode par défaut

Vous pouvez également accéder à d'autres méthodes d'interface à partir de votre méthode par défaut.

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 déclaration suivante imprimera 3 :

System.out.println(new Sum().calculateSum());

Les méthodes par défaut peuvent également être utilisées avec les méthodes statiques d’interface:

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 déclaration suivante imprimera également 3:

System.out.println(new Sum().calculateSum());

Accès aux méthodes par défaut remplacées à partir de la classe d'implémentation

Dans les classes, super.foo() recherchera uniquement les superclasses. Si vous souhaitez appeler une implémentation par défaut à partir d'une superinterface, vous devez vous qualifier super avec le nom de l'interface: 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
    }
}

Pourquoi utiliser les méthodes par défaut?

La réponse simple est que cela vous permet de faire évoluer une interface existante sans casser les implémentations existantes.

Par exemple, vous avez l'interface Swim que vous avez publiée il y a 20 ans.

public interface Swim {
    void backStroke();
}

Nous avons fait un excellent travail, notre interface est très populaire, il y a beaucoup d'implémentations à ce sujet partout dans le monde et vous n'avez pas le contrôle de leur code source.

public class FooSwimmer implements Swim {
    public void backStroke() {
         System.out.println("Do backstroke");
    }
}

Après 20 ans, vous avez décidé d'ajouter de nouvelles fonctionnalités à l'interface, mais il semble que notre interface soit gelée car elle brisera les implémentations existantes.

Heureusement, Java 8 introduit une toute nouvelle fonctionnalité appelée méthode par défaut.

Nous pouvons maintenant ajouter une nouvelle méthode à l'interface Swim .

public interface Swim {
    void backStroke();
    default void sideStroke() {
        System.out.println("Default sidestroke implementation. Can be overridden");
    }
}

Maintenant, toutes les implémentations existantes de notre interface peuvent encore fonctionner. Mais surtout, ils peuvent mettre en œuvre la méthode nouvellement ajoutée à leur propre rythme.

L'une des principales raisons de ce changement, et l'une de ses utilisations les plus importantes, réside dans le cadre des collections Java. Oracle n'a pas pu ajouter une méthode foreach à l'interface Iterable existante sans casser tout le code existant qui implémentait Iterable. En ajoutant des méthodes par défaut, l'implémentation Iterable existante héritera de l'implémentation par défaut.

Classe, classe abstraite et préséance de la méthode d'interface

Les implémentations dans les classes, y compris les déclarations abstraites, ont priorité sur toutes les valeurs par défaut de l'interface.

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 déclaration suivante

new FooSwimmer().backStroke();

Produira

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 déclaration suivante

new FooSwimmer().backStroke();

Produira

FooSwimmer.backStroke

Méthode par défaut collision par héritage multiple

Prenons l'exemple suivant:

public interface A {
    default void foo() { System.out.println("A.foo"); }
}

public interface B {
    default void foo() { System.out.println("B.foo"); }
}

Voici deux interfaces déclarant la méthode default foo avec la même signature.

Si vous essayez d' extend ces deux interfaces dans la nouvelle interface, vous devez en choisir deux, car Java vous oblige à résoudre cette collision explicitement.

Tout d'abord , vous pouvez déclarer la méthode foo avec la même signature que abstract , ce qui remplacera B comportements A et B

public interface ABExtendsAbstract extends A, B {
    @Override
    void foo();
}

Et lorsque vous allez implement ABExtendsAbstract dans la class vous devrez fournir une implémentation foo :

public class ABExtendsAbstractImpl implements ABExtendsAbstract {
    @Override
    public void foo() { System.out.println("ABImpl.foo"); }
}

Ou deuxièmement , vous pouvez fournir une implémentation default complètement nouvelle. Vous pouvez également réutiliser le code des méthodes foo A et B en accédant aux méthodes par défaut substituées à partir de la classe d'implémentation .

public interface ABExtends extends A, B {
    @Override
    default void foo() { System.out.println("ABExtends.foo"); }
}

Et lorsque vous implement ABExtends dans la class vous not devrez not fournir d'implémentation foo :

public class ABExtendsImpl implements ABExtends {}


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow