Les connaisseurs doivent se dire : “Ca y est ! Il a pété une durite Casa ! Il n’y a pas de préprocesseur en Java”. Ce n’est pas faux, et cela peut d’ailleurs paraître une hérésie pour les fanatiques du C/C++. Cependant, il y a moyen de s’en passer pour au moins deux points :
La compilation conditionnelle consiste, comme chacun sait, à ne compiler certaines portions du code que si une condition est réalisée. On utilise en C les directives de compilation #IF..#ENDIF. Le préprocesseur lit le fichier source avant la compilation et manipule le fichier source de manière à satisfaire à ces directives. Une utilisation de cette compilation conditionnelle est la réalisation des différentes versions d’un même logiciel avec un même et unique fichier source.
Imaginons que vous écriviez un shareware 1 : vous souhaitez réaliser deux versions, l’une que vous diffuserez à grande échelle et qui se bloquera au bout de 30 jours d’utilisation, et une autre version complète pour les utilisateurs enregistrés. Il serait très pénible de devoir réaliser deux versions avec deux sources différents, car des modifications réalisées sur un source doivent être répercutées sur le second. La solution consiste à utiliser la compilation conditionnelle de manière à pouvoir compiler les deux versions avec le même source. Une autre utilisation est l’élimination de portions de code destinées au débuggage lors de la compilation de la version finale du projet.
On peut réaliser cette compilation conditionnelle avec un peu d’astuce, examinons le source suivant :
public class Preprocesseur
{
static final boolean DEBUG=false;
public static void main(String[] args) {
System.out.print("Le programme a été compilé en ");
if(DEBUG)
System.out.println("mode débuggage.");
else
System.out.println("mode version finale.");
}
}
On remarque tout d’abord que l’on définit une variable
static final boolean BUG
. Cette variable est static donc c’est
une variable de classe, commune à toutes les instances d’une même
classe. D’autre part, elle est final ce qui veut dire que l’on ne peut
modifier sa valeur en cours d’exécution. Cette variable peut donc être
assimilée à une constante, et le compilateur le sait, et va en tenir
compte lors de la compilation en éliminant le test et la portion de code
qui n’est jamais exécutée. On peut le vérifier facilement avec un
éditeur hexadécimal : suivant que ce programme a été compilé avec DEBUG
sur true ou false, on trouve les chaînes “débuggage” ou “version
finale”.
En C, on peut définir des constantes avec la directive define : #DEFINE PI=3.141592 par exemple. Ce qui peut paraître étrange à première vue, c’est qu’on ne donne pas le type de cette constante. On comprend que ce n’est pas nécessaire lorsqu’on sait que le préprocesseur agit avant la compilation en remplaçant les “PI” par “3.141592” dans le code source. Cette méthode de définition des constantes permet de gagner du temps à la compilation car le processeur n’a pas à aller chercher la valeur de PI, on lui fournit directement comme constante.
Il est possible de procéder de même avec Java, en déclarant une variable static final : le compilateur sait alors que c’est une constante (on ne peut pas lui allouer une autre valeur que celle donnée lors de la compilation), donc il remplace les références à la variable PI par sa valeur. On peut donc encore se passer du préprocesseur dans ce cas.
Dans le même ordre d’idée, on peut aussi parler du cas des méthodes déclarées final, static ou private. Ces méthodes ne pouvant être surchargées par un objet qui en hérite, le compilateur peut les inliner (c’est à dire les inclure dans le code à la place de l’appel à ces méthodes). Si une méthode n’est pas déclarée final, static ou private, le compilateur ne peut le faire car lors de l’appel à cette méthode, la machine virtuelle doit appeler la version surchargée et non celle de la classe parent. On peut donc optimiser son code Java en déclarant les méthodes que l’on est sur de ne pas surcharger comme final.