Site Perso de

Thomas JANNAUD

Des actualités personnelles sous un style impersonnel, et inversement.



Tutoriel Java
Bases pour créer une appli en Java avec interface + astuces et code sources 02 Avril 2008
java

Le Java est un langage objet très agréable à programmer, et proche du C++; d'autre part ce langage dispose d'une librairie très fournie et accessible, disponible sur ce lien. Il parait qu'apprendre le Java revient à apprendre 80 % du C++ et réciproquement. C'est peut-être vrai.

La seule différence que j'ai pu noter est cependant de taille : en C++, de plus bas niveau, on doit gérer la mémoire nous même, et lorsque l'on envoie un objet en paramètre à une fonction, l'objet est recopié (par le constructeur de copie) et ça fait souvent le bazar ; en C++ il vaut mieux ne passer que des pointeurs en paramètres. Par contre, les objets en Java sont automatiquement des pointeurs vers ces objets, ce qui rend la programmation plus naturelle.

Un grand intérêt de Java est qu'il fonctionne sur toutes les plateformes (Windows, Mac, ...), et que d'autre part on peut faire des Applets en Java, c'est à dire de petites applications qui s'affichent sur des pages web. De nos jours le HTML 5 / Javascript remplace ça mais jusqu'en 2010 disons que c'était une des solutions concurrentes à Flash.

Les gros geeks qui se la jouent aux puristes diront que Java est cependant plus lent que le C++. C'est vrai puisqu'il est interprété plus ou moins à la volée par la machine virtuelle Java. Il est presque aussi rapide que le C++ sur le calcul pur, mais quand même plus lent pour les graphiques.
En pratique, à moins que vous ne vouliez programmer Quake 6, Java conviendra (sauf si à 20 ans vous avez une grosse barbe et des cheveux longs et gras et que vous aimez passer vos journées dans votre chambre, volets tirés, et haleine de cow-boy de rigueur).

Les éditions O'Reilly sont réputées pour leurs livres de qualité, je ne saurais que trop les recommander aussi.
D'autre part, par expérience, vous verrez qu'un site internet ne remplacera jamais un livre papier.
Cette version est en anglais, il n'y a pas de version récente en français. Si vous ne le parlez pas bien, ça vous fera d'une pierre deux coups (apprendre l'anglais et le java) ! :)

Utilisation du compilateur

Normalement, la JVM (Java Virtual Machine) est déjà installée sur votre ordinateur. Vous n'avez donc rien besoin d'installer de plus en soi. Mais comme je vous l'ai rappelé dans la page de présentation des différents langages, un éditeur de texte avec coloration syntaxique + un raccourci pour compiler et lancer votre programme ne vous fera pas de mal, bien au contraire (cf EditPlus, Notepad++, ...).

Sinon compilez avec javac votrenomdefichier.java (dans n'importe quelle console/terminal) puis lancez avec java votrenomdefichier (sans extension) (ou plus exactement le nom de la classe qui était dans le fichier, mais vous allez voir ça juste après, et puis on met souvent les mêmes pour ne pas se tromper).

compilation : ($(FileName) et $(FileDir) sont propres à EditPlus, mais j'imagine que tous les éditeurs ont bien leur petit équivalent)
Command="C:\\Program Files\\Java\\bin\\javac.exe"
Argument="$(FilePath)"
InitDir=$(FileDir)

exécution :
Command="C:\\Program Files\\Java\\bin\\java.exe"
Argument="$(FileNameNoExt)
InitDir=$(FileDir)

Plusieurs choses à savoir avant de commencer

Il y a 2 façons d'utiliser Java : un mode puremement console, et un autre avec une interface graphique, des événements (clic, ...)... La partie graphique sera traitée dans la partie graphique, mais il faut savoir qu'à part l'interface, elle nécessite cependant tout le reste de ce que je vais dire avant, à savoir les classes, et la programmation objet.

Java fonctionne différement de CamL ou du C++ : pour que la fonction f puisse faire appel à la fonction g, il n'est pas nécessaire d'écrire g avant dans le code ; il n'y a pas non plus de prototypes au début.

Enfin, comme en CamL ou en C++, il y a un point virgule (';') qui termine chaque ligne de commande, et il y a aussi des "blocs" de code (à l'intérieur d'une fonction) qui sont ici délimités par des accolades ('{' et '}').

Et la plus importante de toutes (je ne sais pas pourquoi d'ailleurs je la mets à la fin) concerne la manière dont Java traite les objets, en interne et en mémoire.

Comme je le disais plus haut, l'utilisateur ne pourra jamais avoir, en Java, une variable et son pointeur comme il aimerait par exemple le faire en C++. Pour Java, mis à part les types 'basiques' (int, char, double, bool, ...), tout est pointeur. En gros, quand vous créez un objet (un tableau ou une chaîne de caractères par exemple, si vous n'êtes pas familiers avec la notion d'objet), Java alloue la mémoire nécessaire à vos objets, et crée un pointeur qui pointe vers cet espace mémoire. Et un pointeur n'est donc qu'un entier finalement : l'adresse de l'emplacement en mémoire. C'est pour cela que si on passe un objet en paramètre à une fonction (i.e tout sauf un type de base), Java se contentera de recopier l'adresse mémoire vers laquelle pointe le pointeur.

Ceci a peut être l'air anodin, mais cela veut dire que la fonction va pouvoir modifier l'objet que l'on passe (ça peut être bien), et ça veut aussi dire qu'il faut faire attention à ce que si l'on souhaite créer une copie de quelque chose, ça peut nous jouer des tours. Vous êtes avertis, beaucoup d'erreurs viennent de là, du fait qu'on ne sait jamais trop ce qui est passé en paramètre, si c'est un pointeur ou un objet.

Par exemple, vous avez un objet qui possède 2 objets en champ. Vous décidez de copier votre objet. Manque de pot, si vous faîtes la copie sans faire attention, le nouvel objet aura ses 2 objets en champ égaux aux premiers, i.e qui pointeront sur les mêmes objets. Alors que ce n'est pas ce qui est voulu en général.

Morphologie d'un programme Java

dans le fichier principal.java :

import java.util.*; (par exemple)
import autrechose;
...

class principal {

la déclaration de f
la déclaration de g
...

public static void main(string[] args) {
  du code
  du code
  du code
}

le code de f

le code de g

...
}

dans un autre fichier, voiture.java par exemple

class voiture {
  déclaration des données et méthodes membres,

  code des méthodes et fonctions

  pas besoin de fonction main

}

C'est chaque fichier qu'il faut compiler séparément, puis ensuite exécuter 'principal'. La classe voiture déclare un nouvel objet et ainsi dans n'importe quel autre classe on pourra déclarer un objet voiture.

A l'exécution du programme, l'ordinateur lance la fonction main de la classe que l'on exécute. Vous comprendrez par vous même plus loin pourquoi la déclaration d'une fonction main est un peu plus complexe que pour d'autres fonctions.

Structures de contrôle

Déclaration d'une variable basique :

int x = 3; // A mettre en début de fichier pour les variables globales.

On a aussi le droit aux types bool, double, char, long, string, et à n'importe quelle autre classe (définie dans l'énorme librairie Java, ou par nos soins).

constantes
Mettre 'final' devant la déclaration d'une variable, pour la déclarer comme constante : final double pi = 3.1415 par exemple.
Opérations

J'avais dit que je ne mettrais pas ce genre de lignes dans mon tutoriel pour ne pas alourdir... mais bon, là il faut bien

x += 5; au lieu de faire x = x + 5; par exemple.
Idem avec -, * et /.
x++; au lieu de x = x + 1; ou x += 1; (quel gain de temps !!!)
x--; au lieu de x -= 1;

Sinon dans les conditions booléennes :

a && b pour 'a et b', a || b pour 'a ou b'
a == b pour savoir si a est égal à b ou pas.

Attention ! Cela ne marche pas pour les chaines de caractères !!! (ni pour des objets autres que les 'simples' int, bool, double, ...) En effet, si string a = "toto"; string b = "toto", a == b renvoie false. En effet, a et b sont des pointeurs vers des espaces mémoires représentant une chaîne et donc leur valeur n'est pas la chaîne, mais l'adresse de l'espace mémoire où est la chaîne. Et c'est ça qui est comparé ici. (faire a.equal(b) pour savoir s'il y a égalité ou pas)

Déclaration d'un objet plus complexe
voiture bagnole = new voiture();
// appelle le constructeur de voiture ; peut prendre des arguments, ça dépend de la classe.
voiture nimportequoi;

dans le premier cas, on déclare 'bagnole' comme une voiture. L'ordinateur réserve un nouvel espace mémoire pour bagnole (pour mettre les variables attenantes par exemple) et initialise peut être même ces variables (ça, ça dépend de la manière dont on a déclaré le constructeur de la classe voiture). Mais bagnole reste un pointeur qui pointe vers cet espace mémoire. nimportequoi est juste un pointeur vers un espace mémoire de type voiture. Ici nimportequoi pointe encore vers rien, c'est un pointeur nul. En C++ on peut savoir si p est un pointeur nul en faisant p == 0 (ou p = 0 pour l'affectation). En Java, il y a une valeur spéciale, qui joue exactement le même rôle que le 0 des pointeurs : c'est null. Pour l'instant nimportequoi == null. Mais on pourrait faire ensuite nimportequoi = bagnole. Auquel cas il y aurait 2 pointeurs pour le même espace mémoire.

boucle for
for(int i = 0; i < n ; i++)
  action;
ou si on a plusieurs instructions à effectuer dans la boucle :
for(int i = 0; i < n ; i++) {
  ...
  ...
}

Remarque : la syntaxe générale est for(initialisation; condition; action), et donc si i est déjà déclaré plus haut, pas besoin de le redéclarer (si on le fait, à l'intérieur des accolades, le 'nouveau' i cachera 'l'ancien' i, à l'extérieur de la boucle). On peut donc mettre une condition plus complexe que i < n, et même quelque chose qui ne porte pas forcément sur i, comme ok == true. A noter, mais ça sert très peut, que l'on peut faire for(int i = 0, j = 0; (i+j) < n ; i++) par exemple, et qu'on peut si on veut travailler sur i et j (i.e leur affecter de nouvelles valeurs) à l'intérieur de la boucle.

boucle while
while (condition) do
// il est nécessaire de mettre de parenthèses autour de la condition
...
done;
condition :
if (condition) // encore besoin de parenthèses
...; // si on n'a qu'une seule ligne à écrire et que l'on veut condenser

ou bien
if (condition) {
...;
...;
...
}

/* Ceci est un commentaire sur plusieurs lignes,
très pratique quand on veut voir si une partie du code seulement compile bien
ou que l'on veut tester juste un bout du code sans attendre */ 
Déclaration d'une fonction
double f(int x, double y) {
double a = 2 * x + y;
double b = x + 3.4 * y;
return (a + b);
}

Dès que le programme voit return, il renvoie la valeur et ne continue pas plus avant l'exécution de la fonction, ce qui peut être pratique (par exemple dans les boucles for, si on cherche dans une chaîne de caractères la position du premier '(' par exemple).

Fonction ne renvoyant rien :
Il suffit de déclarer f de type void (au lieu de double par exemple, plus haut). Le programme exécutera tout le corps de la fonction, sauf s'il voit return;

Tableaux

Les indices de tous les tableaux commencent à 0. Un tableau peut être passé en paramètre à une fonction (exemple : une fonction trier()). La question est de savoir ce qui se passe lorsque qu'un tableau est passé en paramètre... Est-il recopié pour pouvoir être donné à la fonction ? Réponse : Non. Et tant mieux. En fait cela prendrait un temps fou. cf l'introduction pour une explication plus poussée.

Déclaration d'un tableau
int[] x = new int[10];, par exemple, ou même int[n], pour avoir un tableau de taille dépendant uniquement de nos données. On peut bien entendu se faire un tableau de n'importe quoi d'autre que des int, par exemple avec n'importe quelle classe. Seulement, rappelez vous que ça ne créera qu'un tableau de pointeurs, au lieu de réserver n espaces mémoires : voiture[] tab = new voiture[10] va créer 10 pointeurs null sur des objets voitures. A vous ensuite de les affecter. Remarque : int[] table = {2, 3, 4} si l'on veut aller plus vite et qu'on connait les éléments du tableau un tant soit peut à l'avance.
accès à un élément du tableau
tableau[i] pour avoir accès à l'élément d'indice i, soit le (i+1)ème élément.
Taille d'un tableau
a.length renvoie la taille d'un tableau. Cela sert surtout lorsque l'on passe un tableau en paramètre à une fonction : cela se fait très bien et d'ailleurs le code pour une fonction f prenant un tableau d'entiers a est : type_de_f f(int[] tabl, ...). On peut aussi passer la taille du tableau en argument en même temps que passer le tableau.
Tableaux multidimensionnels
int[][][][] tableau = new int[1][231][823][432]; pour un tableau à 4 dimensions (choisissez ensuite le nombre de colonnes). En interne, un tableau à n dimensions est un tableau à 1 dimension où chaque case est un pointeur sur un tableau à (n - 1) dimensions. Ainsi tableau[1][5] est ici de type int[][] (et le compilateur est d'accord !).

Chaines de caractères

Les chaînes de caractères sont en fait un type prédéfini de Java (comme les tables de hachage, les listes, les piles (FIFO, LIFO, ...), ...), mais nous les présentons quand même car ce sont des objets plus simples que ces derniers.

Une chaîne n'est ni plus ni moins qu'un tableau de char. Ainsi les indices des lettres d'une chaîne commencent à 0. Cependant le compilateur grimacera si vous passez un char[] en paramètre, quand une String est attendue.

Déclaration d'une chaîne
String machaine = "bonjour !"; Il n'y a pas besoin d'importer une librairie spécialisée, String est déjà inclus par défaut, si j'ose dire. (sinon ce serait import java.lang.String; tout en haut du fichier, cf morphologie d'un programme)
Caractères spéciaux
  • Faites \ dans la chaîne pour ajouter un saut de ligne (exemple : System.out.print("bonjour\ Au revoir.\ ");
  • Faites \\t pour ajouter un espace de la taille d'une tabulation
  • Faites \\\\ pour faire le \\. (quand l'ordinateur voit un slash, il regarde tout de suite juste derrière pour voir le caractère. Exemple, si vous voulez afficher \ (et non un saut de ligne), vous devrez écrire \\\ , sinon...)
  • idem : \\" pour écrire un guillemet dans une chaîne. comment ferais l'ordi pour savoir si c'est la fin d'une chaîne ou un simple guillement dedans, sinon ???
Propriétés usuelles
machaine.length() pour avoir la taille de la chaine, machaine.charAt(i) pour avoir le (i+1)eme caractère (ou celui d'indice i), machaine1 + machaine2 pour concaténer 2 chaînes (ou bien machaine1.concat(machaine2)).
Autres propriétés
Elles sont toutes disponibles ici : Dans la gigantesque liste déroulante d'en bas à gauche qui a le bon goût d'être triée par ordre alphabétique, cherchez String et cliquez sur le lien. Cette librairie est ultra-pratique, très vite vous ne vous en passerez plus.

Classes

Les classes servent à se construire nos propres "types". Par exemple, vous souhaitez posséder une structure d'arbre binaire. Et bien c'est possible avec Java et les classes. Je vous conseille fortement d'implémenter vous même les types "liste", "arbre", ... même si vous n'en avez pas besoin dans l'immédiat (d'ailleurs "list" existe déjà en Java). C'est un excellent exercice pour comprendre le mécanisme des classes.

Déclaration d'une classe :
// déclaration des fonctions, puis implémentation, pour avoir
// d'un simple coup d'oeil à l'ouverture d'un fichier quelles
// sont les fonctions implémentées par une classe.
class enfant /* extends etrehumain */ {
  public:
    int age;
    int poids;
    enfant();
    enfant(int aaage);                  //
    enfant(int aaage, int pppoids);     /// constructeurs
    double calculer_imc(double taille); // appelés quand on fait new enfant(...)
    static void pleurer();

  private:
    static double au_carre(double t);
    list<enfant> freres_et_soeurs;


  enfant() {
    // super();
    age = 0;
    poids = 0;
    freres_et_soeurs = new list<enfant>();
  }

  enfant(int a) {
    age = a;
    poids = 0;
    freres_et_soeurs = new list<enfant>();
  }

  enfant(int a, int p) {
    age = a;
    poids = p;
    freres_et_soeurs = new list<enfant>();
  }

  double au_carre(double t) {
    return (t * t);
  }

  double calculer_imc(double taille) {
    return ((double) (poids) / au_carre(t));
  }

  static void pleurer() {
    System.out.println("ouinnnnn");
  }
}

Explication des termes

public:, private:

signifie que ce qu'il y a après public: (et avant private:) est... public : on peut y avoir accès depuis l'extérieur. Et que ce qu'il y a après private: est... privé : il n'y a que les fonctions de la classe 'enfant' qui connaissent ces fonctions/variables privées. Que ce soit pour les variables ou pour les méthodes/fonctions.

Ainsi, l'âge et le poids des enfants seront "vus" depuis toutes les autres classes, par contre la liste des frères et soeurs, ou bien la fonction qui sert à élever au carré sont privés : depuis "l'extérieur" on se fiche un petit peu de savoir que enfant possède une fonction déjà écrite pour élever au carré. Pour la liste des frères et soeurs j'aurai bien aimé la mettre en private, mais c'est pour vous donner un exemple.

Par exemple, je peux faire enfant thomas = new enfant();, je pourrais faire thomas.age = 21 ou System.out.println(thomas.poids);, mais pas enfant.f(5.3).

Les livres vous diront qu'il n'y a pas vraiment de "sécurité" puisque d'autres classes peuvent accéder aux données internes de 'enfant'. Ils vous encourageront à mettre ces données (age et poids par exemple) dans private, puis ensuite à définir les fonctions 'definir_age(int)' et 'lire_age(int)' et idem pour le poids.

Mais ce qu'ils ne disent pas c'est que le code n'est pas allégé et on perd finalement beaucoup de temps. Mais c'est vrai que s'il y a des données dont on n'a a priori pas besoin d'y avoir accès depuis l'extérieur, autant les mettre en private.

static

Si ne donnée ou fonction est 'Static' cela signifie qu'elle existe indépendamment des instances créées de la classe. Explication : ici par exemple, calculer_imc (indice de masse corporel) n'est pas 'static' : l'indice existe pour chaque enfant. D'ailleurs il prend la taille de l'enfant en paramètre (parce que je ne l'ai pas déclarée en tant que variable de la classe) et utilise un paramètre de la classe enfant pour pouvoir retourner un résultat : le poids de l'enfant. Bref, chaque enfant a sa fonction imc. Par contre, au_carre et pleurer sont 'static', parce que qu'elles n'utilisent pas le fait qu'on les applique à un enfant en particulier. Pour l'appeler, si enfant thomas = new enfant(), on ne fait donc pas thomas.pleurer(), mais enfant.pleurer() : une fonction static appartient à la classe, non pas aux instances de la classe.

Par exemple Pi est une donnée membre static de la classe Math.

les constructeurs enfant::enfant(...)
Quand on crée un nouvel objet enfant, grâce à new (sinon rappelez-vous qu'on ne crée qu'un pointeur vers un enfant), suivant où l'on est dans le programme, on peut avoir plusieurs besoins. Par exemple créer un 'enfant' sans spécifier ni son poids ni son âge, ou alors juste son âge, ou son poids et son âge. L'exemple n'est pas frappant. Mais par exemple pour les chaînes de caractères, on peut créer un 'string' avec un tableau de caractères, ou bien avec un 'string' déjà existant (pour copier, donc) ou bien (ça n'existe pas mais on peut imaginer, vu qu'en CamL ça se passe comme ça) avec un nombre et un caractère : string(5, 'a') renverrait "aaaaa" ce qui pourrait être pratique. Ou encore string(chaine1, chaine2) renverrait une nouvelle chaîne contenant la concaténation des deux. On voit mieux l'intérêt de créer plusieurs constructeurs différents avec l'exemple des chaînes, où il existe réellement un plus grand besoin.
extends etrehumain
C'est le mot clé pour dire que la classe enfant dérive de la classe 'etrehumain'. Elle en hérite donc toutes les propriétés et méthodes.
super()
Ça appelle le constructeur de la classe supérieure. Obligatoire.

Les classes abstraites

Il n'y a pas d'objet qui peuvent être des objets d'une classe abstraite à proprement parler, mais des classes peuvent dériver d'une classe abstraite. Cela sert souvent quand on veut faire un tableau qui contient soit ça, soit ça : on ne sait pas trop comment déclarer notre tableau, alors on crée une super classe (abstraite), et on peut déclarer le tableau de ce super type.

Déclaration d'une classe abstraite : virtual class toto { puis comme dans une classe } ; Si une classe "dérive" d'une classe abstraite, on écrit class petite extends toto.

Les interfaces

Une interface est une classe abstraite qui n'a que des méthodes ou des prototypes de méthodes. Elle n'a pas de variable membre. Une classe qui "dérive" d'une interface doit implémenter toutes les méthodes des prototypes laissés vides dans l'interface.

Une interface est donc quelque chose qui oblige toutes les classes héritières à posséder certaines fonctions, méthodes. Exemple : l'interface "List" en Java déclare une méthode "ajouter_en_tete", "ajouter_en_queue", "supprimer_la_tete", "compter_nb_elements", ... Comme ça quand on a un objet qui implémente List, on sait qu'il possède ces méthodes.

Si une méthode est déclarée dans l'interface et dans une classe qui l'implémente, c'est celle déclarée dans la classe qui va être appelée.

Déclaration d'une interface : interface toto { puis comme dans une classe, mais que des fonctions ou prototypes }.

Types prédéfinis

Java implémente déjà pour vous des dizaines (des centaines) d'objets prédéfinis. Vous les trouverez tous ici, dans la gigantesque liste déroulante d'en bas à gauche. Je vous conseille très fortement de vous familiariser avec cette librairie, fort pratique. Pour pouvoir utiliser un objet de cette liste, vous devez le dire dans votre code, tout en haut du fichier, avec import ....; En effet vous pouvez vous rendre compte qu'il y a vraiment beaucoup d'objets, et s'ils étaient tous inclus par défaut avec chaque programme ça pèserait vraiment lourd à chaque compilation/exécution.

Quelles sont les informations utiles?

quel est le nom du fichier à importer ?

Cliquez sur le tout premier de la liste, AbstractAction. Il y a écrit "javax.swing", "Class AbstractAction" et en dessous une espèce d'arborescence à 2 niveaux : java.lang.Object et javax.swing.AbstractAction, et en dessous il y a beaucoup de tableaux. Ça se présente toujours comme ça. Dans tous les cas, faites import puis le dernier nom de la petite arborescence. Ici 'import javax.swing.AbstractAction;'. Cela veut dire que cette classe dérive de celle juste au dessus dans l'arborescence, qui elle même dérive de celle juste au dessus, ... Si dans la liste tout en haut à gauche vous cliquez sur javax.swing, vous ferez réduirez la liste d'en bas à gauche strictement aux éléments de javax.swing. (les objets sont donc placés dans des espèces de sous-bibliothèques, qui ont souvent une signification à elles seules. Par exemple swing c'est pour tout ce qui est dessin ou interface graphique.)

Comment créer une instance de l'objet en question ?
Si vous descendez un peu, vous avez un tableau intitulé "Constructor Summary" : c'est là que sont indiqués tous les constructeurs de l'objet
Quelles sont les propriétés sympas de l'objet ?
Si vous descendez encore un peu, vous avez la liste des propriétés de l'objet. Le nom est d'ailleurs souvent révélateur.
Que signifient tous les autres trucs qui traînent ?
Une classe implémente souvent une interface, ou bien une hérite d'une autre classe qui elle même hérite d'une autre classe. A ce sens, il y a beaucoup d'autres méthodes applicables à votre objet, et c'est ça qui est écrit. Elles ne figurent pas dans le tableau mais sont écrites quand même.
Pourquoi certains noms sont en italiques ?
Figurent en italiques les noms d'interfaces. Ce ne sont donc pas des objets. Par contre il y a souvent des classes qui implémentent ces interfaces. A quoi servent les interfaces alors ? A dire que si une classe implémente telle interface, alors nécessairement elle possède telles et telles méthodes. Par exemple, List est une interface. Toutes les classes qui seront destinées à implémenter des listes devront avoir (et c'est normal) un méthode d'insertion en tête de liste, de taille de la liste, ... Comme ça ça crée une sorte de standard pour toutes les classes implémentant le type List.

Objets usuels en algorithmique

Dans la librairie, cliquez sur java.util dans la liste en haut à gauche. Dans la liste en bas à gauche apparaissent les objets de java.util, qui sont, étrangement !, les objets les plus utiles en algorithmique. Faites donc import java.util.*; ou bien import java.util.lenomdelobjetdecettelibrairie;

le type LinkedList
C'est pour implémenter les listes chaînées (ou listes 'normales'). Cliquez dessus pour voir les méthodes adjacentes. Remarque : cet objet est utilisé avec les 'template', ce qui est normal ; cela veut dire qu'il n'y a pas un type 'liste d'entiers', 'liste de double', 'liste de ...', puisqu'ils seraient programmés de la même manière. Il faut donc faire LinkedList<Integer> maliste = new LinkedList<Integer>(); maliste pourra ici accueillir des éléments int. On peut même faire des listes de listes de ... avec cette écriture très pratique.
le type HashSet

Un set est un ensemble. HashSet signifie que cet ensemble est implémenté comme une table de hachage. Qu'est-ce que c'est ? Un ensemble, avec insertion et recherche en temps constant, i.e O(1). Si ça ne vous dit toujours rien, pensez que vous devez avoir un dictionnaire, et vérifier si certains mots sont dedans ou pas. Mais tout cela très vite. Et bien vous pourriez avoir un tableau très grand avec tous vos mots rangés par ordre alphabétique. Très bonne idée. La recherche dichotomique est très rapide. Mais il y a mieux, car votre ensemble supporte mal l'insertion ou la suppression d'un élément par exemple. Ce qu'une table de hachage fait très bien. Et même mieux pour la recherche. En fait, le premier jour où l'on utilise une table de hachage, on est très content parce que ça résoud tous les soucis qu'on a pu avoir avec les listes, tableaux, ... Bref, une structure très souple, très belle, très propre.

Idem HashSet<LinkedList<Integer>> = new HashSet<LinkedList<Integer>>()

le type HashMap
C'est comme une HashSet, sauf qu'ici on veut véritablement associer à un élément de l'ensemble quelque chose d'autre. Exemple : associer un numéro de téléphone à une personne.
les itérateurs
Comment faire pour parcourir tous les éléments d'un Set ou d'une Map ? (exemple : envoyer un mail à tout votre carnet d'adresse) Parce qu'avec une liste on sent qu'on n'a pas ce problème, vu qu'il y a un ordre dans les éléments : le premier de la liste, le second, ... et idem pour les tableaux. Et bien le problème est résolu grâce aux itérateurs.
LinkedHashSet et LinkedHashMap
Jouent le même rôle que sans le 'linked', sauf que lorsqu'on parcourt tous les éléments grâce aux itérateurs, on le fait dans le même ordre que l'ajout de ceux-ci.
Vector
Cet objet joue un peu le même rôle que les tableaux, mais avec certaines propriétés des listes, comme l'insertion, ...
PriorityQueue
C'est une liste, où l'on attribue à chaque élément un nombre (sa priorité, sous-entendu "de sortie"). Quand on ajoute l'élément à cette file, Java va insérer l'élément à la "bonne place", dans la file, de manière à ce que celle-ci reste triée avec au début les éléments à grosse priorité et à la fin ceux à plus faible priorité.

Attention

Si vous utilisez une HashSet ou une HashMap sur des classes faites maison, il ne faut pas oublier de redéfinir equals et hashCode dans vos classes. Si vous ne le faites pas, la table de hachage va prendre les fonctions de hash et d'égalité déjà définies (cest à dire l'adresse en mémoire de votre objet).

Exemple :

class pair {
	public int x, y;
	...
	public int hashCode() {
		return x + 1000 * y;
	}
	
	public boolean equals(Object obj) {
		if (obj instanceof pair) {
			pair p = (pair) (obj);
			return (x == p.x && y == p.y);
		}
		else
			return false;
	}
}

Faites le test :

HashSet<pair> h = new HashSet<pair>();
h.add(new pair(3, 5));
h.add(new pair(2, 0));
h.add(new pair(3, 5));
pair p = new pair(3, 5);
h.add(p);
...
for(pair p : h)
	System.out.println(p.x + ", " + p.y);

System.out.println("2,0 est dans la table ? " + h.contains(new pair(2, 0)));
System.out.println("p=3,5 est dans la table ? " + h.contains(p));

Si vous ne redéfinissez pas equals pour "pair" comme je l'ai fait, vous verrez :

3,5
2,0
3,5
2,0 est dans la table ? 0
p=3,5 est dans la table ? 1

Si vous redéfinissez equals pour "pair" comme je l'ai fait, vous verrez :

3,5
2,0
2,0 est dans la table ? 1
p=3,5 est dans la table ? 1

Comme je le disais, la table ne "sait" pas qu'elle possède 2,0, puisqu'au moment où on lui demande, java crée une nouvelle paire 2,0 à l'adresse adr et la table compare cette nouvelle paire à toutes les autres. Et comme le critère d'égalité est l'égalité d'adresse dans la mémoire... ça ne marche pas. C'est aussi pour ça que ça marche quand on demande l'existence de p.

Bref, redéfinissez "equals", vous obtenez le comportement voulu.

Remarque : les objets de Java (String, Integer, ...) définissent déjà de "vrais" equals.

Commandes en vrac

Ecrire dans la console
System.out.print(x); où x peut être un entier, un double, ou une chaîne de caractères. Utilisez println au lieu de print si vous souhaitez par la même occasion sauter une ligne après l'affichage.
Lancer une exception
Il faut absolumentmettre throws Exception après la déclaration d'une fonction renvoyant une exception : int f(int x) throws Exception{...} et dans le code : throw new Exception("texte d'erreur"); là où l'on veut. Attention : si f peut renvoyer une exception et que g ne gère pas ça, alors g va peut être déclencher une exception aussi, et il faut aussi faire throws Exception dans la déclaration de g.
Rattraper une exception
try {....} catch (Exception e) {...} catch (FileNotFoundException f) catch(IOException g) ... : normalement un seul catch suffit, mais certaines fonctions du système renvoient des exceptions qui ne sont pas de type Exception, mais IOException par exemple (cf librairie sur le web de Java). En fait, il peut y avoir plusieurs exceptions renvoyées de type Exception. Dans les accolades {} du catch on peut vérifier si e.equals("texte d'erreur") ou e.equals("erreur : division par 0"), ...
Ouverture d'un fichier par n'importe quel programme (ici : notepad)
try {
	Runtime r = Runtime.getRuntime();
	Process pr = r.exec("notepad.exe C:\\\\unfichier.txt");
}
catch(Exception e) {
	System.out.println("erreur d'exécution du bloc note");
}
Convertir un entier en string
Integer.toString(5) renvoie "5". toString de Integer est donc une méthode static
Ouvrir une boîte de dialogue "ouvrir un fichier"

On peut créer un filtre si l'on veut (exemple : montrer dans la boîte de dialogue tous les fichiers *.txt).

JFileChooser filebox = new JFileChooser();
FiltreSimple data_filter = new FiltreSimple("Fichiers Texte",".txt");
filebox.addChoosableFileFilter(data_filter);
// filebox.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

une option que l'on ne met pas ici, mais que l'on peut choisir si l'on veut forcer l'utilisateur à sélectionner un dossier

int returnVal = filebox.showOpenDialog(panneau);
if(returnVal == JFileChooser.APPROVE_OPTION) {
...filebox.getSelectedFile().getPath()...
// pour avoir le chemin du fichier ou dossier selectionné par l'utilisateur
}
// cette classe sert à filtrer les fichiers dans la boite de dialogue
class FiltreSimple extends FileFilter{

   //Description et extension acceptée par le filtre
   private String description;
   private String extension;

   //Constructeur à partir de la description et de l'extension acceptée
   public FiltreSimple(String description, String extension){
      if(description == null || extension ==null){ // n'est pas sensé arriver
         throw new NullPointerException("La description (ou extension) ne peut être null.");
      }
      this.description = description;
      this.extension = extension;
   }

   //Implémentation de FileFilter
   public boolean accept(File file){
      if(file.isDirectory()) { 
         return true; 
      } 
      String nomFichier = file.getName().toLowerCase(); 

      return nomFichier.endsWith(extension);
   }
      public String getDescription(){
      return description;
   }
}

Threads

Un thread est quelque chose qui va tourner en arrière plan de votre programme. Pour ce faire, il faut soit qu'une classe dérive de Thread, soit qu'elle implémente Runnable.

Petit exemple :

class toto extends Thread {
public void run() {
	... do something
}
}

main() {
	toto t = new toto();
	t.start();
}

Pour lancer un thread, il ne faut pas appeler run() mais start(). Sinon ce ne sera pas lancé en parallèle comme un thread. Java s'occupe de créer un thread tout seul puis d'appeler run.

Une fois qu'un thread est terminé, on ne peut plus le rappeler avec start(). Il faut créer une nouvelle instance et appeler start() dessus à nouveau.

Petites choses en plus : ce thread fait quelque chose puis attend 1 seconde (= 1000 millisecondes) puis refait qqchose, et ainsi de suite jusqu'à ce qu'un autre bout du programme mette tostop à true.


public boolean tostop = false;
public void run() {
while(!tostop) {
Thread.sleep(1000);
...
}
}

Interface graphique, gestion d'événements

L'interface graphique d'une application Java est plus compliquée que dans les systèmes type Visual Basic où à l'interface se crée de manière VWYSIWYG comme avec un logiciel de dessin : on place ses contrôles où l'on veut, et le compilateur écrit le code pour nous. Non, Java est une technologie développée pour le Web, et pour tous les systèmes.

D'un côté il va falloir écrire le code à la main, ce qui est long et chiant : il va falloir placer les contrôles en disant "met le en haut à gauche", "au milieu", ... au lieu d'indiquer des coordonnées. Mais d'un autre côté, le fait de faire ça comme ça rend notre programme plus transportable, et l'on est content d'avoir quelque chose qui n'est pas destiné à avoir une taille fixe : cf mon tutoriel pour se faire un site web. On crée donc notre interface par code une bonne fois pour toutes, et ensuite tout sera dimensionné automatiquement ! Plutôt agréable au final.

Il existe deux manières de faire son interface. Une première, "à la Visual Basic", non conseillée pour Java, mais je le mets parce qu'on peut le faire quand même et il y a peut être des cas où ça peut servir. En gros, ça ne tient pas compte de ce que je dis dans mon tutoriel sur le design des sites web : on fixe la taille de la fenêtre et on place les contrôles en disant "ceci à tant de pixels en haut et à tant à gauche et de taille tant sur tant". L'inconvénient est que si le contrôle doit grossir (le texte doit devenir plus grand dedans, ...) et bien on ne peut pas. C'est très peut dynamique.

Une deuxième manière de faire est de mettre tout dans des gestionnaires, en disant ce contrôle à gauche de celui-ci, celui là au dessus de ce groupe de contrôles là, ... et débrouille toi avec la taille.

Construction d'interface "à la Visual Basic"

En gros on déclare tous les contrôles qu'on a, et on les ajoute les uns après les autres au "panneau". On ajoute sur ceux qu'on veut un Listener si on veut récupérer un événement dessus.

Vous pouvez récupérer le code ici.

interface java construite pixels par pixel
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class TestInterfaceVB extends JFrame implements ActionListener {
	
	// utilisé pour l'affichage seulement
	JPanel panneau = new JPanel();
	JLabel label = new JLabel();
	JButton bouton = new JButton();
	JButton quitter = new JButton();
	JTextArea entree_txt = new JTextArea(); // une zone texte
	//  ainsi que en dessous les barres de défilement
	JScrollPane scrollPane = new JScrollPane(entree_txt, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
	
	public static void main (String args[]) {
		new TestInterfaceVB();
	}
	
	TestInterfaceVB() {
		super("");
		setTitle("Test d'interface");
		try {
			jbInit();
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	// gestion des événements : clics sur les boutons, ...
	public void actionPerformed(ActionEvent ae){
		if(ae.getSource()== bouton)
			System.out.println("clic sur un des boutons");
		else if (ae.getSource() == quitter) // clic sur quitter
			dispose();
	}
	
  	private void jbInit() throws Exception {
		panneau.setBackground(Color.lightGray);
		panneau.setBounds(new Rectangle(0, 1, 706, 605));
		panneau.setLayout(null);
		
		bouton.setBounds(new Rectangle(362, 165, 20, 20));
		bouton.setText("cliquez ici");
		bouton.addActionListener(this);//ecouteur
		
		quitter.setText("Quitter");
		quitter.setBounds(new Rectangle(270, 380, 108, 22));
		quitter.addActionListener(this);//ecouteur
		
		entree_txt.setFont(new java.awt.Font("Times New Roman", 0, 12));
		entree_txt.setText("du texte...");
		entree_txt.setLineWrap(true);
		entree_txt.setWrapStyleWord(true);
		entree_txt.setBounds(new Rectangle(125, 190, 325, 150));
		scrollPane.setBounds(new Rectangle(125, 190, 345, 150));
		scrollPane.getViewport().add(entree_txt, null);
		
		panneau.add(bouton);
		panneau.add(quitter, null);
		panneau.add(scrollPane, null);
		
		add(panneau);
		setSize(new Dimension(485, 440));
		setVisible(true);
	}
	
}

Remarques

Construction d'interface typique Java

On l'a dit plus haut, cela sert à avoir une interface "mobile", qui s'adapte à n'importe quelle taille de fenêtre, ce qui est très pratique au fond. C'est un peu le principe des pages web dans l'ancien temps où l'on n'utilisait pas les feuilles de style CSS mais les tableaux pour positionner les éléments : ceci en haut, ceci à droite, ceci dans le bas de la zone au centre, ... C'est cette psychologie qu'il faut avoir avant de s'attaquer à son interface Java.

On va comme plus haut créer tous les éléments, puis ensuite les mettre dans des Layout (que l'on peut imbriquer les uns dans les autres), et ensuite appliquer ça à la fenêtre. Un Layout est un gestionnaire de contrôle, c'est lui qui s'occupe tout seul de leurs tailles, en fonction de la longueur du texte qu'ils contiennent par exemple.

Exemple avec 3 boutons "radio" sur la droite, 2 cases à cocher sur la gauche

Vous pouvez récupérer le code ici.

interface java construite pixels par pixel
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class TestInterfaceLayoutJava extends JFrame implements ItemListener {
	
	JRadioButton petit = new JRadioButton("petit", true);
	JRadioButton moyen = new JRadioButton("moyen", false);
	JRadioButton grand = new JRadioButton("grand", false);
	ButtonGroup groupe = new ButtonGroup();
	JCheckBox maigre = new JCheckBox("maigre");
	JCheckBox gros = new JCheckBox("gros");
	JPanel panneauRadio = new JPanel();
	JPanel panneauCocher = new JPanel();
	JPanel toutalafois = new JPanel();
	
	public static void main (String args[]) {
		new TestInterfaceLayoutJava();
	}
	
	TestInterfaceLayoutJava() {
		super("");
		setTitle("Test d'interface");
		try {
			jbInit();
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}
	public void itemStateChanged(ItemEvent e)  {
		JRadioButton r = (JRadioButton) (e.getItem());
		System.out.println("clic sur " + r.getText());
	}
 	
  	private void jbInit() throws Exception {
		petit.addItemListener(this);
		moyen.addItemListener(this);
		grand.addItemListener(this);
		
		panneauRadio.setLayout(new GridLayout(3, 1));
		panneauRadio.add(petit);
		panneauRadio.add(moyen);
		panneauRadio.add(grand);
		
		panneauCocher.setLayout(new GridLayout(2, 1));
		panneauCocher.add(maigre);
		panneauCocher.add(gros);
		
		toutalafois.setLayout(new GridLayout(1, 2));
		toutalafois.add(panneauCocher);
		toutalafois.add(panneauRadio);
		
		add(toutalafois);
		setVisible(true);
		setSize(300, 300);
	}
	
}

Là où ça se passe : ce sont les Layout. Il y en a bien 6 différentes je crois, mais elles fonctionnent toutes plus ou moins pareil. Ici c'est la "GridLayout" que j'ai pris : (3, 1) signifie 3 lignes, 1 colonne. On ajoute ensuite de haut en bas nos 3 contrôles qui iront se placer comme par magie dans les 3 cases d'un tableau (invisible). Ensuite pareil avec PanneauCocher, mais on n'a que 2 CheckBox cette fois.

Et pour réunir ces 2 panneaux... encore un layout. Horizontal cette fois : (1, 2). Et on place comme si on insérait un contrôle, notre sous-interface. Soit dit en passant, vive la programmation objet (pour l'imbrication des layout les uns dans les autres comme s'ils s'agissaient de contrôles).

Remarques

Boutons Radio

Pour pouvoir fonctionner (exercice !!), il faut utiliser de plus un Group Button (en fait, autant de Group Buttons que de groupe de boutons radio, puisque lorsqu'un bouton est "coché", tous les autres du même groupe doivent être "décochés").

JRadioButton choix1 = new JRadioButton();
JRadioButton choix2 = new JRadioButton();
ButtonGroup group1 = new ButtonGroup();
group1.add(choix1);
group1.add(choix2);
panneau.add(choix1, null);
panneau.add(choix2, null);

Dessiner

Pour dessiner, le mieux est de se créer une nouvelle classe qui dérive de quelque chose qui se redessine (JLabel, ...) et de redéfinir paint(Graphics g). Après dans l'interface, il n'y a qu'à ajouter un new MaClasseDerivantDUnTrucRedessinable() et le tour est joué (cf dans mes codes sources la détection de contours)

Une autre manière est de prendre le paramètre Graphics du paint de votre JFrame (la fenêtre, quoi) et de la donner à un objet.

Dans tous les cas, c'est cet objet Graphics qui dessine pour vous, avec des méthodes comme setColor, fillRect, ...

import java.awt.*;
import javax.swing.*;

public class MaClasse extends JLabel {
...
	public void paint(Graphics g) {
		int w = g.getClipBounds().width; h = g.getClipBounds().height;
		g.setColor(Color.blue);
		g.fillRect(w/3, 10, w/2, h-5);
	}

Evénements

Pour pouvoir répondre aux événements (clic souris, déplacement souris, clavier, ...), Java met en place un système "d'écouteurs". On dit aux contrôles que l'on veut écouter ce qu'ils doivent écouter, et on dit aussi quelle fonction appeler à ce moment. En fait on ne dit pas quelle fonction appeler, mais quelle classe appeler. Si on a fait bouton.addMouseListener(uneclasse), alors au clic sur le bouton c'est la méthode mouseClicked(MouseEvent e) de uneclasse qui sera appelée.

Dans la section qui montre comment construire une interface, on a géré certains événements.

Pour qu'une classe puisse être une "classe écouteur", elle doit implémenter l'interface correspondante (et donc souvent 3 ou 4 fonctions) ; Par exemple si vous voulez écouter juste les clics, vous devez implémenter MouseListener et les fonctions mousePressed(MouseEvent e), mouseReleased(...), mouseClicked(...) et ainsi de suite. cf la documentation java.

import java.awt.event.*;

public class MaClasse extends JApplet implements MouseListener, MouseMotionListener {
	int lastX = -1, lastY = -1;
	
	public void init() {
		addMouseMotionListener(this);
		addMouseListener(this);
	}
	
	public void mousePressed(MouseEvent event) {
		lastX = event.getX();
		lastY = event.getY();
	}
	public void mouseDragged(MouseEvent event) {
		int x = event.getX();
		int y = event.getY();
		
		System.out.println("x:" + x.toString());
		System.out.println("y:" + y.toString());
		
		lastX = x;
		lastY = y;
	}
	public void mouseReleased(MouseEvent event) {} // obligés d'être implémentés
	public void mouseClicked(MouseEvent e) {}
	public void mouseEntered(MouseEvent e) {}
	public void mouseExited(MouseEvent e) {}
	public void mouseMoved(MouseEvent e) {}
}

Applets

Une applet Java est ni plus ni moins qu'un programme Java qui peut s'exécuter dans une page web, avec un format un poil différent. Vous pouvez visiter ma page Rubik's Cube ou ma page de codes sources pour voir le genre de choses que l'on peut faire niveau graphique.

Une applet est une classe sans méthode main, qui doit dériver de JApplet, donc implémenter init() pour l'initialiser, update() et paint(). Rappelons que pour demander à une fenêtre de se redessiner, on n'appelle pas paint() directement, mais repaint() et c'est Java qui se charge de donner le bon argument Graphics à la méthode paint() de la classe voulue.

La méthode que l'on donne ici avec l'"image" sert surtout à faire du "double buffering" : dessiner qqchose en mémoire pour ensuite l'afficher d'un coup, pour ne pas avoir des effets de rafraîchissement. C'est "à ce qu'il paraît", je ne vois personnellement pas la différence, mais ça complique pas beaucoup plus le code non plus.

Source complète (inclut aussi la gestion de la souris : Clic et MouseMove)

import java.applet.*; import javax.swing.*; public class MaClasse extends JApplet { Image monimage; Graphics graphiques; public void init() { image = createImage(150, 150); graphiques = image.getGraphics(); } public void update(Graphics g) { paint(g); } public void paint (Graphics g) { super.paint(g); graphiques.setColor(Color.Blue); graphiques.FillRect(10, 10, 100, 100); g.drawImage(image, 0, 0, this); } }

Signer votre Applet

Il y a des problèmes de sécurité liés aux applets : si elles étaient comme des programmes Java pouvant s'exécuter sur votre site, alors l'applet pourrait aller ouvrir des fichiers de votre ordinateur et récolter des informations, ou même être un virus. Pour ne pas que ça arrive, Java a sécurisé le procédé : les applets ont l'interdiction d'ouvrir et d'écrire des fichiers, ainsi que d'aller chercher des choses sur internet comme new URL("http://...")). Mais vous pouvez contourner cette interdiction en signant votre applet.

Ceci aura pour effet d'ouvrir une petite fenêtre "voulez vous accepter le certificat ...", et si l'utilisateur clique sur oui, alors votre applet aura accès à tout !

Dans un terminal :

Remarques

Laissez un commentaire !

Pas besoin de vous connecter, commencez à taper votre nom et une case "invité" apparaîtra.

Tutoriel CamL
Bases, astuces et codes sources pour se familiariser avec ce language
Tutoriel VB6/VBA
Bases pour créer une belle app en VB6/VBA + astuces et codes sources
Programmation
Tutoriels et conseils pour bien démarrer, outils à utiliser
Tutoriel AppleScript
Les bases pour créer une belle app en AppleScript + astuces et codes sources
iOS Code Signing Provisioning Profile
In French and English
Copier un énorme fichier sur Mac OS X
Comment copier un gros fichier sur votre disque dur
How to copy big files to a hard drive
Poupée Vaudou
Un vieux compte à régler...
Une nouvelle compagne dans ma vie
relation longue durée souhaitée
Que gagne un petit programmeur d'iPhone apps ?
Arrêt sur image 1 an après le début de l'aventure.
Soirées pas folles au Japon
les pires choses ont une fin
Au cœur d'une manif
que fait la police ?
Alcatraz
un petit tour sur cette île mythique
Happy few
Un film détonnant
Fabrication des iPhone Apps
Vue de derrière les coulisses
Soirée
Mon premier vendredi soir au Japon
Acheter un vtt
Pour rouler sans se faire rouler
Un africain dans la ville
à la rencontre des migrants à Paris
La patrie du hamburger
Quête vers le Saint Graal
Les bains de Tokyo
Toujours revenir à la source
J-4, avant le grand départ
Plus que quelques jours pour penser à tout et savoir où je vais… la pression monte !
Le quotidien de mon stage ouvrier au Japon
On transpire beaucoup
Le Japon et les Japonais
Quelques préjugés tombent