Un objet peut avoir été déclaré avec un type très général tout en représentant, à l’exécution, une classe beaucoup plus précise. L’opérateur instanceof en Java permet de vérifier cette situation avant d’utiliser un cast, avec des exemples sur les classes, les interfaces, les valeurs nulles et le pattern matching moderne. Je montre aussi les erreurs fréquentes et les cas où une meilleure conception objet évite d’y recourir.
L’essentiel à retenir sur le test de type en Java
- instanceof renvoie un booléen indiquant si un objet est compatible avec un type.
- Le test renvoie toujours false pour null.
- Une sous-classe et une classe qui implémente une interface passent également le test.
- Depuis Java 16, le pattern matching permet de tester et de convertir l’objet dans une seule expression.
- Pour accepter uniquement une classe exacte, getClass() est souvent plus adapté.
À quoi sert instanceof en Java
La syntaxe de base est simple, mais son intérêt vient du polymorphisme Java. Une variable peut être déclarée comme `Animal`, alors que l’objet réellement stocké est un `Chien`. Le test permet de savoir si cet objet peut être utilisé comme une instance d’un type donné.
Object valeur = "Bonjour";
if (valeur instanceof String) {
System.out.println("La valeur est une chaîne");
}
Le résultat est un boolean : `true` si l’objet est compatible avec le type demandé, sinon `false`. Le compilateur vérifie aussi que les deux types peuvent avoir un rapport cohérent, ce qui évite certains tests absurdes dès la compilation.
Le test ne compare pas le texte du nom de la classe et ne vérifie pas simplement le type de la variable. Il examine le type réel de l’objet à l’exécution. C’est cette distinction qui le rend utile dans les collections, les API génériques et les méthodes recevant un objet déclaré sous un type abstrait.
Une sous-classe passe aussi le test
class Animal {
}
class Chien extends Animal {
}
Animal animal = new Chien();
System.out.println(animal instanceof Animal); // true
System.out.println(animal instanceof Chien); // true
Le premier résultat paraît évident, mais le second est souvent la vraie raison d’utiliser l’opérateur. Même si la variable est déclarée comme `Animal`, l’instance concrète est un `Chien`. À l’inverse, un objet `Animal` créé directement ne passerait pas le test pour `Chien`.
Comment fonctionne le test avec les interfaces
Une interface décrit un comportement sans imposer une classe unique. Si une classe l’implémente, ses objets sont considérés comme compatibles avec cette interface, ce qui permet d’écrire du code indépendant de l’implémentation concrète.
interface Exportable {
void exporter();
}
class Rapport implements Exportable {
public void exporter() {
System.out.println("Export du rapport");
}
}
Object objet = new Rapport();
if (objet instanceof Exportable) {
System.out.println("L'objet peut être exporté");
}
Dans cet exemple, je teste la capacité de l’objet à fournir un comportement précis plutôt que son nom de classe. C’est généralement plus souple qu’un test contre `Rapport`, surtout lorsque plusieurs classes différentes peuvent implémenter `Exportable`.
| Objet réel | Test | Résultat |
|---|---|---|
new Rapport() |
instanceof Rapport |
true |
new Rapport() |
instanceof Exportable |
true |
new Rapport() |
instanceof String |
false |
Le même principe s’applique aux tableaux. Un tableau de chaînes est aussi un tableau d’objets, car `String` hérite de `Object`. Les tableaux Java implémentent également certaines interfaces comme `Cloneable` et `Serializable`, ce qui peut expliquer un résultat inattendu au premier abord.
Le pattern matching rend le code plus sûr
Avant Java 16, il fallait effectuer le test puis convertir séparément l’objet. Cette forme reste valide, mais elle répète le type et laisse davantage de place à une erreur de cast.
Object valeur = "Bonjour";
if (valeur instanceof String) {
String texte = (String) valeur;
System.out.println(texte.length());
}
Avec le pattern matching for instanceof, le test et la déclaration de la variable se font ensemble.
Object valeur = "Bonjour";
if (valeur instanceof String texte) {
System.out.println(texte.length());
}
La variable `texte` n’existe que lorsque le test a réussi. Java applique ici une règle de portée appelée flow scoping : le compilateur suit le chemin logique du programme pour déterminer où la variable est forcément disponible.
Combiner plusieurs conditions
Object valeur = "Bonjour";
if (valeur instanceof String texte && !texte.isBlank()) {
System.out.println(texte.toUpperCase());
}
Cette écriture fonctionne parce que la partie située après `&&` n’est évaluée que si le premier test est vrai. En revanche, utiliser la variable après un `||` est généralement impossible, car le second côté pourrait être évalué alors que le premier test a échoué.
if (valeur instanceof String texte || texte.isBlank()) {
// Erreur : texte n'est pas garanti dans tous les chemins
}
Je recommande aussi la forme avec négation et sortie rapide lorsque la méthode doit poursuivre son traitement uniquement pour un type précis.
if (!(valeur instanceof String texte)) {
return;
}
System.out.println(texte.length());
Le code devient plus lisible, car le cas non pertinent est évacué immédiatement. Cette approche réduit souvent l’imbrication de plusieurs blocs `if` et rend les méthodes plus faciles à relire.
Les limites à connaître avant d’utiliser l’opérateur
Le cas particulier de null
Une référence nulle ne pointe vers aucune instance. Même si la variable est déclarée avec le bon type, le test renvoie donc false.
String texte = null;
System.out.println(texte instanceof String); // false
Ce comportement est pratique, car le test ne déclenche pas de `NullPointerException`. Il ne faut toutefois pas en déduire que toutes les opérations suivantes sont sûres : appeler directement `texte.length()` reste interdit lorsque `texte` vaut `null`.
Les types primitifs
`instanceof` s’utilise principalement avec des références d’objets. Une valeur primitive comme `int`, `double` ou `boolean` n’est pas une instance de classe et ne peut pas être testée directement avec cette syntaxe.
int nombre = 10;
// Incorrect : une valeur primitive n'est pas une référence d'objet
// nombre instanceof Integer
Si un test de type est réellement nécessaire, Java peut travailler avec la classe enveloppe correspondante, comme `Integer`. Je préfère cependant éviter le boxing uniquement pour effectuer ce contrôle, car une comparaison ou une validation classique est souvent plus claire.
Lire aussi : LocalDateTime en Instant - Évitez les pièges de l'heure d'été
Le cast reste une opération distincte
Le test confirme la compatibilité, mais il ne transforme pas automatiquement une variable dans toutes les situations. Avec le pattern matching, la nouvelle variable typée est créée dans la condition. Sans lui, il faut encore effectuer le cast explicitement.
Object valeur = Integer.valueOf(42);
if (valeur instanceof Integer) {
Integer nombre = (Integer) valeur;
System.out.println(nombre + 1);
}
Effectuer un cast sans test peut provoquer une ClassCastException. Le contrôle est donc utile lorsque plusieurs types sont réellement possibles, mais il ne doit pas servir à masquer une hiérarchie de classes mal conçue.
Instanceof ou getClass quelle méthode choisir
Ces deux approches répondent à des questions différentes. `instanceof` demande si l’objet est compatible avec un type, tandis que `getClass()` vérifie si son type d’exécution est exactement celui attendu.
| Approche | Ce qu’elle vérifie | Cas d’usage |
|---|---|---|
objet instanceof Parent |
La classe ou une sous-classe | Accepter une famille d’objets |
objet.getClass() == Parent.class |
La classe exacte uniquement | Refuser les sous-classes |
type.isInstance(objet) |
Compatibilité avec un type stocké dans un objet Class
|
Réflexion et frameworks |
class Parent {
}
class Enfant extends Parent {
}
Parent objet = new Enfant();
System.out.println(objet instanceof Parent); // true
System.out.println(objet.getClass() == Parent.class); // false
Cette différence est particulièrement importante dans `equals()`. Utiliser `instanceof` peut considérer deux instances de classes différentes comme comparables, alors que `getClass()` impose une égalité stricte des classes. Le bon choix dépend du contrat métier de la classe, pas d’une préférence de style.
Avec une classe abstraite ou une interface, instanceof exprime généralement mieux l’intention polymorphe. Avec une logique qui doit absolument distinguer chaque implémentation, un test de classe exact peut être justifié.
Les erreurs fréquentes et les bons réflexes
La première erreur consiste à confondre l’opérateur avec une affectation. Il ne modifie pas la variable et ne crée pas une nouvelle instance. Il répond uniquement à une question de compatibilité de type.
if (objet instanceof String) {
// Le test ne change pas objet
}
La deuxième erreur est de multiplier les tests dans une méthode qui devrait déléguer le comportement aux objets eux-mêmes. Si chaque sous-classe possède une manière différente de calculer ou d’exporter une donnée, le polymorphisme est souvent plus propre qu’une longue suite de `instanceof`.
for (Forme forme : formes) {
forme.dessiner();
}
Dans cet exemple, chaque objet sait dessiner sa propre représentation. Le code appelant ne doit pas vérifier s’il reçoit un cercle, un rectangle ou un triangle. C’est plus extensible et cela évite de modifier une méthode centrale à chaque ajout de classe.
J’utilise tout de même `instanceof` lorsqu’une frontière technique l’exige, par exemple pour traiter le contenu hétérogène d’une collection, adapter une réponse externe ou sécuriser une conversion. Mon repère est simple : un test ponctuel peut clarifier le code, une cascade de tests signale souvent un problème de conception.
- Testez une interface lorsque vous dépendez d’un comportement.
- Préférez le pattern matching pour éviter un cast séparé.
- Traitez explicitement les valeurs nulles si la suite du code les utilise.
- Évitez les tests de type si une méthode virtuelle peut porter la logique.
- Utilisez
getClass()uniquement lorsque les sous-classes doivent être refusées.
Le réflexe à garder pour vos prochains contrôles de type
Pour retenir l’essentiel, l’opérateur répond à la question suivante : « Cet objet peut-il être considéré comme une instance de ce type ? » Il fonctionne avec les classes et les interfaces, renvoie `false` pour `null` et permet, depuis Java 16, de déclarer la variable typée directement dans le test.
Dans un code moderne, je privilégie donc une condition comme objet instanceof Type valeur, puis je vérifie si le contrôle est vraiment nécessaire. Lorsqu’une interface, une méthode abstraite ou un traitement polymorphe peut faire le travail, cette solution sera souvent plus robuste sur le long terme.