Il y a quelque temps, j'ai écrit cette réponse à une question sur la façon d'éviter d'avoir un getter et un setter pour chaque variable mutable. À l'époque, je n'avais qu'une intuition difficile à verbaliser que c'était une mauvaise idée, mais OP demandait explicitement comment le faire. J'ai cherché ici pourquoi cela pourrait être un problème, et j'ai trouvé cette question , dont les réponses semblent prendre pour acquis que l'utilisation de la réflexion, sauf si absolument nécessaire, est une mauvaise pratique.
Alors, quelqu'un peut-il expliquer pourquoi il est certain que la réflexion doit être évitée, en particulier dans le cas du getter et du setter génériques?
D'après mon expérience, la réflexion est une fonctionnalité de "niveau expert" en Java. Je dirais que la plupart des programmeurs n'utilisent jamais la réflexion activement (c'est-à-dire que la consommation de bibliothèques qui utilisent la réflexion ne compte pas). Cela rend le code plus difficile à comprendre pour ces programmeurs.
Supposons que j'ai un getter getFoo
dans ma classe et que je veuille le renommer getBar
. Si je n'utilise aucune réflexion, je peux simplement rechercher la base de code pour getFoo
et trouverai chaque endroit qui utilise le getter afin que je puisse le mettre à jour, et même si j'en manque un, le compilateur se plaindra.
Mais si l'endroit qui utilise le getter est quelque chose comme callGetter("Foo")
et callGetter
fait getClass().getMethod("get"+name).invoke(this)
, alors la méthode ci-dessus ne le trouvera pas, et le compilateur ne le fera pas se plaindre. Ce n'est que lorsque le code est réellement exécuté que vous obtiendrez un NoSuchMethodException
. Et imaginez la douleur que vous ressentez si cette exception (qui est suivie) est avalée par callGetter
car "elle n'est utilisée qu'avec des chaînes codées en dur, elle ne peut pas réellement se produire". (Personne ne ferait cela, quelqu'un pourrait argumenter? Sauf que l'OP a exactement cela dans sa réponse SO. Si le champ est renommé, les utilisateurs du setter générique ne remarquez jamais, à l'exception du bug extrêmement obscur du setter qui ne fait rien en silence. Les utilisateurs du getter peuvent, s'ils sont chanceux, remarquer la sortie console de l'exception ignorée.)
Il s'agit essentiellement d'un grand sous-point de ce qui précède. Le code de réflexion concerne tout Object
. Les types sont vérifiés lors de l'exécution. Les erreurs sont découvertes par des tests unitaires, mais uniquement si vous avez une couverture. ("C'est juste un getter, je n'ai pas besoin de le tester.") Fondamentalement, vous perdez l'avantage en utilisant Java sur Python vous a gagné dans le première place.
Peut-être pas en théorie, mais en pratique, vous ne trouverez pas de machine virtuelle Java qui inline ou crée un cache en ligne pour Method.invoke
. Des appels de méthode normaux sont disponibles pour de telles optimisations. Cela les rend beaucoup plus rapides.
La recherche de méthode dynamique et la vérification de type nécessaires au code de réflexion sont plus lentes que les appels de méthode normaux. Si vous transformez ce getter à une ligne bon marché en une bête de réflexion, vous pourriez (je n'ai pas mesuré cela) examiner plusieurs ordres de grandeur de ralentissement.
C'est juste une mauvaise idée, car votre classe n'a plus d'encapsulation. Chaque champ dont il dispose est accessible. Autant les rendre tous publics.
Parce que les getter/setters pass-through sont une abomination qui n'apporte aucune valeur. Ils ne fournissent aucune encapsulation puisque les utilisateurs peuvent obtenir les valeurs réelles via les propriétés. Ils sont YAGNI parce que vous allez rarement, voire jamais, changer la mise en œuvre de votre stockage sur le terrain.
Et parce que c'est beaucoup moins performant. En C # au moins, un getter/setter s'alignera directement sur une seule instruction CIL. Dans votre réponse, vous remplacez cela par 3 appels de fonction, qui à leur tour doivent charger des métadonnées pour y réfléchir. J'ai généralement utilisé 10x comme règle de base pour les coûts de réflexion, mais lorsque j'ai recherché des données, l'une des premières réponses comprenait cette réponse qui la rapprochait de 1000x.
(Et cela rend votre code plus difficile à déboguer, et il nuit à la convivialité car il y a plus de façons de l'utiliser à mauvais escient, et il nuit à la maintenabilité parce que vous utilisez maintenant des chaînes magiques plutôt que des identifiants appropriés ...)
En plus des arguments déjà présentés.
Les utilisateurs s'attendent à appeler foo.getBar () et foo.setBar (valeur), pas foo.get ("bar") et foo.set ("bar", valeur)
Pour fournir l'interface que les utilisateurs attendent, vous devez écrire un getter/setter séparé pour chaque propriété. Une fois que vous avez fait cela, il est inutile d'utiliser la réflexion.