Aller au contenu

Java : vue d'ensemble

Avant d'écrire du code, une carte du territoire : ce qu'est vraiment « Java », comment ton code s'exécute, et en quoi il diffère de Rust, C++ et Python.

Dans ce chapitre

  • Java = un langage + une machine virtuelle (la JVM) + une grosse bibliothèque standard
  • Le trajet .java → bytecode → code machine, et le rôle du JIT et du GC
  • Java face à Rust, C++ et Python, point par point
  • Ce qui va te surprendre en arrivant
  • Les conventions de nommage, et le plan de la partie Java

Java, c'est trois choses

Quand on dit « Java », on parle en fait de trois éléments distincts :

Le langage
La syntaxe et les règles de typage (classes, interfaces, génériques, record...). Le compilateur javac le traduit en bytecode, pas en code machine.
La JVM (Java Virtual Machine)
Le programme qui exécute le bytecode : elle charge les classes, les vérifie, les interprète, compile à la volée les parties chaudes et gère la mémoire. D'autres langages ciblent la même JVM (Kotlin, Scala, Clojure).
La bibliothèque standard
Le JDK livre des milliers de classes : collections, E/S, réseau, HTTP, dates, concurrence, cryptographie... C'est beaucoup plus large que la std de Rust, plus proche des « piles incluses » de Python.

Le JDK (Java Development Kit) regroupe les trois, plus les outils (javac, jshell, javap, jar...). Tu l'installes au chapitre suivant.

Du fichier source à l'exécution

flowchart LR
    A["Calcul.java<br/>(source)"] -->|javac| B["Calcul.class<br/>(bytecode portable)"]
    subgraph JVM["JVM"]
        direction TB
        C["Chargeur de classes<br/>+ vérificateur"] --> D["Interpréteur"]
        D -->|"méthodes chaudes"| E["JIT : compilation<br/>en code machine"]
        F["GC : ramasse-miettes"]
    end
    B -->|java Calcul| JVM

Prenons un programme minuscule :

Calcul.java
public class Calcul {
    static int carre(int x) {
        return x * x;
    }

    public static void main(String[] args) {
        System.out.println(carre(7));   // affiche : 49
    }
}

javac Calcul.java produit Calcul.class. L'outil javap -c montre le bytecode de carre (commentaires ajoutés) : des instructions pour une machine à pile, indépendantes du processeur.

static int carre(int);
  Code:
     0: iload_0      // empile le 1er argument
     1: iload_0      // l'empile encore
     2: imul         // multiplie les deux sommets de pile
     3: ireturn      // renvoie le résultat

Ce même .class tourne sur Linux, macOS ou Windows, x86 ou ARM : c'est le fameux write once, run anywhere. Pas besoin de recompiler par cible comme en Rust ou en C++.

Sous le capot : interprète, JIT et GC

  • Au démarrage, la JVM interprète le bytecode (lent mais immédiat).
  • Elle compte les appels : une méthode appelée souvent est compilée en code machine par le JIT (Just-In-Time), d'abord rapidement (compilateur C1), puis de façon très optimisée (C2) en s'appuyant sur ce qu'elle a observé à l'exécution (types réels, branches prises). Elle peut même revenir en arrière si une hypothèse devient fausse.
  • Le GC (Garbage Collector, ramasse-miettes) libère les objets inaccessibles. Par défaut c'est G1 ; d'autres existent (ZGC vise des pauses de moins d'une milliseconde).

Conséquence pratique : un programme Java est lent pendant ses premières secondes (phase de warm-up), puis atteint des performances proches du natif. C'est idéal pour un serveur qui tourne des jours, moins pour un petit outil en ligne de commande.

Un fichier .class commence toujours par les octets CA FE BA BE, suivis du numéro de version du format : 69 pour Java 25. Une JVM plus ancienne refuse un .class trop récent.

Java face à ce que tu connais

Aspect Rust Java
Compilation native, en avance (LLVM) bytecode, puis JIT dans la JVM
Mémoire possession et emprunts, libération déterministe GC, libération quand il le décide
Typage statique, inférence très large statique, inférence locale (var)
Absence de valeur Option<T> null partout, Optional<T> en retour
Erreurs Result<T, E>, panic! exceptions, certaines vérifiées par le compilateur
Génériques monomorphisés effacés à la compilation (type erasure)
Performance maximale, démarrage instantané proche après chauffe, démarrage plus lent, plus de RAM
Aspect C++ Java
Compilation native, une par plateforme bytecode portable + JIT
Mémoire manuelle / RAII, objets sur la pile par défaut objets toujours sur le tas, GC
Méthodes non virtuelles par défaut toutes virtuelles
Héritage multiple simple, plus des interfaces multiples
Absence de valeur nullptr, comportement indéfini si déréférencé null, NullPointerException (jamais d'UB)
Erreurs exceptions ou codes de retour exceptions partout
Performance maximale, contrôle total proche après chauffe, moins de contrôle
Aspect Python Java
Exécution bytecode .pyc interprété bytecode .class interprété puis compilé (JIT)
Typage dynamique, annotations facultatives statique, vérifié à la compilation
Mémoire comptage de références + GC des cycles GC traçant, pas de compteur
Absence de valeur None null
Erreurs exceptions exceptions, certaines vérifiées par le compilateur
Parallélisme limité par le GIL (par défaut) vrais threads parallèles
Performance lent sur du calcul pur nettement plus rapide après chauffe

En résumé : Java a le typage statique de Rust et C++, mais le modèle mémoire de Python (tout objet est manipulé par référence et libéré automatiquement).

Ce qui va te surprendre

Tout est classe
Pas de fonction libre : tout code vit dans une classe (sauf dans un petit fichier « compact » de Java 25, vu au chapitre suivant). Seuls 8 types primitifs (int, double, boolean...) ne sont pas des objets. Voir Syntaxe et types.
Des références partout
Une variable de type objet contient une référence, jamais l'objet lui-même. a = b ne copie rien : les deux noms désignent le même objet. Voir Mémoire et références.
Le GC
Pas de free, pas de delete, pas de destructeur appelé à un moment précis. Pour fermer un fichier, on utilise try-with-resources, l'équivalent de with en Python.
Les exceptions
C'est le mécanisme d'erreur standard, et le compilateur t'oblige à traiter certaines d'entre elles (les checked exceptions). Voir Exceptions et Optional.
null
N'importe quelle référence peut valoir null, et le compilateur ne t'aide pas. La NullPointerException sera ta compagne de route.
Java moderne est concis
La réputation de verbosité vient du Java des années 2000. Depuis Java 8 (lambdas), 10 (var), 16 (record) et 21 (pattern matching), le code ressemble beaucoup plus à du Kotlin ou du Rust.

Compare le même programme écrit « à l'ancienne » et en Java 25 :

Java à l'ancienne (extrait)
static final class Personne {
    private final String nom;
    private final int age;

    Personne(String nom, int age) {
        this.nom = nom;
        this.age = age;
    }

    public String getNom() { return nom; }
    public int getAge() { return age; }
    // + equals(), hashCode() et toString() écrits à la main (une quinzaine de lignes)
}

// dans main :
List<String> plusDe30 = new ArrayList<String>();
for (Personne p : gens) {
    if (p.getAge() >= 30) {
        plusDe30.add(p.getNom());
    }
}
Java 25 (programme complet)
record Personne(String nom, int age) {}   // equals, hashCode, toString inclus

void main() {
    var gens = List.of(new Personne("Ada", 36), new Personne("Linus", 28));
    var plusDe30 = gens.stream()
            .filter(p -> p.age() >= 30)
            .map(Personne::nom)
            .toList();
    IO.println(plusDe30);   // affiche : [Ada]
}

Tu croiseras les deux styles : beaucoup de code existant (et de réponses sur Internet) date d'avant Java 17. Ce cours utilise le style moderne, et signale l'ancien quand il est encore courant.

Conventions de nommage

Java est très homogène sur ce point, et Spring s'appuie parfois sur ces noms (par exemple les getters, voir Classes et objets).

Élément Convention Exemple
Classe, interface, record, enum PascalCase TodoService, HttpClient
Méthode, variable, paramètre camelCase findById, nbTaches
Constante (static final) MAJUSCULES_SOULIGNEES MAX_TENTATIVES
Constante d'enum MAJUSCULES Statut.EN_COURS
Paquet minuscules, domaine inversé xyz.dunkan.todo.web
Paramètre de type générique une majuscule T, E, K, V

Piège : pas de snake_case

Contrairement à Rust et Python, les méthodes et variables sont en camelCase : find_by_id choque tout lecteur Java. Et dans Spring Data, le nom d'une méthode est même analysé pour générer une requête (findByTitreContaining) : la casse compte.

Le domaine inversé sert à garantir des noms de paquets uniques au monde : si tu possèdes dunkan.xyz, tes paquets commencent par xyz.dunkan. Le paquet correspond à un dossier (xyz/dunkan/todo/), comme un module Rust ou un paquet Python.

Plan de la partie Java

Chapitre Ce que tu y apprends
Installer et lancer JDK 25, java Fichier.java, javac, jshell, IDE
Syntaxe et types primitives, String, switch moderne, tableaux, méthodes
Mémoire et références pile et tas, passage par valeur, == vs equals, null, GC, ressources
Classes et objets classes, visibilité, héritage, interfaces, equals/hashCode
Records, enums, sealed types algébriques à la Rust, pattern matching
Génériques et collections List, Map, Set, génériques et leurs limites
Exceptions et Optional try/catch, exceptions vérifiées, Optional
Lambdas et streams fonctions en paramètre, traitements de collections façon itérateurs Rust
Threads et concurrence threads, executors, threads virtuels, synchronisation
Annotations et réflexion les mécanismes qui font fonctionner Spring
Maven pom.xml, dépendances, cycle de build, le wrapper

Comment travailler

Chaque chapitre se termine par des exercices corrigés. Tape le code toi-même : java Fichier.java suffit pour tout exécuter jusqu'au chapitre Maven, sans projet ni configuration.