Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 4

Types primitifs vs. types référence : les classes wrapper

Recommended resources

Bonjour ! Dans la leçon précédente, nous avons mis en place une structure de projet professionnelle avec Maven. Vous êtes maintenant prêt à écrire du code Java dans un environnement organisé, capable de gérer des dépendances et de suivre des standards de l'industrie.

Aujourd'hui, nous plongeons au cœur du langage Java pour comprendre comment il manipule les données. L'objectif est d'apprendre à distinguer les types primitifs des types référence, un concept fondamental qui a des implications sur la performance, l'utilisation de la mémoire et la manière d'écrire votre code. Nous verrons également comment les classes wrapper (ou classes enveloppes) font le pont entre ces deux mondes. Votre expérience avec JavaScript, qui possède une distinction similaire, vous donnera une bonne base de départ.

1. La grande distinction : Primitifs vs. Référence

En Java, il existe deux grandes familles de types de données. Comprendre leur différence est crucial.

Cette image illustre la différence fondamentale : les types primitifs (à gauche) stockent directement une valeur simple, tandis que les types référence (à droite) stockent une adresse qui pointe vers un objet plus complexe en mémoire.

Les Types Primitifs

Un type primitif est le type de donnée le plus simple. Une variable de type primitif contient directement la valeur elle-même. C'est comme une petite boîte qui contient un nombre, un caractère ou une valeur vrai/faux.

Java en compte huit :

  • Types entiers : byte, short, int, long
  • Types à virgule flottante : float, double
  • Type caractère : char
  • Type booléen : boolean

Les types primitifs — Le langage Java

Cet article du site gayerie.dev offre une excellente introduction aux types primitifs en Java.

Lisez les sections intitulées : "Les types primitifs" : pour voir la liste complète. "Le type booléen : boolean", "Le type caractère : char", "Les types entiers" et "Les types à virgule flottante". Focalisez-vous sur le tableau "Taille mémoire" qui vous donne une idée de l'espace occupé par chaque type. Notez aussi la convention de nommage : les types primitifs s'écrivent toujours en minuscules.

Voici comment on les utilise :

int age = 30; // La variable 'age' contient la valeur 30
double prix = 19.99; // La variable 'prix' contient la valeur 19.99
boolean estActif = true; // La variable 'estActif' contient la valeur true
char initiale = 'A'; // La variable 'initiale' contient la valeur 'A'

La caractéristique la plus importante des types primitifs est qu'ils ne sont pas des objets. Cela signifie que vous ne pouvez pas appeler de méthodes sur eux. Le code age.toString() provoquerait une erreur de compilation.

Les Types Référence

À l'opposé, une variable de type référence ne contient pas l'objet lui-même, mais une référence (ou une adresse mémoire) qui pointe vers l'emplacement de l'objet. C'est comme une télécommande qui pointe vers la télévision, ou une adresse postale qui indique où se trouve une maison.

Toutes les classes que vous créerez, ainsi que les classes intégrées comme String, ArrayList ou Object, sont des types référence.

La vidéo suivante explique très bien ce concept de "pointeur" vers un emplacement en mémoire.

Apprendre Java #2 Variable et Référence

La vidéo "Apprendre Java #2 Variable et Référence" de la chaîne JirAWS utilise une excellente analogie avec les "cases" de la mémoire RAM pour visualiser ce qu'est une référence.

Regardez ces trois passages clés : L'explication de la RAM : Visualisez comment un objet est stocké dans une "case" mémoire et comment la variable n'est qu'un pointeur vers cette case. L'unicité des objets : Comprenez que même deux objets identiques sont des instances distinctes dans deux cases mémoire différentes. Références partagées : Voyez ce qui se passe lorsque deux variables pointent vers le même objet. Une modification via une variable est visible via l'autre.

Voici un exemple en code :

// 'nomUtilisateur' est une référence qui pointe vers un objet String
String nomUtilisateur = new String("Alice"); 

// 'autreNom' est une autre référence
// Elle ne crée pas un nouvel objet, elle pointe vers le MÊME objet que 'nomUtilisateur'
String autreNom = nomUtilisateur; 

Contrairement aux primitifs, vous pouvez appeler des méthodes sur les types référence, car ils représentent de véritables objets (nomUtilisateur.toUpperCase()). De plus, une variable de type référence peut avoir la valeur spéciale null, signifiant qu'elle ne pointe vers aucun objet.


2. Les Classes Wrapper : Le Pont entre Deux Mondes

Alors, que se passe-t-il si nous avons besoin de traiter un type primitif comme un objet ? Par exemple, pour le stocker dans une collection qui n'accepte que des objets (nous verrons cela bientôt).

C'est là qu'interviennent les classes wrapper (ou classes enveloppes). Chaque type primitif a une classe correspondante qui "enveloppe" la valeur primitive dans un objet.

Type Primitif Classe Wrapper
int Integer
double Double
char Character
boolean Boolean
long Long
short Short
byte Byte
float Float

Notez la convention : les classes wrapper commencent par une majuscule.

Cette vidéo explique très clairement leur utilité.

PROGRAMMER EN JAVA (Débutant) - 57 - Les Wrappers

Cette vidéo de la chaîne "LES TEACHERS DU NET" intitulée "Les Wrappers" est une excellente introduction.

Regardez les sections suivantes : Qu'est-ce qu'un wrapper ? : Il présente la correspondance entre types primitifs et wrappers. Avantages des wrappers : Il démontre le principal avantage : la possibilité d'utiliser des méthodes sur ces objets. Exemple pratique : L'exemple avec Integer.parseInt() est très pertinent. En tant que développeur web, vous avez constamment besoin de convertir des entrées (qui sont des chaînes de caractères) en nombres.

Utilités des classes wrapper :

  1. Méthodes utilitaires : Elles fournissent des méthodes très pratiques. La plus courante est la conversion d'une chaîne de caractères en un type numérique.
    String input = "123";
    int nombre = Integer.parseInt(input); // Convertit la chaîne "123" en l'entier 123
    
  2. Utilisation dans les collections : Comme nous le verrons dans le prochain module, les collections standards de Java comme ArrayList ne peuvent stocker que des objets. On ne peut pas faire ArrayList<int>, mais on peut faire ArrayList<Integer>.
  3. Représenter l'absence de valeur : Un Integer peut être null, alors qu'un int doit toujours avoir une valeur (par défaut, 0). C'est très utile pour représenter une donnée qui n'a pas encore été fournie, par exemple dans une base de données.

3. Autoboxing et Unboxing : La Magie du Compilateur

Pour faciliter la vie du développeur, Java effectue des conversions automatiques entre les types primitifs et leurs wrappers.

  • Autoboxing : Conversion automatique d'un primitif vers son wrapper.
    Integer nombreWrapper = 100; // Le compilateur traduit ça en : Integer nombreWrapper = Integer.valueOf(100);
    
  • Unboxing : Conversion automatique d'un wrapper vers son primitif.
    int nombrePrimitif = nombreWrapper; // Le compilateur traduit ça en : int nombrePrimitif = nombreWrapper.intValue();
    

Les types primitifs — Le langage Java

Retournez à l'article de gayerie.dev pour lire la section finale sur ce mécanisme.

Lisez la section "L’autoboxing". Elle explique ce mécanisme avec des exemples clairs et, surtout, elle met en évidence un piège très important à connaître.

⚠️ Attention, bonne pratique professionnelle :
L'autoboxing est pratique, mais dangereux si la variable wrapper est null. Tenter de faire l'unboxing d'une référence null provoquera une erreur à l'exécution : une NullPointerException.

Integer monNombre = null;
int nombre = monNombre; // CRASH ! Provoque une NullPointerException à l'exécution.

C'est l'une des erreurs les plus courantes en Java. Soyez donc toujours prudent lorsque vous manipulez des wrappers qui pourraient être null.


4. Exercice Pratique

Mettons ces concepts en application dans notre projet Maven.

  1. Ouvrez votre projet dans IntelliJ IDEA.
  2. Dans le dossier src/main/java, créez un package (clic droit sur java -> New -> Package). Nommez-le avec votre groupId inversé (par exemple, fr.votreentreprise.fundamentals).
  3. Dans ce nouveau package, créez une classe Java nommée TypesDemo.
  4. Copiez-collez le code suivant dans cette classe et suivez les instructions en commentaires.
package fr.votreentreprise.fundamentals; // Adaptez avec votre nom de package

public class TypesDemo {
    public static void main(String[] args) {
        // --- Partie 1: Primitifs vs. Référence ---
        int a = 10;
        Integer b = 10; // Autoboxing

        System.out.println("Type primitif int: " + a);
        System.out.println("Type référence Integer: " + b);

        // Essayez de décommenter la ligne suivante. Que se passe-t-il ?
        // a.toString(); // -> Erreur de compilation : "int cannot be dereferenced"
        
        // Ceci fonctionne car 'b' est un objet avec des méthodes.
        System.out.println("Conversion de l'Integer en String: " + b.toString());

        // --- Partie 2: Méthodes utilitaires ---
        String userInput = "25";
        int age = Integer.parseInt(userInput);
        System.out.println("L'année prochaine, vous aurez: " + (age + 1));
        
        // --- Partie 3: Le piège du NullPointerException ---
        Integer valeurNulle = null;
        
        // Que se passe-t-il si on essaie d'affecter un Integer null à un int ?
        try {
            int valeurPrimitive = valeurNulle; // Cette ligne va lancer une exception
            System.out.println("Cette ligne ne sera jamais affichée.");
        } catch (NullPointerException e) {
            System.out.println("Erreur attrapée ! On ne peut pas 'unboxer' une valeur nulle.");
            System.out.println("Message de l'exception: " + e.getMessage());
        }
        
        System.out.println("Le programme continue après l'erreur gérée.");
    }
}

Exécutez ce code (clic droit sur TypesDemo -> Run 'TypesDemo.main()'). Observez les sorties dans la console et assurez-vous de bien comprendre chaque partie.


Conclusion

Félicitations ! Vous avez exploré une des distinctions les plus fondamentales de Java.

Ce qu'il faut retenir :

  • Types primitifs (int, double, etc.) : stockent directement la valeur, sont rapides, en minuscules, et n'ont pas de méthodes.
  • Types référence (String, Integer, etc.) : stockent une adresse vers un objet, en majuscules, peuvent être null et possèdent des méthodes.
  • Les classes wrapper (Integer, Double, etc.) servent de pont en encapsulant une valeur primitive dans un objet. Elles sont indispensables pour les collections et offrent des méthodes utilitaires.
  • L'autoboxing/unboxing facilite la conversion, mais il faut se méfier de la NullPointerException lors de l'unboxing d'une référence null.

Dans la prochaine leçon, nous allons nous concentrer sur un type référence que vous utiliserez constamment : la classe String. Nous verrons en détail comment manipuler des chaînes de caractères, une compétence essentielle pour tout développeur backend.