Type tableau

En programmation tableau simple peut être vu comme une liste finie d'éléments d'un même type. En Java, un type tableau simple s'obtient en ajoutant une paire de crochets [] après le type des éléments du tableau. Par exemple int[] est le type tableau correspondant aux listes finies contenant des entiers int.

Valeurs littérales

En Java, on peut définir la valeur d'un tableau directement lors de l'initialisation d'une variable de type tableau, comme dans l'exemple suivant :

import java.util.Arrays;
public class TableauEntier {
    public static void main(String[] args) {
	int[] x = { 2, -3, 4};
	System.out.println(x);
	System.out.println(Arrays.toString(x));
    }
}

En effet, la ligne System.out.println(x); produit un affichage de la forme [I@57801e5f. La partie [I correspond au type du tableau, le @ est un séparateur et le reste est un nombre (en hexadécimal) relié à l'adresse en mémoire du tableau. Tout ceci n'est pas très utile en pratique, c'est pourquoi on utilisera la méthode Arrays.toString (attention à l'import correspondant) qui associe à un tableau une chaîne de caractères lisible. Ici, le programme affiche finalement :

[I@57801e5f [2, -3, 4]

Longueur et contenu

L'attribut length d'un tableau indique le nombre d'éléments contenus dans le tableau (la longueur de la liste). Les éléments sont numérotés de 0 à length-1. On accède à un élément grâce à la construction [index], comme dans l'exemple suivant :

public class TableauAccess {
    public static void main(String[] args) {
	int[] x = { -1, 2, -3, 4};
	System.out.println(x.length);
	for(int j = 0; j < x.length; j++) {
	    System.out.println(x[j]);
	}
    }
}

qui affiche :

4 -1 2 -3 4

On peut utiliser la construction [index], pour modifier le contenu d'un tableau, par exemple :

import java.util.Arrays;
public class TableauModif {
    public static void main(String[] args) {
	double[] z = { 1.0, 5.0, 2.5 };
	System.out.println(Arrays.toString(z));
	for(int j = 0; j < z.length ; j++) {
	    z[j] = z[j] + j;
	}
	System.out.println(Arrays.toString(z));
    }
}

qui affiche :

[1.0, 5.0, 2.5] [1.0, 6.0, 4.5]

Boucle for spécifique

Java propose une version spéciale de la boucle for pour certains types, notamment les tableaux. Comme le montre l'exemple suivant, cette version simplifiée est utile pour parcourir tous les éléments d'un tableau, dans l'ordre, car la variable j prend, à chaque tour de la boucle, la valeur d'un des éléments du tableau :

public class TableauFor {
    public static void main(String[] args) {
	int[] u = { 4, 2, 5, 27};
	for(int j: u) {
	    System.out.println(j);
	}
    }
}

Le programme affiche donc :

4 2 5 27

On peut pas utiliser cette boucle pour modifier le contenu du tableau, comme le montre l'exemple suivant :

import java.util.Arrays;
public class TableauForModif {
    public static void main(String[] args) {
	int[] u = { 4, 2, 5, 27};
	for(int j: u) {
	    j = j + 1;
	}
	System.out.println(Arrays.toString(u));
    }
}

qui affiche :

[4, 2, 5, 27]

Référence

Une variable de type tableau ne contient pas le tableau mais un pointeur vers celui-ci (une référence dans le vocabulaire de Java, mais c'est la même chose en pratique). Cela conduit à des phénomènes d'alias comme dans l'exemple suivant :

import java.util.Arrays;
public class TableauAlias {
    public static void main(String[] args) {
	double[] x = { 2.5, 0.5 };
	double[] y = x;
	System.out.println("x = " + Arrays.toString(x));
	System.out.println("y = " + Arrays.toString(y));
	x[0] = -0.5;
	System.out.println("x = " + Arrays.toString(x));
	System.out.println("y = " + Arrays.toString(y));
	System.out.println(x==y);
    }
}

qui affiche :

x = [2.5, 0.5] y = [2.5, 0.5] x = [-0.5, 0.5] y = [-0.5, 0.5] true

ce qui montre que x et y sont deux noms différents pour le même tableau (comme le confirme l'expression booléenne finale).

Une variable contenant un pointeur peut pointer sur rien, ce qui s'obtient en utilisant en Java la référence particulière null. Son emploi est cependant déconseillé en pratique et il vaut mieux toujours donner une valeur significative à un pointeur.

Allocation

Bien que les valeurs littérales tableaux soient pratiques, on doit parfois s'en passer, comme dans l'exemple suivant où la taille du tableau est déterminée par l'utilisateur :

import java.util.Arrays;
import java.util.Scanner;
public class TableauNew {
    public static void main(String[] args) {
	System.out.println("Nombre de valeurs ?");
	Scanner scan = new Scanner(System.in);
	int nb = scan.nextInt();
	int[] x = new int[nb];
	for(int i = 0; i < x.length; i++) {
	    System.out.println("Valeur n° "+i+" ?");
	    x[i] = scan.nextInt();
	}
	System.out.println("x = " + Arrays.toString(x));
	scan.close();
    }
}

Ici, on permet à l'utilisateur de déterminer la taille du tableau. Celui-ci est créé par l'instruction new int[nb] qui fabrique donc un tableau de longueur nb dont toutes les cases sont initialisées à 0. Puis l'utilisateur remplit le tableau, comme dans l'interaction suivante :

Nombre de valeurs ? 4 Valeur n° 0 ? 2 Valeur n° 1 ? -2 Valeur n° 2 ? -4 Valeur n° 3 ? 5 x = [2, -2, -4, 5]

Simuler un changement de taille

Comme indiqué ci-dessus, on ne peut pas changer la taille d'un tableau une fois que celui-ci a été créé. On peut en revanche recopier le contenu d'un tableau dans un autre, ce qui permet de faire comme si un tableau était de taille variable. Dans l'exemple ci-dessous, on calcule un « vol » de la suite de Syracuse. Cette suite est définie à partir d'un paramètre entier \(P\) par récurrence suivante :

\( \begin{align*} u_0&=P\\ u_{n+1}&=\left\{\begin{array}{cl} \frac{u_n}{2}&\text{si }u_n\text{ est pair}\\ 3u_n+1 &\text{si }u_n\text{ est impair} \end{array}\right. \end{align*} \)

La conjecture de Syracuse indique que pour tout \(P\), il existe un \(n\) tel que \(u_n=1\), terme après lequel la suite devient cyclique (elle prend les valeurs 4, 2, et 1 de façon infinie). Le programme ci-dessous construit un tableau contenant exactement les \(n+1\) termes correspondant au « vol » de la suite en partant d'un \(P\) fixé par l'utilisateur. Comme on ne connaît pas \(n\) à l'avance, le tableau « grossit » au fur et à mesure de la progression du programme.

import java.util.Scanner;
import java.util.Arrays;
public class Syracuse {
    public static void main(String[] args) {
	Scanner scan = new Scanner(System.in);
	System.out.printf("Entrez la valeur de P : ");
	int P = scan.nextInt();
	int n = 1; // on a calculé un terme 
	int[] u = { P }; // le seul terme connu
	while(u[n - 1] > 1) {
	    // a-t-on assez de place pour stocker u[n] ?
	    if(n >= u.length) {
		// non 
		int[] nouveau = new int[2*u.length];
		// on recopie u dans nouveau
		System.arraycopy(u, 0, nouveau, 0, u.length);
		// puis on change de tableau
		u = nouveau;
	    }
	    // on calcule u_n
	    if(u[n - 1] % 2 == 0) {
		// cas pair
		u[n] = u[n - 1] / 2;
	    } else {
		// cas impair
		u[n] = 3 * u[n - 1] + 1 ;
	    }
	    // on prépare le calcul suivant
	    n++;
	}
	// le tableau est peut être plus grand qu'il ne le fallait
	if(n < u.length) {
	    // c'est le cas
	    int[] nouveau = new int[n];
	    // on recopie la partie utile
	    System.arraycopy(u, 0, nouveau, 0, n);
	    u = nouveau;
	}
	System.out.printf("Vol pour %d: %s%n",P,Arrays.toString(u));
	scan.close();
    }
}

Ce programme affiche par exemple :

Entrez la valeur de P : 50 Vol pour 50: [50, 25, 76, 38, 19, 58, 29, 88, 44, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1]

Notons l'utilisation de la méthode System.arraycopy. Un appel de cette méthode prend la forme System.arraycopy(source,s,destination,d,l) avec :

  • source désigne le tableau depuis lequel on copie les valeurs ;
  • destination désigne le tableau dans lequel on copie les valeurs ;
  • s indique l'indice de départ de la copie dans le tableau source (on copie les cases à partir de la case de numéro s) ;
  • d indique l'indice de départ de la copie dans le tableau destination (on copie dans les cases à partir de la case de numéro d) ;
  • l indique le nombre de cases à copier.