Java – Private montre toi

Depuis votre premier biberon et jusqu’à maintenant vos parents n’ont cessé de vous rappeler que tout attribut private contenu dans une classe n’est en aucun cas accessible directement depuis l’extérieur. Et en tant que bon développeur, vous avez donc toujours fait des accesseurs et mutateurs pour accéder à ces private quand il était nécessaire de les utiliser.
Cependant, pour des raisons obscures, vous avez besoin aujourd’hui de passer outre cette règle d’or. Vous avez par exemple besoin d’accéder à un attribut privé lors de tests unitaires et vous ne voulez pas compromettre de la robustesse de vos classes. Et bien sûr, pour ajouter un peu de piment, l’attribut privé que vous voulez contrôler n’a de raisons d’exister que dans votre classe. Pas même un getter.
Voilà donc une solution pour accéder à votre attribut private sans rien changer à votre classe …

Prenons comme exemple la classe « Person » présentée ci dessous.

package me.test;
public class Person {
	private String firstName = null;
	private String lastName = null;
	public Person(String firstName, String lastName) {
		super();
		this.firstName = firstName;
		this.lastName = lastName;
	}
	public String getFullName() {
		return firstName + " " + lastName;
	}
}

Vous ne pouvez, pour l’instant, en aucun cas connaitre le contenu exact de firstName hors de la classe Person. Et dans votre application c’est normal, vous n’avez jamais besoin de cet attribut tout seul.
Voici un petit programme qui montre l’utilisation de votre classe :

package me.test;
public class Main {
	public static void main(String[] args) {
		Person olivier = new Person("Olivier", "P.");
		System.out.println("Person : " + olivier.getFullName());
	}
}

Ce programme affichera simplement « Olivier P. » et vous n’avez aucun moyen fiable d’afficher juste « Olivier ».
Nous allons donc utiliser un mécanisme tordu de Java : la réflexion. Le principe est de « fouiller » à l’intérieur des classes pour trouver n’importe quoi même bien caché. Attention, à part dans certains cas très spécifiques et à condition que vous soyez extrêmement sûr de ce que vous faites, vous ne devez jamais utiliser ce genre de choses.

package me.test;
import java.lang.reflect.Field;
public class Main {
	public static void main(String[] args) {
		Person olivier = new Person("Olivier", "P.");
		System.out.println("Person : " + olivier.getFullName());
		try {
			/*1*/ Field firstNameField = Person.class.getDeclaredField("firstName");
			/*2*/ firstNameField.setAccessible(true);
			/*3*/ String firstName = (String)firstNameField.get(olivier);
			System.out.println("First Name : " + firstName);
		} catch (NoSuchFieldException e) {
			System.out.println("The field doesn't exist : " + e.getMessage());
		} catch (IllegalAccessException e) {
			System.out.println(e.getMessage());
		}
	}
}

Explication :

  1. On accède à la définition de l’attribut « firstName » (la casse est importante)
  2. Ensuite on rend l’attribut accessible (là on casse totalement le principe d’encapsulation)
  3. On fini en récupérant le contenu de cet attribut dans l’objet « olivier »

Voilà, vous savez comment fouiller dans les attributs d’une classe. Si vous n’êtes pas sûr, pour une raison X ou Y du nom de l’attribut que vous cherchez, utiliser les lignes si dessous pour lister les éléments intéressants d’une classe :

package me.test;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Main {
	public static void main(String[] args) {
		for(Field f : Person.class.getDeclaredFields()) {
			System.out.println("- " + f.getName());
		}
		for(Method m : Person.class.getDeclaredMethods()) {
			System.out.println("+ " + m.getName());
			for (Class c : m.getParameterTypes()) {
				System.out.println("+-- " + c.getSimpleName());
			}
		}
	}
}