Aller au contenu

1. Le JDK et les outils de build

Exemple complet : code/java-for-csharp/l01 — chaque commande ci-dessous est exécutée en CI sur les trois OS.

.NET Java Rôle
SDK .NET JDK (Java Development Kit) compilateur, runtime et outils en une seule installation
CLR JVM (HotSpot) exécute le code compilé, le compile à la volée (JIT), ramasse les miettes
IL dans une .dll bytecode dans des fichiers .class, zippés dans un .jar le résultat de la compilation
Roslyn (csc) javac le compilateur
dotnet run app.cs java Hello.java exécuter un fichier source sans projet
REPL C# JShell essayer une expression de manière interactive
CLI dotnet + .csproj Maven (pom.xml) ou Gradle (build.gradle.kts) compiler, tester, empaqueter, résoudre les dépendances
NuGet Maven Central registre public de paquets

La grande différence : le JDK n’a pas de système de projet. dotnet build comprend le .csproj ; javac ne comprend que des fichiers source. Les dépendances, les tests et l’empaquetage viennent d’un outil de build séparé, et Maven comme Gradle sont courants.

Une nouvelle version de Java sort tous les six mois, et une sur quatre est une version à support long terme (LTS) : Java 21 en 2023, Java 25 en septembre 2025. C’est le même rythme que les versions annuelles de .NET, avec une LTS tous les deux ans. La version du langage suit celle du JDK : il n’y a pas de <LangVersion>, on choisit un JDK et une cible avec --release.

OpenJDK est open source et plusieurs fournisseurs le compilent : Eclipse Temurin, le Microsoft Build of OpenJDK, Amazon Corretto, les builds d’Oracle. Ils exécutent le même code ; ils diffèrent par leurs conditions de support et leur calendrier de mises à jour. La CI de ce cours utilise Temurin ; ma machine fait tourner le build OpenJDK 25 d’Oracle.

Fenêtre de terminal
winget install Microsoft.OpenJDK.25

Ouvrez un nouveau terminal pour que java soit dans le PATH.

$ java --version
openjdk 25 2025-09-16
OpenJDK Runtime Environment (build 25+36-3489)
OpenJDK 64-Bit Server VM (build 25+36-3489, mixed mode, sharing)

Depuis Java 25, un fichier source peut être aussi court qu’un programme C# à instructions de niveau supérieur (JEP 512, fichiers source compacts) :

Hello.java
void main() {
IO.println("Hello, world!");
}
$ java Hello.java
Hello, world!

java Hello.java compile le fichier en mémoire et l’exécute ; rien n’est écrit sur le disque. Un fichier source compact importe aussi automatiquement tout le module java.base : List, Map ou Files n’ont donc besoin d’aucun import.

La forme classique, que vous verrez dans toutes les bases de code existantes, déclare une classe avec une méthode public static void main(String[] args) :

Greeter.java
public class Greeter {
public static void main(String[] args) {
System.out.println("Hello, " + (args.length > 0 ? args[0] : "world") + "!");
}
}

Avec les outils du JDK, compiler et exécuter sont deux étapes : javac écrit des fichiers .class, et java exécute une classe trouvée sur le class path (-cp), l’équivalent JVM de la recherche des assemblies.

Fenêtre de terminal
javac -d out Greeter.java # écrit out\Greeter.class
java -cp out Greeter Ada
Hello, Ada!

Quand on exécute un fichier source, une erreur de compilation arrête tout avant la moindre exécution :

Oops.java
void main() {
int count = "three";
}
$ java Oops.java
Oops.java:2: error: incompatible types: String cannot be converted to int
int count = "three";
^
1 error
error: compilation failed

Maven repose sur des conventions : placez le code là où il l’attend, et un pom.xml très court suffit.

hello-maven/
├── pom.xml ← comme le .csproj
└── src/
├── main/java/com/example/App.java
└── test/java/ ← les tests sont à côté, pas dans un autre projet
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>hello</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.release>25</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
  • groupId:artifactId:version (les coordonnées) identifient le projet, comme l’ID et la version d’un paquet NuGet. groupId est généralement un nom de domaine inversé.
  • -SNAPSHOT marque une version encore en développement, à peu près un suffixe de préversion.
  • maven.compiler.release est la version de Java ciblée par la compilation, comme <TargetFramework>.
  • Le nom de package com.example doit correspondre au dossier com/example. En C#, espaces de noms et dossiers sont indépendants ; en Java, le compilateur et le chargeur de classes s’appuient sur cette correspondance.
$ mvn package
...
[INFO] --- compiler:3.15.0:compile (default-compile) @ hello ---
[INFO] Compiling 1 source file with javac [debug release 25] to target\classes
...
[INFO] --- surefire:3.5.4:test (default-test) @ hello ---
[INFO] No tests to run.
[INFO]
[INFO] --- jar:3.5.0:jar (default-jar) @ hello ---
[INFO] Building jar: …\hello\target\hello-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS

mvn package exécute un cycle de vie : chaque phase inclut toutes les précédentes, et ce sont des plugins qui font le travail (compiler, surefire pour les tests, jar). Le premier build télécharge ces plugins dans ~/.m2/repository, l’équivalent Maven du dossier global des paquets NuGet.

Tâche dotnet Maven Gradle
Compiler dotnet build mvn compile gradle build (ou classes)
Exécuter les tests dotnet test mvn test gradle test
Produire l’artefact dotnet publish mvn package gradle jar
Nettoyer dotnet clean mvn clean gradle clean
Installer localement pour d’autres projets flux NuGet local mvn install gradle publishToMavenLocal (avec le plugin maven-publish)
Ajouter une dépendance dotnet add package modifier pom.xml modifier build.gradle.kts

Le JAR contient les classes compilées mais n’indique pas laquelle démarrer :

$ java -cp target/classes com.example.App
Hello from Maven!
$ java -jar target/hello-1.0-SNAPSHOT.jar
no main manifest attribute, in target\hello-1.0-SNAPSHOT.jar

Un JAR n’est pas un .exe : tant que son manifeste ne nomme pas de Main-Class, java -jar n’a rien à exécuter. L’exercice 2 corrige cela.

gradle init génère un projet avec un wrapper : des scripts gradlew qui téléchargent la version exacte de Gradle dont le projet a besoin, pour que les contributeurs n’aient pas à installer Gradle eux-mêmes (Maven a la même idée avec le Maven Wrapper).

hello-gradle/
├── settings.gradle.kts ← comme un .sln : liste les projets
├── gradle/libs.versions.toml ← catalogue de versions : les versions des dépendances au même endroit
├── gradlew, gradlew.bat ← le wrapper
└── app/
├── build.gradle.kts ← le script de build, en Kotlin
└── src/main/java/com/example/App.java
// app/build.gradle.kts (généré par Gradle 9.7.1, commentaires retirés)
plugins {
application
}
repositories {
mavenCentral()
}
dependencies {
testImplementation(libs.junit.jupiter)
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
implementation(libs.guava)
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(25)
}
}
application {
mainClass = "com.example.App"
}
Fenêtre de terminal
.\gradlew run
> Task :app:run
Hello World!
BUILD SUCCESSFUL in 2s

Les scripts de build Gradle sont du code, ce qui les rend puissants et plus difficiles à lire ; le XML de Maven est déclaratif et verbeux. Les guides de démarrage de Spring proposent les deux. Ce cours utilise Maven : ses conventions se retrouvent dans presque tous les projets Java que vous ouvrirez.

  • Le JDK fournit java, javac, jar et jshell ; le modèle de projet vient de Maven ou de Gradle.
  • java File.java exécute un fichier unique ; depuis Java 25, ce peut être un fichier compact avec void main().
  • Les packages doivent correspondre aux dossiers, et src/main/java / src/test/java est l’arborescence qu’attendent tous les outils.
  • Un JAR a besoin d’une entrée Main-Class dans son manifeste pour que java -jar puisse l’exécuter.
  1. Sans le compiler au préalable, exécutez Greeter.java avec votre nom en argument. Que fait le JDK pour rendre cela possible ?
Solution
$ java Greeter.java Grace
Hello, Grace!

Le mode fichier source du lanceur (JEP 330, étendu à plusieurs fichiers par la JEP 458) compile le fichier en mémoire, puis exécute la méthode main de la première classe de niveau supérieur avec les arguments restants. Aucun fichier .class n’est écrit.

  1. Faites fonctionner java -jar target/hello-1.0-SNAPSHOT.jar.
Solution

Configurez le plugin JAR pour qu’il écrive une entrée Main-Class dans le manifeste :

<build>
<plugins>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.example.App</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
$ mvn -q package
$ java -jar target/hello-1.0-SNAPSHOT.jar
Hello from Maven!

Le META-INF/MANIFEST.MF généré contient désormais Main-Class: com.example.App. Si l’application avait des dépendances, elles manqueraient toujours dans le class path : c’est le problème que résolvent les plugins de « fat JAR » (ou l’empaquetage propre à Spring Boot).

  1. Dans JShell, calculez -7 % 3 et Math.floorMod(-7, 3). Lequel correspond au % de C# ?
Solution
jshell> -7 % 3
$1 ==> -1
jshell> Math.floorMod(-7, 3)
$2 ==> 2

Dans les deux langages, % est un reste qui prend le signe du dividende : -7 % 3 vaut donc aussi -1 en C#. Math.floorMod est le modulo mathématique, toujours dans [0, 3) pour un diviseur positif.