Question 1 : Quelle est la différence fondamentale entre surcharge (overloading) et redéfinition (overriding) ?
La surcharge se produit lorsqu'une classe possède plusieurs méthodes avec le même nom mais des paramètres différents (nombre, type ou ordre). Il s'agit d'une liaison statique : le compilateur détermine la méthode à appeler lors de la compilation, sans intervention de la JVM.
La redéfinition, en revanche, concerne l'héritage et implique une liaison dynamique à l'exécution. La JVM utilise une table de méthodes virtuelles (vtable) pour appeler la méthode appropriée en fonction du type réel de l'objet, pas du type de la référence.
Exemple en code :
class Parente {
void afficher() {
System.out.println("Méthode parente");
}
}
class Enfant extends Parente {
@Override
void afficher() {
System.out.println("Méthode enfant");
}
}
// Appel dynamique
Parente ref = new Enfant();
ref.afficher(); // Affiche "Méthode enfant"
Au niveau du bytecode, la surcharge génère des signatures de méthodes distinctes, tandis que la redéfinition modifie l'entrée dans la vtable.
Question 2 : Pourquoi faut-il redéfinir à la fois equals() et hashCode() ?
Dans les collections comme HashMap, la méthode hashCode() détermine l'index du seau de stockage, puis equals() vérifie si la clé correspond. Si deux objets sont logiquement égaux mais ont des hashcodes différents, ils seront placés dans des seaux distincts, rendant la recherche impossible.
Convention essentielle : si a.equals(b) est vrai, alors a.hashCode() doit être égal à b.hashCode(). L'inverse n'est pas obligatoire, car des collisions de hashage sont normales et gérées efficacement par les structures de données modernes.
Exemple illustratif :
class Utilisateur {
String nom;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Utilisateur autre = (Utilisateur) obj;
return Objects.equals(nom, autre.nom);
}
// Oublier de redéfinir hashCode() entraîne des bugs cachés dans HashMap
// @Override
// public int hashCode() {
// return Objects.hash(nom);
// }
}
Question 3 : Poruquoi la classe String est-elle immuable ?
L'immuabilité de String repose sur trois piliers :
- Sécurité : String est utilisé dans les chemins de fichiers, les connexions réseau et les clés de collections. Une modification non contrôlée pourrait compromettre la sécurité.
- Pool de chaînes : Le string pool (répertoire des constantes) réutilise les instances existantes. Si String était mutable, altérer une instance affecterait toutes les références partagées.
- Cache du hashcode : La valeur de hashcode est calculée une fois et stockée. Si le contenu changeait, le hashcode deviendrait instable, perturbant les collections.
Implémentation sous-jacente :
// Dans JDK 8
private final char[] value; // Tableau final et privé
// Dans JDK 9+, optimisation mémoire avec byte[] et un encodeur
private final byte[] value;
private final byte coder;
Question 4 : Combien d'objets sont créés par new String("abc") ?
Le nombre dépend du contexte :
- Lors de la première exécution, deux objets sont créés : une instance dans le pool de constantes pour "abc" et un objet String dans le tas (heap) qui référence cette constante.
- Lors des exécutions suivantes, seul un objet est créé dans le tas, car "abc" est déjà dans le pool.
Exemple de bytecode simplifié :
// Instruction typique
ldc "abc" // Charge ou crée "abc" dans le pool
new String // Crée un nouvel objet String
invokespecial <init> // Initialise avec la référence du pool
astore_1 // Stocke la référence dans une variable
Question 5 : Pourquoi la plage de cache de Integer est-elle [-128, 127] ?
Cette plage correspond aux entiers les plus fréquemment utilisés en Java, réduisant ainsi le coût des allocations. L'implémentation se trouve dans la classe IntegerCache :
private static class CacheEntier {
static final Integer[] cache;
static {
cache = new Integer[256];
for (int i = -128; i <= 127; i++) {
cache[i + 128] = new Integer(i);
}
}
}
Il est possible de modifier la limite supérieure via un arguement JVM :
-XX:AutoBoxCacheMax=2000
Cela étend le cache jusqu'à 2000, utile dans les scénarios à haute performance.