TABLEAUX
La possibilité de manipuler des tableaux se retrouve dans tous les langages de programmation; toutefois Java, qui est un langage avec des objets, manipule les tableaux d’une fa¸con particuli`ere que l’on va décrire ici.
Déclaration, construction, initialisation
L’utilisation d’un tableau permet d’avoir à sa disposition un tr` es grand nombre de variables en utilisant un seul nom et donc en effectuant une seule déclaration. En effet, si on déclare un tableau de nom tab et de taille n contenant des valeurs de type typ, on a à sa disposition les variables tab[0],tab[1], …, tab[n-1] qui se comportent comme des variables ordinaires de type typ. En Java, on sépare la déclaration d’une variable de type tableau, la construction effective d’un tableau et l’initialisation du tableau. La déclaration d’une variable de type tableau de nom tab dont les éléments sont de type typ, s’effectue par1 :
typ[] tab;
Lorsque l’on a déclaré un tableau en Java on ne peut pas encore l’utiliser compl` etement. Il est en effet interdit par exemple d’affecter une valeur aux variables tab[i], car il faut commencer par construire le tableau, ce qui signifie qu’il faut réserver de la place en mémoire (on parle d’allocation mémoire) avant de s’en servir. L’opération de construction s’effectue en utilisant un new, ce qui donne :tab = new typ[taille];
Dans cette instruction, taille est une constante enti`ere ou une variable de type entier dont l’évaluation doit pouvoir ˆetre effectuée à l’exécution. Une fois qu’un tableau est créé avec une certaine taille, celle-ci ne peut plus ˆetre modifiée. On peut aussi regrouper la déclaration et la construction en une seule ligne par :
typ[] tab = new typ[taille];L’exemple de programme le plus typique est le suivant :
int[] tab = new int[10];
for(int i = 0; i < 10; i++) tab[i] = i;
Pour des tableaux de petite taille on peut en mˆeme temps construire et initialiser un tableau et initialiser les valeurs contenues dans le tableau. L’exemple suivant regroupe les 3 opérations de déclaration, construction et initialisation de valeurs en utilisant une affectation suivie de {, } : int[] tab = {1,2,4,8,16,32,64,128,256,512,1024};
La taille d’un tableau tab peut s’obtenir grˆace à l’expression tab.length. Complétons l’exemple précédent :int[] tab = {1,2,4,8,16,32,64,128,256,512,1024};for(int i = 0; i < tab.length; i++) System.out.println(tab[i]);
Insistons encore une fois lourdement sur le fait qu’un tableau tabde n éléments en Java commence nécessairement à l’indice 0, le dernier élément accessible étant tab[n-1]. Si tab est un tableau dont les éléments sont de type typ, on peut alors considérer tab[i] comme une variable et effectuer sur celle-ci toutes les opérations admissibles concernant le type typ, bien entendu l’indice i doit ˆetre inférieur à la taille du tableau donnée lors de sa construction. Java vérifie cette condition à l’exécution et une exception est levée si elle n’est pas satisfaite. Donnons un exemple simple d’utilisation d’un tableau. Recherchons le plus petit élément dans un tableau donné :public static int plusPetit(int[] x){ int k = 0, n = x.length;for(int i = 1; i < n; i++) // invariant : k est l’indice du plus petit // élément de x[0..i-1] if(x[i] < x[k]) k = i; return x[k];}
Représentation en mémoire et conséquences
La mémoire accessible au programme peut ˆetre vue comme un ensemble de cases qui vont contenir des valeurs associées aux variables qu’on utilise. C’est le compilateur qui se charge d’associer aux noms symboliques les cases correspondantes, qui sont repérées par des numéros (des indices dans un grand tableau, appelés encore adresses). Le programmeur moderne n’a pas à se soucier des adresses réelles, il laisse ce soin au compilateur (et au programme). Aux temps historiques, la programmation se faisait en manipulant directement les adresses mémoire des objets, ce qui était pour le moins peu confortable2. Quand on écrit :
int i = 3, j; une case mémoire3 est réservée pour chaque variable, et celle pour i remplie avec la valeur 3. Quand on exécute :
j = i;
le programme va chercher la valeur présente dans la case affectée à i et la recopie dans la case correspondant à j. Que se passe-t-il maintenant quand on déclare un tableau?
int[] tab;
Le compilateur réserve de la place pour la variable tab correspondante, mais pour le moment, aucune place n’est réservée pour les éléments qui constitueront tab. C’est ce qui explique que quand on écrit :
public class Bug1{ public static void main(String[] args){ int[] tab;
tab[0] = 1;}}
on obtient à l’exécution :
java.lang.NullPointerException at Bug1.main(Bug1.java:5)
C’est une erreur tellement fréquente que les compilateurs récents détectent ce genre de probl`eme à la compilation. Quand on manipule un tableau, on travaille en fait de fa¸con indirecte avec lui, comme si on utilisait une armoire pour ranger ses affaires. Il faut toujours imaginer qu’écrire
tab[2] = 3;
veut dire au compilateur ”retrouve l’endroit o` u tu as stocké tab en mémoire et met à jour la case d’indice 2 avec 3”. En fait, le compilateur se rappelle d’abord o` u il a rangé son armoire, puis en déduit quel tiroir utiliser.
Tableaux à plusieurs dimensions, matrices
Un tableau à plusieurs dimensions est considéré en Java comme un tableau de tableaux. Par exemple, les matrices sont des tableaux à deux dimensions, plus précisément des tableaux de lignes. Leur déclaration peut se faire par :
typ[][] tab;
On doit aussi le construire à l’aide de new. L’instruction
tab = new typ[N][M];
construit un tableau à deux dimensions, qui est un tableau de N lignes à M colonnes. L’instruction tab.length retourne le nombre de lignes, alors que tab[i].length retourne la longueur du tableau tab[i], c’est-`a-dire le nombre de colonnes. On peut aussi, comme pour les tableaux à une dimension, faire une affectation de valeurs en une seule fois :
int[2][3] tab = {{1,2,3},{4,5,6}};
qui déclare et initialise un tableau à 2 lignes et 3 colonnes. On peut écrire de fa¸con équivalente :
int[][] tab = {{1,2,3},{4,5,6}};
Comme une matrice est un tableau de lignes, on peut fabriquer des matrices bizarres. Par exemple, pour déclarer une matrice dont la premi`ere ligne a 5 colonnes, la deuxi`eme ligne 1 colonne et la troisi`eme 2, on écrit
public static void main(String[] args){ int[][] M = new int[3][];
M[0] = new int[5]; M[1] = new int[1]; M[2] = new int[2];
}
Par contre, l’instruction :
int[][] N = new int[][3];
est incorrecte. On ne peut définir un tableau de colonnes. On peut continuer à écrire un petit programme qui se sert de cela :
public class Tab3{ public static void ecrire(int[] t){ for(int j = 0; j < t.length; j++) System.out.println(t[j]); } public static void main(String[] args){ int[][] M = new int[3][];
M[0] = new int[5]; M[1] = new int[1]; M[2] = new int[2]; for(int i = 0; i < M.length; i++) ecrire(M[i]);
}}