3. La classe String

3.1. Introduction

La classe String est une classe fondamentale du langage Java, puisque c'est elle qui permet de gérer les chaînes de caractères. C'est une classe qui comporte une quarantaine de méthodes, et quelques caractéristiques importantes. Tout d'abord cette classe est déclarée final. Ce qui signifie qu'il n'est pas possible de l'étendre. Toute méthode qui reçoit un objet de type String a donc la garantie que les méthodes qu'elle appelle sur cet objet ont bien le comportement nominal : elles n'ont pas pu être surchargées. Ensuite, un objet String contient un tableau de char, qui stocke la chaîne de caractères proprement dite. La classe est conçue de telle manière, qu'une fois un objet String construit, il n'est plus possible de modifier le contenu de ce tableau. On dit que la classe String est immutable , ce qui signifie que la donnée qu'elle contient est en "lecture seule". Elle suit en cela un design pattern qui porte ce nom. Entre autres choses, cela permet deux optimisations, sur la méthode hashCode() et la méthode equals() que nous verrons en détails.

3.2. Construction d'un objet de type String

On peut construire un objet String de deux façons :

Exemple 8. Construction d'un objet String

String s1 =  "Bonjour le monde !" ;  
String s11 =  "Bonjour le monde !" ;  
String s2 =  new String("Bonjour le monde !") ;

Ces deux façons ne sont pas rigoureusement équivalentes, et la différence entre les deux est assez subtile. Effectivement, si l'on évalue (s1 == s11), on se rend compte que la valeur retournée est true. Ce qui signifie que la machine Java n'a en fait créé qu'un seul objet, qu'elle a affecté aux deux variables s1 et s11. C'est une optimisation parfaitement valide : comme il n'est pas possible de changer la chaîne de caractères portée par l'objet créé par la JVM, on peut parfaitement n'en créer qu'un au lieu de deux. En revanche, si l'on évalue (s1 == s2), là le résultat est false, on a forcé la création d'un nouvelle chaîne de caractères par un appel explicite au new.

3.3. Concaténation, StringBuffer et StringBuilder

L'opérateur de concaténation est le +, comme on peut s'y attendre. Là encore, le principe de non modification d'une chaîne de caractères s'applique. Examinons le code suivant.

Exemple 9. Concaténation de deux String

String s1 =  "Bonjour" ;  
String s2 =  "le monde !" ;  
String s3 = s1 +  " " + s2 ;

Les trois opérations de concaténation s'enchaînent. Une première chaîne de caractères est créée, concaténation de s1 et " ", puis une deuxième, concaténation de cette première chaîne et de s2. Si l'on n'y prend pas garde, on voit que ces opérations de concaténation peuvent mener à la création de nombreuses chaînes intermédiaires, ce qui a un coût non négligeable en calculs et en ressources mémoire, surtout si ces chaînes sont longues. Une bonne habitude à prendre, est donc de n'utiliser ces opérations de concaténation que dans les cas où l'on se fiche de consommer trop de ressoures (rares), et dans les cas où l'on n'a que peu de concaténations à faire. Dans tous les autres cas (c'est à dire dans tous les cas ou presque), on préfèrera utiliser un StringBuilder, de la manière suivante.

Exemple 10. Concaténation de deux String avec StringBuilder

String s1 =  "Bonjour" ;  
String s2 =  "le monde !" ;  
StringBuilder sb =  new StringBuilder() ;  
sb.append(s1).append(" ").append(s2) ;  
String s3 = sb.toString() ;

Le résultat dans s3 est identique au précédent, mais dans ce cas, aucune chaîne de caractères intermédiaire n'a été utilisée. Notons que la concaténation peut opérer sur des objets qui ne sont pas des chaînes de caractères. Dans le cas des objets, la machine Java appelle la méthode toString() des objets à convertir. Dans le cas d'un type de base, un appel particulier à la méthode String.valueOf() est fait. Cette méthode est définie dans la classe String, il n'est donc pas possible de la surcharger.

Exemple 11. Concaténation de String et d'objets quelconques

Marin marin =  new Marin("Surcouf",  "Robert") ;  
String s1 =  "Bonjour " + marin ;  
  
StringBuilder sb =  new StringBuilder() ;  
sb.append("Bonjour ").append(marin) ;  
String s2 = sb.toString() ;

Les deux chaînes s1 et s2 contiennent des résultats identiques. Encore une fois, il a été plus rapide de calculer s2 que s1. La première classe (historiquement) utilisée pour concaténer efficacement des chaînes de caractères est StringBuffer. La classe StringBuilder est apparue dans l'API de Java 5, elle n'est donc pas disponible en version 4 et précédentes. Les méthodes exposées par ces deux classes sont identiques, on peut donc utiliser l'une ou l'autre de façon équivalente, du point de vue de la compilation. La seule différence entre ces deux classes est que StringBuffer est une classe synchronisée. Elle doit donc être utilisée en environnement multithreadé . La conséquence est que son utilisation est plus coûteuse que StringBuilder qui ne l'est pas. Par défaut, on doit donc utiliser StringBuilder. Si la variable concernée est en concurrence d'accès, alors on doit utiliser StringBuffer.

3.4. Concaténations de chaînes de caractères depuis Java 8

La problématique de la concaténation de chaînes de caractères est un sujet très délicat, qui a fait l'objet d'attentions depuis le milieu des versions 7 du JDK. Tout d'abord, lors de la concaténation simple de chaînes de caractères, le compilateur Java implémente lui-même le pattern StringBuilder. On peut donc éviter d'écrire un StringBuilder à la main lorsque l'on concatène entre elles quelques chaînes de caractères. Lorsque l'on doit concaténer des chaînes de caractères qui se trouvent dans une liste, on dispose de plusieurs patterns, tous introduits en Java 8. Le premier consiste à ouvrir un stream sur notre liste et a concaténer les éléments de ce stream en utilisant un collector, objet qui fait partie de l'API Stream et que nous verrons dans la suite de ce cours. L'API Stream apporte d'autres fonctionnalités très utiles, telles que la possibilité de retirer des chaînes de ce stream suivants certains critères, et celle de transformer ces chaînes. Si l'on n'a pas besoin des fonctionnalités complémentaires de l'API Stream, on peut encore utiliser deux autre méthodes. La première est la méthode join() de la classe String. Dans l'exemple suivant, concat1 vaut onetwothree, car la concaténation se fait sans caractère de séparation. La chaîne concat2 vaut one, two, three, du fait du caractère de séparation passé en premier paramètre.

Exemple 12. Concaténation d'une liste de String en utilisant String.join()

String [] strings = {"one",  "two, "three"} ; // déclaration d'un tableau de String
String concat1 = String.join(strings) ;
String concat2 = String.join(", ", strings) ;

Le dernier pattern introduit pour concaténer les chaînes de caractères consiste à utiliser la classe StringJoiner. Cette classe permet de concaténer des chaînes de caractères avec un séparateur et avec éventuellement un caractère de début de chaîne et un caractère de fin de chaîne. Le pattern est le suivant.

Exemple 13. Concaténation d'une liste de String en utilisant la classe StringJoiner

StringJoiner stringJoiner =  new StringJoiner(", ",  "{",  "}") ;
stringJoiner.add("one") ;
stringJoiner.add("two") ;
stringJoiner.add("three") ;
   
String concat = stringJoiner.toString() ;

La valeur de concat est {one, two, three} dans l'exemple précédent.

3.5. Concaténations de chaînes de caractères depuis Java 11

Les choses changent encore à partir de Java 11, puisque la concaténation des chaînes de caractères est désormais déléguée à la machine virtuelle elle-même et compilée avec une instruction de bytecode particulière : INVOKE_DYNAMIC. Cela apporte des nouvelles performances, qui rendent obsolète l'utilisation de StringBuilder.

3.6. Extraction d'une sous-chaîne de caractères

On peut extraire une chaîne de caractères d'une autre à l'aide de la méthode substring(), qui existe en deux versions :

Exemple 14. Extraction d'une sous-chaîne

String s1 =  "Bonjour le monde !" ;  
String s2 = s1.substring(8) ;      // extrait une sous-chaîne à partir du 8ème caractère  
String s3 = s1.substring(8,  10) ;  // extrait une sous-chaîne allant du 8ème au 
                                   // 10ème caractère

A la différence de la concaténation, la méthode substring() est très peu coûteuse en calculs. On peut examiner son code à titre d'exemple.

Exemple 15. Extraction d'une sous-chaîne : code exécuté

public String substring(int beginIndex,  int endIndex) {  
     if (beginIndex <  0) {  
         throw  new StringIndexOutOfBoundsException(beginIndex);  
    }  
     if (endIndex > count) {  
         throw  new StringIndexOutOfBoundsException(endIndex);  
    }  
     if (beginIndex > endIndex) {  
         throw  new StringIndexOutOfBoundsException(endIndex - beginIndex);  
    }  
     return ((beginIndex ==  0) && (endIndex == count)) ?  this :  
         new String(offset + beginIndex, endIndex - beginIndex, value);  
}

Les trois premiers tests servent à gérer les erreurs, le calcul proprement dit est effectué dans la clause return. Tout d'abord, si la sous-chaîne demandée est égale à la chaîne actuelle, alors aucun calcul n'est fait : on retourne la chaîne actuelle. On utilise ici l'optimisation rendue possible par le fait qu'un objet String est immutable. Si ce n'est pas le cas, on construit un nouvel objet String, à partir du tableau de caractères de notre chaîne, et de deux index, qui délimitent le début et la fin de la chaîne dans ce tableau. Ce constructeur de String va simplement enregistrer la référence de ce tableau, et les deux index. Aucune duplication de tableau n'est faite, et les deux objets : la chaîne originale et la sous-chaînes référencent en fait le même tableau de caractères en mémoire. Encore une fois, cette optimisation est rendue possible par le fait qu'un objet de type String est immutable.

3.7. Comparaison de deux chaînes de caractères

Il est intéressant d'étudier la façon dont les méthodes hashCode() et equals() ont été écrites dans la classe String. Commençons par regarder le code de la méthode hashCode().

Exemple 16. Code de la méthode hashCode() de la classe String

private  int hash ;  // déclaré plus haut dans la classe String

 public  int hashCode() {  
     int h = hash;  
     if (h ==  0) {  
         int off = offset;  
         char val[] = value;  
         int len = count;  
  
         for (int i =  0; i < len; i++) {  
            h =  31*h + val[off++];  
        }  
        hash = h;  
    }  
     return h;  
}

En interne, la classe String enregistre une chaîne de caractères dans un tableau de char. Dans certains cas, notamment si la chaîne que l'on manipule a été construite par extraction d'une sous-chaîne d'une première chaîne, ce tableau n'est pas utilisé dans sa totalité : seule une portion, allant de l'index offset et de longueur count est prise en compte. Cela explique pourquoi le calcul ne parcourt le tableau que de offset à offset + count. Lors du premier appel à cette méthode, la variable hash vaut 0. Donc, cette méthode calcule un code de hachage, sur la portion du tableau utilisée par cet objet String dans une variable h. La valeur de cette variable est ensuite sauvegardée dans la variable hash, qui est relue lors des appels suivants. Lors d'appels successifs à cette méthode, le calcul n'est donc pas repris. Le fait qu'une chaîne de caractères soit immutable est ici utilisé : le code de hachage n'est calculé qu'une seule fois, lors du premier appel à hashCode(), et simplement relu lors des appels suivants. Examinons à présent le code de equals().

Exemple 17. Code de la méthode equals() de la classe String

public  boolean equals(Object anObject) {  
     if (this == anObject) {  
         return true;  
    }  
     if (anObject  instanceof String) {  
        String anotherString = (String)anObject;  
         int n = count;  
         if (n == anotherString.count) {  
             char v1[] = value;  
             char v2[] = anotherString.value;  
             int i = offset;  
             int j = anotherString.offset;  
             while (n-- !=  0) {  
                 if (v1[i++] != v2[j++])  
                 return false;  
            }  
             return true;  
        }  
    }  
     return false;  
}

Ici on constate que les caractères sont comparés un par un, jusqu'à ce que la totalité de la chaîne ait été analysée. Dès qu'un caractère différent est détecté, le processus s'arrête, et la valeur false est retournée. Sinon, la méthode renvoie true. La comparaison ne se fonde pas sur la comparaison des codes de hachage, car la spécification exacte est que deux chaînes égales ont même code de hachage, mais l'inverse n'est pas nécessairement vrai.

3.8. Méthodes de comparaisons lexicographiques

La comparaison lexicographique consiste à comparer deux chaînes caractère par caractère. Voici les méthodes qui permettent de ce faire :
  • compareTo() et compareToIgnoreCase() : ces deux méthodes retournent un entier ( int), négatif si la chaîne comparée est inférieure au sens lexicographique, 0 si elles sont égales, positif si la chaîne comparée est supérieure.
  • startsWith(), endsWith() : renvoient true si la chaîne commence ou se termine par la chaîne passée en paramètre.
  • matches(), regionMatches() : renvoient true si la chaîne correspond à la chaîne passée en paramètre au sens des expressions régulières.
Voyons des exemples d'utilisation.
 String s1 = "Bonjour" ; String s2 = "le monde !" ; 

 int c = s1.compareTo(s2) ;
 -> c = -42 ; 

 c = s2.compareToIgnoreCase(s1) ;
 -> c = 10 ;

 boolean b = s1.startsWith("a") ;
 -> b = true

 b = s1.matches("[a-z]*") ;
 -> b = false

 b = s1.matches("[a-zA-Z]*") ;
 -> b = true

3.9. Méthode de recherche de caractères

Les méthodes de recherche de caractères ou de groupes de caractères permettent de localiser des éléments d'une chaîne de caractères.
  • indexOf() et lastIndexOf() : ces deux méthodes permettent de localiser un caractère donné dans une chaîne. indexOf() existe en deux versions : la première permet de localiser la première occurrence d'un élément dans une chaîne, la seconde prend en plus un index en paramètre, à partir duquel on recherche l'élément passé. Ces deux méthodes retournent -1 si l'élément n'est pas trouvé, ou une valeur entière correspondant à la position de l'élément trouvé dans la chaîne.
  • substring() : permet d'extraire une sous-chaîne d'une chaîne donnée.
  • charAt() : permet d'extraire un caractère à une position donnée d'une chaîne.
Voyons un exemple d'utilisation.

Exemple 18. Recherche d'un caractère dans une chaîne

String texte =  "Quand le ciel bas et lourd pèse comme un couvercle" ;  
  
 int index = texte.indexOf("e") ;  
 while (index >  0) {  
    System.out.println("J'ai trouvé un e en position " + index) ;  
    index = texte.indexOf("e", index +  1) ;  
}

Ce code opère sur un texte donné, qui a l'avantage d'être agréable à lire, et de rappeler de bons souvenirs aux anciens du bac français. Tout le monde connait l'amour indicible qu'ont les imprimeurs pour Lorem ipsum , semble-t-il depuis fort longtemps, mais un peu d'originalité de temps en temps ne peut pas faire de mal. On initialise l'algorithme en cherchant la première occurrence de la lettre e . Il continue à chercher des e dans cette phrase tant qu'il en trouve. L'appel à indexOf() prend alors en paramètre l'index de la lettre qui suit le dernier e que l'on a trouvé. Le résultat de l'exécution de ce code est le suivant.
 J'ai trouvé un e en position 7
 J'ai trouvé un e en position 11
 J'ai trouvé un e en position 18
 J'ai trouvé un e en position 30
 J'ai trouvé un e en position 36
 J'ai trouvé un e en position 45
 J'ai trouvé un e en position 49

3.10. Méthode de modification de chaîne

Les méthodes de modification de chaînes ne dérogent pas à la règle qu'une chaîne de caractères est immutable. Elles retournent toutes une nouvelle chaîne, sans modifier la chaîne originale. Éventuellement, en interne, elles peuvent référencer des portions du tableau de caractères qui contient la chaîne originale, dans un but d'optimisation.
  • replace(), cette méthode prend des variables char en paramètre, et se borne à remplacer toutes les occurrences du premier caractère par le second.
  • replaceFirst() et replaceAll() : ces deux méthodes prennent deux chaînes de caractères en paramètre. La première contient une expression régulière. Toutes les occurrences de cette expression sont remplacées par la deuxième chaîne.
  • toUpperCase(), toLowerCase() : retourne une chaîne résultat de la mise en majuscules ou en minuscules de la chaîne originale.
  • trim() : retourne une chaîne de laquelle les caractères blancs en tête ou en début de chaîne ont été retirés.
Redisons ici que la chaîne originale n'est pas modifiée par ces méthodes. Si l'on souhaite nettoyer une chaîne de caractères de ses espaces blancs en tête et en queue, il faut écrire le code suivant :
String s =  "      Quand le ciel        " ;  
s = s.trim() ;
Dans ce cas la chaîne s originale, qui n'est plus référencée par rien, sera effacée par le garbage collector .

3.11. Méthode de duplication

  • getBytes(), getChars() : retourne un tableau de byte ou de char correspondant à cette chaîne de caractères.
  • split() : découpe une chaîne de caractères à l'aide d'une expression régulière passée en paramètre. Le résultat du découpage est retourné dans un tableau de String.
  • toCharArray() : retourne un tableau de char correspondant à cette chaîne de caractères.
Remarquons que la notion de caractère a évolué en Java 6, dans le sens d'un meilleur support de l'UTF-8. L'UTF-8 est un codage de caractères qui supporte le standard Unicode. Or ce codage prévoit que certains caractères sont codés sur trois octets, alors que le type char de Java est codé sur deux octets. Cette différence menait à des incohérences, par exemple dans la méthode length(), qui retourne, jusqu'en Java 5, le nombre de char, et non pas le nombre de caractères au sens d'Unicode. Ce point a été réglé en Java 6. Il n'a a priori pas d'impacts pour le codage des alphabets latins classiques.

3.12. Support de l'unicode, internationalisation

Comme nous l'avons vu, Java supporte le codage UTF-8 du standard Unicode. Le support exact d'UTF-8 a été revu sur de nombreux points dans la version 6 de Java. En particulier la définition de la méthode length() a changé :
  • Java 5 : length() returns the length of this string. The length is equal to the number of 16-bit Unicode characters in the string.
  • Java 6 : length() returns the length of this string. The length is equal to the number of Unicode code units in the string.
L'internationalisation a également un impact sur de nombreuses méthodes de la classe String, c'est le cas par exemple pour la mise en majuscules ou en minuscules. Nous verrons ce point plus en détails quand sera présentée la classe Locale, centrale pour la question de l'internationalisation des applications.
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