Mémento de programmation Java

Afficher le sommaire en parallèle

Packaging

Un fichier java est structuré de la manière suivante : Remarques :

Visibilité

 
package Toto;
public class A {
 ...
}
class B {
...
}
import Toto;

// je vois la classe A de Toto car elle est publique 
// je ne vois pas la classe B (elle n'est pas publique). Pour // voir la classe B, il faudrait que j'appartienne au package // Toto.

Exécution

java <nom de la classe qui contient le point d'entrée main à exécuter>
le point d'entrée est public static void main(String args[]) car :

Types, identifiants et littéraux

Types

Java définit la taille des types primitifs connus (la taille ne dépend pas de la plateforme) :
un booléen (boolean) ou un byte feront toujours 1 octet (logique !) de taille
un short ou un char feront toujours 2 octets de taille
un int ou un float feront toujours 4 octets de taille
un long ou un double feront toujours 8 octets de taille.

Les types dans Java sont tous signés SAUF boolean et char (ce ne sont pas des types numériques).
L'étendue des types numériques est donc :
 
Type numérique Minimum Maximum
Byte -2^7 (-128) 2^7 -1 (127)
Short -2^15 (-32768) 2^15-1 (32767)
Int -2^31 2^31-1
Long -2^63 2^63-1

Les caractères Java sont au format Unicode (ce qui est un format codé sur 16 bits ie 2 octets). Si les 9 bits de poids fort sont à 0, alors les 7 autres correspondent aux 7 bits du format ASCII.

Les opérations sur des types flottants peuvent aboutir à des valeurs qui ne sont pas des nombres :
Float.NaN (Not A Number), Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY
idem pour Double.

Littéraux

Un nombre peut être écrit : Par défaut, un nombre sera considéré comme un int, donc sur 32 bits ; à moins d'avoir spécifié un L (pour 64 bits, long).
Pour un floattant, par défaut, on considère que c'est un double.

Les floattants peuvent s'écrire :

Les caractères peuvent s'écrire : Rem. On ne peut pas écrire un caractère avec char c = -1, char c = 500L ...

Variables, identifiants

Un identifiant de variable doit Quelques mots clés : ils sont case sensitive, goto ...
union n'est pas un mot clé.

Initialisation

Tableaux

L'utilisation d'un tableau se fait en trois temps :
  1. déclaration : les [] peuvent se trouver avant ou après le nom de la variable. Pour les tableaux multidimentionnels, on a plusieurs séries de [].
  2. construction (new) : il faut déclarer la taille du tableau. Comme la taille n'est utilisée qu'au run-time, il est parfaitement légal de mettre une variable en guise de taille. Par ex. tab = new int[isize];
  3. initialisation : par défaut, tous les items sont initialisés à 0 (qu'ils s'agissent de variable locale, ou statique).
On peut combiner déclaration et construction :
int tab [] = new int [25];

On peut combiner déclaration, construction et initialisation par :
float diam[] = {1.1f, 2.2f };

On peut obtenir la taille d'un tableau par length : tab.length (ne pas mettre de length( ) ce n'est pas une méthode !).
Les tableaux sont numérotés de 0 à length - 1.

Remarque : int tab[5] = new int [5]; est interdit.

Les opérateurs


shifts :


Remarques :
les opérandes sont promotés au moins à la taille d'un int avant d'effectuer un shift. Ensuite, le résultat est tronqué.
le shift ne peut se faire au maxi que sur la taille du type (32 pour les int, 64 pour les long). Au delà, le shift sera effectué sur %32(64) du shift indiqué.

Exemple :
192 (int) =               00000000 00000000 00000000 11000000
192 >> 1 = (96)      00000000 00000000 00000000 01100000
-192 (int) =             11111111 11111111 11111111 01000000
-192 >> 1 = (-96)   11111111 11111111 11111111 10100000
 

Passage d'arguments : par copie (p17)
pour les variables, on passe une copie.
pour les objets (idem pour les tableaux), la construction d'un objet renvoie un identifiant référencant uniquement l'objet (généralement, c'est un pattern sur 32 bits). Le passage par argument d'un objet passe donc la copie d'une référence, ie, on ne peut modifier la référence de l'appelant, en revanche, on peut modifier l'objet lui-même.
 

Les boucles

while ( ) et do { ... }, if ...

attention, l'expression doit être booléenne !
Exemple : int i; if (i) est interdit !

for ( ... )

On peut déclarer une variable dans un for(...). Alors cette variable n'est utilisable qu'au sein de la boucle.
Exemple : for (int i = 0; i<2;i++) { .... // i valable } // i invalide

On peut mettre plusieurs expressions dans le premier élément et le dernier du for, séparés par des virgules.
Mais, attention, on ne peut plus déclarer de variable après une expression.
Exemple : int i=2, j=1 // ok mais j=1, int i=2 // illegal

labels

même utilisation qu'en C du continue, break.
si on ne nomme pas le break, on doit le faire sur un for, while, do ou switch.
le continue se fait toujours sur une boucle.
break arrête l'itération de la boucle actuelle, tandis que continue passe directement à l'itération suivante sans avoir complété l'actuelle.
exemple : mainLoop : for (int i = 0; i<2;i++) { ... }

switch

fonctionne avec des byte, short, char et int.
ne fonctionne pas avec des long, float, double...
 

Conversions, affectations, cast

Des conversions de types peuvent apparaître dans chacun des cas suivants : Convertir, c'est l'opération qui fait changer de type une valeur.
Une opération de réduction, c'est une opération où l'on garde seulement les bits de poids faible.

Régle : le cast explicite est nécessaire pour toute opération de réduction. Il n'est pas nécessaire pour les autres opérations.
Attention :

Conversion, cast d'objets

float f = 1.2f;
int i = (int) f; // ok, et i est à 1.

Remarques :
byte b = 123; // ok... c'est un peu une sorte d'exception, et ça ne marche que si le littéral est bien dans les limites d'un byte.
short s = 12; // ok
short s =35557; // ko à la compilation.

La conversion d'un objet peut se faire vers un objet de classe mère.
Object est la classe mère de tout objet.
Cloneable est la classe mère de toute interface.

Exemple :
si Thésard dérive de Chercheur, que Chercheur implémente Individu, alors :

Thesard ludo = new Thesard();
Chercheur c = ludo;  // ok
Individu i = ludo;   // ok
Thesard axelle = c;  // ko
Thesard toto = i;    // ko

Au run-time, la JVM effectue la comparaison des vrais types des objets (ceci est possible car Java garde une trace du type effectif de chaque objet). Ainsi, certaines exceptions peuvent être levées au run-time.
Mettons que Thésard et Génie dérivent de Chercheur, alors :
Thesard toto = new Thesard();
Chercheur c = toto;         // ok
Thesard t = (Thesard) c;    // ok au run-time parce que c est effectivement un thésard
Genie g = (Genie) c;        // ko au run-time, parce que c est un thésard, et rien ne permet de caster en Genie (à priori).
 

Classes et objet

Surcharge (termes overloading et overriding)

Réutiliser le nom d'une fonction, avec des arguments différents, à plusieurs effets, c'est du overloading.
Dans ce cas, la surcharge uniquement par type de retour n'est pas autorisée.
Réutiliser exactement la même fonction à plusieurs effets (via dérivation), c'est du overriding.
On ne peut surcharger une fonction qu'en la rendant plus (ou égal) accessible.
On ne peut lancer des exceptions non déclarées par la fonction mère.
Late binding : dans le cas de surcharge de méthodes, le compilateur ne peut pas déterminer quelle méthode il doit appeler, car celle-ci dépendra de la classe de l'objet qui invoquera la méthode. Le binding se fait alors au dernier moment : à l'allocation, le runtime indique de quelle classe il s'agit, et cette information est conservée durant la vie de l'objet.

Constructeurs

Garbage collector

On ne peut jamais forcer son démarrage, même System.gc() ne fait que suggérer son utilisation.

Interface

Une interface permet de mettre en oeuvre facilement le polymorphisme :
interface Toto {
...
}
class A implements Toto {
...
}
Toto t = new A(); // légal. A l'exécution, on saura que t est de classe A.
 

Classes intrinsèques

classe intrinsèque à une autre classe

public class OuterClass {
    public class InnerClass {
    ...
    }
    public void makeInner(){
    InnerClass a = new InnerClass(); // c'est comme si on avait this.InnerClass()
    ...
    }
}

Pour construire une instance de la classe intrinsèque, il faut avoir une référence à la classe extrinsèque.
(d'ailleurs, une instance de la classe extrinsèque sera forcément créée).

Puisque la classe intrinsèque possède une référence à la classe extrinsèque, elle peut accéder aux variables membres de la classe extrinsèque.
Si la classe intrinsèque est déclarée statique, attention, elle ne peut accéder qu'aux variables membres statiques de la classe extrinsèque.

classe intrinsèque à une méthode

On peut définir une classe intrinsèque à une méthode. Cependant, attention, cette classe intrinsèque ne peut accéder qu'aux données marquées "final" de la méthode.
En effet, les données d'une classe peuvent survivre à l'exécution de la méthode. Or, conventionnellement, les données locales de la méthode sont détruites au retour de la méthode. Donc, on ne peut les garder. En revanche, les données final peuvent être recopiées dans l'objet.
 

classe anonyme

public void f(){
    bttn.addActionListener(
      new ActionListener() {            // rem. c'est le nom de l'interface
            public void actionPerformed(ActionEvent ae){
                System.out.println("action");
            }
        }
    );
}
 

Polymorphisme

Vérification dynamique

La vérification dynamique de type ne fonctionne que pour les méthodes d'instances.

class Chercheur {
    void travailler(){
        System.out.println("CHERCHEUR: travailler.");
    }
}
class Thesard extends Checheur {
    void travailler(){
        System.out.println("THESARD: travailler.");
    }
}

Chercheur c = new Chercheur();
Thesard t = new Thesard();
Chercheur ludo = new Thesard();    // ok
c.travailler(); // appelle la méthode travailler de Chercheur - logique
t.travailler(); // appelle la méthode travailler de Thesard - logique
ludo.travailler(); // appelle la méthode travailler de THESARD. Il y a vérification dynamique du type de ludo, et on appelle la méthode correspondante.

Attention, cette vérification dynamique ne s'effectue pas pour les méthodes statiques, ni pour les variables d'instances (ni quoique ce soit d'autres qu'une méthode d'instance).
 

Quelques classes remarquables ...

Math

c'est une classe finale : on ne peut donc pas en créer d'instance
ses méthodes sont statiques : donc on peut les utiliser sans créer d'instance (ouf !)
La classe Math est final. On ne peut donc pas en créer d'instance
 

Wrapper classes

Il s'agit de classes d'encapsulation des types primitifs de Java. Il y en a pour chaque type.
Ce sont également des classes finales.

On peut les construire :

Remarque :
dans le cas d'un booléen, true est construit si la chaine correspond à "true" (sans considérer la casse). Sinon, on a un false.

On peut récupérer la valeur d'une instance d'une wrapper classe par une méthode public xxxx xxxxValue( );
Exemple :
Character wrapperC = new Character('m');
char d = wrapperC.charValue();

Toutes les classes, sauf Character, définissent la méthode statique valueOf( ) qui parse une string, construit une instance de la wrapper class appelée et la retourne.
Exemple :
Double d = d.valueOf("12.23");
Remarque : il n'y a pas de différence entre Double d = d.valueOf("12.23"); et Double d = new Double("12.23"); A la limite, on peut juste considérer que la première sorte est un peu plus rapide.

Comparaison
La comparaison entre deux instances d'une wrapper class se fait par la méthode equals.
if (d1.equals(d2)){
...
}
Attention, d1==d2 opère une comparaison de références d'objets, pas de contenu !

String

Il s'agit d'une classe de chaînes pseudo non modifiable : ie, on peut modifier ce à quoi la chaine fait référence, mais pas le contenu.
L'index des caractères commence à 0.
Exemple :
String s = "5+4=20";
s = s.replace('+','*');
s référence alors une String de valeur "5*4=20". Attention, le littéral "5+4=20" existe toujours en mémoire, mais n'est plus référencé par s.

Il existe une pool de chaines littérales construites dans un programme Java. Ainsi, dès qu'un programme référence une nouvelle chaine, le littéral est ajouté au pool, et on ne retourne qu'une référence à cette chaine
 
String s1 = "toto";
String s2 = "toto"; // référence au littéral toto
if (s1.equals(s2)) {
// fonctionne car s1 et s2 font référence à la même chaine.
}
String s1 = "toto";
String s2 = "toto";
if (s1==s2){
// fonctionne car s2 référence la même valeur dans le pool
}

Aussi, lorsqu'on effectue la commande String s = new String("toto"); il y a en fait deux objets en mémoire :

Rem. Les fonctions trim (enlève les espaces), replace, toUpperCase etc fonctionnent de la manière suivante : si la chaine n'est pas modifiée, on renvoie une référence à la string passée. Sinon, on crée un nouvel objet qui contient le résultat et retourne cet objet là. L'objet en entrée n'est biensûr jamais affecté.

La classe StringBuffer représente une chaine dynamiquement modifiable. Une StringBuffer a une capacité, ie la taille maximale qu'elle peut avoir sans devoir effectuer une réallocation. C'est le système qui se préoccupe de cette réallocation, on n'a pas usuellement à s'en soucier.
Remarque : on ne  peut pas comparer une String à une StringBuffer, ou réciproquement.

L'opération +
si on fait un "toto" + "tata", c'est comme si on faisait new StringBuffer().append(toto).append(tata);
si on fait un "toto"+ dimension, où dimension est un objet, c'est comme si on faisait un appel à dimension.toString();
 

Les modificateurs

modificateurs d'accès

Une classe extrinsèque ne peut être que public ou friendly.
On ne peut surcharger une fonction qu'en la rendant plus accessible

Exemple : la classe Applet définit une méthode init( ) déclarée en public. Lorsqu'on crée une applet, on surchargera habituellement init par notre propre init. Notre init doit alors être public (private, friendly ou protected aboutiraient à une erreur de compilation car on restreint l'accès).

autres modificateurs

final : indique qu'on ne peut pas modifier !

Exemples :
class MyObject {
    int value;
    MyObject(int v){ value = v; }
}
final MyObject m = new MyObject(1500);
m = new MyObject(2); // illegal
m.value = 10; // legal

class Genie {
    final int Nb();
}
class Mari extends Genie {
    int Nb(); // illegal
}

abstract : indique qu'une classe/méthode est incomplète
Remarques :

static : spécifie une donnée propre à la classe (et non à l'instance). Cette donnée est allouée au chargement de la classe. On peut faire référence à une variable statique par le nom de sa classe aussi bien que par le nom de la variable.
Chercheur c = new Chercheur( ); // cette classe définit une variable statique QImin.
Chercheur.QImin = 150;          // valide
c.QImin = 152;                  // valide

native : indique que le code de la méthode se trouve en dehors de la JVM.
Il faut donc faire un System.loadLibrary("MyNativeLib") auparavant. Cet appel est généralement fait un initialisateur statique de la classe, afin de ne pas causer de retard d'exécution.
Le code chargé peut être écrit en C, C++...
si on écrit :
class Example {
 public static void main(String args[]){
  System.out.println("coucou");
 }
 native void machin();
}
cela compile. En revanche, pour appeler machin(), il faudra charger la librairie correspondante.

transient : indique que la donnée ne doit pas être stockée avec l'objet (par confidentialité, par exemple).
Une variable marquée "transient" ne sera pas écrite si on effectue un output de l'objet dans un stream (sérialisation).
Une variable transient ne peut pas être final, ni static.

Exemple :
class BankAccount implements Serializable {
private float amout;
private transient String cardCode;
}

synchronized : définit une section critique. Peut être marqué sur une méthode, une portion de code, ou un objet synchronize(objet){...}

volatile : une variable volatile indique qu'elle peut être modifiée de manière asynchrone (et donc le compilateur fait attention). Les actions effecutées sur une variable volatile se font dans l'ordre dans lequel elles sont indiquées par le thread concerné.
 

Exceptions

Rattraper des exceptions

Il existe plusieurs sortes d'exceptions :


On n'est censé rattraper que les checked exceptions (ie tout java.lang.Exception mis à part RuntimeException).

try {
    ... // code dans durant lequel des exceptions peuvent être lancées
}
catch (ExceptionClassOne e){
    ...// code exécuté uniquement si une exception de classe ExceptionClassOne
       // est lancée.
}
[catch (ExceptionClassTwo f){
   ...// code exécuté uniquement si une exception de classe ExceptionClassTwo
      // est lancée
}]
[finally {
    // code qui est toujours exécuté qu'il y ait une exception ou non.
    // ce code est même exécuté si on fait un return avant !
}]
// suite du code : n'est éxécuté que si aucune exception n'a été levée,
// ou si l'exception a été rattrapée.

Remarques :

Lancer des exceptions

Toute méthode susceptible de lancer une exception doit le déclarer par la formule throws ExceptionClassOne[,ExceptionClassTwo....]
Exemple : void doSomething(int i) throws ArithmeticException { ... }

L'exception est construite tel un objet, et lancée par throw.
Exemple : throw new IOException("File not found");
 

Surcharger des méthodes qui lancent des exceptions...

La méthode qui surcharge ne doit lancer des exceptions que du type déclaré dans la classe mère. Elle peut donc lancer des exceptions de sous-classes de cette classe, mais pas plus.

Exemple : Soit StreamCorruptedException et MalformedURLException deux sous-classes de IOException.
class A {
public int func() throws IOException {
...
}
}
class B extends A {
public int func() throws StreamCorruptedException, MalformedURLException { // ok
...
}
}
class C extends A {
public int func throws IOException, ArithmeticException {        // illegal
}
}

Threads

Création

Pour faire démarrer un thread (ie le rendre éligible), on peut procéder de deux manières différentes :

Terminaison

Un thread se termine lorsque sa méthode run() retourne.
Un démon (isDaemon, setDaemon) se termine également si le main() du programme retourne. En effet, le but d'un démon est d'assurer une tâche de fond non essentielle au programme : il se termine donc au retour du main().
L'appel à System.exit(...) termine l'exécution du programme (mais pas nécessairement un return).
Un programme se termine lorsque tous les threads non démons sont terminés.(ie les démons ne survivent pas à la mort du programme).

Remarque : les threads créés par des démons sont également des démons (on peut changer cela par un setDaemon).
la méthode stop() n'est plus conseillée en Java 1.2 : dans la plupart des cas, il vaut mieux ajouter une variable indiquant quand arrêter le thread. Le thread doit régulièrement consulter cette variable, et retourner de son run lorsque la variable indique un arrêt. Il est conseillé de déclarer cette variable en tant que volatile (indique qu'elle peut être modifiée de manière asynchrone)... ou alors synchroniser l'accès à la variable

Les priorités

Dans Java, les threads ont une priorité de 1 à 10 (plus haute priorité). La priorité par défaut est 5.
Un thread hérite de la priorité du thread qui le crée.
Pour positionner exactement une priorité, on utilise setPriority. On peut utiliser les constantes Thread.NORM_PRIORITY, MAX/MIN_PRIORITY.

Attention, la gestion des priorités est dépendante de la machine virtuelle.
L'implémentation du scheduler est plateforme dépendante. Certains schedulers seront

Le contrôle de threads

Yield : permet de passer de l'état Running à l'état Eligible, et ainsi de laisser une chance à d'autres threads de prendre un peu de temps CPU.

Suspend / resume : permet de passer un thread de l'état Running à l'état Suspendu (Suspend) ou de l'état Suspendu à Eligible (Resume).
Ces primitives peuvent être appelées par un autre thread (et même, resume doit être appelé depuis un autre thread si on veut que l'action soit effectuée !!).
L'utilisation de ces méthodes n'est plus conseillée en Java 1.2. En effet, le suspend peut engendrer des deadlocks. Il suffit que le thread ait acquis le vérou sur un objet, puis se suspend. Personne ne peut accéder à l'objet. Si le thread censé appeler resume() essaie auparavant de prendre contrôle de l'objet vérouillé, il se bloque, et resume( ) ne sera jamais appelé. Il vaut mieux demander au thread de regarder une variable indiquant l'état du thread (suspendu ou actif) (cf stop).

Sleep : permet de passer un thread dans l'état "endormi" pendant au moins quelque temps (précision ms ou ns).
Attention, une exception InterruptedException peut être levée. Le bloc catch correspondant sera alors exécuté lorsque le thread aura à nouvea la main.
C'est une méthode statique, qui s'appelle sur le thread courant.

Synchronize :
Tout objet possède un vérou. A tout moment, le vérou est contrôlé par au plus un thread. Le vérou donne accès au code "synchronize" de l'objet.  Un thread qui veut exécuter le code "synchronize" d'un objet doit tout d'abord acquérir son vérou. Si l'objet est déjà vérouillé par un autre thread, le thread appelant passe dans l'état "bloqué" en attente de la libération du vérou requis (il repasse alors dans l'état "ready")
On peut synchroniser une méthode entière ou simplement une partie de code (il faut alors spécifier l'objet qu'on synchronise - on peut mettre this) :

public synchronized void myMethod(String s){
    ... // code synchronisé
}

public int aMethod(){
    ... // code non synchronisé
    synchronized(anObject){
    ... // code synchronisé
    }
    // code non synchronisé
}

Moniteur
Un moniteur est un objet susceptible de bloquer ou de redémarrer des threads. C'est un objet qui comporte du code synchronisé (synchronized, wait, notify...)

Wait / Notify : ne doit être appelé que dans du code synchronisé. Wait passe le thread dans l'état "En attente" et libère le lock sur l'objet. Notify prend un des threads "en attente" et le passe dans l'état "prêt". La liste d'attente des threads "en attente" est gérée par l'objet qui a appelé Wait. Donc, notify doit être appelé sur le même objet (moniteur). NotifyAll passe tous les threads en attente dans l'état prêt.
Wait peut envoyer des InterruptedExceptions.
 

Interfaces graphiques

Les différents composants visuels

L'appel aux méthodes setSize et setBounds ne marchera généralement pas car la taille des composants est régie par un layout manager. Seul le canvas pourra être dimensionné facilement par ces méthodes.

Radio bouton
Il faut grouper plusieurs cases à cocher dans un CheckboxGroup.
CheckboxGroup cbg = new CheckboxGroup();
Checkbox c1 = new Checkbox("ludo",false,cbg);
Checkbox c2 = new Checkbox("axelle",false,cbg);
Checkbox c3 = new Checkbox("toto",true,cbg);
Un seul radio bouton peut être sélectionné à la fois. Si un autre est sélectionné, cela déselectionne le premier.
La classe CheckboxGroup n'est pas un composant (ie, pas de restrictions pour l'organisation interne de ses membres).
Rem. Un checkbox n'est pas déformable (il ne sera pas influencé par le layout manager quant à sa taille).

FileDialog : une classe qui permet d'ouvrir ou de sauvegarder un fichier. La fenêtre qui apparait est modale.
new FileDialog(myframe,"Un titre",FileDialog.LOAD);

List( <nombre de lignes visibles>, <multiple selection booléan>);

ScrollPane : une classe qui permet de faire apparaitre des scrollbar si nécessaire.
Scrollbar : orientation, initial value, slider size, min value, max value

TextField, TextArea : deux sous-classes de TextComponent.
Dans un espace cartésien, les coordonnées données seront toujours y puis x. Exemple : TextArea(y,x)
Les tailles sont en taille de caractères, et peuvent donc être légèrement différentes d'une font à une autre.
Si on tape du texte hors de la taille du composant, il scrolle automatiquement vers la droite.

Les conteneurs

Les conteneurs dérivent de Components.

Applet : changer la taille d'une applet n'est pas forcément autorisé par le browser. La taille de l'applet est généralement spécifiée dans le tag HTML. Les applets ont une fonction init().
Les applets sont dérivées des Panels, qui eux mêmes dérivent de Container.

Frame : il s'agit d'une fenêtre indépendante, décorée à la manière du système d'exploitation sous-jacent. Ces fenêtres ont un titre (éventuellement vide). Au départ, un frame n'a ni taille ni n'est visible : il faut donc faire appel à setSize()/setBounds() et setVisible(true).
setVisible(false) cache le frame, mais ne le détruit pas. Pour détruire un frame (et les ressources associées), il faut appeler dispose().
Les browsers ont des restrictions sur l'affichage de frames au sein d'applets (titre untrusted) pour des raisons de sécurité (empêcher de transmettre des informations).
Un frame dérive de Window qui dérive lui même de Container.

Panel : sert à grouper des composants au sein de frames ou applets. Un panel dérive de Container.

Dialog : popup-window qui accepte les entrées utilisateur. Modale ou non modale. (layout manager par défaut= Border Layout). Dérive de Window, qui dérive elle même de Container.

Les menus

Il faut : Ne pas confondre les CheckboxMenuItem et les Checkbox !
 

Layout managers

Par défaut

 
Panel FlowLayout
Applet FlowLayout
Frame BorderLayout
Dialog BorderLayout

Généralités


Il existe 5 layout managers différents : GridBag, Border, Card, Flow, et Grid. Ils implémentent tous l'interface java.awt.LayoutManager
Les layouts s'appliquent à des containers (un container contient des composants ou d'autres containers).
setLayout(... layout...);

Tous les composants ont des "tailles préférentielles". Il s'agit d'une taille, hors restrictions de layouts, qui permet d'afficher au mieux le composant : on affiche juste ce dont on a besoin. Par exemple, la taille préférentielle d'un bouton est juste suffisamment large pour montrer son texte etc.
Lorsqu'on ajoute un layout manager au container, les composants s'affichent avec une combinaison entre leur taille préférentielle souhaitée et les restrictions du layout manager. (le layout manager prédominera).

Pour ne pas utiliser de Layout manager, il suffit de faire setLayout(null), et alors le setBounds fonctionnera correctement. mais ce n'est pas conseillé !
 

FlowLayout

affiche les composants en rangées horizontales. S'il n'y a pas assez de place pour les mettre en une rangée, les composants suivants passeront dans une rangée en dessous.
On peut spécifier le style d'alignement souhaité : gauche, droite, centre.
new FlowLayout(FlowLayout.RIGHT);
ce layout respecte toujours la taille préférentielle des composants.
 

GridLayout

divise son territoire en une matrice de rangées et de colonnes (new GridLayout(rows,columns)). Chaque cellule a la même taille, et chaque composant remplit complètement (au maximum) sa cellule.
ce layout ignore complètement la taille préférentielle des composants.
s'il n'y a pas assez de place pour tous les composants, de nouvelles cellules seront créées.

BorderLayout

divise son territoire en cinq régions : nord, sud, est, ouest et centre. Chaque région ne peut contenir au plus qu'un seul composant.
les composants placés en nord et en sud s'étendent horizontalement suivant la taille de la fenêtre, mais gardent impérativement leur hauteur préférentielle.
les composants placés en est et en ouest s'étendent verticalement suivant la taille de la fenêtre, mais gardent impérativement leur largeur préférentielle.
le composant placé au centre prend la taille restante. Le centre est l'emplacement par défaut.
exemple :
setLayout(new BorderLayout())
...
add(<composant, container>, BorderLayout.SOUTH);
 

GridBagLayout

divise son territoire en un tableau de cellules, mais à la différence de GridLayout, les cellules peuvent avoir des tailles différentes. De plus un composant peut occuper une cellule... ou s'étendre sur plusieurs cellules.
la classe GridBagConstraints permet de noter les informations sur la position des composants.
l'utilisation de ce layout se fait :

GridBagLayout g = new GridBagLayout();
GridBagConstraints c = new GridBagConstraints();
...
g.setConstraints(theComponent, c);
add(theComponent);

Remarques
on peut utiliser les mêmes contraintes pour plusieurs composants, mais attention éventuellement à bien remettre les valeurs par défaut...

gridx, gridy = désigne la colonne et la ligne du composant.
GridBagConstraints.RELATIVE pour gridx passe au composant à droite du dernier saisi. Pour gridy, on passe au composant en dessous du dernier saisi.
par défaut, 0,0.

gridwidth, gridheight = désigne le nombre de lignes et colonnes que le composant peut occuper.
.REMAINDER indique qu'il peut occuper tout ce qu'il reste.

fill = valable uniquement lorsque la taille requise pour le composant est largement inférieure à la taille dont on dispose.
.NONE rien, par défaut.
.HORIZONTAL on étend le composant horizontalement sur sa cellule.
.VERTICAL
.BOTH

ipadx, ipady = padding interne du composant. Il indique les tailles entre le "texte" et le bord du composant. Le padding s'applique deux fois (à gauche et à droite, et en haut et en bas).

insets = padding externe du composant. Il indique les tailles entre les bords du composant et sa cellule. Il faut construire un objet Insets(top, left, bottom, right).

anchor = valable uniquement lorsque la taille requise pour le composant est largement inférieure à la taille dont on dispose.
.CENTER par défaut, on met le composant au milieu de sa cellule.
.NORTH on met le composant en haut de sa cellule
.NORTHEAST, .EAST, .SOUTH....

weightx, weighty : permet de gérer l'espace en trop. Les valeurs de ces champs sont comprises entre 0.0 et 1.0
Le composant reçoit un certain pourcentage d'espace "en trop" lorsque la taille affichable est supérieure à la taille nécessaire.
A 0.0, tous les espaces en trop vont sur les bords, si bien que les composants sont tjrs centrés au centre du container.
A 1.0, tous les espaces en trop sont utilisés sur les composants.
 

CardLayout

permet de montrer ou de cacher certains composants, suivant le temps.
les composants affichés prennent toute la place qu'ils ont dans le container.
 

Swing et AWT

Les ajouts de Swing sont : attention :

Les événements

Un event listener est un objet à qui on confie la tâche d'écouter les divers événements qui pourraient se produire.
Le modèle de programmation d'événements du JDK 1.0 et 1.1 est incompatible, ils ne doivent pas apparaitre dans le même programme.
On peut avoir plusieurs écouteurs pour le même événement, mais rien ne garantit l'ordre de réception des événéments.

Deux manières de mettre en place l'écoute des événements :
 
créer sa propre classe qui implémente le listener, et lier son listener au composant souhaité.
class MyClass implements ActionListener {
public void actionPerformed(ActionEvent ae){
}
}
et dans l'application :
Button btn = new Button("Ok");
MyClass listener = new MyClass();
btn.addActionListener(listener);
add(btn);

Suivant l'événement qu'on écoute, il existe différentes classes de listener : KeyListener, MouseListener, ActionListener... 
Le nom de la fonction à implémenter dépend de l'événement écouté : actionPerformed, mouseClicked ... 
Le nom de la fonction à ajouter au composant dépend également de l'événement écouté : addXXXListener.

gestion explicite de l'événement : l'événement est traité dans le code même du composant qui le génère. Il faut ensuite activer l'écoute de l'événement.
public class MyBtn(){
public void processActionEvent(ActionEvent ae){
...
super.processActionEvent(ae);
}
public MyBtn(){
...
enableEvents(AWTEvent.ACTION_EVENT_MASK);
...
}
 

Le nom de la fonction à implémenter dépend de l'événement écouté : processXXXEvent(XXEvent ae); 
Le masque d'écoute d'événement dépend de l'événement écouté : AWTEvent.XXX_EVENT_MASK.

Les adaptateurs


Certaines classes (ex. WindowListener) demandent l'implémentation de plusieurs méthodes. En conséquence, si on veut juste écouter un événement (ex windowIconified), faire une classe avec juste cette méthode ne compile pas : il manque le code des autres méthodes.
Pour éviter de devoir le faire, les adaptateurs reprennent ce code en faisant "rien du tout" pour chacune de ces fonctions. Il suffit alors de créer une classe de XXXAdapter (ex. WindowAdapter) et de modifier seulement la méthode (windowIconified) et cette fois-çi, ça compilera.

class MyListener extends WindowAdapter {
    public void windowIconified(WindowEvent we){
    ...
    }
}
 

Paint

Généralités

Le dessin utilise la classe Graphics, qui désigne un contexte graphique.
Un contexte graphique est spécifique à chaque composant.
Le dessin s'implémente dans la fonction paint : public void paint(Graphics g){ ... }

La primitive update( ) met tout le composant à sa couleur de fond puis appelle paint en lui passant le contexte graphique. Lors de dessins intensifs, il peut être plus intéressant d'appeler plutôt repaint( ) qui poste une requête de update( ) plutôt que de faire des tas d'update( ) successifs.

Ne pas mettre de code de dessin dans les gestions d'événements, mais au contraire tout regrouper dans la méthode paint( ).
 

Primitives de dessin

Les signatures des fonctions de dessins sont généralement du style :
coordonnée x, coordonnée y, largeur, hauteur (où x et y sont pris dans un repère "cartésien", y étant donc vertical vers le bas).

drawPolygon : dessine un polygone fermé, pour lequel on spécifie un certain nombre de points (int y[], int x[], nb).
drawPolyline : dessine un polygone ouvert.
drawArc : dessine un arc d'ovale. Les angles sont mesurés en dégrés, le 0 partant à droite, et 90 degrés étant en haut.
drawString : dessine une string dont la baseline est spécifiée en argument.

Pour remplir des formes, utiliser les primitives fillXXX.

Couleur

Si on la spécifie, il faut la mentionner avant de dessiner (cela ne change pas la couleur des éléments déjà dessinés).
On appelle un setColor sur une instance de Graphics.

Polices

Si on la spécifie, on faut la mentionner avant de dessiner.
Sur tous les systèmes on trouve les polices Serif, SansSerif et Monospaced.
On peut récupérer les polices disponibles sur le système par Toolkit.getDefaultToolkit().getFontList();
exemple : new Font("SansSerif",Font.BOLD, 24);

Clipping

restreint la région géographique surlaquelle un contexte graphique peut agir. Par défaut, la clipping region contient tout le composant.
setClip(x,y,width,height);

HTML et Applets

<BODY>
...
<APPLET CODE=... class WIDTH=x HEIGHT=y>

<PARAM NAME=nom VALUE=valeur>
...
</APPLET>
</BODY>

Définition

Le marqueur APPLET comprend impérativement : Optionnellement, on peut spécifier Pour CODE et CODEBASE, on peut spécifier des URL ou des noms de répertoires.

Paramètres

Pour passer des paramètres à l'applet, il faut le faire par la mention PARAM Récupérer les paramètres dans l'applet : il faut utiliser getParameter(nom), ce qui retourne une String (ou null).
 

Input and output

Généralités

Tout ce qui concerne l'input ou output se trouve dans java.io
Cela peut lever des IOException.
Les chaines représentées en interne sont en Unicode (16 bits). Pour l'input et l'output, on utilise l'UTF (taille variable).

File

La classe File ne fait qu'encapsuler une string représentant un fichier ou un répertoire.
Il n'y a pas de création de fichier/répertoire.
Le garbage collecting d'une instance de file ne détruit pas le fichier/répertoire
Cette classe permet de :

RandomAccessFile

La classe RandomAccessFile permet d'accéder aux fichiers : recherche de position  dans le fichier, lecture, écriture.
Ce modèle d'accès de fichiers est totalement incompatible avec le modèle stream / reader / writer.
Il n'y a pas de création de fichier/répertoire, et le garbage collecting ne détruit pas le fichier/répertoire.
On peut :

Streams

Dans les streams de bas niveau , on peut faire les opérations : Ils sont dérivés des classes abstraites InputStream et OutputStream.
Exemple : FileInputStream, PipedInputStream...

Les streams de haut-niveau (DataInputStream, BufferedInputStream...) sont obligatoirement chainés à des streams de bas-niveau (ou à un autre stream de haut niveau lui même chainé à un stream de bas niveau).
Ils sont dérivés des classes FilterInputStream ou FilterOutputStream (elles mêmes dérivées de InputStream et OutputStream).
Il faut fermer ces streams dans l'ordre inverse de leur chainage.

La classe BufferedInputStream en particulier permet de lire de gros blocs de données.
La classe DataInputStream permet de lire directement les données attendues : readUTF(), readInt()...

Reader / writer

Ce sont des streams orientés lecture / écriture de chaines Unicode.
Ils sont dérivés des classes abstraites Reader et Writer.
Exemple : FileReader, PipedReader, StringReader.
On peut :


Les readers / writers de haut niveau doivent être chainés à des readers / writer de plus bas niveau.
Ils sont également dérivés de Reader et Writer.
Exemple : BufferedReader, LineNumberReader, PushbackReader, PrinterWriter ...
 

Collections (interface Collection ... java.util)

Généralités

Une collection permet de regrouper des objets de manière générique. On ne se préoccupe pas à ce niveau d'ordre, de duplication, d'implémentation.
Exemples de collections : Vector, Hashtable, array...

La nouveauté en Java 1.2 est d'avoir mis en place les collections framework : ie pour chaque collection on a une interface, une (des) implémentations et des algorithmes. On permet à l'utilisateur d'une collection de manipuler sa collection sans avoir à connaitre les détails de son implémentation.

Toutes les collections dérivent de l'interface Collection, mis à part les Map (qui dérivent de Map) qui ne sont pas de "vraies" collections. L'interface Collection sert essentiellement à passer une collection de manière générale (il n'existe pas d'implémentation de la classe Collection).

On trouve également dans Collection :

De plus, toutes les collections ont un constructeur qui prend une collection en argument :
exemple : List l = ArrayList(c);

Remarque de programmation : faites toujours référence à votre collection en utilisant l'interface, et non pas le type d'implémentation utilisé. Ainsi, vous pourrez facilement changer d'implémentation de votre collection....
Exemple : Set s = new Hashset(); // où Hashset est une implémentation de l'interface Set.

import java.util.*;
class Example {
 public static void main(String args[]){
  Set s = new HashSet();    // une implémentation non ordonnée de Set.
  s.add(new String("Axelle"));
  s.add(new String("Ludo"));
  s.add(new String("Croco Junior"));

  //
  System.out.println("s="+s);

  for (Iterator i = s.iterator(); i.hasNext() ; ){
   System.out.println("Parcours : "+i.next());
  }

 }
}
 

List :

c'est une autre collection qui regroupe des éléments ordonnés, dans laquelle on peut avoir des doublons. Les implémentations les plus fréquentes sont ArrayList ou LinkedList. Ces collections utilisent les méthodes définies dans l'interface Collection + des méthodes concernant la position des éléments.
iterator retourne un ListIterator (possibilité d'accéder aux index, et aux précédents : nextIndex, previousIndex, hasPrevious...). Attention, le previous après une succession de next retournera l'objet retourné par le dernier next. En effet, il faut considérer que le pointeur se trouve tjrs entre deux élements.

Les différences avec la classe Vector :

Map

mappe des clés (uniques) vers des objets. Les implémentations les plus courantes sont HashMap et TreeMap.

Les différences avec la class Hashtable :


Dans Map, on trouve les méthodes :

Exemple :
for (Iterator i=m.entrySet().iterator(); i.hasNext(); ) {
     Map.Entry e = (Map.Entry) i.next();
     System.out.println("clé: "+e.getKey() + " valeur: " + e.getValue());
}

multimap : chaque clé est mappée vers plusieurs valeurs. Cela peut s'implémenter en mappant chaque clé vers un objet List.

Afficher le sommaire en parallèle