Java - notes

Adrien Foucart

2025-09-03

Retour aux notes

But de ce document

Documenter les aspects importants et/ou non-intuitifs pour moi du langage Java en développant des programmes divers.

Organisation des sources

Le modèle standard est d’utiliser un template basé sur l’adresse web de l’organisation. Donc, pour un projet perso pour moi, ce serait be.adfoucart.monprojet comme package “racine” du projet. Au niveau des sources, on aurait par exemple:

root_project
--- src
------ be
--------- adfoucart
------------ monprojet
--------------- Main.java
--------------- souspackage
--- bin
--- build.xml
--- makefile

build.xml est utilisé si on utilise ant pour compiler, ce qui est un bon compromis facilité d’utilisation / facilité de compréhension de ce qui se passe. maven à garder surtout si on a beaucoup de dépendances à gérer, a priori.

Compiler sans outils

Si on veut rester sur juste java et javac, on va avoir:

javac -g -d target -cp target Source.java

Pour compiler Source.java et mettre Source.class dans target.

Notes: -g permet de donner plus d’infos de debugging, -d target donne la destination, -cp target indique qu’il faut utiliser target comme classpath, et donc y chercher les .class déjà générer pour trouver les imports.

Si on utilise javac comme ça, il faut donc bien gérer l’ordre dans lequel on compile les classes, en terminant toujours par la classe Main.

On va ensuite lancer le programme avec:

java -cp target be.adfoucart.monprojet.Main

Avec à nouveau -cp target pour donner le classpath.

Compiler avec ant

Créer un fichier build.xml avec la structure:

<project default="compile">
    <target name="compile">
        <mkdir dir="bin"/>
        <javac srcdir="src" destdir="bin"/>
    </target>
</project>

Pour compiler les sources de src et mettre les .class dans le dossier bin. L’ordre de compilation sera automatiquement détecté.

Si on veut aller un peu plus loin et permettre différentes actions (clean, générer .jar), on peut se référer à l’exemple fourni par Apache, pour arriver à quelque chose comme:

<project name="Mon projet" default="compile" basedir=".">
    <description>Mon beau projet Java</description>
    <property name="src" location="src"/>
    <property name="build" location="bin"/>
    <property name="dist" location="target"/>
    <target name="init">
        <tstamp/>
        <mkdir dir="${build}" />
    </target>
    <target name="compile" depends="init">
        <javac srcdir="${src}" destdir="${build}"/>
    </target>
    <target name="dist" depends="compile">
        <mkdir dir="${dist}"/>
        <!-- Put everything in ${build} into the MyProject-${DSTAMP}.jar file -->
        <jar jarfile="${dist}/Monprojet-${DSTAMP}.jar" manifest="manifest.mf" basedir="${build}"/>
    </target>
    <target name="clean">
        <!-- Delete the ${build} and ${dist} directory trees -->
        <delete dir="${build}"/>
        <delete dir="${dist}"/>
    </target>
</project>

Ici, ant créera les .class comme avant (mais en créant le dossier bin si il n’existait pas). ant clean supprimera tout sauf les sources. ant dist créera un fichier jar pour distribuer le projet plus facilement. Pour que ce JAR puisse être exécuté, cependant, il faut aussi ajouter un fichier manifest.mf au même niveau que build.xml:

Manifest-version: 1.0
Main-Class: be.adfoucart.monprojet.Main

Grâce auquel on pourra lancer le programme avec java -jar ./dist/MonProjet-250903.jar (pour un projet généré le 3 septembre 2025).

Gestion des callbacks

Il y a certainement d’autres manières de faire, mais une méthode intéressante pour s’approcher du “passer une méthode en argument” de Python est de passer par les fonctions lambda.

Si on a par exemple une class Popup créée par une classe Window, et qu’on veut que Window passe un callback à Popup à appeler en cas d’action, on peut utiliser Runnable pour faire:

# Popup.java
public class Popup {
    Runnable callback;

    public Popup(Runnable c){
        this.callback = c;
    }

    public void action(){
        System.out.println("Calling callack:");
        this.callback.run();
    }
}

# Window.java
public class Window {
    public void do_callback(){
        System.out.println("Callback!");
    }

    public void createPopup(){
        Popup popup = new Popup(() -> this.do_callback());
    }
}

Lire le user input

Utiliser la classe java.util.Scanner avec System.in pour récupérer l’input sur le terminal.

Example:

Scanner scanner = new Scanner(System.in);
String user_input = scanner.nextLine();

Exemple de Makefile

On peut se faciliter la vie pour le build - run avec un makefile. Une version qui fonctionne pour Windows et Linux ressemble à ceci:

ifeq ($(OS),Windows_NT) 
RM = del /Q /F /S
CP = copy /Y
BUILD = "$(ANT)"
ifdef ComSpec
SHELL := $(ComSpec)
endif
ifdef COMSPEC
SHELL := $(COMSPEC)
endif
else
RM = rm -rf
CP = cp -f
BUILD = ant
endif

JVM = java
JVM_FLAGS = -cp bin
MAIN_CLASS = be.adfoucart.monprojet.Main

.PHONY: build clean run

default: build

build:
        @echo "Building project"
        @$(BUILD)

clean:
        $(RM) ./bin/be/adfoucart/monprojet
        
run:
        @$(JVM) $(JVM_FLAGS) $(MAIN_CLASS)

La première partie gère les différences Linux/Windows dans les commandes. Ici, le makefile nécessite que ant soit installé et, pour Windows, que la variable d’environnement ANT contienne le chemin vers l’exécutable.

On prépare ensuite les commandes de la JVM pour l’exécution.

make build va lancer ant et utiliser le build.xml pour générer les fichiers .class du projet. make run va lancer la JVM sur la classe principale.