De bonnes raisons d'interdire l'héritage en Java?

178

Quelles sont les bonnes raisons d'interdire l'héritage en Java, par exemple en utilisant des classes finales ou des classes utilisant un seul constructeur privé sans paramètre? Quelles sont les bonnes raisons de rendre une méthode définitive?

cretzel
la source
1
Pédanterie: Le constructeur par défaut est celui généré pour vous par le compilateur Java si vous n'en écrivez pas explicitement un dans votre code. Vous voulez dire le constructeur sans argument.
John Topley
N'est-ce pas le "constructeur implicite par défaut"?
cretzel
2
"Vous n'avez pas à fournir de constructeur pour votre classe, mais vous devez être prudent en faisant cela. Le compilateur fournit automatiquement un constructeur par défaut sans argument pour toute classe sans constructeur." java.sun.com/docs/books/tutorial/java/javaOO/constructors.html - John Topley
John Topley

Réponses:

184

Votre meilleure référence ici est l'article 19 de l'excellent livre de Joshua Bloch "Effective Java", intitulé "Conception et document pour l'héritage ou bien l'interdire". (C'est le point 17 dans la deuxième édition et le point 15 dans la première édition.) Vous devriez vraiment le lire, mais je vais résumer.

L'interaction des classes héritées avec leurs parents peut être surprenante et imprévisible si l'ancêtre n'a pas été conçu pour être hérité.

Les classes devraient donc être de deux types:

  1. Classes conçues pour être étendues et avec suffisamment de documentation pour décrire comment cela doit être fait

  2. Classes marquées comme finales

Si vous écrivez du code purement interne, cela peut être un peu excessif. Cependant, l'effort supplémentaire impliqué dans l'ajout de cinq caractères à un fichier de classe est très faible. Si vous écrivez uniquement pour la consommation interne, alors un futur codeur peut toujours supprimer le 'final' - vous pouvez le considérer comme un avertissement disant "cette classe n'a pas été conçue avec l'héritage à l'esprit".

DJClayworth
la source
6
D'après mon expérience, ce n'est pas exagéré si quelqu'un d'autre que moi utilisera le code. Je n'ai jamais compris pourquoi Java a des méthodes remplaçables par défaut.
Eric Weilnau
9
Pour comprendre certains aspects de Java efficace, vous devez comprendre le point de vue de Josh sur la conception. Il dit [quelque chose comme] vous devriez toujours concevoir des interfaces de classe comme s'il s'agissait d'une API publique. Je pense que l'opinion de la majorité est que cette approche est généralement trop lourde et rigide. (YAGNI, etc.)
Tom Hawtin - tackline
9
Bonne réponse. (Je ne peux pas résister à souligner qu'il s'agit de six caractères, car il y a aussi un espace ... ;-)
joel.neely
36
Veuillez noter que rendre les cours finaux peut rendre les tests plus difficiles (plus difficiles à stuber ou à
simuler
10
Si la classe finale écrit sur une interface, la moquerie ne devrait pas être un problème.
Fred Haslam
26

Vous souhaiterez peut-être rendre une méthode finale afin que les classes de substitution ne puissent pas changer le comportement qui est compté dans d'autres méthodes. Les méthodes appelées dans les constructeurs sont souvent déclarées finales afin que vous n'ayez pas de mauvaises surprises lors de la création d'objets.

Bill le lézard
la source
Je n'ai pas bien compris cette réponse. Si cela ne vous dérange pas, pourriez-vous expliquer ce que vous entendez par «les classes de remplacement ne peuvent pas changer le comportement sur lequel on compte dans d'autres méthodes»? Je demande d'abord parce que je n'ai pas compris la phrase, et deuxièmement parce que Netbeans recommande que l'une de mes fonctions que j'appelle depuis le constructeur devrait l'être final.
Nav
1
@Nav Si vous rendez une méthode définitive, alors une classe redéfinie ne pourra pas avoir sa propre version de cette méthode (ou ce sera une erreur de compilation). De cette façon, vous savez que le comportement que vous implémentez dans cette méthode ne changera pas plus tard lorsque quelqu'un d'autre étendra la classe.
Bill the Lizard
19

Une des raisons de rendre une classe définitive serait si vous vouliez forcer la composition sur l'héritage. Ceci est généralement souhaitable pour éviter un couplage étroit entre les classes.

John Topley
la source
15

Il existe 3 cas d'utilisation où vous pouvez utiliser les méthodes finales.

  1. Pour éviter qu'une classe dérivée ne remplace une fonctionnalité de classe de base particulière.
  2. C'est pour des raisons de sécurité, où la classe de base donne des fonctionnalités de base importantes du framework où la classe dérivée n'est pas censée la changer.
  3. Les méthodes finales sont plus rapides que les méthodes d'instance, car il n'y a pas d'utilisation du concept de table virtuelle pour les méthodes finales et privées. Donc, partout où il y a une possibilité, essayez d'utiliser les méthodes finales.

Objectif pour rendre une classe finale:

Pour qu'aucun organisme ne puisse étendre ces classes et modifier leur comportement.

Exemple: classe Wrapper Integer est une classe finale. Si cette classe n'est pas définitive, alors n'importe qui peut étendre Integer dans sa propre classe et changer le comportement de base de la classe entière. Pour éviter cela, java a créé toutes les classes wrapper en tant que classes finales.

user1923551
la source
Pas très convaincu par votre exemple. Si tel est le cas, pourquoi ne pas rendre les méthodes et les variables finales au lieu de rendre toute la classe finale. Pouvez-vous s'il vous plaît modifier votre réponse avec les détails.
Rajkiran
4
Même si vous définissez toutes les variables et méthodes comme finales, d'autres encore peuvent hériter de votre classe et ajouter des fonctionnalités supplémentaires et modifier le comportement de base de votre classe.
user1923551
3
> Si cette classe n'est pas définitive, alors n'importe qui peut étendre Integer dans sa propre classe et changer le comportement de base de la classe entière. Disons que cela a été autorisé et que cette nouvelle classe a été appelée DerivedInteger, elle ne change toujours pas la classe Integer d'origine, et quiconque l'utilise le DerivedIntegerfait à ses risques et périls, donc je ne comprends toujours pas pourquoi c'est un problème.
Siddhartha
7

L'héritage est comme une tronçonneuse - très puissante, mais horrible entre de mauvaises mains. Soit vous concevez une classe dont vous héritez (ce qui peut limiter la flexibilité et prendre beaucoup plus de temps), soit vous devez l'interdire.

Voir les articles 16 et 17 de la 2e édition effective de Java, ou mon article de blog "Inheritance Tax" .

Jon Skeet
la source
Le lien vers le billet de blog est rompu. Pensez-vous également pouvoir élaborer un peu plus à ce sujet?
@MichaelT: Correction du lien, merci - suivez-le pour plus d'élaboration :) (Je n'ai pas le temps d'ajouter à cela pour le moment, j'en ai peur.)
Jon Skeet
@JonSkeet, Le lien vers l'article de blog est rompu.
Lucifer
@Kedarnath: Cela fonctionne pour moi ... il a été cassé pendant un certain temps, mais il pointe maintenant vers la bonne page, sur codeblog.jonskeet.uk ...
Jon Skeet
4

Hmmm ... je peux penser à deux choses:

Vous pourriez avoir une classe qui traite de certains problèmes de sécurité. En le sous-classant et en alimentant votre système dans la version sous-classée de celui-ci, un attaquant peut contourner les restrictions de sécurité. Par exemple, votre application peut prendre en charge des plugins et si un plugin peut simplement sous-classer vos classes pertinentes pour la sécurité, il peut utiliser cette astuce pour en quelque sorte faire passer une version sous-classée de celui-ci en place. Cependant, c'est plutôt quelque chose dont Sun doit faire face en ce qui concerne les applets et autres, peut-être pas un cas aussi réaliste.

Une solution beaucoup plus réaliste consiste à éviter qu'un objet ne devienne mutable. Par exemple, puisque les chaînes sont immuables, votre code peut en conserver les références en toute sécurité

 String blah = someOtherString;

au lieu de copier d'abord la chaîne. Cependant, si vous pouvez sous-classer String, vous pouvez y ajouter des méthodes qui permettent de modifier la valeur de la chaîne, maintenant aucun code ne peut plus compter sur le fait que la chaîne restera la même si elle copie simplement la chaîne comme ci-dessus, à la place, il doit dupliquer le chaîne.

Mecki
la source
2

De plus, si vous écrivez une classe de source fermée commerciale, vous ne voudrez peut-être pas que les gens puissent changer la fonctionnalité en bout de ligne, surtout si vous avez besoin de la soutenir et que les gens ont remplacé votre méthode et se plaignent que son appel donne des résultats inattendus.

DavidG
la source
2

Si vous marquez les classes et les méthodes comme finales, vous remarquerez peut-être un petit gain de performances, car le runtime n'a pas besoin de rechercher la bonne méthode de classe à appeler pour un objet donné. Les méthodes non finales sont marquées comme virtuelles afin de pouvoir être correctement étendues si nécessaire, les méthodes finales peuvent être directement liées ou compilées en ligne dans la classe.

ensemble
la source
1
Je pense que c'est un mythe, car les machines virtuelles de la génération actuelle devraient être capables d'optimiser et de désoptimiser au besoin. Je me souviens avoir vu certains bencharmking ceci et le classer comme mythe.
Miguel Ping
1
La différence de génération de code n'est pas un mythe, mais peut-être que les gains de performances le sont. Comme je l'ai dit, c'est un petit gain, un site a mentionné un gain de performance de 3,5%, certains plus élevé, ce qui dans la plupart des cas ne vaut pas la peine d'être entièrement nazi sur votre code.
seanalltogether
1
Ce n'est pas un mythe. En fait, le compilateur ne peut intégrer que les méthodes V finales. Je ne sais pas si des JITs "inline" sont exécutables mais je doute que ce soit possible puisque vous pourriez charger une classe dérivée plus tard.
Uri
1
Microbenchmark == grain de sel. Cela dit, après le préchauffage du cycle 10B, une moyenne de plus de 10B appels ne montre pratiquement aucune différence sous Eclipse sur mon ordinateur portable. Deux méthodes retournant String, final était 6,9643us, non final était 6,9641us. Ce delta est le bruit de fond, à mon humble avis.
joel.neely
1

Pour empêcher les gens de faire des choses qui pourraient confondre eux-mêmes et les autres. Imaginez une bibliothèque de physique où vous avez des constantes ou des calculs définis. Sans utiliser le mot-clé final, quelqu'un pourrait venir redéfinir des calculs de base ou des constantes qui ne devraient JAMAIS changer.

Anson Smith
la source
2
Il y a quelque chose que je ne comprends pas à propos de cet argument - si quelqu'un a changé ces calculs / constantes qui ne devraient pas changer - les échecs ne sont-ils pas dus à 100% à leur faute? En d'autres termes, en quoi le changement profite-t-il à quelque chose?
matt b
Oui, c'est techniquement "leur" faute, mais imaginez que quelqu'un d'autre utilisant la bibliothèque qui n'a pas été informé des changements, pourrait prêter à confusion. J'ai toujours pensé que c'était la raison pour laquelle de nombreuses classes Java Base étaient marquées comme finales, pour empêcher les gens de modifier les fonctionnalités de base.
Anson Smith
@matt b - Vous semblez supposer que le développeur qui a effectué le changement l'a fait sciemment ou qu'il se soucierait que ce soit de sa faute. Si quelqu'un d'autre que moi utilise mon code, je le marquerai comme définitif à moins qu'il ne soit censé être modifié.
Eric Weilnau
Il s'agit en fait d'un usage différent de «final» de celui demandé.
DJClayworth
Cette réponse semble confondre l'héritage et la mutabilité des champs individuels avec la capacité d'écrire une sous-classe. Vous pouvez marquer des champs et des méthodes individuels comme finaux (empêchant leur redéfinition) sans interdire l'héritage.
joel.neely
0

Vous souhaitez rendre une méthode définitive afin que le remplacement des classes ne modifie pas son comportement. Lorsque vous souhaitez pouvoir modifier le comportement, rendez la méthode publique. Lorsque vous remplacez une méthode publique, elle peut être modifiée.

Alexander Robinson
la source