1. Interface Comparable<T>
Avez-vous déjà trié une liste de nombres ou de chaînes ? Bien sûr que oui ! Et maintenant, imaginez que vous avez une liste de vos propres objets — par exemple, une liste d’étudiants, de produits ou de chats. Comment Java va‑t‑il comprendre dans quel ordre les trier ? C’est précisément à cela que sert l’interface Comparable<T>.
Cette interface définit l’« ordre naturel » des objets — c’est‑à‑dire l’ordre qui est logique pour ce type de données. Par exemple, pour les nombres — par ordre croissant, pour les chaînes — par ordre alphabétique, pour les étudiants — par nom de famille ou par âge (à vous de choisir).
Comment fonctionne Comparable
L’interface est très simple : elle ne contient qu’une seule méthode :
public interface Comparable<T> {
int compareTo(T o);
}
La méthode compareTo doit renvoyer :
- un nombre négatif si l’objet courant est « plus petit » que l’autre ;
- 0 s’il est « égal » ;
- un nombre positif s’il est « plus grand ».
Exemple : trier des étudiants par âge
Ajoutons une classe Student et implémentons pour elle l’interface Comparable<Student> :
public class Student implements Comparable<Student> {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// Getters pour l’exemple
public String getName() { return name; }
public int getAge() { return age; }
@Override
public int compareTo(Student other) {
// Tri par âge (ordre croissant)
return Integer.compare(this.age, other.age);
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
Nous pouvons maintenant trier facilement un tableau ou une liste d’étudiants :
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Vasya", 20));
students.add(new Student("Petya", 18));
students.add(new Student("Masha", 22));
Collections.sort(students); // Fonctionne grâce à Comparable !
System.out.println("Étudiants triés:");
for (Student s : students) {
System.out.println(s);
}
}
}
Résultat :
Petya (18)
Vasya (20)
Masha (22)
Point important
Si vous implémentez Comparable, veillez à ce que compareTo soit cohérent avec equals. Autrement dit, si a.compareTo(b) == 0, alors a.equals(b) doit être true. Sinon, le tri et les collections peuvent se comporter de façon imprévisible — et vous aurez de quoi philosopher sur le sens de la vie du développeur.
2. Interface Serializable
La sérialisation est la capacité d’un objet à se transformer en une séquence d’octets (par exemple, pour se sauvegarder dans un fichier ou être envoyé sur le réseau), puis à être restauré. Imaginez que vous souhaitiez sauvegarder l’état de votre jeu ou envoyer un objet au serveur — sans sérialisation, impossible.
En Java, il existe pour cela une interface marqueur : Serializable. « Marqueur » signifie qu’elle ne contient pas de méthodes — elle « marque » simplement la classe comme sérialisable.
import java.io.Serializable;
public class Student implements Serializable {
private String name;
private int age;
// ... reste du code
}
Comment sérialiser un objet
Pour sérialiser et désérialiser, on utilise les classes ObjectOutputStream et ObjectInputStream. Exemple — on enregistre un objet dans un fichier puis on le relit :
import java.io.*;
public class Main {
public static void main(String[] args) throws Exception {
Student s = new Student("Katya", 19);
// On enregistre l’objet dans un fichier
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("student.dat"))) {
out.writeObject(s);
}
// On lit l’objet depuis le fichier
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("student.dat"))) {
Student loaded = (Student) in.readObject();
System.out.println("Chargé: " + loaded);
}
}
}
Remarque : Tous les champs de l’objet (et des objets imbriqués) doivent eux aussi être sérialisables, sinon une erreur surviendra.
Pourquoi une interface marqueur
L’interface Serializable n’exige aucune méthode — elle indique simplement à la JVM : « cet objet peut être sérialisé ». Si vous oubliez de l’implémenter, toute tentative de sérialisation entraînera l’exception NotSerializableException.
3. Autres interfaces importantes de la bibliothèque standard
Interface Cloneable
Encore une interface marqueur. Son rôle est d’indiquer à la JVM qu’un objet peut être cloné via la méthode Object.clone(). Sans cela, un appel à clone() lèvera une exception.
Cependant, le clonage en Java est piégeux. Par défaut, le clonage est superficiel (shallow copy), et il est souvent préférable d’écrire ses propres méthodes de copie.
public class Student implements Cloneable {
private String name;
private int age;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Interface AutoCloseable
Cette interface ne contient qu’une seule méthode close(). Toute classe qui l’implémente peut être utilisée avec la construction try-with-resources — fermeture automatique des ressources (par ex. fichiers, flux) :
public class MyResource implements AutoCloseable {
@Override
public void close() {
System.out.println("Ressource fermée!");
}
}
public class Main {
public static void main(String[] args) {
try (MyResource res = new MyResource()) {
System.out.println("On travaille avec la ressource");
}
// Ici, res.close() sera appelé automatiquement
}
}
Interface Iterable<T>
Cette interface permet à votre objet d’être « itérable » dans une boucle for-each. Elle ne contient qu’une seule méthode iterator(), qui renvoie un objet Iterator<T>.
public class MyList implements Iterable<String> {
// ... stockage interne
@Override
public java.util.Iterator<String> iterator() {
// Retourner un itérateur pour parcourir les éléments
return ...;
}
}
Toutes les collections standard (ArrayList, HashSet, etc.) implémentent Iterable, c’est pourquoi on peut les parcourir en for-each.
Interface Comparator<T>
Cette interface permet de comparer des objets selon différentes règles sans modifier les objets eux‑mêmes. Par exemple, trier des étudiants par nom plutôt que par âge.
import java.util.Comparator;
Comparator<Student> byName = new Comparator<Student>() {
@Override
public int compare(Student a, Student b) {
return a.getName().compareTo(b.getName());
}
};
En Java moderne, on le fait généralement avec des expressions lambda :
Comparator<Student> byName = (a, b) -> a.getName().compareTo(b.getName());
Observer, EventListener
Ces interfaces servent à mettre en place les patrons « observateur » et « écouteur d’événements » — lorsqu’un objet réagit aux événements survenant dans un autre. Par exemple, dans les interfaces graphiques (Swing, JavaFX), les gestionnaires de boutons implémentent l’interface ActionListener.
4. Pratique : implémenter Comparable et sérialiser un objet
Exemple 1. Comparable pour une classe personnalisée
Écrivons une classe Book que l’on peut trier par année de publication :
public class Book implements Comparable<Book> {
private String title;
private int year;
public Book(String title, int year) {
this.title = title;
this.year = year;
}
@Override
public int compareTo(Book other) {
return Integer.compare(this.year, other.year);
}
@Override
public String toString() {
return title + " (" + year + ")";
}
}
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Book> books = Arrays.asList(
new Book("Java pour les Nuls", 2018),
new Book("Guerre et Paix", 1869),
new Book("Harry Potter", 1997)
);
Collections.sort(books);
System.out.println(books);
}
}
Résultat :
[Guerre et Paix (1869), Harry Potter (1997), Java pour les Nuls (2018)]
Exemple 2. Sérialisation d’un objet
import java.io.*;
public class Book implements Serializable {
private String title;
private int year;
// ... constructeur, getters, toString
public Book(String title, int year) {
this.title = title;
this.year = year;
}
@Override
public String toString() {
return title + " (" + year + ")";
}
}
public class Main {
public static void main(String[] args) throws Exception {
Book book = new Book("Java pour les Nuls", 2018);
// On enregistre l’objet dans un fichier
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.dat"))) {
out.writeObject(book);
}
// On lit l’objet depuis le fichier
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("book.dat"))) {
Book loaded = (Book) in.readObject();
System.out.println("Chargé: " + loaded);
}
}
}
5. Tableau : principales interfaces de la bibliothèque standard
| Interface | Rôle | Méthodes clés | Exemple d’utilisation |
|---|---|---|---|
|
Ordre naturel des objets | |
Tri des listes |
|
Comparaison personnalisée des objets | |
Tri selon des règles différentes |
|
Sérialisation des objets | — (marqueur) | Sauvegarde/chargement d’objets |
|
Clonage d’objets | — (marqueur) | Création de copies d’objets |
|
Fermeture automatique des ressources | |
try-with-resources |
|
Itération d’éléments dans les collections | |
boucle for-each |
| Observer / EventListener | Réaction aux événements | |
Traitement des événements dans l’UI, patrons |
6. Erreurs courantes avec les interfaces standard
Erreur n° 1 : l’interface n’est pas implémentée alors que la fonctionnalité est requise.
Par exemple, vous oubliez d’implémenter Serializable mais tentez de sérialiser un objet — vous obtiendrez NotSerializableException. De même avec Cloneable et l’appel à clone().
Erreur n° 2 : violation du contrat entre Comparable et equals.
Si a.compareTo(b) == 0 mais que ce n’est pas le cas pour a.equals(b), les collections peuvent se comporter étrangement. Par exemple, TreeSet peut « perdre » des objets.
Erreur n° 3 : copie superficielle lors du clonage.
La méthode clone() copie par défaut uniquement la « couche supérieure » de l’objet. Si vous avez des champs qui référencent d’autres objets, ils ne sont pas copiés en profondeur. Cela peut mener à des bugs déroutants.
Erreur n° 4 : non‑respect de try-with-resources.
Si une classe implémente AutoCloseable mais que vous ne l’utilisez pas avec try-with-resources, vous risquez d’oublier de fermer la ressource — et d’obtenir une fuite mémoire ou un fichier verrouillé.
Erreur n° 5 : mauvaise implémentation de compareTo ou compare.
Si vous retournez seulement 0 ou 1, au lieu d’un nombre négatif/zéro/positif, le tri fonctionnera de manière incorrecte.
GO TO FULL VERSION