Utilisations
La réflexion aide les programmeurs à créer des bibliothèques logicielles génériques pour afficher des données, traiter différents formats de données, effectuer la sérialisation et la désérialisation de données pour la communication, ou encore le regroupement et le dégroupage de données pour des conteneurs ou des flux de communication. Les systèmes réflexifs peuvent fournir un type miroir , utilisé comme reflet découplé d'un objet ou d'un identifiant.
L'utilisation efficace de la réflexion nécessite presque toujours un plan : un cadre de conception, une description de l'encodage, une bibliothèque d'objets, une carte d'une base de données ou des relations entre entités.
La réflexion rend un langage plus adapté au code orienté réseau. Par exemple, elle permet à des langages comme Java de fonctionner efficacement sur les réseaux en fournissant des bibliothèques pour la sérialisation, le regroupement et la gestion de formats de données variés. Les langages sans réflexion, tels que le C, doivent utiliser des compilateurs auxiliaires pour des tâches comme la notation syntaxique abstraite ( ASN) afin de générer le code nécessaire à la sérialisation et au regroupement.
La réflexion permet d'observer et de modifier l'exécution d'un programme en temps réel . Un composant de programme orienté réflexion peut surveiller l'exécution d'un bloc de code et se modifier en fonction de l'objectif visé par ce bloc. Ceci est généralement réalisé en assignant dynamiquement du code au cours de l'exécution.
Dans les langages de programmation orientés objet comme Java , la réflexion permet d'inspecter les classes, les interfaces, les champs et les méthodes à l'exécution sans connaître leurs noms à la compilation . Elle permet également l'instanciation de nouveaux objets et l'appel de méthodes.
La réflexion est souvent utilisée dans le cadre des tests logiciels , notamment pour la création/instanciation à l'exécution d' objets factices .
La réflexion est également une stratégie clé pour la métaprogrammation .
Dans certains langages de programmation orientés objet comme C# et Java , la réflexion permet de contourner les règles d'accessibilité des membres . Pour les propriétés C#, cela s'effectue en écrivant directement dans le champ de stockage (généralement invisible) d'une propriété non publique. Il est également possible de trouver les méthodes non publiques des classes et des types et de les appeler manuellement. Ceci fonctionne aussi bien pour les fichiers internes au projet que pour les bibliothèques externes telles que les assemblys .NET et les archives Java.
Mise en œuvre
Ces fonctionnalités peuvent être implémentées de différentes manières. En programmation orientée objet ( MOO) , la réflexion fait partie intégrante du langage de programmation courant. Lors de l'appel de verbes (méthodes), diverses variables, telles que verble nom du verbe appelé et thisl'objet sur lequel il est appelé, sont initialisées afin de fournir le contexte de l'appel. La sécurité est généralement gérée par l'accès programmatique à la pile d'appel : cette pile contenant callers()la liste des méthodes ayant finalement appelé le verbe courant, l'exécution de tests sur callers()[0]la commande invoquée par l'utilisateur initial permet au verbe de se protéger contre toute utilisation non autorisée.
Les langages compilés s'appuient sur leur environnement d'exécution pour obtenir des informations sur le code source. Un exécutable Objective-C compilé , par exemple, enregistre les noms de toutes les méthodes d'un bloc, fournissant une table permettant de les associer aux méthodes sous-jacentes (ou aux sélecteurs de ces méthodes) compilées dans le programme. Dans un langage compilé prenant en charge la création de fonctions à l'exécution, tel que Common Lisp , l'environnement d'exécution doit inclure un compilateur ou un interpréteur.
La réflexion peut être implémentée pour les langages dépourvus de réflexion intégrée en utilisant un système de transformation de programmes pour définir des modifications automatisées du code source.
Considérations de sécurité
La réflexion peut permettre à un utilisateur de créer des chemins d'exécution inattendus au sein d'une application, contournant potentiellement les mesures de sécurité. Cette vulnérabilité peut être exploitée par des attaquants. Des failles historiques dans Java, dues à une réflexion non sécurisée, permettaient à du code récupéré depuis des machines distantes potentiellement non fiables de s'échapper du mécanisme de sécurité du bac à sable Java . Une étude à grande échelle portant sur 120 vulnérabilités Java en 2013 a conclu que la réflexion non sécurisée est la vulnérabilité la plus courante dans Java, bien que ce ne soit pas la plus exploitée.
performances d'exécution
Les systèmes de réflexion à l'exécution introduisent une surcharge de performance non négligeable . Par exemple, en Java , les opérations de réflexion étant résolues dynamiquement à l'exécution, de nombreuses optimisations du compilateur et de la JVM (comme l'inclusion de méthodes , la liaison statique et la spécialisation JIT agressive) ne peuvent être pleinement appliquées. Par conséquent, les appels réflexifs sont généralement plus lents que leurs équivalents résolus statiquement. Des études de microbenchmarks et d'applications sur Java ont montré que les opérations réflexives peuvent engendrer une surcharge d'exécution substantielle, notamment pour l'appel de méthodes et la création dynamique d'objets, avec des ralentissements allant de 20 à 40 % dans les cas modérés à plus de 300 fois dans les scénarios de dispatch fortement réflexifs. Dans les applications séquentielles, la réflexion peut augmenter considérablement le temps d'exécution et la consommation de mémoire lorsqu'elle est utilisée dans des chemins de code critiques en termes de performances. Dans les applications multithreadées , les implémentations réflexives préservent généralement l'évolutivité , mais présentent tout de même des temps d'exécution absolus sensiblement plus élevés — généralement entre 1,5x et 10x plus lents — que le code non réflexif équivalent.
Dans des langages comme C++ et Rust , la réflexion est effectuée à la compilation . Les systèmes de réflexion à la compilation sont moins performants que les systèmes de réflexion à l'exécution comme ceux de Java et C#, mais n'entraînent aucune surcharge à l'exécution puisqu'ils sont effectués lors de la compilation.
Exemples
Les extraits de code suivants créent une instanceclasseméthodelangage de programmation , les séquences d'appel classiques et par réflexion sont présentées.
Common Lisp
Voici un exemple en Common Lisp utilisant le système d'objets Common Lisp :
C
La réflexion n'est pas possible en C , bien que certaines parties de la réflexion puissent être émulées.
C++
Voici un exemple en C++ (utilisant la réflexion ajoutée dans C++26 ).
C#
Voici un exemple en C# :
Delphi, Object Pascal
Cet exemple Delphi et Object Pascal suppose qu'une classe TFoo a été déclarée dans une unité appelée Unit1 :); Instance foo = eInstance_New ( fooClass ); Method m = eClass_FindMethod ( fooClass , "hello" , fooClass . module ); (( void ( * )())( void * ) m . function )( foo );
Aller
Voici un exemple en Go :
Java
Voici un exemple en Java :
Java fournit également une classe interne (non officiellement incluse dans la bibliothèque de classes Java ) dans le modulejdk.unsupported `java.json` , sun.reflect.Reflectionutilisée par ` java.json` sun.misc.Unsafe. Elle contient une méthode `get` permettant d'obtenir la classe effectuant un appel à une profondeur spécifiée. Cette classe est désormais remplacée par la classe `java.json` et sa méthode ` get` .JavaScript :
importer 'reflect-metadata' ;// Sans réflexion const foo : Foo = new Foo (); foo . hello ();// Avec réflexion const foo : Foo = Reflect . construct ( Foo ); const hello : ( this : Foo ) => void = Reflect . get ( foo , 'hello' ) as ( this : Foo ) => void ; Reflect . apply ( hello , foo , []);// Avec eval eval ( 'new Foo().hello()' );
Julia
Voici un exemple en Julia :
julia> struct Point x :: Int y end# Inspection avec réflexion julia> fieldnames ( Point ) (:x, :y)julia> fieldtypes ( Point ) (Int64, Any)julia> p = Point ( 3 , 4 )# Accès par réflexion julia> getfield ( p , :x ) 3
Kotlin
Utilisation de la réflexion Java :
Utilisation de Kotlin pur :
Objectif-C
Voici un exemple en Objective-C , impliquant l' utilisation du framework OpenStep ou Foundation Kit :
Perl
Voici un exemple en Perl :
# Sans réflexion, mon $foo = Foo -> nouveau ; $foo -> bonjour ;# ou Foo -> nouveau -> bonjour ;# Par réflexion, ma $class = "Foo" ma $constructeur = "new" ; ma $méthode = "hello" ;mon $f = $class -> $constructeur ; $f -> $méthode ;# ou $classe -> $constructeur -> $méthode ;# avec eval eval "new Foo->hello;" ;
PHP
Voici un exemple en PHP :
// Sans réflexion $foo = new Foo (); $foo- > hello ();// Avec réflexion, en utilisant l'API Reflections $reflector = new ReflectionClass ( "Foo" ); $foo = $reflector -> newInstance (); $hello = $reflector -> getMethod ( "hello" ); $hello -> invoke ( $foo );
Python
Voici un exemple en Python :
from typing import Anyclasse Foo : # ... def print_hello ( self ) -> None : print ( "Bonjour, monde !" )if __name__ == "__main__" : # Sans réflexion obj : Foo = Foo () obj . print_hello ()# Avec réflexion obj : Foo = globals ()[ "Foo" ]() _ : Any = getattr ( obj , "print_hello" )()# Avec eval eval ( "Foo().print_hello()" )
R
Voici un exemple en R :
Rubis
Voici un exemple en Ruby :
Rouiller
Voici un exemple en Rust (utilisant des macros procédurales ).
// Une macro de base qui associe une chaîne de caractères représentant le nom d'une méthode à un appel de méthode réel. macro_rules! invoke_method { ( $instance : expr , $method_name : expr ) => { match $method_name { "print_hello" => $instance . print_hello (), _ => eprintln! ( "Une erreur s'est produite : Méthode introuvable." ), } }; }struct Foo ;impl Foo { fn print_hello ( & self ) { println! ( "Bonjour, monde !" ); } }fn main () { let foo = Foo ;// Séquence d'appel normale foo . print_hello ();// Séquence de style réflexion via un mappage à la compilation let method_to_call = "print_hello" ; invoke_method ! ( foo , method_to_call ); }
Rapide
La réflexion Swift est en lecture seule, et l'invocation dynamique n'est donc possible que via Objective-C .
Le système de réflexion de Swift, avec Swift.Mirror, est beaucoup plus limité dans sa portée :
Xojo
Voici un exemple utilisant Xojo :