Reflection

Capacité du langage à se décrire et se manipuler
NB : Il s'agit de reflection comme "refléter", et non pas de réfexion comme "réfléchir".
Java permet de manipuler toute classe que la JVM peut charger en mémoire : nos classes, les classes de l'api java, les classes des libs chargées ...

ReflectiveOperationException

Exception à utiliser lorsqu'on travaille avec la reflection.
public class java.lang.ReflectiveOperationException extends Exception implements Serializable
Direct Known Subclasses:
ClassNotFoundException IllegalAccessException InstantiationException InvocationTargetException NoSuchFieldException NoSuchMethodException

Analyser une classe

Les classes étant le type le plus utilisé en java, java.lang.Class (qui représente une classe) est une classe centrale de la reflection.

On peut connaître la Class de tout objet via getClass(), définie dans java.lang.Object.

Création

Class.forName()

Manière habituelle de créer un objet de type Class.
Class<?> c = Class.forName("java.util.Scanner");

Suffixe .class

Raccourci syntaxique : un type java suivi de .class représente une instance de java.lang.Class.
Class<?> c = String[].class; // Describes the array type String[]                                                                    
Class<?> c = Runnable.class; // Describes the Runnable interface
Class<?> c = int.class;      // Describes the int type
Class<?> c = void.class;     // Describes the void type

Nom de la classe

String getName()             // [Ljava.lang.String;  => nom bizarre
String getCanonicalName()    // java.lang.String[]   => nom lisible
String getSimpleName()       // String[]
String getTypeName()         // java.lang.String[]
String toString()            // class [Ljava.lang.String;
String toGenericString()     // public abstract final class [Ljava.lang.String;

Autres infos

Class<? super T>    getSuperclass()
Class<?>[]          getInterfaces()
Package             getPackage()
int                 getModifiers()
boolean isPrimitive()
boolean isArray()
boolean isEnum()
boolean isAnnotation()
boolean isMemberClass()
boolean isLocalClass()
boolean isAnonymousClass()
Class<?>        getDeclaringClass()
Class<?>        getEnclosingClass()
Constructor     getEnclosingConstructor()
Method          getEnclosingMethod()
Tableau des champs, méthodes et constructeurs publics de la classe
Incluent ceux hérités des superclasses.
Constructor<?>[]    getConstructors()
Field[]             getFields()
Method[]            getMethods()
Tableau des champs, méthodes et constructeurs déclarés dans la classe ;
renvoie les membres private, package, protected.
Ne renvoie pas les membres des super-classes.
Constructor<?>[]    getDeclaredConstructors()
Field[]             getDeclaredFields()
Method[]            getDeclaredMethods()

Exemple : lister les méthodes d'une classe

Class<?> c = Class.forName("java.lang.reflect.Method");
System.out.println("===== Methods of : " + c.getCanonicalName() + " =====");
for(Method m : c.getDeclaredMethods()){
    System.out.println();
    String modifiers = Modifier.toString(m.getModifiers());
    System.out.print(modifiers == "" ? "void" : modifiers);
    System.out.print(" " + m.getReturnType().getSimpleName());
    System.out.print(" " + m.getName());
    // params
    System.out.print("(");
    List<String> params = new ArrayList<>();
    for(Parameter param : m.getParameters()){
        params.add(param.getType().getSimpleName());
    }
    System.out.print(String.join(", ", params));
    System.out.print(")");
}
System.out.println();
(code dans ClassMethods.java)
java ClassMethods 
===== Methods of : java.lang.reflect.Method =====

public transient Object invoke(Object, Object[])
public boolean equals(Object)
public String toString()
etc.

Détails sur les membres d'une classe

Les classes Field, Method, and Constructor de java.lang.reflect décrivent les champs, méthodes et constructeurs d'une classe.
Ces 3 classes ont une méthode getName() qui retourne le nom du membre.

java.lang.reflect.Method

Pour obtenir toutes sortes d'informations sur les méthodes, par exemple :
Class<?>     getDeclaringClass()
int          getModifiers()
int 	     getParameterCount()
Class<?>[]   getParameterTypes()
Class<?>     getReturnType()
getModifiers() renvoie un entier exprimant les modifiers de la méthode (public, abstract final etc.).
Utilisable avec les méthodes statiques de la classe java.lang.reflect.Modifiers :
isAbstract(int mod)     isPublic(int mod)
isFinal(int mod)        isStatic(int mod)
isInterface(int mod)    isStrict(int mod)
isNative(int mod)       isSynchronized(int mod)
isPrivate(int mod)      isTransient(int mod)
isProtected(int mod)    isVolatile(int mod)

Divers

Inspecter un objet

La méthode get() de Field permet de connaître la valeur d'un champ.
Object obj = new String("toto");
try{
    for (Field f : obj.getClass().getDeclaredFields()) {
        f.setAccessible(true);
        Object value = f.get(obj);
        System.out.println(f.getName() + ":" + value);
    }
}
catch(ReflectiveOperationException e){
    e.printStackTrace();
}
(code dans InspectObject.java)

Invoquer une méthode

Dans la classe Method, on utilise la méthode public Object invoke(Object obj, Object... args).
Invoque la méthode représentée par cet objet (mettre null pour des variables statiques).
import java.lang.reflect.*;

class Person{
    private String name;
    public Person(String name){ this.name = name; }
    public String getName(){ return name; }
    public void setName(String name){ this.name = name; }
}

public class InvokeMethod {
    
	public static void main(String[] args) {
	    try{
            Person p = new Person("toto");
            
            Method m;
            
            m = p.getClass().getMethod("getName");
            Object name = m.invoke(p);
            System.out.println(name);
            
            m = p.getClass().getMethod("setName", String.class);
            m.invoke(p, "titi");
            System.out.println(p.getName());
	    }
        catch(ReflectiveOperationException e){
            e.printStackTrace();
        }
    }
}

(code dans InvokeMethod.java)

Construire un objet

Exemple : invoquer le constructeur de Person qui prend une String en paramètre.
Class c = Person.class;
Constructor constr = c.getConstructor(String.class); // varargs
Object obj = constr.newInstance("tutu");
System.out.println(((Person)obj).getName());
(code aussi ans InvokeMethod.java)

Attention, bien utiliser Constructor.newInstance() et non pas Class.newInstance() (a été déprécié en java 9).

Infos sur le fichier .java courant

Equivalent java de php pour :
                                          PHP               Java
                                        -------------------------
Current line number of the file         __LINE__ 	    System.out.println(Thread.currentThread().getStackTrace()[1].getLineNumber());
Full path and filename of current file  __FILE__ 	    ????
Directory of current file               __DIR__ 	    this.getClass().getProtectionDomain().getCodeSource().getLocation().toExternalForm()
Current class name                      __CLASS__ 	    this.getClass().getCanonicalName()
Current method name                     __METHOD__ 	    ????
Current namespace                       __NAMESPACE__       this.getClass().getPackage().getName()

Exercice / TD
Utilisation de la reflection avec le pattern Command