Approche fonctionnelle vs orientée objet en Java : que choisir pour le développeur moderne ?
\n\nLe monde Java est un champ de bataille entre deux géants. D'un côté, la programmation orientée objet (POO) classique, sur laquelle toute une génération de développeurs a grandi. De l'autre, l'approche fonctionnelle qui gagne du terrain, entrée dans l'écosystème avec la sortie de Java 8 et qui suscite encore des débats : « En avons-nous vraiment besoin ? ».
\n\nSi vous codez en Java, vous êtes confronté quotidiennement à ce choix. Faut-il créer une classe entière avec des méthodes pour un simple filtrage ? Ou plutôt enchaîner des lambdas et oublier ? Dans cet article, nous ne prendrons pas parti. Nous analyserons les deux approches avec des exemples concrets, les comparerons selon des métriques clés et vous aiderons à comprendre quand utiliser quel outil.
\n\nPetit historique : comment en sommes-nous arrivés là
\n\nJava a été conçu à l'origine comme un langage purement orienté objet. Tout est objet. Même une chaîne de caractères est un objet de la classe String. Classes, héritage, polymorphisme – c'était la religion. Mais avec le temps, les développeurs ont compris : la POO modélise bien le monde réel (voitures, employés, commandes), mais gère très mal le traitement de flux de données.
C'est alors que les expressions lambda et le Stream API ont fait leur apparition. Java n'est pas devenu un langage fonctionnel (comme Haskell ou Scala), mais a emprunté le meilleur de ce paradigme. Désormais, nous avons le choix : écrire une boucle for à l'intérieur d'une méthode de classe ou utiliser filter().map().collect().
Approche orientée objet : un grand classique
\n\nLa POO en Java repose sur quatre piliers : l'encapsulation, l'héritage, le polymorphisme et l'abstraction. Lorsque vous travaillez avec une logique métier qui comporte des entités (Utilisateur, Commande, Produit), la POO est le choix naturel.
\n\nVoici un exemple de tâche classique : trouver le total de toutes les commandes d'un client effectuées en 2024.
\n\nSolution orientée objet :
\n\nimport java.util.List;\n\npublic class OrderService {\n\n public double calculateTotal2024(List<Order> orders) {\n double total = 0.0;\n for (Order order : orders) {\n if (order.getYear() == 2024) {\n total += order.getAmount();\n }\n }\n return total;\n }\n}\n\nclass Order {\n private int year;\n private double amount;\n\n public Order(int year, double amount) {\n this.year = year;\n this.amount = amount;\n }\n\n public int getYear() { return year; }\n public double getAmount() { return amount; }\n}\n\nTout est transparent, lisible, facile à déboguer. N'importe quel débutant comprendra ce qui se passe. Mais il y a un bémol : le code est impératif. Nous disons comment faire (crée une variable, parcours la liste, vérifie la condition, additionne).
\n\nApproche fonctionnelle : écrivons de manière déclarative
\n\nLa programmation fonctionnelle en Java, c'est ce que nous voulons obtenir, et non comment. Nous utilisons des fonctions d'ordre supérieur, l'immutabilité et l'absence d'effets secondaires.
\n\nLa même tâche, mais en style fonctionnel :
\n\nSolution fonctionnelle :
\n\nimport java.util.List;\n\npublic class OrderServiceFunctional {\n\n public double calculateTotal2024(List<Order> orders) {\n return orders.stream()\n .filter(order -> order.getYear() == 2024)\n .mapToDouble(Order::getAmount)\n .sum();\n }\n}\n\nSeulement trois lignes. Pas de variables mutables, pas de boucles. Nous disons simplement : « Prends le flux, filtre par année, prends les montants, additionne ». C'est un style déclaratif.
\n\nComparaison : où chaque approche l'emporte-t-elle ?
\n\nDétaillons dans quels scénarios chaque approche se montre la meilleure.
\n\n1. Lisibilité et concision
\nGagnant : Approche fonctionnelle