Contexte d'utilisation
Dans le développement d'applications d'entreprise, il est fréquent de devoir transformer une liste d'objets plats provenant d'une base de données en une structure hiérarchique (arbre). Ce besoin se présente souvent pour l'affichage de menus, d'organigrammes ou de catégories de produits.
Modélisation de la base de données
La structure minimale pour gérer une hiérarchie repose sur l'auto-référencement d'une table. Voici les colonnes types :
id: Identifiant unique de l'enregistrement.parent_id: Référence à l'identifiant du parent (0 ou null pour les racines).label: Nom de l'élément.position: Ordre d'affichage au sein d'un même niveau.
Définition du modèle de données (DTO)
Pour représenter l'arbre en Java, nous utilisons une classe contenant une liste d'objets du même type pour stocker les branches descendantes.
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class DepartementNode implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private Long parentId;
private String nom;
private Integer ordre;
private List<DepartementNode> enfants;
}
Implémentation de la logique récursive
L'approche suivante utilise l'API Stream de Java pour filtrer les nœuds racines et déclencher la récursivité pour construire les branches.
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class ArborescenceService {
public List<DepartementNode> construireArbre(List<DepartementNode> tousLesNoeuds) {
// Extraction des nœuds racines (ceux dont le parentId est 0)
return tousLesNoeuds.stream()
.filter(node -> Objects.equals(node.getParentId(), 0L))
.map(node -> {
node.setEnfants(rechercherEnfants(node, tousLesNoeuds));
return node;
})
.sorted((n1, n2) -> n1.getOrdre() - n2.getOrdre())
.collect(Collectors.toList());
}
/**
* Méthode récursive pour peupler les niveaux inférieurs
*/
private List<DepartementNode> rechercherEnfants(DepartementNode parent, List<DepartementNode> catalogue) {
return catalogue.stream()
.filter(node -> Objects.equals(node.getParentId(), parent.getId()))
.map(node -> {
// Appel récursif pour chercher les petits-enfants
node.setEnfants(rechercherEnfants(node, catalogue));
return node;
})
.sorted((n1, n2) -> n1.getOrdre() - n2.getOrdre())
.collect(Collectors.toList());
}
}
Analyse de la méthode
Le processus se décompose en deux étapes majeures :
- Identification des racines : Nous isolons les éléments de niveau supérieur qui n'ont pas de parent.
- Exploration récursive : Pour chaque élément identifié, nous parcourons la liste complète pour trouver ses enfants directs. Le processus se répète de manière ascendante jusqu'à ce qu'aucune correspondance ne soit trouvée.
L'utilisation de Objects.equals est recommandée pour éviter les erreurs de comparaison entre objets Long. Le tri via sorted garantit que la structure finale respecte l'ordre défini en base de données.