Rapide introduction à Java

Le principe

Le langage Java a été conçu pour permettre l'exécution du même code sur diverses plate-formes. En particulier, mais pas uniquement, sur le web. Il y a plusieurs types de programmes Java, dont en particulier les applets Java, qui sont intégrées à des pages web et doivent respecter des règles très strictes pour ne pas risquer de causer des dégâts sur les machines d'innocents surfers, et les applications Java, qui fonctionnent comme d'autres programmes, en local sur une machine, et qui ne sont pas limités comme les applets. Dans les deux cas, le code Java est "compilé", mais les fichiers résultant de la compilation nécessitent encore une interprétation différente suivant chaque plate-forme: cette opération est réalisée par la JVM (Java Virtual Machine).

Ne pas confondre avec Javascript

Il ne faut pas confondre Java et Javascript, qui sont deux langages de programmation sans aucun rapport. Javascript est un langage exécuté directement par les navigateurs web, et qui permet de manipuler des pages web entières pour ajouter de l'interactivité. A l'inverse, les applets Java s'exécutent aujourd'hui avec un plugin qui n'est pas directement intégré au navigateur, et l'action d'une applet se limite à une zone bien déterminée d'une page web. Les applications Java peuvent aussi fonctionner sans navigateur, contrairement aux applications Javascript. Alors que Java est un langage orienté-objet utilisant le concept de classes et avec un typage fort, Javascript ne possède pas le concept de classe et utilise un typage faible. Il est possible de faire de l'orienté-objet malgré tout avec Javascript, mais il n'existe pas de syntaxe permettant à tout le monde de faire cela de la même façon. Les implémentations de Javascript des navigateurs sont incompatibles les unes avec les autres, en particulier Internet Explorer avant la version 9, alors qu'une applet Java testée sur un navigateur a de bonnes chances de fonctionner avec un autre (du moment que l'on fait attention à la version de Java).

Idées préconçues

Avant de poursuivre, j'aimerais débouter certaines idées préconçues au sujet de Java:

L'Orienté-Objet

Java est un langage orienté-objet (OO). Comme pour les autres langages OO, on définit des classes pour représenter des concepts (par exemple Etudiant), et on utilise ensuite des instances de ces classes (par exemple toto, qui est un étudiant). Pour chaque classe, on définit des attributs (par exemple nom) et des méthodes (par exemple afficherLesInfos(), qui affichera les infos sur l'étudiant).

schéma O.O.

On dérive une classe en partant d'une classe donnée et en ajoutant des méthodes ou des attributs. Par exemple, la classe Etudiant pourrait dériver d'une classe Personne, qui aurait entre autres l'attribut nom. Comme cela, on pourrait mettre l'attribut nom dans la classe Personne et on n'aurait plus besoin de le répéter dans Etudiant et d'autres classes dérivant de Personne.

Le principal avantage de l'orienté-objet est d'encapsuler les différentes parties du code d'une application. Comme les différentes classes sont indépendantes et leurs interfaces (méthodes et attributs) sont définies explicitement, on évite les erreurs causées par les dépendances non explicites entre morceaux de codes dans les langages procéduraux.

Les interfaces graphiques de Java

Les interfaces graphiques standard de Java sont au nombre de 2: AWT, avec des composants graphiques simples, et Swing, qui comporte des composants plus "lourds" et qui est basé sur AWT. Elles ne sont pas décrites dans cette introduction car il n'est pas nécessaire d'apprendre AWT ou Swing pour utiliser SimuLab. Il existe des documentations de Sun sur le sujet, que ce soit pour AWT [màj: le lien ne fonctionne plus sur le site d'Oracle] ou Swing. Il existe aussi une interface graphique non développée par Sun, SWT, utilisable avec l'environnement Eclipse.

Les fichiers classes

Chaque classe Java est enregistrée sous la forme d'un fichier, ayant le même nom que la classe. Il est important de respecter les majuscules et les minuscules, sachant que le nom d'une classe Java doit commencer par une majuscule (contrairement aux attributs, variables et méthodes).

Prenons l'exemple de la classe BonjourLeMonde. Elle doit être enregistrée dans un fichier nommé BonjourLeMonde.java.

La compilation génère des fichiers .class, par exemple BonjourLeMonde.class. La façon de compiler dépend du système d'exploitation. Le tutoriel Java de Sun indique quelques conseils pour compiler sur Windows et UNIX.

Les "packages", ou ensembles de classes

Pour organiser les classes, on les rassemble en "package". Cela permet d'encapsuler des ensembles de classes, de la même façon que l'on encapsule des attributs et méthodes dans une classe. Pour utiliser des classes d'un autre package, on utilise l'instruction import en début de fichier. L'instruction package permet de donner le nom du package de la classe définie.

Dans l'exemple de BonjourLeMonde, on utilise l'interface graphique AWT, contenue dans les packages java.awt et java.awt.event. Comme on veut pouvoir utiliser toutes les classes de ces packages sans avoir à les nommer, on utilise l'étoile:

import java.awt.*;
import java.awt.event.*;

Pour réunir des classes d'un ou plusieurs packages dans un seul fichier, on utilise des archives jar. L'utilitaire jar permet de créer ces archives, de la même façon que l'on crée des archives tar sous UNIX.

La variable d'environnement CLASSPATH

Pour que la machine Java retrouve ses petits dans tous les dossiers et archives, il faut lui indiquer où chercher. La variable d'environnement CLASSPATH comporte la liste des dossiers et archives jar où les classes peuvent se trouver, sachant que l'on n'indique que la racine du chemin à chaque fois. Par exemple, si le fichier Vector.class se trouve dans les dossiers java/util (c'est la règle à suivre si Vector se trouve dans le package java.util), il faut indiquer dans le CLASSPATH le chemin vers le répertoire java, et non le chemin vers le répertoire util.

La façon de changer la variable CLASSPATH est différente suivant chaque système d'exploitation. Par exemple:

La structure d'une classe

Voilà comment on peut déclarer BonjourLeMonde après avoir utilisé les instructions import:

/**
 * Application Java qui affiche "Bonjour le monde" dans une fenêtre.
 * La classe BonjourLeMonde dérive de Frame (une fenêtre),
 * et implémente l'interface ActionListener qui permet de
 * gérer des actions.
 *
 * @author Damien Guillaume
 */
public class BonjourLeMonde extends Frame implements ActionListener {
    // on va ajouter les attributs et méthodes ici
}

Tous les caractères entre /* et */, et ceux après // sur une ligne sont des commentaires et sont ignorés pour la compilation (ils n'en sont pas moins importants !). public signifie que cette classe pourrait être utilisée dans une autre classe (elle est "publique"). extends Frame signifie que la classe BonjourLeMonde dérive de la classe Frame. En effet, en Java, on ne doit pas écrire de "print" comme dans d'autres langages, car certains systèmes d'exploitation (comme MacOS 9) n'ont pas de fenêtre de terminal où ce texte pourrait être affiché: il faut donc au minimum ouvrir une fenêtre. Notons au passage que BonjourLeMonde est une application Java, et non une applet. Sinon, il aurait fallu dériver BonjourLeMonde de la classe Applet. Finalement, implements ActionListener signifie que BonjourLeMonde respecte l'interface ActionListener. Une interface est une classe sans attribut, et avec des définitions de méthodes mais sans implémentation pour ces méthodes. L'implémentation des méthodes doit être effectuée par la classe implémentant l'interface.

Après la ligne définissant le nom de la classe et les héritages, on trouve une liste des attributs de la classe. La valeur de ces attributs changera pour chaque instance, à moins que l'on utilise le mot-clé static, qui indique que la variable a une valeur unique pour toutes les instances de la classe. Pour BonjourLeMonde, on définit un attribut "s", instance de la classe String (en clair, s est tout simplement une chaîne de caractères):

private String s; // variable globale à la classe (mais privée:
// on ne peut pas l'utiliser à l'extérieur de la classe)

Chaque classe comporte au moins un constructeur, c'est-à-dire une méthode utilisée pour créer une nouvelle instance de la classe et l'initialiser. Le nom de ces méthodes doit être le nom de la classe, et la seule différence entre les différents constructeurs, quand il y en a plusieurs, est la liste d'arguments.

Voici le constructeur pour notre exemple. Il commence par un appel du constructeur de Frame, la classe dont BonjourLeMonde est dérivée, et ajoute ses propres initialisations.

/**
 * Constructeur de la classe BonjourLeMonde
 * @param titre Le titre de la fenêtre
 * @param s La chaîne de caractères à afficher
 */
public BonjourLeMonde(String titre, String s) {
    super(titre); // appel du constructeur de Frame, qui prend aussi
    // le titre en argument
    this.s = s; // la variable globale s (this.s) prend la valeur
    // de la variable locale s passée en paramètre
    afficher();
}

Les applications Java doivent déclarer une méthode statique main. Pour l'exemple:

/**
 * Méthode principale, appelée au lancement d'une application Java
 * avec ses arguments
 */
public static void main(String[] args) {
    // crée une nouvelle instance de la classe BonjourLeMonde,
    // avec "bonjour" comme titre de la fenêtre
    // et "Bonjour le monde" à afficher à l'intérieur
    new BonjourLeMonde("bonjour", "Bonjour le monde");
}

Les déclarations de variables et de tableaux

En plus des attributs de la classe, on peut déclarer des variables à l'intérieur des méthodes. Ces variables peuvent être déclarées à n'importe quel endroit, mais elles ne sont utilisables qu'à l'intérieur du bloc dans lequel elles sont déclarées (par exemple à l'intérieur de la méthode, ou à l'intérieur d'une boucle for). On déclare une variable avec à chaque fois le type ou la classe, et le nom de la variable. Quelques exemples:

Contrairement à la plupart des langages de programmation, Java possède un mécanisme permettant de libérer l'espace mémoire occupé par les variables qui ne sont plus utilisées. Il faut donc allouer de la mémoire pour commencer à travailler avec des objets (les instances des classes), mais il n'est pas nécessaire de gérer la désallocation de la mémoire, la machine Java se chargeant régulièrement (ou quand nécessaire) de nettoyer l'espace mémoire.

Appels de méthodes

Pour appeler la méthode d'une classe à partir d'une instance de cette classe, on utilise la syntaxe instance.méthode(paramètres), par exemple:

Personne p;
p = new Personne();
p.setNom("toto");

Les boucles et conditions

Quelques exemples de boucles et conditions en Java:

Notez qu'il est nécessaire de mettre des caractères { et } pour délimiter un bloc à partir du moment où il y a plus d'une instruction dans ce bloc. Pour ceux qui ont l'habitude du Pascal ou du Fortran, attention à bien commencer les boucle avec i=0 (les indices des tableaux commencent en effet par 0 en Java). Attention aussi à ne pas confondre i=0 (i prend la valeur 0) de i==0 (qui teste si i est égal à 0).

Les exceptions

Java a un mécanisme particulier pour gérer les erreurs. Chaque méthode peut "lancer" (throw) une "exception", qui signifie qu'une erreur est survenue pendant l'exécution de la méthode. Les méthodes lançant des exceptions doivent déclarer quels types d'erreurs (correspondant à des classes dérivées de la classe Exception) sont possibles. Quand on exécute une telle méthode, on peut éventuellement essayer (try) d'"attraper" (catch) l'erreur et de la gérer.

Par exemple, la transformation d'une chaîne de caractères en entier peut éventuellement lancer une erreur. La méthode valueOf de la classe Integer permet de faire cette transformation. Elle est déclarée ainsi:

public static Integer valueOf(String s) throws NumberFormatException

L'exception qui peut être lancée ici est NumberFormatException, qui indique un problème de formatage. On peut attrapper l'erreur comme cela:

String s = "1;2"; // mauvais formatage
Integer valeurNumerique;
try {
    valeurNumerique = Integer.valueOf(s);
} catch (NumberFormatException ex) {
    System.err.println("Erreur de formattage: " + ex.getMessage());
}

getMessage est une méthode de Exception, qui est donc aussi utilisable pour les instances de la classe NumberFormatException étant donné que cette classe dérive de Exception. Elle renvoie le message d'erreur correspondant. System.err correspond à la sortie UNIX "stderr", qui permet d'afficher du texte sous la ligne de commande. Attention à n'utiliser ça que pour débugger, car les utilisateurs qui ne voient pas de ligne de commande pourraient ne pas voir de message d'erreur du tout avec l'exemple précédent !

Les commentaires javadoc

Last but not least, les commentaires ! Il existe un petit utilitaire très pratique, javadoc, qui permet de générer automatiquement de la documentation HTML à partir du code Java... A condition bien sûr de respecter certaines règles pour qu'il retrouve ces commentaires dans le code. En particulier, il convient de mettre des commentaires devant chaque classe et méthode, formatés comme dans l'exemple. Tous les détails se trouvent sur le site d'Oracle. On gagne beaucoup de temps à documenter le code de cette façon.

D'autres introductions

Il existe bien sûr de nombreuses autres introductions à Java, plus complètes que celle-ci. Par exemple:

L'exemple complet

Voilà le fichier de l'exemple complet.