Bonjour,
Je viens d'écrire un petit article qui présente la classe EventHandler permettant de simplifier l'écriture de listeners.
Simplifier l'écriture des listeners avec EventHandler.
Bonne lecture.
le y@m's
Bonjour,
Je viens d'écrire un petit article qui présente la classe EventHandler permettant de simplifier l'écriture de listeners.
Simplifier l'écriture des listeners avec EventHandler.
Bonne lecture.
le y@m's
Merci pour cet article le y@m's
Il y a là un avantage esthétique évident, mais j'avoue que je préfère personnellement éviter de telles surcouches juste pour cette raison.
En effet si le code est destiné a être maintenu par d'autre, j'ai personnellement eu la douleur d'avoir à gérer/comprendre de nombreuses surcouches "inutiles".
Comme il a été mentionné en introduction, la raison n'est pas que purement esthétique.
En plus de la lisibilité (qui est bien plus que de l'esthétique, surtout quand on maintient du code), la création des listeners avec la classe EventHandler permet aussi de réduire l'empreinte disque et mémoire de son application (particulièrement avec les grandes applications).
J'ai moi aussi fait beaucoup de maintenance de code et la lisibilé, avec la documentation, est un critère primordial. En fait je dirais que le cas de la maintenance de code est le meilleur argument pour l'utilisation de l'EventHandler.
Tu parles de la douleur de maintenir du code avec beaucoup de couches inutiles, ce que je comprends, mais là la "surcouche" est très faible. On est loin d'un gros framework nécessitant plusieurs heures d'apprentissage, c'est une simple classe standard de type factory appréhendable en 5 min grace à notre amie la javadoc.
Le gain de lisibilité (déjà maintenu des classes de plusieurs milliers de lignes avec plusieurs dizaines de classes anonymes, c'est assez douloureux aussi ) est quand même très largement supérieur au "coût" .
A noter qu'avec java 8 ça sera faisable bien plus proprement et efficacement grâce aux références de méthode.
Pour reprendre un exemple du tuto:Deviendra :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { helloworld.sayHello(); } });
Code : Sélectionner tout - Visualiser dans une fenêtre à part button.addActionListener(helloworld::sayHello);
Merci pour l'article, ca peut etre interessant. Concernant la lisibilité, je pense qu'il suffit de regarder le code pour comprendre comment ca marche. Quoique en lisant l'article, le passage de parametres ne me parait pas tres intuitif. Ceci dit, ca rend le code plus concis, ce qui est bien.
En revanche, on parle d'empreinte mémoire mais on parle bien du jar généré, non ? Si oui, il faudrait voir l'efficacité à l'execution et l'impact sur la consommation de mémoire. C'est efficace à ce niveau la ?
Ca commence a ressembler à du C++/Csharp. Bref, ce qui voulait etre évité lors de la création du langage, ca, non ?
En effet, la Java a toujours souhaité éviter certaines fonctionnalités dangereuses du C++ comme les goto, l’héritage multiple, ... Mais il ne me semble pas que Java ait jamais explicitement refusé les références de méthode.
En tout cas par rapport aux classes anonymes et à l'introspection, ça devrait être carrément plus avantageux autant en terme de performances que de lisibilité.
Le gain se fait et sur l'empreinte mémoire et sur la taille du jar.
En effet, en utilisant des classes anonymes, chaque classe donnera un fichier .class supplémentaire (du genre MaClasse$1.class, MaClasse$2.class, etc.). On aura donc autant de .class suplémentaires que de classes anonymes. Cela augmente donc la taille du jar.
Ensuite toutes ces classes doivent être chargées par la jvm pour pouvoir ensuite être instanciées. Cela augmente donc l'empreinte mémoire.
En utilisant l'EventHandler, les classes sont construitent dynamiquement via un proxy, il n'y a donc pas de fichier .class supplémentaire dans le jar.
Ensuite une seule classe est créée (et donc chargée en mémoire) par type de listener ce qui diminue l'empreinte mémoire.
Je n'ai fait aucune mesure pour quantifier le gain. Il est cependant clair qu'il est minime pour de petites applications mais que plus le nombre de listeners augmente, plus il sera significatif .
J'ai failli le dire (mais je me suis censuré) : A quand le goto en java ?
Autant pour le jar, ca me parait normal, autant ca m'etonne qu'une instance ne soit pas créé à chaque utilisation de l'EventHandler. C'est sur, ca ? Parce que ca suppose qu'on doit trainer des references de tous les objets sur lesquels on a mis un listener pour bien router... Ca me parait compliqué (surtout qu'il y a des risques de garder des references sur des objets que le GC ne collectera plus)...
Il ne s'agit pas des instance des listeners (il y en a forcément une par listener) mais des classes elle même, des implémentations.
Soit le code suivantqui affiche quelque chose comme :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14 ActionListener al1 = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { action1(); } }; ActionListener al2 = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { action2(); } }; System.out.println(al1.getClass()); System.out.println(al2.getClass());Il y a bien deux classes différentes.class test.Test$1
class test.Test$2
Maintenant avec
le résultat devient :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 ActionListener al1 = EventHandler.create(ActionListener.class, this, "action1"); ActionListener al2 = EventHandler.create(ActionListener.class, this, "action2"); System.out.println(al1.getClass()); System.out.println(al2.getClass());
Il n'y a plus qu'une seule classe .class $Proxy0
class $Proxy0
C'est le nombre d'implémentation qui se limite à un par type de listener, pas le nombre d'instance de listener .
Or comme ces classes sont chargées par la jvm (instances de la classe Class<?>), on obtient donc bien une réduction de l'empreinte mémoire en réduisant ce nombre de classes.
Ok, j'avais pas pensé à ce gain. Ca doit pas etre enorme mais c'est toujours ca de pris. Si les performances ne sont pas impactées, ca peut servir en attendant java 8
Le goto existe déjà plus ou moins avec les break sur un label .
Java n'a rien contre intégrer des nouvelles fonctionnalités si elles ne posent pas de problème particulier.
Dans le cas des Références de méthodes, les deux alternatives que sont les classes anonymes et l'introspection sont à la fois plus couteuses en ressources, carrément moins lisibles et source de complexité supplémentaire. Il n'y a pas vraiment de raison de s'y opposer.
Les performances sont impactées car un appel de méthode par introspection est plus lent qu'un appel direct.
A voir si le plus important est la consommation mémoire ou la vitesse de déclenchement des action.
De toute façon dan les deux cas, il est fort probable que la différence ne se remarquera pas.
Bah comme raison, je vois au moins la logique du tout objet. Mais bon, je suis d'accord que c'est un débat philosophique plus que technique. Débat qui a d'ailleurs probablement eu lieu lors de la sortie de la premiere version de java (puisque les pointeurs de fonction existaient deja depuis longtemps) et qui a été tranché en faveur des classes anonymes (à l'évidence). Mais bon, on a le droit de changer d'avis. Surtout que comme tu le dis, ca simplifie l'ecriture et la relecture. De plus, des langages plus recents l'utilisent (par exemple csharp)...
Ca impacte le moment ou on ajoute le listener, ca, c'est sur. Par contre, pour l'execution (qui est à mes yeux le plus important), c'est moins sur. A la lecture de l'article, je dirais que ca a un impact négatif vu comment sont utilisés les parametres. Mais comme précisé, ca doit pas non plus beaucoup jouer...
Un petit problème avec ce EventHandler, c'est que du coup le compilateur ne vérifie pas si la méthode "foo" existe. L'erreur se verra à l'exécution, pas à la compilation.
Raison de plus d'y préférer les références de méthode, mais bon, face à l'horreur des classes anonymes, je crois que ce petit défaut sera facilement accepté.
En effet, d'ailleurs la solution retenue pour le références de méthode a pris soin de rester compatible avec la philosophie (presque)tout objet, car la référence de méthode est convertie implicitement en objet au final.
De ce que je comprend, l'introspection intervient au moment du déclenchement de la méthode des listeners, c'est pourquoi ça peut avoir un impact non négligeable dans des cas où il se déclencherait extrêmement souvent.
D'après un petit test fait chez moi un method.invoke() est 30 fois plus lent qu'un appel classique
J'ajouterais que pour les feignants (comme moi), c'est plus compliqué de suivre le code (on peut pas faire F3 dans Eclipse)
J'ai jamais fait de test mais j'aurais pas pensé que c'etait si lent
Je m'attendais à ce que la création de l'objet Method ne soit pas super rapide mais une fois créé, j'aurais pensé que l'invocation ne serait pas lente...
En tout cas, merci pour l'info je garderais ca en tete au besoin...
Je ne sais pas comment tu as fait ton test mais je n'ai absolument pas autant de différence.
Pour un traitement de 100ms, l'invocation par classe anonyme est de 100ms et avec l'EventHandler 120ms.
Pour un traitement de 200ms, l'invocation par classe anonyme est de 100ms et avec l'EventHandler 220ms.
Donc le surcoût est relativement faible.
Tu n'aurais pas fait une boucle par hasard ? (dans ce cas effectivement c'est normal que la différence soit si grande)
Ce qui compte c'est l'expérience utilisateur. Même un x30 peut ne pas être un problème.
Une invocation de 10ms, avec un facteur de x30 devient 300ms. Cela peut rester acceptable pour un clic bouton.
Tout dépendant de l'utilisation, il ne faut pas avoir peur d'allonger un peu un temps d'exécution si celui-ci reste acceptable (non perçu par l'utilisateur, en dessous de limites contractuelles, etc.).
Oui mais pourquoi tu crées un objet Method ?
Ce n'est pas ce que va faire l'EventHandler. L'EventHandler va se servir des string pour récupérer les méthodes sur les objets (target pour l'action et event pour la propriété).
Fait le test en comparant vraiment ce dont il est question (l'appel à la méthode du listener) .
Voici par exemple le code de mon test :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 public class Test { public void doAction(Object source) { try { Thread.sleep(200); } catch (InterruptedException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } } /** * @param args the command line arguments */ public static void main(String[] args) { final Test test = new Test(); ActionListener al1 = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { test.doAction(e.getSource()); } }; ActionListener al2 = EventHandler.create(ActionListener.class, test, "doAction", "source"); ActionEvent evt = new ActionEvent(test, 0, null); long start = System.currentTimeMillis(); al1.actionPerformed(evt); long elapsed = System.currentTimeMillis() - start; System.out.println("Anonymous, elapsed = " + elapsed); start = System.currentTimeMillis(); al2.actionPerformed(evt); elapsed = System.currentTimeMillis() - start; System.out.println("EventHandler, elapsed = " + elapsed); } }
Partager