4. Tableaux

Les tableaux ont plusieurs particularités en Java :
  • Leur premier indice est 0, comme en C / C++ ;
  • Ce sont presque des objets. Ils ne peuvent pas être étendus, n'ont pas de nom de classe, sont utilisés avec une syntaxe qui leur est propre, et ils héritent des méthodes de la classe Object, notamment toString() ;
  • Une variable de type tableau est une référence vers un objet ;
  • La validité des indices d'un tableau est systématiquement vérifiée lors de l'exécution d'un code Java. Il n'est pas possible de lire au-delà du dernier indice existant d'un tableau. Si un code tente de le faire, la machine Java génère une exception.

4.1. Création d'un tableau

Du point de vue de sa création, un tableau se comporte comme un objet. Aussi, la création d'un tableau se déroule en deux temps : la déclaration d'une variable de type tableau, et la création du tableau proprement dit. Il existe deux façons de déclarer une variable de type tableau, strictement équivalentes :

Exemple 43. Déclaration d'un tableau

int [] tab1 ;  // déclaration d'un tableau pouvant contenir des entiers  
  
 int tab2 [] ;  // déclaration équivalente à la précédente

Déclarer cette variable est comme déclarer un objet. Aucun espace mémoire n'est réservé, autre celui qui permet de stocker cette variable. Réserver de la mémoire pour stocker un tableau nécessite de fournir la taille de ce tableau. Une fois cette taille fixée, il n'est plus possible de la modifier. Cela dit, la taille d'un tableau peut être fixée à l'exécution du code, à la différence du C ou du C++.

Exemple 44. Création d'un tableau, initialisation dans une boucle for

int nombreMarins =  10 ;  // taille initiale du tableau  
    
Marin [] marins =  new Marin [nombreMarins] ;  // création du tableau  
    
 for (int i =  0 ; i < marins.length ; i++) {  
    marins[i] =  new Marin() ;  // initialisation de chaque case du tableau  
}

Cet exemple montre comment créer un tableau, et une façon de l'initialiser. On remarquera l'utilisation du champ public (il sagit bien d'un champ, et non pas d'une méthode) length, disponible pour tout tableau, qui donne la longueur de ce tableau. Ce champ est à utiliser toutes les fois que l'on souhaite parcourir les cases de ce tableau. Notons que l'on peut aussi parcourir les tableaux à l'aide de la syntaxe foreach, à partir de Java 5.

Exemple 45. Parcours d'un tableau dans une boucle foreach

for (Marin marin : marins) {  
    marin =  new Marin() ;  // initialisation de chaque case du tableau  
}

D'une façon générale, créer un tableau d'objets n'initialise pas les objets qu'il contient. Tenter d'exécuter ce genre de code, c'est s'exposer à l'ignoble NPE, que nous avons déjà vue.
 Marin [] marins = new Marin[10] ;
 String nom = marins[3].getNom() ; // ERREUR !!!!! 

4.2. Initialisation d'un tableau

Il est possible d'initialiser un tableau avec des valeurs explicites, que ce tableau contienne des types de base ou des objets. Voyons ceci sur un exemple.

Exemple 46. Initialisation d'un tableau de types de base

int tableau [] = {0,  1,  2,  3,  4} ;  // on peut aussi placer les [] juste après int  
  
 int [] autreTableau ;   
autreTableau =  new  int [] {0,  1,  2,  3,  4} ;

La première syntaxe ne peut être utilisée que lors de la déclaration du tableau. La seconde est utilisable une fois que le tableau a été déclaré. Il est possible d'utiliser des expressions entre les paires d'accolades, comme sur l'exemple suivant.

Exemple 47. Initialisation d'un tableau d'objets

Marin marins [] ;  
  
marins =  new Marin [] {  
     new Marin(),   
     new Marin("Surcouf"),   
    null  
} ;

4.3. Utilisation d'un tableau comme un Object

Comme il a déjà été dit, les méthodes de la classe Object sont accessibles sur toute variable de type tableau. Malheureusement, on pourrait s'attendre de leur part à un comportement sympathique, ou tout du moins qu'elles expriment un peu de compassion pour les malheureux développeurs que nous sommes, mais il n'en est rien. Si l'on déclare deux tableaux de la façon suivante :
 int [] tab1 = {0, 1, 2, 3, 4} ;
 int [] tab2 = {0, 1, 2, 3, 4} ;
La méthode toString() de tab1 nous retourne l'indigeste chaîne de caractères suivante :
 tab1.toString() -> [I@18d107f
La comparaison de ces deux tableaux par la méthode equals() nous retourne un désespérant :
 tab1.equals(tab2) -> false
Et pour couronner le tout, les deux codes de hachage de ces deux tableaux sont différents. Il n'est malheureusement pas possible de surcharger ces méthodes. Cela dit, on dispose d'une classe utilitaire, Arrays, qui nous propose des méthodes statiques afin de palier ce manque. Parmi elles, des méthodes de comparaison, de calcul de code de hachage, et toString(). Nous verrons cette classe dans la partie sur l'API Java Collection.

4.4. Tableaux de tableaux

Les tableaux multidimensionnels sont en fait des tableaux de tableaux, comme dans la plupart des langages. La syntaxe pour créer de tels tableaux se déduit aisément :

Exemple 48. Déclaration d'un tableau bidimensionnel

int [][] tableauBidi ;  
tableauBidi =  new  int [10][5] ;

La syntaxe pour initialiser un tableau bidimensionnel se déduit elle aussi :

Exemple 49. Initialisation d'un tableau bidimensionnel

int tableauBidi [][] ;  
tableauBidi =  new  int [][] {  
    {0,  1,  2},   
    {1,  2,  3},  
    {2,  3,  4}  
} ;

Il est également possible d'initialiser un tableau ligne par ligne.

Exemple 50. Initialisation d'un tableau bidimensionnel - bis

int tableauBidi [][] =  new  int [3][] ;  
  
 for (int i =  0 ; i < tableauBidi.length ; i++) {  // tableauBidi.length vaut 3  
    tab[i] =  new  int [] {i, i +  1, i +  2} ;  
}

Dans la mesure où tableauBidi est un tableau de tableau, tableauBidi.length est défini, de même que tableauBidi[i].length, qui, dans notre exemple, vaut 3 pour le valeurs de i : 0, 1, 2. On peut s'amuser à construire des tableaux bizarres, c'est assez amusant à faire, et encore plus à débugger :

Exemple 51. Un tableau bizarre

int tableauBidi [][] =  new  int [3][] ;  
  
tableauBidi[0] =  new  int [] {0,  1,  2} ;  
tableauBidi[1] =  new  int [2] ;  
tableauBidi[2] = null ;

On voit ici l’intérêt qu’il y a à utiliser systématiquement le champ length lorsque l’on veut accéder aux différents éléments d’un tableau.

4.5. Copie de tableaux

La copie d'un tableau dans l'autre peut toujours se faire par itération sur les éléments du premier tableau, et recopie dans le second. Cela dit, l'API standard de Java nous fournit une méthode particulièrement rapide, qui fonctionne par copie de zone mémoire, sur le modèle du clonage d'objets. La méthode à invoquer est System.arraycopy, dont voici la signature.
 public static void arraycopy(Object src, int src_pos, Object dest, int dest_pos, int length)
Les objets src et dest doivent être des tableaux de même type, sans quoi une exception est générée. Ces deux tableaux doivent avoir été correctement déclarés et initialisés. Lors de cette opération, la machine Java va tenter de copier length éléments du tableau src à partir de l'index src_pos, vers le tableau dest, à partir de l'index dest_pos. Si un dépassement de capacité a lieu, une exception est générée. Ce dépassement peut avoir lieu en lecture (si src_pos + length dépasse la taille du tableau src), ou en écriture (si dest_pos + length dépasse la taille du tableau dest). Voici un exemple d'utilisation de cette méthode :

Exemple 52. Copie de tableau par System.arraycopy

int tab1 [] =  new  int [] {0,  1,  2,  3,  4,  5} ;  
 int tab2 [] =  new  int [] {0,  10,  20,  30,  40} ;  
  
System.arraycopy(tab1,  1, tab2,  1,  2) ;

À l'issue de cette copie, tab1 est inchangé, et le contenu de tab2 est le suivant :
 tab2 -> {0, 1, 2, 30, 40}
Rappelons qu'une fois un tableau créé, il n'est plus possible d'en changer la taille. L'utilisation de System.arraycopy ne déroge pas à cette règle.
Java langage & API
Retour au blog Java le soir
Cours & Tutoriaux
Table des matières
Introduction : un peu d'histoire
1. Java : genèse d'un nouveau langage
Programmer en Java
1. Un premier exemple
1.1. Aperçu général, cycle de vie
1.2. Un premier programme
1.3. Programmes, applets, servlets, etc...
2. Une première classe
2.1. Écriture d'une classe
2.2. Instanciation de cette classe
3. Types de base, classes et objets
3.1. Types de base
3.2. Classes et objets
Classes Object et String
1. Introduction
2. La classe Object
2.1. La méthode toString()
2.2. La méthode clone()
2.3. La méthode equals()
2.4. La méthode hashCode()
2.5. La méthode finalize()
2.6. La méthode getClass()
3. La classe String
3.1. Introduction
3.2. Construction d'un objet de type String
3.3. Concaténation, StringBuffer et StringBuilder
3.4. Concaténations de chaînes de caractères depuis Java 8
3.5. Concaténations de chaînes de caractères depuis Java 11
3.6. Extraction d'une sous-chaîne de caractères
3.7. Comparaison de deux chaînes de caractères
3.8. Méthodes de comparaisons lexicographiques
3.9. Méthode de recherche de caractères
3.10. Méthode de modification de chaîne
3.11. Méthode de duplication
3.12. Support de l'unicode, internationalisation
Structure d'une classe
1. Introduction
2. Classes
2.1. Classes publiques
2.2. Classes internes
2.3. Classe membre
2.4. Classes locales
2.5. Classes anonymes
2.6. Le mot-clé this
3. Éléments statiques
3.1. Champ statique
3.2. Cas des constantes
3.3. Bloc statique
3.4. Classe membre statique
4. Membres d'une classe, visibilité
4.1. Bloc non statique
4.2. Accès à un membre, visibilité
4.3. Les champs
4.4. Signature d'une méthode
4.5. Les méthodes
4.6. Getters et Setters
5. Constructeur, instanciation
5.1. Chargement d'une classe
5.2. Constructeurs d'une classe
5.3. Instanciation d'un objet
5.4. Destruction d'objets
5.5. Le mot-clé final
6. Énumérations
6.1. Déclaration d'une énumération
6.2. Classe énumération
6.3. Méthode toString()
6.4. Méthode valueOf()
6.5. Méthode values()
6.6. Méthode ordinal()
6.7. Méthode compareTo()
6.8. Constructeurs privés
6.9. Classe utilitaire : EnumSet
Noms, opérateurs, tableaux
1. Introduction
2. Identificateurs, noms et expressions
2.1. Identificateurs et noms
2.2. Expressions
3. Opérateurs, ordre d'exécution
3.1. Ordre d'exécution
3.2. Les opérateurs
3.3. Les opérateurs ++ et --
3.4. Les opérateurs % et /
3.5. Les opérateurs <<, >> et >>>
3.6. L'opérateur instanceof
3.7. Les opérateurs &, | et ^
3.8. Les opérateurs && et ||
3.9. L'opérateur ? ... :
3.10. Les opérateurs d'affectation
4. Tableaux
4.1. Création d'un tableau
4.2. Initialisation d'un tableau
4.3. Utilisation d'un tableau comme un Object
4.4. Tableaux de tableaux
4.5. Copie de tableaux
5. Blocs, boucles et contrôles
5.1. Blocs
5.2. Mots-clés réservés
5.3. Tests : if et switch
5.4. Boucles : for, while, do ... while
5.5. Commandes continue et break
5.6. Commandes return et goto
Nombres, précision, calculs
1. Introduction
2. Calculs
2.1. Précision
2.2. Codage des nombres flottants
2.3. Le mot-clé strictfp
2.4. Conversion de types
3. Dépassements de capacité
3.1. Cas des entiers
3.2. Cas des flottants
3.3. Bibliothèques BigInteger et BigDecimal
4. Fonctions mathématiques
4.1. Fonctions usuelles
4.2. Générateurs aléatoires
5. Classes enveloppe
5.1. Associer les types de base à des objets
5.2. Auto-boxing
Héritage, abstraction, interfaces
1. Introduction
2. Abstraction et encapsulation
2.1. Abstraction
2.2. Encapsulation
3. Héritage
3.1. Définition de l'héritage
3.2. Conséquences pour les membres
3.3. Polymorphisme
3.4. Empêcher l'héritage
4. Classes abstraites
5. Interfaces
5.1. Introduction
5.2. Définition
5.3. Java 8 et les interfaces
5.4. Utilisation des interfaces
5.5. Définition de constantes dans les interfaces
5.6. Utilité des interfaces
Packages
1. Introduction
2. Notion de paquet
2.1. Déclaration d’appartenance à un paquet
2.2. Chargement d’une classe
2.3. Choix de nom
3. Archives, chemin de recherche, classpath
3.1. Archives
3.2. Variable CLASSPATH
3.3. Notion de classloader
3.4. Bilan sur les classes chargées
3.5. Visibilité
3.6. Conseils d'écriture, bibliothèque standard
Exceptions
1. Introduction
2. Erreurs et Exceptions
2.1. Classe Throwable, notion de stack trace
2.2. Classe Error
2.3. Classe RuntimException
2.4. Classe Exception
3. Déclenchement d'une exception
3.1. Exceptions déclenchées par la JVM
3.2. Exceptions déclenchées par l'application
4. Capter une exception
4.1. Traiter une exception localement
4.2. Code de captage
5. Créer ses propres exceptions
Entrées / sorties
1. Introduction
2. Notion de fichier
2.1. Introduction
2.2. La classe File
2.3. Construction d'une instance de File
2.4. Méthodes exposées
3. Flux de sortie
3.1. Introduction, notion de flux
3.2. Écriture de caractères, classe Writer
3.3. Bufferisation, construction d'un flux sur un autre
3.4. Utilisation de PrintWriter
3.5. Écriture d'octets, OuputStream
3.6. Écriture de types primitifs : DataOutputStream
3.7. Écriture d'objets : ObjectOutputStream
4. Flux d'entrée
4.1. Introduction
4.2. Lecture de caractères, classe Reader
4.3. Bufferisation, lecture ligne par ligne
4.4. Lecture d'octets : InputStream
4.5. Lecture de types primitifs : DataInputStream
4.6. Lecture d'objets : ObjectInputStream
5. Lecture et écriture de flux croisés
5.1. Introduction
5.2. Lire des caractères sur un flux binaire : classe InputStreamReader
5.3. Écrire des caractères sur un flux binaire : classe OutputStreamWriter
5.4. Remarque sur les jeux de caractères
6. Serialization d'objets
6.1. Enjeu de la sérialization d'objets
6.2. Serialization d'un objet
6.3. Sérialization d'une grappe d'objets
6.4. Première surcharge : méthode writeObject() readObject()
6.5. Deuxième surcharge : utilisation d'un externalizer
6.6. Troisième surcharge : utilisation d'un objet proxy
7. Flux compressés
7.1. Introduction
7.2. Flux de type gzip
7.3. Flux de type zip