Quand l'utiliser: méthode par défaut de l'interface Java 8+, vs méthode abstraite


540

Java 8 permet l'implémentation par défaut des méthodes dans les interfaces appelées méthodes par défaut .

Je suis confus entre quand utiliserais-je ce genre de interface default method, au lieu d'un abstract class(avec abstract method(s)).

Alors, quand utiliser l'interface avec les méthodes par défaut et quand utiliser une classe abstraite (avec des méthodes abstraites)? Les classes abstraites sont-elles toujours utiles dans ce scénario?


38
Peut-être que vous ne pouvez toujours pas avoir de champs, de méthodes privées, etc. dans les interfaces, alors que vous pouvez en classe abstraite?
sp00m

2
Je me posais des questions sur ce sujet auparavant, maintenant je suis clair.Merci à @Narendra Pathai. Je voudrais ajouter le lien d'un autre fil que vous avez demandé concernant le même sujet, car ces deux étaient mes doutes. stackoverflow.com/questions/19998309/…
Ashutosh Ranjan

Vous pouvez trouver un bon article sur celui-ci ici: blog.codefx.org/java/everything-about-default-methods
Fabri Pautasso

Vous pouvez toujours parfois coder une classe de base en tant qu'interface même si la classe de base a un état. C'est juste que l'interface doit définir des setters et des getters pour l'état et que les classes concrètes doivent les implémenter et définir le champ. Une restriction à cela est que dans une classe abstraite, la propriété du bean peut être privée ou protégée. Dans les interfaces ont uniquement des méthodes publiques. Donc, une des raisons pour lesquelles vous utiliseriez une classe de base abstraite est si vos classes ont une propriété qui doit être privée ou protégée.
DaBlick

@DaBlick Pourriez-vous ne pas résoudre le problème d'état dans une interface via un HashMap. Ex: si vous voulez une classe Foo qui contient int a, b, String c. et vous voulez qu'ils aient un état, créez un HashMap </ * nom de l'objet Foo * / String, / * carte des champs * / Hashmap </ * nom spécifique Field * / String, / * valeur du champ * / Object >> map . Lorsque vous voulez "instancier" la classe théorique Foo, vous avez une méthode, instancier (String nameOfFoo) qui fait map.put (nameOfFoo, fields) où fields est un HashMap <String, Object> fields.put ("a", new int ("5")); fields.put ("b", new int ("6")); fields.put ("c", "blah"));
George Xavier

Réponses:


307

Les classes abstraites ont bien plus à offrir que les implémentations de méthodes par défaut (comme l'état privé), mais à partir de Java 8, chaque fois que vous avez le choix, vous devez utiliser la defaultméthode defender (aka. ) Dans l'interface.

La contrainte de la méthode par défaut est qu'elle ne peut être implémentée qu'en termes d'appels à d'autres méthodes d'interface, sans référence à l'état d'une implémentation particulière. Le cas d'utilisation principal est donc les méthodes de niveau supérieur et de commodité.

La bonne chose à propos de cette nouvelle fonctionnalité est que, où auparavant vous étiez obligé d'utiliser une classe abstraite pour les méthodes de commodité, contraignant ainsi l'implémenteur à l'héritage unique, vous pouvez maintenant avoir une conception vraiment propre avec juste l'interface et un minimum d'implémentation effort imposé au programmeur.

La motivation initiale pour introduire des defaultméthodes dans Java 8 était le désir d'étendre les interfaces Collections Framework avec des méthodes orientées lambda sans casser aucune implémentation existante. Bien que cela soit plus pertinent pour les auteurs des bibliothèques publiques, vous pouvez également trouver la même fonctionnalité utile dans votre projet. Vous disposez d'un emplacement centralisé pour ajouter de nouvelles fonctionnalités et vous n'avez pas à vous fier à l'apparence du reste de la hiérarchie de types.


34
Par ce raisonnement, la prochaine chose qu'ils ajouteraient serait des déclarations de méthode par défaut. Je ne suis toujours pas sûr de cela, la fonctionnalité me semble plus être un hack qui est exposé à tout le monde pour une mauvaise utilisation.
panther

3
La seule utilisation des classes abstraites à l'ère Java 8 que je peux voir est pour définir des champs non finaux. Dans les interfaces, les champs sont par défaut définitifs, vous ne pouvez donc pas les modifier une fois qu'ils sont attribués.
Anuroop

7
@Anuroop Pas seulement par défaut --- c'est la seule option. Les interfaces ne peuvent pas déclarer l'état d'instance, c'est pourquoi les classes abstraites sont là pour rester.
Marko Topolnik

2
@PhilipRego Les méthodes abstraites n'appellent rien car elles n'ont aucune implémentation. Les méthodes implémentées dans une classe peuvent accéder à l'état de la classe (variables d'instance). Les interfaces ne peuvent pas les déclarer, les méthodes par défaut ne peuvent donc pas y accéder. Ils doivent compter sur la classe fournissant une méthode implémentée qui accède à l'état.
Marko Topolnik

2
Marko Topolnik, votre réponse est morte. Mais je voudrais recommander une mise à jour de votre réponse. Vous voudrez peut-être ajouter que la beauté des méthodes par défaut est que, si l'interface ajoute de nouvelles méthodes par défaut, votre implémentation précédente de cette interface ne se cassera pas. Ce n'était pas vrai avant Java 8.
hfontanez

125

Il existe quelques différences techniques. Les classes abstraites peuvent encore faire plus par rapport aux interfaces Java 8:

  1. La classe abstraite peut avoir un constructeur.
  2. Les classes abstraites sont plus structurées et peuvent contenir un état.

Sur le plan conceptuel, l'objectif principal des méthodes de défense est une compatibilité descendante après l'introduction de nouvelles fonctionnalités (comme les fonctions lambda) dans Java 8.


20
Cette réponse est en fait correcte et a du sens en particulier "Conceptuellement, le but principal des méthodes de défenseur est une compatibilité descendante"
Trying

1
@UnKnown cette page donne plus d'informations: docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html
bernie

@UnKnown, fondamentalement, il vous permet d'ajouter des méthodes à une interface et les classes qui implémentent cette interface obtiennent automatiquement cette fonctionnalité.
LegendLength

3
Un point plus subtil sur le point no. 2 ci-dessus à propos de "peut tenir l'état est-ce". Les classes abstraites peuvent contenir un état qui peut être modifié ultérieurement. Les interfaces peuvent également contenir l'état mais une fois qu'un état est attribué après la création de l'instance, l'état ne peut pas être modifié.
Anuroop

3
@Anuroop Je ne décrirais pas les public static finalchamps d'une interface comme "état". La staticpartie signifie que vous n'êtes pas du tout lié à une instance particulière. Ils sont attribués lors de l'instanciation de classe , ce qui n'est pas la même chose qu'après la création d'une instance .
geronimo

65

Cela est décrit dans cet article . Pensez aux forEachcollections.

List<?> list = 
list.forEach(…);

Le forEach n'est pas encore déclaré par java.util.Listni l' java.util.Collectioninterface. Une solution évidente serait d'ajouter simplement la nouvelle méthode à l'interface existante et de fournir l'implémentation là où cela est requis dans le JDK. Cependant, une fois publié, il est impossible d'ajouter des méthodes à une interface sans interrompre l'implémentation existante.

L'avantage des méthodes par défaut est qu'il est désormais possible d'ajouter une nouvelle méthode par défaut à l'interface et qu'elle ne casse pas les implémentations.


1
'il est impossible d'ajouter des méthodes à une interface sans casser l'implémentation existante' - est-ce?
Andrey Chaschev

26
@AndreyChaschev Si vous ajoutez une nouvelle méthode à l'interface, tous les implémenteurs doivent implémenter cette nouvelle méthode. Par conséquent, il casse les implémentations existantes.
Marko Topolnik

4
@MarkoTopolnik merci, j'ai raté ça. Juste pour mentionner qu'il existe un moyen d' éviter partiellement cela - en présentant cette méthode dans une implémentation abstraite par défaut. Pour cet exemple, ce serait AbstractList::forEachlancer un UnsupportedOperationException.
Andrey Chaschev

3
@AndreyChaschev Oui, c'était l'ancienne méthode (khm ... est la méthode actuelle :), avec le défaut de limiter l'implémenteur à l'héritage unique de l'implémentation abstraite fournie.
Marko Topolnik

Je ne casserai pas s'il se trouve qu'à l'avance, toutes les implémentations incluaient cette méthode. Ce qui est peu probable mais possible.
George Xavier

19

Ces deux sont assez différents:

Les méthodes par défaut consistent à ajouter des fonctionnalités externes aux classes existantes sans modifier leur état.

Et les classes abstraites sont un type d'héritage normal, ce sont des classes normales qui sont destinées à être étendues.


18

Comme décrit dans cet article,

Classes abstraites et interfaces dans Java 8

Après avoir introduit la méthode par défaut, il semble que les interfaces et les classes abstraites soient identiques. Cependant, ils sont encore différents concept dans Java 8.

La classe abstraite peut définir le constructeur. Ils sont plus structurés et peuvent avoir un état qui leur est associé. En revanche, la méthode par défaut ne peut être implémentée qu'en termes d'invocation d'autres méthodes d'interface, sans référence à l'état d'une implémentation particulière. Par conséquent, les deux utilisations à des fins différentes et le choix entre deux dépendent vraiment du contexte du scénario.


Je crois que la classe Abstract a un constructeur qui peut être défini contrairement à Interface. En Java 8, ils sont également tous les deux différents pour cette raison.
Hemanth Peela

1
pourquoi une classe abstraite a-t-elle un constructeur s'il ne peut pas être instancié?
George Xavier

Nous pouvons appeler super () à partir de la classe enfant qui appellera le constructeur de la classe abstraite, ce qui a un impact sur l'état de la classe abstraite.
Sujay Mohan

14

Concernant votre requête de

Alors, quand utiliser l'interface avec les méthodes par défaut et quand utiliser une classe abstraite? Les classes abstraites sont-elles toujours utiles dans ce scénario?

la documentation java fournit une réponse parfaite.

Classes abstraites comparées aux interfaces:

Les classes abstraites sont similaires aux interfaces. Vous ne pouvez pas les instancier et elles peuvent contenir un mélange de méthodes déclarées avec ou sans implémentation.

Cependant, avec les classes abstraites, vous pouvez déclarer des champs qui ne sont ni statiques ni définitifs, et définir des méthodes concrètes publiques, protégées et privées.

Avec les interfaces, tous les champs sont automatiquement publics, statiques et définitifs, et toutes les méthodes que vous déclarez ou définissez (comme méthodes par défaut) sont publiques. De plus, vous ne pouvez étendre qu'une seule classe, qu'elle soit abstraite ou non, alors que vous pouvez implémenter un nombre illimité d'interfaces.

Les cas d'utilisation pour chacun d'entre eux ont été expliqués dans le post SE ci-dessous:

Quelle est la différence entre une interface et une classe abstraite?

Les classes abstraites sont-elles toujours utiles dans ce scénario?

Oui. Ils sont toujours utiles. Ils peuvent contenir des méthodes et des attributs non statiques et non finaux ( protégés, privés en plus du public ), ce qui n'est pas possible même avec les interfaces Java-8.


Maintenant , les interfaces ont des méthodes privées trop howtodoinjava.com/java9/java9-private-interface-methods
Valijon

13

Chaque fois que nous avons le choix entre la classe abstraite et l'interface, nous devrions toujours (presque) préférer les méthodes par défaut (également appelées défenseur ou extensions virtuelles).

  1. Les méthodes par défaut ont mis fin au modèle d'interface classique et à une classe compagnon qui implémente la plupart ou la totalité des méthodes de cette interface. Un exemple est Collection and AbstractCollection. Maintenant, nous devons implémenter les méthodes dans l'interface elle-même pour fournir des fonctionnalités par défaut. Les classes qui implémentent l'interface ont le choix de remplacer les méthodes ou d'hériter de l'implémentation par défaut.
  2. Une autre utilisation importante des méthodes par défaut est interface evolution. Supposons que j'aie une classe Ball comme:

    public class Ball implements Collection { ... }

Maintenant, dans Java 8, une nouvelle fonctionnalité est introduite. Nous pouvons obtenir un flux en utilisant une streamméthode ajoutée à l'interface. Si ce streamn'était pas une méthode par défaut, toutes les implémentations de l' Collectioninterface se seraient rompues car elles ne mettraient pas en œuvre cette nouvelle méthode. L'ajout d'une méthode non par défaut à une interface ne l'est pas source-compatible.

Mais supposons que nous ne recompilions pas la classe et que nous utilisions un ancien fichier jar qui contient cette classe Ball. La classe se chargera correctement sans cette méthode manquante, des instances peuvent être créées et il semble que tout fonctionne correctement. MAIS si le programme invoque la streamméthode sur l'instance de Ballwe obtiendra AbstractMethodError. Donc, la méthode par défaut a résolu les deux problèmes.


9

Les méthodes par défaut dans l'interface Java permettent l' évolution de l'interface .

Étant donné une interface existante, si vous souhaitez y ajouter une méthode sans rompre la compatibilité binaire avec les anciennes versions de l'interface, vous avez deux options: ajouter une méthode par défaut ou une méthode statique. En effet, toute méthode abstraite ajoutée à l'interface devrait être implémentée par les classes ou interfaces implémentant cette interface.

Une méthode statique est unique à une classe. Une méthode par défaut est unique à une instance de la classe.

Si vous ajoutez une méthode par défaut à une interface existante, les classes et interfaces qui implémentent cette interface n'ont pas besoin de l'implémenter. Ils peuvent

  • implémenter la méthode par défaut, et elle remplace l'implémentation dans l'interface implémentée.
  • re-déclarer la méthode (sans implémentation) qui la rend abstraite.
  • ne faites rien (alors la méthode par défaut de l'interface implémentée est simplement héritée).

Plus d'informations sur le sujet ici .


7

Bien que ce soit une vieille question, permettez-moi également de vous faire part de mes commentaires.

  1. classe abstraite: à l' intérieur de la classe abstraite, nous pouvons déclarer des variables d'instance, qui sont obligatoires pour la classe enfant

    Interface: à l' intérieur de l'interface, toutes les variables sont toujours statiques publiques et nous ne pouvons pas déclarer de variables d'instance

  2. classe abstraite: la classe abstraite peut parler de l'état de l'objet

    Interface: l' interface ne peut jamais parler de l'état de l'objet

  3. classe abstraite: à l' intérieur de la classe abstraite, nous pouvons déclarer des constructeurs

    Interface: à l' intérieur de l'interface, nous ne pouvons pas déclarer les constructeurs
    constructeurs est d'initialiser des variables d'instance. Alors, quel est le besoin de constructeur là-bas si nous ne pouvons pas avoir de variables d'instance dans les interfaces .

  4. classe abstraite: à l' intérieur de la classe abstraite, nous pouvons déclarer des blocs d'instance et statiques

    Interface: interfaces ne peuvent pas avoir d'instance et de blocs statiques.

  5. classe abstraite: abstraite ne peut pas faire référence à l'expression lambda

    Interfaces: interfaces avec une seule méthode abstraite peuvent faire référence à l'expression lambda

  6. classe abstraite : à l'intérieur de la classe abstraite, nous pouvons remplacer les méthodes OBJECT CLASS

    Interfaces: nous ne pouvons pas remplacer les méthodes OBJECT CLASS dans les interfaces.

Je terminerai sur la note que:

Les concepts de méthode par défaut / concepts de méthode statique dans l'interface sont venus juste pour enregistrer les classes d'implémentation mais pas pour fournir une implémentation utile significative. Les méthodes par défaut / méthodes statiques sont une sorte d'implémentation factice, "si vous le souhaitez, vous pouvez les utiliser ou vous pouvez les remplacer (dans le cas des méthodes par défaut) dans la classe d'implémentation", ce qui nous évite d'implémenter de nouvelles méthodes dans les classes d'implémentation chaque fois que de nouvelles méthodes dans les interfaces sont ajoutés. Par conséquent, les interfaces ne peuvent jamais être égales à des classes abstraites.


5

La règle de Remi Forax est Vous ne concevez pas avec des classes abstraites. Vous concevez votre application avec des interfaces . Watever est la version de Java, quel que soit le langage. Il est soutenu par le I principe de ségrégation nterface en SOL I D principes.

Vous pouvez ultérieurement utiliser des classes abstraites pour factoriser le code. Maintenant, avec Java 8, vous pouvez le faire directement dans l'interface. C'est une installation, pas plus.


2

quand utiliser l'interface avec les méthodes par défaut et quand utiliser une classe abstraite?

Compatibilité descendante: imaginez que votre interface est implémentée par des centaines de classes, la modification de cette interface forcera tous les utilisateurs à implémenter la méthode nouvellement ajoutée, même si cela pourrait ne pas être essentiel pour de nombreuses autres classes qui implémentent votre interface, en plus cela permet à votre interface être une interface fonctionnelle

Faits et restrictions:

1-Ne peut être déclaré que dans une interface et non dans une classe ou une classe abstraite.

2-Doit fournir un corps

3-Il n'est pas supposé être abstrait comme les autres méthodes normales utilisées dans une interface.


1

Dans Java 8, une interface ressemble à une classe abstraite, bien qu'il puisse y avoir des différences telles que:

1) Les classes abstraites sont des classes, elles ne sont donc pas limitées à d'autres restrictions de l'interface en Java. Par exemple, la classe abstraite peut avoir l'état , mais vous ne pouvez pas avoir l'état sur l'interface en Java.

2) Une autre différence sémantique entre l'interface avec les méthodes par défaut et la classe abstraite est que vous pouvez définir des constructeurs dans une classe abstraite , mais vous ne pouvez pas définir d'interface constructeur dans Java


Je suis d'accord avec # 2 mais pour # 1, ne pouvez-vous pas simplement implémenter l'interface et ainsi avoir un état via la classe d'implémentation?
George Xavier

0

Les méthodes par défaut dans l'interface Java doivent être utilisées davantage pour fournir une implémentation factice d'une fonction, ce qui évite à toute classe d'implémentation de cette interface de devoir déclarer toutes les méthodes abstraites même si elles ne souhaitent en traiter qu'une seule. Les méthodes par défaut dans l'interface remplacent donc en quelque sorte davantage le concept de classes d'adaptateurs.

Les méthodes de la classe abstraite sont cependant censées donner une implémentation significative que toute classe enfant ne doit remplacer que si elle est nécessaire pour remplacer une fonctionnalité commune.


0

Comme mentionné dans d'autres réponses, la possibilité d'ajouter l'implémentation à une interface a été ajoutée afin de fournir une compatibilité descendante dans le cadre des collections. Je dirais que fournir une compatibilité descendante est potentiellement la seule bonne raison pour ajouter une implémentation à une interface.

Sinon, si vous ajoutez une implémentation à une interface, vous enfreignez la loi fondamentale expliquant pourquoi les interfaces ont été ajoutées en premier lieu. Java est un langage d'héritage unique, contrairement au C ++ qui permet l'héritage multiple. Les interfaces offrent les avantages de frappe qui viennent avec un langage qui prend en charge l'héritage multiple sans introduire les problèmes qui viennent avec l'héritage multiple.

Plus précisément, Java n'autorise que l'héritage unique d'une implémentation, mais il autorise l'héritage multiple d'interfaces. Par exemple, ce qui suit est un code Java valide:

class MyObject extends String implements Runnable, Comparable { ... }

MyObject hérite d'une seule implémentation, mais hérite de trois contrats.

Java a transmis l'héritage multiple de l'implémentation car l'héritage multiple de l'implémentation s'accompagne d'une multitude de problèmes épineux, qui sortent du cadre de cette réponse. Des interfaces ont été ajoutées pour permettre l'héritage multiple des contrats (aka interfaces) sans les problèmes d'héritage multiple d'implémentation.

Pour appuyer mon propos, voici une citation de Ken Arnold et James Gosling du livre The Java Programming Language, 4e édition :

L'héritage unique empêche certaines conceptions utiles et correctes. Les problèmes d'héritage multiple proviennent de l'héritage multiple d'implémentation, mais dans de nombreux cas l'héritage multiple est utilisé pour hériter d'un certain nombre de contrats abstraits et peut-être d'une implémentation concrète. Fournir un moyen d'hériter d'un contrat abstrait sans hériter d'une implémentation permet les avantages de typage de l'héritage multiple sans les problèmes d'héritage d'implémentation multiple. L'héritage d'un contrat abstrait est appelé héritage d'interface . Le langage de programmation Java prend en charge l'héritage d'interface en vous permettant de déclarer un interfacetype


-1

Veuillez d'abord penser au principe ouvert / fermé. Les méthodes par défaut dans les interfaces le VIOLENT. C'est une mauvaise fonctionnalité en Java. Il encourage une mauvaise conception, une mauvaise architecture, une faible qualité logicielle. Je suggérerais d'éviter d'utiliser complètement les méthodes par défaut.

Posez-vous quelques questions: pourquoi ne pouvez-vous pas mettre vos méthodes dans la classe abstraite? Auriez-vous besoin de plus d'une classe abstraite? Réfléchissez ensuite aux responsabilités de votre classe. Êtes-vous sûr que toutes les méthodes que vous allez mettre dans la même classe remplissent vraiment le même objectif? Peut-être que vous distinguerez plusieurs objectifs et diviserez ensuite votre classe en plusieurs classes, pour chaque objectif sa propre classe.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.