web-dev-qa-db-fra.com

Méthodes facultatives dans l'interface Java

A ma connaissance, 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 optionnelles, mais qu'est-ce que cela signifie exactement? Sa m'a jeté un peu comme je pensais que toutes les méthodes spécifiées dans l'interface seraient nécessaires?

99
mjsey

Il semble y avoir beaucoup 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 deux sortes de conformité à une interface:

  1. Ce que le langage Java peut vérifier. En gros, cela revient simplement à: existe-t-il une implémentation pour chacune des méthodes?

  2. Réaliser le contrat. En d’autres termes, l’implémentation fait-elle ce que la documentation de l’interface dit qu’elle devrait?

    Les interfaces bien écrites comprendront une documentation expliquant exactement ce que l’on attend 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 le contrat dit, 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 de disposer d'interfaces très fines permettant de distinguer différentes variantes de collections (par exemple: lisible, inscriptible, accès aléatoire, etc.), il ne disposait que d'un ensemble très grossier d'interfaces, principalement Collection, List, Set et Map, puis documentez certaines opérations comme "facultatives". Cela devait éviter l'explosion combinatoire qui résulterait d'interfaces à grain fin. Depuis la Conception de l'API Java Collections FAQ :

Pour illustrer le problème en détail sanglant, supposons que vous souhaitiez ajouter le notion de modifiabilité à la hiérarchie. Vous avez besoin de quatre nouveaux interfaces: ModifiableCollection, ModifiableSet, ModifiableList et Carte modifiable. Ce qui était auparavant une simple hiérarchie est maintenant un désordre hétérarchie. En outre, vous avez besoin d’une nouvelle interface Iterator à utiliser avec Collections non modifiables, qui ne contiennent pas l'opération de suppression . Maintenant, pouvez-vous vous passer de UnsupportedOperationException? Malheureusement ne pas.

Considérons les tableaux. Ils implémentent la plupart des opérations de liste, mais pas enlever et ajouter. Ce sont des listes "de taille fixe". Si vous voulez capturer cette notion dans la hiérarchie, vous devez ajouter deux nouvelles interfaces: VariableSizeList et VariableSizeMap. Vous n'êtes pas obligé d'ajouter VariableSizeCollection et VariableSizeSet, car ils seraient identique à ModifiableCollection et ModifiableSet, mais vous pourriez choisissez de les ajouter quand même pour des raisons de cohérence. En outre, vous avez besoin d'un nouveau variété de ListIterator qui ne prend pas en charge l’ajout et la suppression de opérations, pour aller avec liste non modifiable. Maintenant nous sommes à dix ou douze interfaces, plus deux nouvelles interfaces Iterator, au lieu de notre quatre d'origine. Avons-nous terminé? Non.

Envisagez les 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 qui ne comprennent que des append, qui prennent en charge toutes les opérations de liste sauf pour remove et set (remplacer). Ils nécessitent une nouvelle interface principale et un nouvel itérateur.

Et qu’en est-il des collections immuables, par opposition aux collections non modifiables? (c’est-à-dire que les collections ne peuvent pas être modifiées par le client ET ne changeront jamais pour une autre raison). Beaucoup soutiennent que c'est le plus distinction importante de toutes, car elle permet à plusieurs threads de 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 autres interfaces.

Nous avons maintenant une vingtaine d'interfaces et cinq itérateurs, et c'est presque certain qu'il existe encore des collections dans la pratique qui ne rentre pas proprement dans aucune des interfaces. Par exemple, le Les vues de collection renvoyées par Map sont des collections naturelles avec suppression uniquement . De plus, il existe des collections qui rejetteront certains éléments du fichier base de leur valeur, de sorte que nous n'avons toujours pas supprimé le temps d'exécution exceptions.

En fin de compte, nous avons estimé qu’il s’agissait d’une bonne ingénierie compromis pour éviter toute la question en fournissant un très petit ensemble d'interfaces principales pouvant générer une exception d'exécution.Lorsque les méthodes de l'API Collections sont documentées comme 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 (pour une chose, plusieurs ils ont besoin de renvoyer un résultat). Cela signifie plutôt qu'un choix d'implémentation valide (celui qui est toujours conforme au contrat) consiste à lancer une UnsupportedOperationException .

Notez que, étant donné que UnsupportedOperationException est une RuntimeException, vous pouvez la lancer depuis n'importe quelle implémentation de méthode, en ce qui concerne le compilateur. Par exemple, vous pouvez le lancer depuis une implémentation de Collection.size(). Cependant, une telle implémentation enfreindrait le contrat car la documentation de Collection.size() n'indique pas que cela est autorisé. 

À part: L'approche utilisée par l'API Collections de Java est quelque peu controversée (probablement moins maintenant que lors de sa première introduction). Dans un monde parfait, les interfaces n'auraient _ = pas d'opérations facultatives et les interfaces à granularité fine seraient utilisées. Le problème est que Java ne prend en charge ni les types de structure inférés ni les types d'intersection, ce qui explique pourquoi tenter de faire les choses "dans le bon sens" finit par devenir extrêmement difficile à manier dans le cas de collections.

Aside: The approach used by Java's Collections API is somewhat controversial (probably less now than when it was first introduced, however). In a perfect world, interfaces would not have optional operations, and fine grained interfaces would instead be used. The problem is that Java supports neither inferred structural types or intersection types, which is why attempting to do things the "right way" ends up becoming extremely unwieldy in the case of collections.

205

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 qu'une méthode implémentée est une simple exception et qu'elle est non implémentée (comme certaines méthodes de l'interface Collection), l'interface Collection est l'exception dans ce cas et non la normale. Cas. Habituellement, la classe d'implémentation doit (et va) implémenter 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 lancera simplement NotSupportedException ). 

Un bon exemple - la méthode add() pour les collections immuables - le concret implémentera juste une méthode qui ne fait que lancer NotSupportedException

Dans le cas de Collection, ceci est fait pour éviter les arbres d'héritage en désordre, ce qui rendra les programmeurs malheureux - mais dans les {la plupart} _ cas, ce paradigme n'est pas conseillé et doit être évité si possible.


Mettre à jour:

A partir de Java 8, une méthode default a été introduite.

Cela signifie qu'une interface peut définir une méthode - y compris sa mise en œuvre .
Cela a été ajouté afin de permettre l'ajout de fonctionnalités aux interfaces, tout en maintenant la compatibilité avec les versions antérieures pour les éléments de code ne nécessitant pas 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.

25
amit

Une interface en Java ne fait que déclarer le contrat d'implémentation de classes. Toutes les méthodes de cette interface doivent doivent être implémentées, mais les classes d'implémentation sont libres de les laisser non implémentées, à savoir vierges. À 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, j'ai laissé doSomethingElse() non implémenté, le laissant libre à mes sous-classes à mettre en œuvre. C'est optionnel.

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

Toutefois, si vous parlez d’interfaces Collection, comme d’autres l’ont déjà dit, elles constituent une exception. Si certaines méthodes ne sont pas implémentées et que vous les appelez, elles peuvent générer des exceptions UnsupportedOperationException.

17
S.R.I

Les méthodes optionnelles de l'interface Collection signifient que l'implémentation de la méthode est autorisée à lever une exception, mais que cette dernière doit être implémentée de toute façon. Comme spécifié dans la documentation :

Certaines implémentations de collection ont des restrictions sur les éléments qui ils peuvent contenir. Par exemple, certaines implémentations interdisent null éléments, et certains ont des restrictions sur les types de leurs éléments . Tenter d’ajouter un élément non éligible lève une exception non contrôlée, généralement NullPointerException ou ClassCastException. Essayer de interroger la présence d'un élément inéligible peut générer une exception, ou il peut simplement retourner faux certaines implémentations présenteront le comportement antérieur et certains exposeront le dernier. 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 le fichier collection peut générer une exception ou réussir, au choix de la mise en oeuvre. Ces exceptions sont marquées comme "optionnelles" dans le fichier spécification pour cette interface.

16
MByD

Toutes les méthodes doivent être implémentées pour que le code puisse être compilé (à l'exception de celles avec des implémentations default en Java 8+), mais l'implémentation n'a pas à faire de fonctionnalité utile. Plus précisément, il:

  • Peut être vide (une méthode vide.)
  • Peut simplement lancer une 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 générer une exception si elles sont appelées à l'exécution.

9
Michael Berry

Dans Java 8 et les versions ultérieures, la réponse à cette question est toujours valide mais est maintenant plus nuancée.

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

  • les interfaces sont destinées à spécifier leurs comportements implicites dans un contrat (énoncé de règles de comportement que les classes d'implémentation doivent obéir pour être considérées comme valides)
  • il existe 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? Quand on parle de "Optional Methods", l’un des suivants est maintenant apte:

1. Une méthode dont la mise en oeuvre est contractuellement optionnelle

La "troisième déclaration" indique que les méthodes d'interface abstraites doivent toujours être implémentées, ce qui reste le cas dans Java 8+. Cependant, comme dans Java Collections Framework, il est possible de décrire certaines méthodes d'interface abstraite 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. Cependant, le compilateur insistera sur une implémentation et l'auteur utilise donc 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 facultative" qui existait, c’est-à-dire une méthode qui, si elle n’était pas implémentée, renvoyait une exception UnsupportedOperationException. Ce comportement est nécessairement spécifié par le contrat d'interface (par exemple, les méthodes d'interface facultatives de Java Collections Framework).

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

Java 8 a introduit le concept de méthodes default. 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 si le corps de la méthode peut être écrit à l’aide d’autres méthodes d’interface (les "primitives") et lorsque this peut signifier "cet objet dont la classe a implémentée cette interface".

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

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

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

De cette manière, la méthode "facultative" add() a pour comportement par défaut de générer une exception UnsupportedOperationException si la classe d'implémentation ne fournit pas de nouveau comportement, ce qui est exactement ce que vous souhaiteriez et ce qui est conforme au contrat pour List. Si un auteur est en train d'écrire une classe qui n'autorise pas l'ajout de nouveaux éléments à une implémentation de List, l'implémentation de add() est facultative car le comportement par défaut correspond 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 retourne un résultat Optional

Le dernier type de méthode facultatif final est simplement une méthode qui renvoie Optional. La classe Optional fournit un moyen nettement plus orienté objet de traiter les résultats null.

Dans un style de programmation fluide, 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 classe Optional fournit un mécanisme permettant de renvoyer les résultats nuls au code client d'une manière qui active le style fluide sans provoquer le blocage du code client.

4
scottb

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 le sens des méthodes facultatives. Voici le code de la méthode add (e) dans la classe AbstractCollection. La méthode add (e) est facultative selon collection interface

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 son invocation. Si nous voulons rendre notre collection modifiable, nous devons remplacer les méthodes optional dans l'interface de collection. 

4
IsAs

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 "d'implémenter Callback" . Si votre classe a besoin d'implémenter des méthodes optionnelles, il vous suffit "d'implémenter CallbackExtended".

Désolé pour la merde anglais.

3
Wonson

Eh bien, ce sujet a été adressé à ... ouais .. mais réfléchis, il manque une réponse. Je parle des "méthodes par défaut" des interfaces . Par exemple, imaginons que vous ayez une classe pour fermer quoi que ce soit (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 optionnels.

Vous pouvez vous en rendre compte en utilisant les "méthodes par défaut" des interfaces. Je sais que cela nierait 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, il semblerait que

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

Que se passera-t-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 élément avec "défaut" peut être implémenté, mais ne doit pas, cependant, tout élément sans doit être implémenté.

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

Bonne journée à tous!

[edit1]: Veuillez noter que cela ne fonctionne qu'avec Java 8.

3
Thorben Kuck

Tutoriel sur les collections Java d'Oracle:

Pour que le nombre d'interfaces de collection principales 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 append-only.) Au lieu de cela, les opérations de modification dans chaque interface sont désignées par option - 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 exception UnsupportedOperationException . Les mises en œuvre sont responsables de la documentation des 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.

0
Trent Steele

Je cherchais un moyen d'implémenter l'interface de rappel; l'implémentation de méthodes facultatives était donc nécessaire, car je ne souhaitais pas implémenter toutes les méthodes à chaque appel. 

Ainsi, 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 ça, 

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

alors appelez ça comme ça.

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

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

0
green0range

Bien qu'il ne réponde pas à la question du PO, il convient de noter qu'à partir de Java 8, l'ajout de méthodes par défaut aux interfaces est en fait réalisable . Le mot clé default placé dans la signature de méthode d'une interface donnera à une classe la possibilité de remplacer la méthode, sans toutefois l'exiger.

0
Troy Stopera