9
votes

Pourquoi les interfaces doivent-elles être déclarées en Java?

Parfois, nous avons plusieurs classes qui ont des méthodes avec la même signature, mais qui ne correspondent pas à une interface Java déclarée. Par exemple, les deux jtextfield et jbutton (parmi plusieurs autres dans javax.swing. * ) ont une méthode xxx

Maintenant, supposons que je souhaite faire quelque chose avec des objets qui ont cette méthode; Ensuite, j'aimerais avoir une interface (ou peut-être de le définir moi-même), par exemple xxx

afin que je puisse écrire: xxx < / Pré>

Mais, malheureusement, je ne peux pas: xxx

Cette distribution serait illégale. Le compilateur sait que jbutton n'est pas a canadaldactionListener , car la classe n'a pas déclaré que la classe interface .. . Cependant, il "réellement" implémente .

C'est parfois un inconvénient - et Java lui-même a modifié plusieurs classes principales pour mettre en œuvre une nouvelle interface composée d'anciennes méthodes ( Chaîne implémente Chapharce , par exemple).

Ma question est la suivante: pourquoi cela est tellement? Je comprends l'utilité de déclarer qu'une classe implémente une interface. Mais de toute façon, en regardant mon exemple, pourquoi le compilateur ne peut-il pas en déduire que la classe jbutton "satisfait" de la déclaration d'interface (en regardant à l'intérieur) et accepte la distribution? Est-ce une question de l'efficacité du compilateur ou il y a des problèmes plus fondamentaux?

mon résumé des réponses : C'est un cas dans lequel Java aurait pu faire une allocation pour une "typographie structurelle" "(Sorte de taper de canard - mais vérifié à l'heure de la compilation). Ce n'est pas le cas. Outre des difficultés de performance et de mise en œuvre, il y a un concept beaucoup plus fondamental ici: en Java, la déclaration d'une interface (et en général de tout) n'est pas censée être simplement structurelle (avoir des méthodes avec ces signatures) mais sémantique : les méthodes sont censées mettre en œuvre un comportement / une intention spécifique. Donc, une classe qui structurellement satisfait une interface (c'est-à-dire qu'elle dispose des méthodes avec les signatures requises) ne le satisfait pas nécessairement sémantique (un exemple extrême: rappeler le "marqueur interfaces ", qui n'ont même pas de méthodes!). Par conséquent, Java peut affirmer qu'une classe implémente une interface car (et seulement parce que) cela a été explicitement déclaré. D'autres langues (go, scala) ont d'autres philosophies.


1 commentaires

Vous pourriez être intéressé par le langage de programmation Go. Voir golang.org . Il a une interface sémantique exactement comme vous le souhaitez dans votre question.


6 Réponses :


5
votes

Pourquoi le compilateur ne peut-il pas déduire que la classe Jbutton "satisfait" la déclaration d'interface (en regardant à l'intérieur) et accepter le casting? Est-ce une question de l'efficacité du compilateur ou des problèmes plus fondamentaux?

C'est un problème plus fondamental.

Le point d'une interface consiste à spécifier qu'il existe une API commune / un ensemble de comportements comportant un certain nombre de classes. Donc, quand une classe est déclarée sous la forme de implémente quelque expertface , toutes les méthodes de la classe dont les signatures correspondent aux signatures de la méthode de l'interface sont supposées être des méthodes qui fournissent ce comportement.

En revanche, si la langue correspondait simplement à des méthodes basées sur des signatures ... Indépendamment des interfaces ... Ensuite, nous serions susceptibles d'obtenir de faux matchs, lorsque deux méthodes avec la même signature signifient / fais quelque chose de sémantiquement non liée .

(le nom de cette dernière approche est "Début de canard" ... et Java ne le supporte pas.)


La page Wikipedia sur Type Systems dit que la typographie du canard n'est ni "typing nominatif" ou «typage structurel». En revanche, Pierce ne mentionne même pas «de dactylographie de canard», mais il définit nominatif (ou «nominal» tel qu'il l'appelle) en tapant et en tapant structurel comme suit:

"Les systèmes de type tels que Java, dans lequel les noms [de types] sont significatifs et le sous-typing est explicitement déclaré, s'appelle nominal . Les systèmes de type comme la plupart de ceux de ce livre dans lequel les noms sont des noms inessentiels et le sous-typing est défini directement sur la structure des types, sont appelés structurels . "

Donc, par la définition de Pierce, le typing de canard est une forme de typage structurel, bien que l'une qui est typiquement mise en œuvre à l'aide de chèques d'exécution. (Les définitions de Pierce sont indépendantes du temps de compilation par rapport à la vérification de l'exécution.)

Référence:

  • "Types et langages de programmation" - Benjamin C Pierce, Mit Press, 2002, ISBN 0-26216209-1.

2 commentaires

+1 N'ayez-ce pas alors plus apte (plus de consensus) d'appeler mon comportement souhaité «Typage structurel» plutôt que «de canard de dactylographie»?


@LeonBloy - Peut-être que ce serait. Mais vous êtes plus susceptible d'entendre ce genre de chose appelé dactylographie de canard. Si vous appelez la typographie structurelle, vous risquez d'obtenir des gens de dire qu'il s'agit d'un comportement plutôt que d'une structure (de représentation). Le problème est que la terminologie du système de type est très caoutchouteuse.



2
votes

Je ne peux pas dire que je sais pourquoi certaines décisions de conception ont été prises par l'équipe de développement Java. Je vous remettez aussi ma réponse avec le fait que ces personnes sont bien plus intelligentes que je ne serai jamais en ce qui concerne le développement de logiciels et (en particulier) la conception de la langue. Mais voici une fissure pour essayer de répondre à votre question.

Pour comprendre pourquoi ils n'ont peut-être pas choisi d'utiliser une interface comme "CanaddactionListener", vous devez examiner les avantages de ne pas utiliser d'interface et préférez-vous préférer des classes abstraites (et, finalement, concrètes).

Comme vous le savez peut-être, lors de la déclaration de fonctionnalité abstraite, vous pouvez fournir une fonctionnalité par défaut aux sous-classes. Ok ... alors quoi? Big Deal, non? Eh bien, en particulier dans le cas de la conception d'une langue, c'est une grosse affaire. Lors de la conception d'une langue, vous devrez maintenir ces classes de base sur la durée de vie de la langue (et vous pouvez être sûr qu'il y aura des changements lorsque votre langue évolue). Si vous aviez choisi d'utiliser des interfaces, au lieu de fournir une fonctionnalité de base dans une classe abstraite, toute classe implémente l'interface se cassera. Ceci est particulièrement important après la publication - une fois que les clients (développeurs dans cette affaire) commencent à utiliser vos bibliothèques, vous ne pouvez pas modifier les interfaces sur un caprice ou que vous allez avoir beaucoup de pisse de développeurs!

Donc, je suppose que l'équipe de développement Java a pleinement compris que nombre de leurs classes Abstractj * partageaient les mêmes noms de méthodes, il ne serait pas avantageux de les faire partager une interface commune car elle rendrait leur API rigide et inflexible.

Pour résumer le montant ( Merci à ce site ici ):

  • Les classes abstraites peuvent facilement être étendues en ajoutant de nouvelles méthodes (non abstraites).
  • Une interface ne peut pas être modifiée sans casser son contrat avec les classes qui l'appliquent. Une fois qu'une interface a été expédiée, son ensemble de membre est fixé en permanence. Une API basée sur des interfaces ne peut être prolongée que par l'ajout de nouvelles interfaces.

    Bien sûr, cela ne veut pas dire que vous pourriez faire quelque chose comme celui-ci dans votre propre code (étendre Abstractjbutton et implémenter l'interface canadienne du CanaddactionListener), mais soyez conscient des pièges à ce sujet.


0 commentaires

2
votes

probablement c'est une fonctionnalité de performance.

Puisque Java est typée de manière statique, le compilateur peut affirmer la conformité d'une classe à une interface identifiée. Une fois validé, cette assertion peut être représentée dans la classe compilée comme une référence à la définition de l'interface conforme.

Plus tard, au moment de l'exécution, lorsqu'un objet a été mis en forme sur le type d'interface, tout le temps d'exécution doit faire est de vérifier les métadonnées de la classe pour voir si la classe qu'il est en train de lancer est également compatible (via la interface ou la hiérarchie d'héritage).

Il s'agit d'une vérification raisonnablement bon marché pour effectuer car le compilateur a fait la majeure partie du travail.

L'esprit, ce n'est pas faisant autorité. Une classe peut dire qu'elle est conforme à une interface, mais cela ne signifie pas que la méthode réelle envoie à être exécutée fonctionnera réellement. La classe conforme peut bien être à jour et la méthode peut tout simplement pas exister.

Mais une composante clé de la performance de Java est que, même si elle doit encore effectuer une forme d'expédition de méthode dynamique au moment de l'exécution, il y a un contrat que la méthode ne va pas disparaître soudainement derrière les roulements. Donc, une fois la méthode située, son emplacement peut être mis en cache à l'avenir. Contrairement à une langue dynamique où les méthodes peuvent venir et aller et continuer à essayer de chasser les méthodes à chaque fois que l'on est invoqué. De toute évidence, les langues dynamiques ont des mécanismes pour bien performer.

Maintenant, si l'heure d'exécution devait vérifier qu'un objet est conforme à une interface en faisant tout le travail lui-même, vous pouvez voir à quel point cela peut être plus cher qui peut être, en particulier avec une grande interface. Les résultats d'un JDBC, par exemple, ont plus de 140 méthodes et telles en elle.

Le typing de canard est une correspondance d'interface dynamique efficacement. Vérifiez quelles méthodes sont appelées sur un objet et la cartographier au moment de l'exécution.

Tout ce type d'information peut être mis en cache et construit au moment de l'exécution, etc. Tout cela peut (et est dans d'autres langues), mais avoir une grande partie de cela fait à l'heure de la compilation est en fait assez efficace à la fois sur la CPU d'ELINTES et sa mémoire. Pendant que nous utilisons Java avec des tas multi-gb pour des serveurs de longueurs longs, il est en fait assez approprié pour de petits déploiements et des roulements maigres. Même en dehors de J2ME. Ainsi, il y a toujours une motivation pour essayer de conserver l'empreinte d'exécution aussi maigre que possible.


2 commentaires

Bonne explication. Mais enregistrant la question de la performance et la chose dynamique de dactylographie de canard, je pense que mon comportement souhaité ne traite que de la compilation. Que JButton "peut" être cassette à CanaddactionListener peut être décidé en cas de compilation.


Vrai, mais pas nécessairement. Maintenant, je ne connais pas les spécificités des différentes implémentations DT. Il peut y avoir beaucoup d'informations capturées à l'heure compilée. Mais si vous pouvez avoir une "liste " et peut apporter des appels arbitraires dessus, qui lave beaucoup d'informations, me permettant de mettre "quoi que ce soit" dans cette liste. Tout cette expédition et cette manipulation devront être effectuées de manière dynamique au moment de l'exécution. Il y a donc probablement toujours un impact sur l'envoi initial.



2
votes

Le typing de canard peut être dangereux pour les raisons de Stephen C discutées, mais ce n'est pas nécessairement le mal qui brise tout type de frappe statique. Une version statique et plus sûre de la typographie de canard réside au cœur du système de type de Go et une version est disponible à Scala où elle s'appelle "Typage structurel". Ces versions effectuent toujours une vérification de l'heure de compilation pour vous assurer que l'objet obéit aux exigences, mais avoir des problèmes potentiels car ils brisent le paradigme de conception qui a la mise en œuvre d'une interface toujours une décision intentionnelle.

voir http://markthomas.info/blog/?p=66 et < un href = "http://programming-scala.labs.oreilly.com/ch12.html" rel = "nofollow"> http://programming-scala.labs.orailly.com/ch12.html et http://beust.com/weblog/2008/ 02/11 / Structural-Typing-VS-Duck-Typing / pour une discussion de la fonction Scala.


0 commentaires

8
votes

Le choix de conception de Java pour faire de la mise en œuvre de classes déclarez expressément l'interface qu'ils implémentent est celle-ci - un choix de conception. Pour être sûr, le JVM a été optimisé pour ce choix et la mise en œuvre d'un autre choix (disons, la typographie structurelle de Scala) peut maintenant viens à des frais supplémentaires, à moins que des nouvelles instructions JVM soient ajoutées.

Qu'est-ce que est le choix de conception? Tout dépend de la sémantique des méthodes. Considérons: sont les méthodes suivantes sémantiquement la même?

  • Draw (String GraphicalShaPename)
  • Draw (String Handgunname)
  • Draw (String PlayCardName)

    Les trois méthodes ont la signature dessin (chaîne) . Un humain pourrait déduire qu'ils ont une sémantique différente des noms de paramètres ou en lisant une documentation. Y a-t-il un moyen pour la machine de dire qu'ils sont différents?

    Le choix de conception de Java est d'exiger que le développeur d'une classe indique explicitement qu'une méthode est conforme à la sémantique d'une interface prédéfinie: < / p> xxx

    Il ne fait aucun doute que la méthode draw dans JavaScriptCanvas est destinée à correspondre au draw Méthode d'affichage graphique. Si on a tenté de passer un objet qui allait retirer une arme de poing, la machine peut détecter l'erreur.

    Le choix de conception d'aller est plus libéral et permet aux interfaces à définir après le fait. Une classe concrète n'a pas besoin de déclarer quelles interfaces il met en œuvre. Au contraire, le concepteur d'un nouveau composant de jeu de carte peut déclarer qu'un objet fournissant des cartes à jouer doit avoir une méthode correspondant à la signature draw (string) . Cela présente l'avantage que toute classe existante avec cette méthode puisse être utilisée sans avoir à modifier son code source, mais l'inconvénient que la classe pourrait retirer une arme de poing au lieu d'une carte à jouer.

    Le choix de conception de Les langues de dactylographie de canard consistent à se dispenser avec des interfaces formelles tout à fait correspondre à des signatures de méthode. Tout concept d'interface (ou «protocole») est purement idiomatique, sans support de langue directe.

    Ce ne sont que trois des nombreux choix de conception possibles. Les trois peuvent être résumés avec glissement comme ceci:

    Java: le programmeur doit déclarer explicitement son intention et la machine le vérifiera. L'hypothèse est que le programmeur est susceptible de faire une erreur sémantique (graphismes / armes de poing / carte).

    Go: Le programmeur doit déclarer au moins une partie de son intention, mais la machine a moins à faire quand Vérifier cela. L'hypothèse est que le programmeur est susceptible de faire une erreur de bureau (entier / string), mais pas susceptible de faire une erreur sémantique (graphismes / armes de poing / carte).

    Dessinage de canard: le programmateur a besoin de 't exprimer une intention, et rien pour la machine n'a rien à vérifier. L'hypothèse est que le programmeur est peu susceptible de faire une erreur de bureau ou sémantique.

    Il dépasse la portée de cette réponse afin de déterminer si les interfaces et la frappe en général sont adéquates pour tester les erreurs cléricales et sémantiques . Une discussion complète devrait envisager une technologie de compilateur de temps de construction, une méthodologie de test automatisée, une compilation de temps d'exécution / ponctuelle et une foule d'autres problèmes.

    Il est reconnu que le draw ) Exemple sont délibérément exagéré pour faire un point. Les exemples réels impliqueraient des types plus riches qui donneraient plus d'indices pour désambiguient les méthodes.


0 commentaires

0
votes

interfaces représentent une forme de classe de substitution. Une référence de type qui implémente ou hérite d'une interface particulière peut être transmise à une méthode qui s'attend à ce type d'interface. Une interface ne précisera pas seulement que toutes les classes de mise en œuvre doivent avoir des méthodes avec certains noms et signatures, mais il aura également un contrat associé qui spécifie que tous les classes de mise en œuvre légitimes doivent avoir des méthodes avec certains noms et Les signatures, qui se comportent de certaines manières désignées . Il est tout à fait possible que même si deux interfaces contiennent des membres présentant les mêmes noms et signatures, une mise en œuvre peut satisfaire le contrat d'un mais pas l'autre.

comme un exemple simple, si on conçoit un cadre à partir de zéro, on pourrait commencer par une interface énumérable (qui peut être utilisée aussi souvent que souhaitée pour créer un énumérateur qui va Sortie d'une séquence de T, mais des demandes différentes peuvent générer différentes séquences), mais en dérivent une interface immutaculableenumerable qui se comporterait comme ci-dessus mais garantirait que chaque demande renvoie la même séquence. Un type de collection mutable prend en charge tous les membres requis pour immutacultablérables , mais étant donné que les demandes de dénombrement reçues après une mutation signaleraient une séquence différente des demandes effectuées auparavant, elle ne respecterait pas la < Code> immutaculable (code) contrat.

La capacité d'une interface à considérer comme encapsulant un contrat au-delà des signatures de ses membres est l'une des choses qui rend la programmation basée sur une interface plus puissante que la typographie de canard simple.


0 commentaires