Méthodes facultatives dans l'interface Java

120

D'après ce que je comprends, si vous implémentez une interface en java, les méthodes spécifiées dans cette interface doivent être utilisées par les sous-classes implémentant ladite interface.

J'ai remarqué que dans certaines interfaces telles que l'interface Collection, il existe des méthodes qui sont commentées comme facultatives, mais qu'est-ce que cela signifie exactement? Cela m'a un peu gêné car je pensais que toutes les méthodes spécifiées dans l'interface seraient nécessaires?

mjsey
la source
De quelles méthodes parlez-vous? Je ne peux pas le trouver dans le JavaDoc ou le code source
dcpomero
duplication possible de Que signifie "opération optionnelle" dans Javadoc de par exemple Set # add (E)?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Réponses:

234

Il semble y avoir énormément de confusion dans les réponses ici.

Le langage Java exige que chaque méthode d'une interface soit implémentée par chaque implémentation de cette interface. Période. Il n'y a pas d'exceptions à cette règle. Dire "Les collections sont une exception" suggère une compréhension très floue de ce qui se passe réellement ici.

Il est important de réaliser qu'il existe en quelque sorte deux niveaux de conformité à une interface:

  1. Ce que le langage Java peut vérifier. Cela se résume à peu près à: y a-t-il une implémentation pour chacune des méthodes?

  2. Remplir réellement le contrat. Autrement dit, l'implémentation fait-elle ce que la documentation de l'interface dit qu'elle devrait?

    Des interfaces bien écrites comprendront une documentation expliquant exactement ce qui est attendu des implémentations. Votre compilateur ne peut pas vérifier cela pour vous. Vous devez lire les documents et faire ce qu'ils disent. Si vous ne faites pas ce que dit le contrat, vous aurez une implémentation de l'interface en ce qui concerne le compilateur , mais ce sera une implémentation défectueuse / invalide.

Lors de la conception de l'API Collections, Joshua Bloch a décidé qu'au lieu d'avoir des interfaces très fines pour distinguer les différentes variantes de collections (par exemple: lisibles, inscriptibles, à accès aléatoire, etc.), il n'aurait qu'un ensemble d'interfaces très grossier, principalement Collection, List, Setet Map, puis documenter certaines opérations comme « facultatif ». C'était pour éviter l'explosion combinatoire qui résulterait des interfaces à grain fin. À partir de la FAQ de conception d'API de collections Java :

Pour illustrer le problème en détail sanglant, supposons que vous vouliez ajouter la notion de modifiabilité à la hiérarchie. Vous avez besoin de quatre nouvelles interfaces: ModifiableCollection, ModifiableSet, ModifiableList et ModifiableMap. Ce qui était auparavant une simple hiérarchie est maintenant une hétérarchie désordonnée. En outre, vous avez besoin d'une nouvelle interface Iterator à utiliser avec des collections non modifiables, qui ne contient pas l'opération de suppression. Maintenant, pouvez-vous vous débarrasser de UnsupportedOperationException? Malheureusement non.

Considérez les tableaux. Ils implémentent la plupart des opérations de liste, mais pas de suppression ni d'ajout. Ce sont des listes de «taille fixe». Si vous souhaitez capturer cette notion dans la hiérarchie, vous devez ajouter deux nouvelles interfaces: VariableSizeList et VariableSizeMap. Vous n'avez pas besoin d'ajouter VariableSizeCollection et VariableSizeSet, car ils seraient identiques à ModifiableCollection et ModifiableSet, mais vous pouvez choisir de les ajouter quand même par souci de cohérence. En outre, vous avez besoin d'une nouvelle variété de ListIterator qui ne prend pas en charge les opérations d'ajout et de suppression, pour accompagner List non modifiable. Nous en sommes maintenant à dix ou douze interfaces, plus deux nouvelles interfaces Iterator, au lieu des quatre d'origine. Avons-nous fini? Non.

Tenez compte des journaux (tels que les journaux d'erreurs, les journaux d'audit et les journaux pour les objets de données récupérables). Ce sont des séquences naturelles d'ajout uniquement, qui prennent en charge toutes les opérations de liste, à l'exception de remove et set (replace). Ils nécessitent une nouvelle interface de base et un nouvel itérateur.

Et qu'en est-il des collections immuables, par opposition aux collections non modifiables? (c'est-à-dire, les collections qui ne peuvent pas être modifiées par le client ET ne changeront jamais pour aucune autre raison). Beaucoup soutiennent que c'est la distinction la plus importante de toutes, car elle permet à plusieurs threads d'accéder simultanément à une collection sans nécessiter de synchronisation. L'ajout de cette prise en charge à la hiérarchie des types nécessite quatre interfaces supplémentaires.

Nous en sommes maintenant à une vingtaine d'interfaces et à cinq itérateurs, et il est presque certain qu'il existe encore des collections en pratique qui ne s'intègrent proprement à aucune des interfaces. Par exemple, les vues de collection renvoyées par Map sont des collections naturelles à suppression uniquement. De plus, il existe des collections qui rejetteront certains éléments en fonction de leur valeur, nous n'avons donc toujours pas supprimé les exceptions d'exécution.

En fin de compte, nous avons estimé que c'était un bon compromis d'ingénierie pour contourner le problème en fournissant un très petit ensemble d'interfaces de base pouvant lever une exception d'exécution.

Lorsque les méthodes de l'API Collections sont documentées comme étant des "opérations facultatives", cela ne signifie pas que vous pouvez simplement laisser l'implémentation de la méthode dans l'implémentation, ni que vous pouvez utiliser un corps de méthode vide (d'une part, beaucoup de ils doivent renvoyer un résultat). Cela signifie plutôt qu'un choix d'implémentation valide (qui est toujours conforme au contrat) consiste à lancer un UnsupportedOperationException.

Notez que parce que UnsupportedOperationExceptionc'est un, RuntimeExceptionvous pouvez le lancer à partir de n'importe quelle implémentation de méthode, en ce qui concerne le compilateur. Par exemple, vous pouvez le lancer à partir d'une implémentation de Collection.size(). Cependant, une telle mise en œuvre violerait le contrat car la documentation Collection.size()ne dit pas que cela est autorisé.

A part: L'approche utilisée par l'API Collections de Java est quelque peu controversée (probablement moins maintenant que lorsqu'elle a été introduite pour la première fois). Dans un monde parfait, les interfaces n'auraient pas d'opérations optionnelles, et des interfaces à grain fin seraient à la place utilisées. Le problème est que Java ne prend en charge ni les types structurels inférés ni les types d'intersection, c'est pourquoi tenter de faire les choses de la «bonne manière» finit par devenir extrêmement compliqué dans le cas des collections.

Laurence Gonsalves
la source
31
+1 pour There are no exceptions to this rule. Vous vous demandez pourquoi cette réponse n'est pas marquée comme acceptée. D'autres sont bons mais vous avez donné plus qu'assez.
xyz
9
"Le langage Java exige que chaque méthode d'une interface soit implémentée par chaque implémentation de cette interface. Point final. Il n'y a pas d'exceptions à cette règle." Sauf ... quand il y en a. :-) Les interfaces Java 8 peuvent spécifier une implémentation de méthode par défaut, Ainsi, dans Java 8 ... il n'est PAS vrai que chaque méthode d'une interface doit être IMPLÉMENTÉE PAR chaque implémentation de l'interface, du moins pas dans le sens où vous devez codez l'implémentation dans la classe conrete.
DaBlick
1
@DaBlick Quand j'ai dit "est implémenté par chaque implémentation", je ne voulais pas dire que ladite implémentation de méthode doit résider dans la source de la classe d'implémentation. Même avant Java 8, on peut hériter d'une implémentation d'une méthode d'interface, même d'une classe qui n'implémente pas ladite interface. par exemple: créer Fooqui n'implémente pas Runnableavec la méthode publique void run(). Maintenant, créez une classe Barqui extends Fooet implements Runnablesans surcharger run. Il implémente toujours la méthode, bien qu'indirectement. De même, une implémentation de méthode par défaut est toujours une implémentation.
Laurence Gonsalves
Toutes mes excuses. Je n'essayais pas tant d'être critique de manière pédante que d'attirer l'attention sur une fonctionnalité Java 8 qui pourrait être pertinente pour le message original. Dans Java 8, vous avez maintenant la possibilité d'avoir des implémentations qui ne sont codées dans AUCUNE super-classe ni sous-classe. Cela (IMHO) ouvre un nouveau monde de modèles de conception, y compris certains qui peuvent être appropriés dans les cas où l'interdiction de l'héritage multiple pourrait avoir présenté des défis. Je pense que cela engendrera un nouvel ensemble de modèles de conception très utiles. \
DaBlick
3
@AndrewS parce que Java 8 removea reçu une implémentation par défaut. Si vous ne l'implémentez pas, votre classe obtient l'implémentation par défaut. Les deux autres méthodes que vous mentionnez n'ont pas d'implémentations par défaut.
Laurence Gonsalves
27

Afin de compiler une classe d'implémentation (non abstraite) pour une interface, toutes les méthodes doivent être implémentées.

Cependant , si nous pensons à une méthode dont l'implémentation est une simple exception lancée comme «non implémentée» (comme certaines méthodes de l' Collectioninterface), alors l' Collectioninterface est l'exception dans ce cas, pas le cas normal. Habituellement , la classe d'implémentation devrait (et implémentera) toutes les méthodes.

Le "optionnel" dans la collection signifie que la classe d'implémentation n'a pas à l'implémenter (selon la terminologie ci-dessus), et elle le lancera NotSupportedException).

Un bon exemple - add()méthode pour les collections immuables - le béton implémentera simplement une méthode qui ne fait que lancerNotSupportedException

Dans le cas où Collectioncela est fait pour éviter des arbres d'héritage désordonnés, cela rendra les programmeurs misérables - mais dans la plupart des cas, ce paradigme n'est pas conseillé et devrait être évité si possible.


Mettre à jour:

À partir de java 8, une méthode par défaut a été introduite.

Cela signifie qu'une interface peut définir une méthode - y compris son implémentation.
Cela a été ajouté afin de permettre l'ajout de fonctionnalités aux interfaces, tout en prenant en charge la compatibilité descendante pour les morceaux de code qui n'ont pas besoin de la nouvelle fonctionnalité.

Notez que la méthode est toujours implémentée par toutes les classes qui la déclarent, mais en utilisant la définition de l'interface.

amit
la source
Au lieu de "ne pas gâcher", je pense que c'est plutôt "c'est comme ça".
@pst: Je crois que ce que pensaient les concepteurs lors de sa mise en œuvre en premier lieu, mais je n'ai aucun moyen de le savoir avec certitude. Je pense que toute approche différente ne ferait que créer un désordre, mais encore une fois - pourrait être erronée. Le point que j'essayais de montrer ici est le suivant: cet exemple est l'exception, pas l'habituel - et bien qu'il puisse parfois être utile - pour le cas général - il devrait être évité, si possible.
amit
12
"n'a pas à l'implémenter (il créera probablement juste une méthode qui lance ...)". C'est en œuvre la méthode.
Marquis de Lorne
1
Comme cela a malheureusement été la réponse acceptée, je suggère de la réécrire. Le " Habituellement , la classe d'implémentation devrait (et va) implémenter toutes les méthodes" est trompeur comme EJP l'a déjà souligné.
Alberto
2
Le "facultatif" dans la collection signifie que la classe d'implémentation n'a pas à l'implémenter. "- c'est tout simplement faux. Par" n'a pas à implémenter "vous voulez dire autre chose.
djechlin
19

Une interface en Java déclare simplement le contrat d'implémentation des classes. Toutes les méthodes de cette interface doivent être implémentées, mais les classes d'implémentation sont libres de les laisser non implémentées, c'est-à-dire vides. À titre d'exemple artificiel,

interface Foo {
  void doSomething();
  void doSomethingElse();
}

class MyClass implements Foo {
  public void doSomething() {
     /* All of my code goes here */
  }

  public void doSomethingElse() {
    // I leave this unimplemented
  }
}

Maintenant, je n'ai pas été doSomethingElse()implémenté, le laissant libre à mes sous-classes à implémenter. C'est facultatif.

class SubClass extends MyClass {
    @Override
    public void doSomethingElse() {
      // Here's my implementation. 
    }
}

Cependant, si vous parlez des interfaces de collection, comme d'autres l'ont dit, elles sont une exception. Si certaines méthodes ne sont pas implémentées et que vous les appelez, elles peuvent lever des UnsupportedOperationExceptionexceptions.

SRI
la source
Je pourrais t'embrasser mon ami.
Micro
16

Les méthodes facultatives de l'interface Collection signifient que l'implémentation de la méthode est autorisée à lever une exception, mais elle doit quand même être implémentée. Comme spécifié dans la documentation :

Certaines implémentations de collection ont des restrictions sur les éléments qu'elles peuvent contenir. Par exemple, certaines implémentations interdisent les éléments nuls et certaines ont des restrictions sur les types de leurs éléments. Tenter d'ajouter un élément inéligible lève une exception non contrôlée, généralement NullPointerException ou ClassCastException. Tenter d'interroger la présence d'un élément inéligible peut lever une exception ou simplement renvoyer false; certaines implémentations présenteront le premier comportement et certaines présenteront le second. Plus généralement, tenter une opération sur un élément inéligible dont l'achèvement n'entraînerait pas l'insertion d'un élément inéligible dans la collection peut lever une exception ou réussir, au choix de l'implémentation. Ces exceptions sont marquées comme "facultatives"

MByD
la source
Je n'ai jamais vraiment compris ce que les javadocs signifiaient par facultatif. Je crois qu'ils voulaient dire comme vous l'avez dit. Mais la plupart des méthodes sont facultatives selon cette norme new Runnable ( ) { @ Override public void run ( ) { throw new UnsupportedOperationException ( ) ; } }:;
emory
Cela ne semble pas s'appliquer aux méthodes optionnelles , mais plutôt que, par exemple, add((T)null)peuvent être valables dans un cas mais pas dans un autre. Autrement dit, cela parle des exceptions / comportements optionnels et des arguments ("restrictions sur les éléments" ... "élément inéligible" ... "exceptions marquées comme optionnelles") et ne traite pas des méthodes optionnelles .
9

Toutes les méthodes doivent être implémentées pour que le code soit compilé (à part celles avec des defaultimplémentations en Java 8+), mais l'implémentation n'a rien à faire fonctionnellement utile. Plus précisément, il:

  • Peut être vide (une méthode vide.)
  • Peut simplement lancer un UnsupportedOperationException(ou similaire)

Cette dernière approche est souvent utilisée dans les classes de collection - toutes les méthodes sont toujours implémentées, mais certaines peuvent lever une exception si elles sont appelées à l'exécution.

Michael Berry
la source
5

En fait, je suis inspiré par SurfaceView.Callback2. Je pense que c'est la manière officielle

public class Foo {
    public interface Callback {
        public void requiredMethod1();
        public void requiredMethod2();
    }

    public interface CallbackExtended extends Callback {
        public void optionalMethod1();
        public void optionalMethod2();
    }

    private Callback mCallback;
}

Si votre classe n'a pas besoin d'implémenter des méthodes optionnelles, il suffit de "implémenter Callback". Si votre classe a besoin d'implémenter des méthodes optionnelles, "implémente simplement CallbackExtended".

Désolé pour la merde anglais.

Wonson
la source
5

Dans Java 8 et versions ultérieures, la réponse à cette question est toujours valable mais est désormais plus nuancée.

Premièrement, ces déclarations de la réponse acceptée restent correctes:

  • les interfaces sont destinées à spécifier leurs comportements implicites dans un contrat (un énoncé de règles de comportement auxquelles les classes implémentantes doivent obéir pour être considérées comme valides)
  • il y a une distinction entre le contrat (règles) et la mise en œuvre (codage programmatique des règles)
  • les méthodes spécifiées dans l'interface DOIVENT TOUJOURS être implémentées (à un moment donné)

Alors, quelle est la nuance qui est nouvelle dans Java 8? En parlant de "méthodes facultatives" une des suivantes est maintenant appropriée:

1. Une méthode dont la mise en œuvre est contractuellement facultative

La "troisième déclaration" dit que les méthodes d'interface abstraites doivent toujours être implémentées et cela reste vrai dans Java 8+. Cependant, comme dans Java Collections Framework, il est possible de décrire certaines méthodes d'interface abstraites comme "facultatives" dans le contrat.

Dans ce cas, l'auteur qui implémente l'interface peut choisir de ne pas implémenter la méthode. Le compilateur insistera sur une implémentation, cependant, et donc l'auteur utilise ce code pour toutes les méthodes facultatives qui ne sont pas nécessaires dans la classe d'implémentation particulière:

public SomeReturnType optionalInterfaceMethodA(...) {
    throw new UnsupportedOperationException();
}

Dans Java 7 et les versions antérieures, c'était vraiment le seul type de «méthode optionnelle» qui existait, c'est-à-dire une méthode qui, si elle n'était pas implémentée, lançait une exception UnsupportedOperationException. Ce comportement est nécessairement spécifié par le contrat d'interface (par exemple, les méthodes d'interface optionnelles de Java Collections Framework).

2. Une méthode par défaut dont la réimplémentation est facultative

Java 8 a introduit le concept de méthodes par défaut . Ce sont des méthodes dont l'implémentation peut être et est fournie par la définition d'interface elle-même. Il n'est généralement possible de fournir des méthodes par défaut que lorsque le corps de la méthode peut être écrit en utilisant d'autres méthodes d'interface (c'est-à-dire les "primitives"), et quandthis peut signifier «cet objet dont la classe a implémenté cette interface».

Une méthode par défaut doit respecter le contrat de l'interface (comme toute autre implémentation de méthode d'interface doit l'être). Par conséquent, la spécification d'une implémentation de la méthode d'interface dans une classe d'implémentation est à la discrétion de l'auteur (tant que le comportement est adapté à son objectif).

Dans ce nouvel environnement, le Java Collections Framework pourrait être réécrit comme suit:

public interface List<E> {
    :
    :
    default public boolean add(E element) {
        throw new UnsupportedOperationException();
    }
    :
    :
}

De cette façon, la méthode "facultative" add()a le comportement par défaut de lancer une exception UnsupportedOperationException si la classe d'implémentation ne fournit pas de nouveau comportement propre, ce qui est exactement ce que vous souhaiteriez voir se produire et qui est conforme au contrat pour List. Si un auteur écrit une classe qui ne permet pas d'ajouter de nouveaux éléments à une implémentation de List, l'implémentation deadd() est facultative car le comportement par défaut est exactement ce qui est nécessaire.

Dans ce cas, la "troisième instruction" ci-dessus est toujours vraie, car la méthode a été implémentée dans l'interface elle-même.

3. Une méthode qui renvoie un Optionalrésultat

Le nouveau type final de méthode optionnelle est simplement une méthode qui renvoie un Optional. La Optionalclasse offre une manière nettement plus orientée objet de traiter les nullrésultats.

Dans un style de programmation courant, tel que celui couramment observé lors du codage avec la nouvelle API Java Streams, un résultat nul à tout moment provoque le blocage du programme avec une exception NullPointerException. La Optionalclasse fournit un mécanisme pour renvoyer des résultats nuls au code client d'une manière qui active le style fluide sans provoquer le blocage du code client.

Scottb
la source
4

Si nous parcourons le code de AbstractCollection.java dans grepCode qui est une classe ancêtre pour toutes les implémentations de collection, cela nous aidera à comprendre la signification des méthodes optionnelles. Voici le code de la méthode add (e) dans la classe AbstractCollection. La méthode add (e) est facultative selon l' interface de collecte

public boolean  add(E e) {

        throw new UnsupportedOperationException();
    } 

La méthode facultative signifie qu'elle est déjà implémentée dans les classes ancêtres et qu'elle lève une exception UnsupportedOperationException lors de l'appel. Si nous voulons rendre notre collection modifiable, nous devons remplacer les méthodes facultatives dans l'interface de collection.

Est tel que
la source
4

Eh bien, ce sujet a été adressé à ... ouais ... mais pensez, il manque une réponse. Je parle des "méthodes par défaut" des interfaces. Par exemple, imaginons que vous aurez une classe pour fermer n'importe quoi (comme un destructeur ou quelque chose). Disons qu'il devrait avoir 3 méthodes. Appelons-les "doFirst ()", "doLast ()" et "onClose ()".

Nous disons donc que nous voulons que tout objet de ce type réalise au moins "onClose ()", mais les autres sont facultatifs.

Vous pouvez vous en rendre compte en utilisant les "méthodes par défaut" des interfaces. Je sais, cela annulerait la plupart du temps la raison d'une interface, mais si vous concevez un cadre, cela peut être utile.

Donc, si vous voulez le réaliser de cette façon, cela ressemblerait à ceci

public interface Closer {
    default void doFirst() {
        System.out.print("first ... ");
    }
    void onClose();
    default void doLast() {
        System.out.println("and finally!");
    }
}

Que se passerait-il maintenant, si vous l'implémentiez par exemple dans une classe appelée "Test", le compilateur conviendrait parfaitement avec ce qui suit:

public class TestCloser implements Closer {
    @Override
    public void onClose() {
        System.out.print("closing ... ");
    }
}

avec la sortie:

first ... closing ... and finally!

ou

public class TestCloser implements Closer {
    @Override
    public void onClose() {
        System.out.print("closing ... ");
    }

    @Override
    public void doLast() {
        System.out.println("done!");
    }
}

avec la sortie:

first ... closing ... done!

Toutes les combinaisons sont possibles. Tout ce qui a "par défaut" peut être implémenté, mais ne doit pas, cependant tout ce qui ne doit pas être implémenté.

J'espère que ce n'est pas complètement faux que je réponds maintenant.

Passez une bonne journée à tous!

[edit1]: Veuillez noter: cela ne fonctionne que dans Java 8.

Thorben Kuck
la source
Oui, désolé, j'ai oublié de le mentionner ... Devrait être édité maintenant.
Thorben Kuck
1

Je cherchais un moyen d'implémenter l'interface de rappel, donc l'implémentation de méthodes optionnelles était nécessaire car je ne voulais pas implémenter chaque méthode pour chaque rappel.

Donc, au lieu d'utiliser une interface, j'ai utilisé une classe avec une implémentation vide telle que:

public class MyCallBack{
    public void didResponseCameBack(String response){}
}

Et vous pouvez définir la variable membre CallBack comme ceci,

c.setCallBack(new MyCallBack() {
    public void didResponseCameBack(String response) {
        //your implementation here
    }
});

alors appelez-le comme ça.

if(mMyCallBack != null) {
    mMyCallBack.didResponseCameBack(response);
}

De cette façon, vous n'avez pas à vous soucier de l'implémentation de toutes les méthodes par rappel, mais ne remplacez que celles dont vous avez besoin.

green0range
la source
0

Bien que cela ne réponde pas à la question de l'OP, il convient de noter qu'à partir de Java 8, l'ajout de méthodes par défaut aux interfaces est en fait faisable . Le defaultmot-clé placé dans la signature de méthode d'une interface donnera à une classe la possibilité de remplacer la méthode, mais ne l'exigera pas.

Troy Stopera
la source
0

Tutoriel sur les collections Java d'Oracle:

Pour que le nombre d'interfaces de collecte de base reste gérable, la plate-forme Java ne fournit pas d'interfaces distinctes pour chaque variante de chaque type de collection. (Ces variantes peuvent inclure immuable, taille fixe et ajout uniquement.) Au lieu de cela, les opérations de modification dans chaque interface sont désignées comme facultatives - une implémentation donnée peut choisir de ne pas prendre en charge toutes les opérations. Si une opération non prise en charge est appelée, une collection lève une UnsupportedOperationException . Les implémentations sont chargées de documenter les opérations facultatives qu'elles prennent en charge. Toutes les implémentations à usage général de la plate-forme Java prennent en charge toutes les opérations facultatives.

Trent Steele
la source