Les fondamentaux essentiels de Java

  1. Types de données primitifs

1.1 Autoboxing et Unboxing

L'autoboxing est la conversion automatique d'un type primitif en son type wrapper correspondant. L'unboxing est l'opération inverse. Depuis Java 5, le compilateur gère ces conversions.


public class DemoTypes {
    public static void main(String[] args) {
        // Autoboxing: int -> Integer
        Integer valeurEntiere = 25; 
        // Unboxing: Integer -> int
        int valeurPrimitive = valeurEntiere; 
    }
}

Liste des types primitifs et wrappers:

  • byte -> Byte
  • boolean -> Boolean
  • char -> Character
  • short -> Short
  • int -> Integer
  • float -> Float
  • long -> Long
  • double -> Double

1.2 Questions d'entretien classiques

1. Résultat de ce code ?


public class TestCache {
    public static void main(String[] args) {
        Integer alpha = 127;
        Integer beta = 127;
        Integer gamma = 200;
        Integer delta = 200;
        System.out.println(alpha == beta);      // true
        System.out.println(gamma == delta);      // false
        
        Long prems = 10L;
        Long deux = 10L;
        Long troisieme = 200L;
        Long quatrieme = 200L;
        System.out.println(prems == deux);       // true
        System.out.println(troisieme == quatrieme); // false
    }
}

Pour Integer et Long, le cache maintient des objets pour les valeurs entre -128 et 127 inclus. Au-delà, de nouveaux objets sont créés. La méthode Integer.valueOf(int) illustre ce mécanisme.

2. Différence entre new Integer(50) et Integer.valueOf(50) ?

La première forme crée systématiquement un nouvel objet. La seforme utilise le cache pour les plages de valeurs prédéfinies, offrant généralement de meilleures performances.

3. == vs equals()

L'opérateur == compare les références (adresses mémoire) pour les objets. La méthode equals(), héritée d'Object, compare le contenu par défaut mais est souvent redéfinie. Les wrappers et String redéfinissent equals() pour comparer les valeurs.

4. hashCode() et equals()

La méthode hashCode() retourne un identifiant numérique pour un objet, utilisé par les collections comme HashMap. Si deux objets sont égaux selon equals(), ils doivent avoir le même hashcode. L'inverse n'est pas vrai. Il est donc crucial de redéfinir les deux méthodes de manière cohérente.

5. Générique et effacement de type

Les génériques (introduits dans Java 5) permettent la création de classes, interfaces et méthodes avec des paramètres de type. L'effacement de type (type erasure) est un processus par lequel le compilateur supprime toute information générique après la compilation pour assurer la compatibilité avec les anciennes versions de Java. Les caractères génériques courants sont ? (wildcard non borné), ? extends T (borne supérieure) et ? super T (borne inférieure).

  1. Pourquoi Java utilise-t-il uniquement le passage par valeur ?

Java passe toujours une copie de la valeur de l'argument à une méthode. Pour un type primitif, c'est une copie de sa valeur. Pour un type référence, c'est une copie de la référence (adresse de l'objet), pas une copie de l'objet lui-même. Ainsi, on ne peut pas changer l'objet original auquel pointait la variable passée en argument, mais on peut modifier l'état interne de cet objet via la référence copiée.


public class PassByValueDemo {
    public static void main(String[] args) {
        int nombre = 10;
        augmenter(nombre);
        System.out.println(nombre); // Affiche 10, inchangé
        
        int[] tableau = {1, 2, 3};
        modifierTableau(tableau);
        System.out.println(tableau[0]); // Affiche 99, l'original est modifié
        
        String texte = "origine";
        modifierChaine(texte);
        System.out.println(texte); // Affiche "origine", inchangé (String est immutable)
    }
    
    static void augmenter(int n) {
        n = n + 5;
    }
    
    static void modifierTableau(int[] tab) {
        tab[0] = 99; // Modifie l'objet original via la référence copiée
    }
    
    static void modifierChaine(String s) {
        s = s + " modifiée"; // Crée un nouvel objet String
    }
}
  1. Copie superficielle (Shallow Copy) vs Copie profonde (Deep Copy)

Copie superficielle

Crée un nouvel objet mais copie les références des champs de type référence. Les champs primitifs et les objets immuables (comme String) sont copiés correctement, mais les objets mutables partagés peuvent causer des effets de bord.


public class Personne implements Cloneable {
    private String nom;
    private Adresse adresse; // Classe mutable

    public Personne(String nom, Adresse adresse) {
        this.nom = nom;
        this.adresse = adresse;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); // Copie superficielle
    }
    // Getters et setters...
}

Copie profonde

Crée un nouvel objet et copie récursivement tous les objets qu'il référence, garantissant une indépendance totale entre la copie et l'original. Elle peut être implémentée via la sérialisation/désérialisation ou en redéfinissant clone() pour cloner aussi les objets référencés.


// Implémentation alternative sans clone
public class Personne {
    private String nom;
    private Adresse adresse;

    public Personne deepCopy() {
        Personne copie = new Personne();
        copie.nom = this.nom;
        copie.adresse = this.adresse.deepCopy(); // Copie profonde de l'objet référencé
        return copie;
    }
    // ...
}
  1. Surcharge (Overloading) vs Redéfinition (Overriding)

Surcharge de méthode

Plusieurs méthodes dans la même classe ont le même nom mais des signatures différentes (types ou nombre de paramètres différents). C'est une résolution à la compilation (polymorphisme statique).

Redéfinition de méthode

Une méthode dans une sous-classe a la même signature (nom et paramètres) qu'une méthode dans sa super-clase. C'est une résolution à l'exécution (polymorphisme dynamique). La méthode redéfinie doit respecter certaines règles (covariance du type retour, contravariance des exceptions).


public class Animal {
    public void parler() {
        System.out.println("...");
    }
    public Animal creerBebe() { return new Animal(); }
}

public class Chien extends Animal {
    @Override // Redéfinition
    public void parler() {
        System.out.println("Woof!");
    }
    
    @Override // Covariance du type retour
    public Chien creerBebe() {
        return new Chien();
    }
    
    // Surcharge dans la même classe
    public void parler(String message) {
        System.out.println("Woof " + message);
    }
}

Étiquettes: Java autoboxing types-primitifs equals hashCode

Publié le 19 juin à 02h35