Définir une structure arborescente en Java

Un petit mot pour vous parler d'une découverte que j'ai récemment faite à propos du langage Java.

Si vous avez déjà développé une interface graphique, vous savez qu'il est fastidieux la déclarer. Il est souvent difficile de suivre le déroulement de la construction car le modèle impératif du langage Java ne s'accorde pas bien avec la structure arborescente d'une interface graphique.

Voici maintenant une solution pour rendre beaucoup plus lisible tous vos codes déclarant des arbres ou des maps !

Le problème avec les structures arborescente est qu'il faut construire un élément et également le rattacher à son parent. Ces deux actions sont parfois difficiles à discerner dans un langage impératif comme Java. Un petit exemple :

JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

JPanel panel = new JPanel();
frame.setContentPane(panel);

JButton button1 = new JButton("Click me");
button1.addActionListener(new ActionListener() 
{
	public void actionPerformed(ActionEvent e)
	{
	 	System.out.println("Clicked");
	}
});
panel.add(button1);

JButton button2 = new JButton("No ! Click me instead");
button2.addActionListener(new ActionListener() 
{
	public void actionPerformed(ActionEvent e)
	{
	 	System.out.println("Really clicked !");
	}
});
panel.add(button2);

frame.setVisible(true);

Voici (en gros) un code Java permettant de créer une fenêtre contenant deux boutons et de l'afficher. On voit clairement que le développeur à choisi de regrouper chaque élément graphique lors de sa création, la construction de la structure de l'arbre étant laissé à la dernière ligne de chaque bloc (setContentPane et add). Le problème ici est que la structure n'est pas très visible et le développeur aura de forte chance de se tromper dans la construction de son arborescence. Un autre problème est que l'ajout d'un enfant à un parent se fait au moment de la construction de l'enfant, la structure du parent est donc disséminé sur ses enfants.

Voici une autre version du même code :

JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

JPanel panel = new JPanel();

JButton button1 = new JButton("Click me");
button1.addActionListener(new ActionListener() 
{
 	public void actionPerformed(ActionEvent e)
	{
		System.out.println("Clicked");
	}
});

JButton button2 = new JButton("No ! Click me instead");
button2.addActionListener(new ActionListener() 
{
 	public void actionPerformed(ActionEvent e)
	{
		System.out.println("Really clicked !");
	}
});


frame.setContentPane(panel);
panel.add(button1);
panel.add(button2);
frame.setVisible(true);

Cette fois-ci, le développeur a choisi d'initialiser d'abord tous ses composants graphiques puis de finalement construire l'arborescense dans un dernier paragraphe. L'arborescence est ici plus lisible puisqu'un paragraphe lui est dédiée. La structure d'un parent est compacte étant donné que l'initialisation des enfants est déjà faite au dessus. Mais les initialisations des composants sont du coup loin de leur utilisation, ce qui pourrait amener le développeur à oublier de supprimer l'initialisation d'un composant après l'avoir supprimé de l'arborescence. C'est également problématique en phase de conception où le développeur devra passer sans cesse de la construction de l'arborescence à l'initialisation des composants, pas forcément proches (cet exemple est volontairement court). On pourrait également discuter de la lisibilité de l'arborescence puisqu'aucun indice ne nous permet de rapidement comprendre la structure.

Voici la version que je propose :

new JFrame()
{{
	this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	this.setContentPane(new JPanel()
	{{
		this.add(new JButton("Click me")
		{{
			this.addActionListener(new ActionListener() 
			{
				public void actionPerformed(ActionEvent e)
				{
					System.out.println("Clicked");
				}
			});
		}});
		this.add(new JButton("No ! Click me instead")
		{{
			this.addActionListener(new ActionListener() 
			{
				public void actionPerformed(ActionEvent e)
				{
					System.out.println("Really clicked !");
				}
			});
		}});
	}});
	this.setVisible(true);
}};

Certains se demandent déjà si c'est vraiment du Java ? Oui c'est du Java. L'astuce se situe dans la double accolade, il s'agit en fait d'une simple accolade permettant de déclarer une classe anonyme, je dérive donc chaque classe que j'instancie. Cette classe anonyme me permet de déclarer un bloc d'initialisation à l'intérieur. N'ayant pas la possibilité de définir un constructeur, je passe par un bloc d'initialisation, principe équivalent à un constructeur en Java. La double accolade est donc un bloc d'initialisation d'une classe anonyme, c'est à dire du code qui va s'exécuter pendant le new.

L'intérêt de cette technique est qu'elle permet de visualiser l'arborescence grâce à l'indentation tout en conservant l'unité de l'initialisation des composants. L'inconvénient est le nombre de classes anonymes générées qui va augmenter le nombre de fichiers class compilés.

Cette technique est pratique pour définir des arborescences comme une interface graphiques mais elle a d'autres avantages. Il est également possible de l'utiliser pour déclarer un objet complexe sans effort, par exemple si je souhaite déclarer une Map non-modifiable et final :

De base, je ferais :

Map mapLetters = new TreeMap();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);

final Map LETTERS = Collections.unmodifiableMap(mapLetters);

Pas mal, mais peut mieux faire. Notamment cette map temporaire que je suis obligé de nommer, forcément avec un nom qui ne convient pas :

final Map LETTERS = Collections.unmodifiableMap(new TreeMap()
{{
	this.put("A", 1);
	this.put("B", 2);
	this.put("C", 3);
}});

Voilà qui est mieux, j'arrive à me passer de la déclaration de la map temporaire, tout simplement parce que j'arrive à déclarer une map et à la remplir sans la nommer, c'est bien plus lisible ! On dirait presque que le langage Java supporte les maps nativement, ce qui le rapproche un peu plus des langages fonctionnels à la mode comme Ruby ou Javascript.

J'espère que ce petit interlude vous aura fait découvrir de nouveaux aspects du langage Java que j'ai moi-même découvert depuis que je m'intéresse aux langages fonctionnels. Comme quoi, il faut parfois allez voir ailleurs :)

Haut de page