Pattern Matching en Java : Une (R)évolution

Pattern Matching en Java : Une (R)évolution

Dans cet article, nous explorerons les bases du pattern Matching en Java (jusqu’à Java 19) et découvrirons comment l’utiliser dans nos projets. 

De quoi s’agit-il ? 

À ne pas confondre avec l’analyse des expressions régulières, le « pattern matching » est une technique qui nous permet de faire correspondre des formes ou des structures de données spécifiques. Le mot « pattern » se réfère à la forme ou à la structure des données, tandis que « matching » désigne le processus d’identification et de réponse à cette forme spécifique. En d’autres termes, nous faisons correspondre la forme des données à un type ou à une valeur.  

En Java, le « pattern matching » fait partie du projet Amber d’OpenJDK,. Le même projet qui nous a apporté les blocs de texte, les enregistrements et les expressions switch. Sur le site du projet, vous pouvez suivre l’état des JEPs (Java Enhancement Proposal) liés au « pattern matching » et à d’autres. De manière plus concrète, il existe plusieurs patterns qu’on peut « matcher » : 

  • Types : faire correspondre une référence à un type.
  • Records : faire correspondre à la structure de l’enregistrement.
  • Classes : faire correspondre à la structure de n’importe quelle classe. 
  • Tableaux : faire correspondre à la structure/contenu d’un tableau. Nous verrons des exemples des deux premiers car nous pouvons les tester en mode preview de Java (19 au moment de la rédaction). 
  1.  

Pourquoi en avons-nous besoin ? 

Cette fonctionnalité permet principalement d’écrire un code plus efficace et expressif en éliminant les branches « if-else » verbeuses et le casting de types, comme nous le verrons dans les exemples ultérieurs. Cela apporte également une validation supplémentaire au compilateur, ce qui signifie moins d’erreurs potentielles à l’exécution.  

Selon José Paumard, le « pattern matching » sera la 3ème révolution dans le langage Java après l’introduction des génériques en Java 5 et des types de fonctions en Java 8. 

Où l’utiliser ? 

instanceof 

Avec la sortie de JEP 394 dans Java 16, le « pattern matching » avec instanceof est disponible.  

Tout d’abord, examinons un exemple de « pattern matching » de type avec une instanceof. Dans l’exemple suivant, nous pouvons voir la syntaxe à la ligne 5 et 7, en termes techniques, nous disons que nous faisons correspondre (matcher) la référence o de type Object aux patterns « Integer i » et « Double d » respectivement. On a donc les variables i et d disponibles sans besoin de les créer et faire un cast explicite. 

Le scope de i et d est dans le scope de l’instruction « if » à moins qu’il ne soit inversé comme suit : 

Ensuite, examinons le « pattern matching » avec des records, toujours dans le contexte d’une instanceof. Cela reste encore en preview (JEPs 405 et 432).  

Comme nous pouvons le voir, nous avons fait correspondre o au pattern du record « Pair(String k, String v) p », cela ressemble à la décomposition d’objets en Javascript. k et v feront référence aux champs du record et p fera référence à l’objet correspondant en tant que le record Pair. Cette syntaxe prend également en charge l’imbrication (nesting), donc si nous avons un record qui a un autre record en tant qu’attribut, il peut également être matché. Cependant, une imbrication excessive dans ce cas n’est pas une bonne idée. 

Switch Case 

Il s’agit toujours d’une fonctionnalité en version preview depuis Java 17 (JEP 406), et la quatrième version preview arrive dans JEP 433 prévue avec Java 20. Si nous réécrivons le premier exemple avec un switch-case, nous obtenons ceci : 

Notez que ceci est la forme améliorée de switch, ou switch expression. Mais, nous pouvons utiliser le pattern matching avec le vieux switch sans aucun problème. Lorsque l’objet référencé correspond au type du case, nous obtenons une nouvelle référence de ce type à utiliser. 

Quelques points à noter sur le pattern matching d’un switch : 

  1. Nous pouvons facilement gérer le cas null (ligne 3 ci-dessus). 
  2. Le compilateur peut vérifier l’exhaustivité des cas. Par exemple, supposons que nous basculions sur un sealed type (c’est-à-dire que les sous-types sont connus au moment de la compilation). Si les cas sont exhaustifs, il n’y a pas besoin de branche par défaut (default case), sinon le compilateur nous alerte sur les cas manquants. 
  3.  
  4. Lorsqu’un cas couvre les cas suivants, ce qui signifie qu’il représente une classe parente des cas suivants, comme dans l’exemple ci-dessous : 

Le compilateur se plaindra : “Label is dominated by a preceding case label ‘Number n’”, et donc ce code ne peut être compilé. 

« Case Refinement », telle que décrit dans le JEP, nous permet d’ajouter une condition à un cas comme on peut voir dans la ligne 4 ci-dessous.  

Le code de ces exemples se trouve sur https://github.com/maximz101/pattern-matching-java. Essayez-le vous-même !  

Références 

Maxime Alise, consultant Java chez novencia.

En réagissant à cet article, vous nous permettez d'affiner les contenus que nous publions ici !

  • Useful (1)
  • Interesting (1)
  • Boring (1)
  • Sucks (1)
  • Awesome (0)

Si cet article vous a plu, n’hésitez pas à le partager via

Ces articles peuvent également vous intéresser