API Reflection

Manipulation

Après la présenation de l'API, nous allons voir comment l'utiliser à travers des exmples concrets. Nous verrons également qu'il est possible d'outrepasser les règles d'encapsulation

Création d'un objet

La méthode newInstance() de la classe Class permet de créer une instance d'un objet en appelant le constructeur par défaut. Dans l'exemple qui suit, nous créons un objet de type Rectangle à partir du nom de la classe.

	
package manipulation;

import java.awt.Rectangle;

public class SampleNoArg {

	public static void main(String[] args) {
	      Rectangle r = (Rectangle) createObject("java.awt.Rectangle");
	      System.out.println(r.toString());
	   }

	   static Object createObject(String className) {
	      Object object = null;
	      try {
	          Class classDefinition = Class.forName(className);
	          object = classDefinition.newInstance();
	      } catch (InstantiationException e) {
	          System.out.println(e);
	      } catch (IllegalAccessException e) {
	          System.out.println(e);
	      } catch (ClassNotFoundException e) {
	          System.out.println(e);
	      }
	      return object;
	   }

}
	

Pour pouvoir instancier un objet avec un constructeur autre que celui par défaut, il faut maintenant utiliser la classe Constructor. Pour ce faire, il faut utiliser la méthode getConstructor() d'un objet Class, en passant en paramètre un tableau de Class représentant les types des paramètres du constructeur dont on veut disposer. Puis il faut appeler la méthode newInstance de l'objet Constructor avec en paramètres un tableau d'objet contenant les paramètres.

	
package manipulation;

import java.awt.Rectangle;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class SampleInstance {

	public static void main(String[] args) {

		Rectangle rectangle;
		Class rectangleDefinition;
		Class[] intArgsClass = new Class[] { int.class, int.class };
		Integer height = new Integer(12);
		Integer width = new Integer(34);
		Object[] intArgs = new Object[] { height, width };
		Constructor intArgsConstructor;

		try {
			rectangleDefinition = Class.forName("java.awt.Rectangle");
			intArgsConstructor = rectangleDefinition
					.getConstructor(intArgsClass);
			rectangle = (Rectangle) createObject(intArgsConstructor, intArgs);
		} catch (ClassNotFoundException e) {
			System.out.println(e);
		} catch (NoSuchMethodException e) {
			System.out.println(e);
		}
	}

	public static Object createObject(Constructor constructor,
			Object[] arguments) {

		System.out.println("Constructor: " + constructor.toString());
		Object object = null;

		try {
			object = constructor.newInstance(arguments);
			System.out.println("Object: " + object.toString());
			return object;
		} catch (InstantiationException e) {
			System.out.println(e);
		} catch (IllegalAccessException e) {
			System.out.println(e);
		} catch (IllegalArgumentException e) {
			System.out.println(e);
		} catch (InvocationTargetException e) {
			System.out.println(e);
		}
		return object;
	}

}
	

Get sur un champ

L'objet Field propose la méthode Object get(Object o) qui permet de récupérer la valeur du champ (représenté par l'instance de Field) de l'objet passé en paramètre. Dans cet exemple, nous récupérons la valeur du champ height d'un rectangle.

	

package manipulation;

import java.awt.Rectangle;
import java.lang.reflect.Field;

public class SampleGet {

	public static void main(String[] args) {
	      Rectangle r = new Rectangle(100, 325);
	      printHeight(r);

	   }

	   static void printHeight(Rectangle r) {
	      Field heightField;
	      Integer heightValue;
	      Class c = r.getClass();
	      try {
	        heightField = c.getField("height");
	        heightValue = (Integer) heightField.get(r);
	        System.out.println("Height: " + heightValue.toString());
	      } catch (NoSuchFieldException e) {
	          System.out.println(e);
	      } catch (SecurityException e) {
	          System.out.println(e);
	      } catch (IllegalAccessException e) {
	          System.out.println(e);
	      }
	   }

}
	

Set sur un champ

L'objet Field propose la méthode Object set(Object o, Objet param) qui permet de changer la valeur du champ (représenté par l'instance de Field) de l'objet passé en paramètre, avec l'objet en second paramètre. Dans cet exemple, nous changeons la valeur du champ height d'un rectangle.

	
package manipulation;

import java.awt.Rectangle;
import java.lang.reflect.Field;


public class SampleSet {

	public static void main(String[] args) {
	      Rectangle r = new Rectangle(100, 20);
	      System.out.println("original: " + r.toString());
	      modifyWidth(r, new Integer(300));
	      System.out.println("modified: " + r.toString());
	   }

	   static void modifyWidth(Rectangle r, Integer widthParam ) {
	      Field widthField;
	      Class c = r.getClass();
	      try {
	        widthField = c.getField("width");
	        widthField.set(r, widthParam);
	      } catch (NoSuchFieldException e) {
	          System.out.println(e);
	      } catch (IllegalAccessException e) {
	          System.out.println(e);
	      }
	   }

}
	

Invocation d'une méthode

La méthode Object invoke(Object arg0, Object... arg1) de la classe Method permet d'appeler la méthode de l'objet passé en premier argument, avec les paramètres passés ensuite. Pour ce faire, il faut tout d'abord récupérer l'objet Method à l'aide de getMethod(). Dans cet exemple, nous allons appeler la méthode concat de l'objet String, qui prendun String en paramètre.

	
package manipulation;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class SimpleInvoke {
	public static void main(String[] args) {
	      String firstWord = "Hello ";
	      String secondWord = "everybody.";
	      String bothWords = append(firstWord, secondWord);
	      System.out.println(bothWords);
	   }

	   public static String append(String firstWord, String secondWord) {
	      String result = null;
	      Class c = String.class;
	      Class[] parameterTypes = new Class[] {String.class};
	      Method concatMethod;
	      Object[] arguments = new Object[] {secondWord};
	      try {
	        concatMethod = c.getMethod("concat", parameterTypes);
	        result = (String) concatMethod.invoke(firstWord, arguments);
	      } catch (NoSuchMethodException e) {
	          System.out.println(e);
	      } catch (IllegalAccessException e) {
	          System.out.println(e);
	      } catch (InvocationTargetException e) {
	          System.out.println(e);
	      }
	      return result;
	   }

}
	

Au delà de l'encapsulation

Dans toutes les manipulations faites jusqu'à présent, nous avons toujours touché à des champs ou des méthodes publiques. Les méthodes getFields(), getMethods(), ... revoient toujours les champs et les méthodes publiques. Mais il existe aussi des méthodes getDeclaredXXX() qui permettent de récupérer toutes les champs et toutes les méthodes d'une classe. Si nous essayons de les manipuler directement, une exception IllegalAccessException est levée. Pour contourner cela, nous avons accès à la méthode void setAccessible(boolean accessible). Cette méthode lève le verrou et permet donc d'accéder aux champs et méthodes non publiques.

L'exemple quivant illustre ce mécanisme. Pour illustrer, la classe PrivateFields contient 2 champs privés x et y, et une méthode privée doSomething()

	
package manipulation;

public class PrivateFields {
	private int x;
	private int y;
	
	public PrivateFields() {
		x=10;
		y=10;
	}
	
	private void doSomething(){
		System.out.println("Qui m'appelle???");
	}
	
	public String toString(){
		return ("x: " + x + ", y: " + y);
	}
}
	

L'exemple ci-dessous effectue les opérations suivantes:

	
package manipulation;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class SampleSetPrivate {

	public static void method(Object p) {
		Method m = null;
		try {
			System.out.println("Appel de doSomething: ");
			System.out.flush();
			Class c = p.getClass();
			m = c.getDeclaredMethod("doSomething", new Class[0]);
			m.invoke(p, new Class[0]);

		} catch (SecurityException e1) {
			e1.printStackTrace();
		} catch (NoSuchMethodException e1) {
			e1.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
			m.setAccessible(true);
			try {
				System.out
						.println("Appel de doSomething après setAccessible: ");
				System.out.flush();
				m.invoke(p, new Class[0]);
			} catch (IllegalArgumentException e2) {
				e2.printStackTrace();
			} catch (IllegalAccessException e2) {
				e2.printStackTrace();
			} catch (InvocationTargetException e2) {
				e2.printStackTrace();
			}
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}

	}

	public static void main(String[] args) {
		PrivateFields p = new PrivateFields();
		System.out.println(p);

		Class c = p.getClass();

		try {
			System.out.println("Get sur x: ");
			System.out.flush();
			Field x = c.getDeclaredField("x");
			System.out.println("Get sur x: " + x.get(p));
			System.out.flush();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}

		try {

			Field x = c.getDeclaredField("x");
			x.setAccessible(true);
			System.out.println("Get sur x après setAccessible: " + x.get(p));
			System.out.flush();

			x.set(p, new Integer(24));
			System.out.println("Set sur x: " + x.get(p));
			System.out.flush();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}

		method(p);
	}
}
	

Le résultat e l'exécution est donné ci-dessous:

	
x: 10, y: 10
Get sur x: 
java.lang.IllegalAccessException: Class manipulation.SampleSetPrivate can not access a member of class manipulation.PrivateFields with modifiers "private"
	at sun.reflect.Reflection.ensureMemberAccess(Unknown Source)
	at java.lang.reflect.Field.doSecurityCheck(Unknown Source)
	at java.lang.reflect.Field.getFieldAccessor(Unknown Source)
	at java.lang.reflect.Field.get(Unknown Source)
	at manipulation.SampleSetPrivate.main(SampleSetPrivate.java:68)
Get sur x après setAccessible: 10
Set sur x: 24
Appel de doSomething: 
java.lang.IllegalAccessException: Class manipulation.SampleSetPrivate can not access a member of class manipulation.PrivateFields with modifiers "private"
	at sun.reflect.Reflection.ensureMemberAccess(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at manipulation.SampleSetPrivate.method(SampleSetPrivate.java:30)
	at manipulation.SampleSetPrivate.main(SampleSetPrivate.java:100)
Appel de doSomething après setAccessible:
Qui m'appelle???
	

L'API autorise ce comportement car beaucoup d'applications doivent avoir accès à ces données, telles que:

Explorateur de classe

Pour finir, voici le code d'un explorateur de classe permettant d'afficher toutes les méthodes et tous les champs d'une classe donnée.

	
package explorer;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class ClassExplorer {
	
	public static void exploreFields(Object o) {
		Field[] f = null;
		Class c = null;

		System.out.println("--------------------CHAMPS--------------------");
		
		c = o.getClass();
		f = c.getFields();

		for (int i = 0; i < f.length; ++i) {
			System.out.print(Modifier.toString(f[i].getModifiers()));
			System.out.print(" ");
			System.out.print(f[i].getType().getName());
			System.out.print(" ");
			System.out.print(f[i].getName());
			System.out.print(" = ");
			try {
				System.out.println(f[i].get(o));
			} catch (IllegalAccessException e) {
				f[i].setAccessible(true);
				try{
					System.out.println(f[i].get(o));
				} catch(IllegalAccessException e4){
					System.out.println("Valeur non consultable");
				}
			}
		}
		System.out.println("------------------------------------------------\n");
	}

	public static void exploreMethods(Object o) {
		Method[] m = null;
		Class c = null;

		c = o.getClass();
		m = c.getDeclaredMethods();
		
		System.out.println("--------------------METHODES--------------------");
		
		Class[] params = null;
		for (int i = 0; i < m.length; ++i) {
			System.out.print(Modifier.toString(m[i].getModifiers()));
			System.out.print(" ");
			System.out.print(m[i].getReturnType().getName());
			System.out.print(" ");
			System.out.print(m[i].getName());
			System.out.print("(");
			params = m[i].getParameterTypes();
			for (int j = 0; j < params.length; ++j) {
				System.out.print(params[j].getName()+ " ");
			}
			System.out.println(")");
		}
		System.out.println("----------------------------------------------\n");
	}
	
	public static void exploreConstructors(Object o) {
		Constructor[] m = null;
		Class c = null;

		c = o.getClass();
		m = c.getDeclaredConstructors();
		System.out.println("-----------------CONSTRUCTEURS------------------");
		Class[] params = null;
		for (int i = 0; i < m.length; ++i) {
			System.out.print(Modifier.toString(m[i].getModifiers()));
			System.out.print(" ");
			//System.out.print(m[i].getReturnType().getName());
			//System.out.print(" ");
			System.out.print(m[i].getName());
			System.out.print("(");
			params = m[i].getParameterTypes();
			for (int j = 0; j < params.length; ++j) {
				System.out.print(params[j].getName()+ " ");
			}
			System.out.println(")");
		}
		System.out.println("------------------------------------------------\n");
	}
}