Java Language
Méthodes par défaut
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.
- La méthode de classe abstraite a priorité sur la méthode d' interface par défaut .
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
- La méthode de classe est prioritaire sur la méthode d'interface par défaut
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 {}