Java
Sommaire

1. Bases
- Environnement de développement
- Bases du langage
- Opérations sur les bits
2. Objets et classes
- Héritage
- Interface
- Exceptions
- Enumération
3. Méthodes
- Passage d'argument
- Package
- Imports
- Collection d'objets
- Lambdas
- API Stream
- Réflexivité
- Généricité
- Threads
4. Connexion BDD
- JPA
- Configuration d'Eclipse pour JPA (implémentation Eclipselink)
- Hibernate
- Connexion à une BDD via Netbeans+Glassfish
5. Exemple application DAO
6. Modele-Vue-Controleur
7. Exemples divers
- JPQL
- Annotation
8. Exemple MVC
9. JUnit
- DBUnit
- Tester des méthodes privées
10. Java FX
- Scene Graph
- Exemples
- Layout
11. Liens
12. Java EE
- JSP/Servlets avec Tomcat et Eclipse
- JSF
- Composants JSF
- Objets managés / Non-managés
- Validation en utilisant les annotations
- Sécurisation des pages
- Connexion à une base de donnée
- Envoi de mail
- Listeners JPA
- Héritage JPA
13. Spring
- Programmation orienté aspect
- Injection
- Spring MVC
- Spring + Spring MVC + Hibernate
- Spring boot
14. REST
15. Android
- Fragments
- Installation sur Linux
- OrmLite
- Exemples
- Pager
- Web
16. Rx Java
- Résumé
- Opérateurs
17. Divers
- Compiler et exécuter
- JMS
- Jenkins
- Maven
- Découpage de listes
- Formatage de String
- Un compteur qui va jusqu'à 1000 et reset
- Une fonction récursive qui calcule les factorielles
- Des outils de monitoring intégrés à java
- Enchainer des get à l'aveugle
- Tracer une erreur
- Equivalence des lettres accentuées dans les fichiers de properties
- Lecture d'un fichier texte
- Deserialisation custom avec Jackson
- Log4j
- Point d'entrée du programme
- Fichier MANIFEST.MF
- Dupliquer un serveur Tomcat
- Ajouter un certificat / un keystore
- Sécurité
- Questions posées lors d'entretiens


Des notes concernant java. Merci a Cyrille Herby pour son cours sur le site du zéro ainsi qu'à mes formateurs.


Bases


Un projet Java SE (simple edition, la version utilisée en général par le grand public) doit contenir au minimum une classe du même nom que le fichier source et une méthode nommée main, de type public et static, admettant en paramètre un tableau string et renvoyant void.

Environnement de développement


Pour exécuter du code java, vous aurez besoin d'une machine virtuelle (JVM, Java Virtual Machine), comme celle qui s'installe avec le JRE (Java Runtime Environnement). Jusqu'à Java 8, il fallait télécharger et installer le JRE sur sa machine pour lancer des applications Java. Désormais, les applications embarquent avec elles les portions du JRE nécessaires à leur fonctionnement (ce qui rend donc les applicationw FileInputStream(file), "s un peu plus grosses mais l'installation plus simple).

Pour développer, vous aurez besoin du JDK (Java Developpment Kit), qui intègre JRE. Il en existe 2 principales versions : celle d'Oracle et une version libre nommée OpenJDK, quasi pas de différences entre les 2.

Sous Windows, il y a un installateur tout prêt : https://www.oracle.com/technetwork/java/javase/downloads/index.html
Vous pouvez aussi télécharger l'archive compressée, dézipper et mettre le chemin vers le dossier "bin" dans vos variables d'environnement Windows.

Sous Linux, Java est souvent installé, vous pouvez le vérifier en tapant java -version. Si aucune version de java ne s'affiche, soit Java n'est pas installé, soit il n'est pas configuré. Essayez la commande find /usr -name java pour voir s'il sort quelque chose s'approchant de "jvm/jre/java"; s'il n'y a rien, installez-le.
Pour installer java : sudo apt-get updated puis sudo apt-get install default-jre.

Pour indiquer à Linux où se trouve l'executable java, taper export PATH=/votre/beau/path/vers/java/jdk1.8.0_77/bin:$PATH dans une console. Attention, java ne marchera que dans CETTE console, si vous la fermez vous perdrez le chemin vers l'exécutable. Pour éviter ça, on peut mettre cette commande à la fin du fichier caché ~/.profile qui va l'exécuter automatiquement.

Sous Linux la variable JAVA_HOME est souvent utilisée par les programmes pour lancer Java, vérifiez qu'elle est renseigné en faisant : echo $JAVA_HOME, sinon, vous pouvez ajouter JAVA_HOME=/votre/beau/path/vers/java/jdk1.8.0_77/ dans le fichier /etc/environnement (attention il faut être root pour modifier le fichier).
Une fois java installé, pour trouver le répertoire où se trouve java readlink -f $(which java)).



Bases du langage


Types primitifs : Entiers avec signe (byte, short, int, long -la valeur doit être préfixé d'un L), réels (float, double), caractère (un seul caractère de 16 bits entre deux ', unicode), booléen. Ils peut ne pas avoir été initialisés mais ne peuvent pas être "null". Ils ont une valeur par défaut quand on les utilise en tant qu'attributs (false pour un boolean, 0 pour les nombre, \u0000 pour char...).

Les "String" sont des objets, des ensembles de char.

Attention si on divise deux int et que essaie de mettre le résultat dans un troisième int, une erreur est possible si le résultat a une virgule.

Structures conditionnelles : if, if/else, if/else if/else, switch.
Les accolades sont facultatives s'il n'y a qu'une seule ligne dans la condition.

Exemple :

if(b==1){ a=3; System.out.print("1"); }



switch (a) { case 0: System.out.println("A vaut zéro"); break; case 20: System.out.println("A vaut vingt"); break; default: System.out.println("A ne vaut ni zéro ni vingt."); }


Structures itératives : while, do/while (à la différence de while, on entre au moins une fois dans la structure), for(debut;tant que;action). Break permet de sortir d'une itération. Continue permet d'arrêter la boucle et de passer éventuellement au tour de boucle suivant.



int a=1; while(a<10){ System.out.print(a); a++; } //Affiche 123456789 int b=1; do{ System.out.print(b); b++; }while(b<10); //Affiche 123456789 int i=0; for(i=1;i<=9;i++){ System.out.print(i); } //Affiche 123456789



Il est possible de demander à break de sortir d'une boucle précise. Ci-dessous on sort directement des 2 boucles en même temps :

maBoucle : for (MaClasseA monObjetA : listeObjets) { for (MaClasseB monObjetB:monObjetA.getListObjetB()) { if (monObjetB.valeur == true) { break maBoucle; //ICI ! } } }


Affectation : a=b=1 signifie qu'on affecte la valeur 1 à A et B.
a=a+1 est équivalent à a+=1.
Par convention les noms de variables doivent commencer par une minuscule. Si une variable est composée de plusieurs mots, le premier est en minuscule et les suivants tout attachés avec leurs majuscules. Exemple : maVariableDeTest.
Il est possible de séparer les nombre par un underscore (qui sera ignoré lors del'exécution du programme) pour les rendre d'avantage lisibles :


int a=800_000;


On peut aussi utiliser la notation binaire (depuis java 7) ou hexadécimale en préfixant avec 0b ou 0x :

int a=0b110; //A=6. int b=0xF; //B=15


On peut faire suivre le nombre d'une lettre indiquant son type :

float a=9.4f; int b=0xF; //B=15


Pour cette raison, une variable ne peut pas commencer par un chiffre (sinon, impossible de distinguer le float 9f de la variable 9f).

Transtypage (ou cast) : permet de transformer un type en un autre. Si utilisé dans la même ligne qu'un calcul, le cast se fait après le calcul (sur le résultat).
Exemple pour utiliser la valeur de l'entier a dans le réel b :

int a = 1; float b = (float)a;



double a=9.6; int b=(int)a; //donnera 9


Attention :

char a='9'; int b=(int)a; //donnera 57, la valeur du caractère 9 dans la table ASCII.



Comparaison : < (inférieur ), <= (inférieur ou égal à), == (égal à), >= (supérieur ou égal à), > (supérieur à),!= (différent de).
Attention, pour comparer des strings utiliser chaineDeCaractere1.equals(chaineDeCaractere2)) ou même mieux depuis java 7 : java.util.Objects.equals(chaineDeCaractere1, chaineDeCaractere2);.

Opérateur ternaire : pour faire rapidement des tests if/else afin d'affecter des variables.
numeroJoueur = (nombreJoueur==1)?2:1; //assigner à numeroJoueur la valeur 2 si nombreJoueur=1, sinon 1.

Avec un setter : joueur.setNumero((nombreJoueur==1)?2:1) //pareil qu'en haut : si le nombre de joueur est à 1, mettre le num du joueur à 1, sinon 2

On peut aussi faire de l'affichage avec : System.out.println( (noteProjet>50)?"Supérieur à 50":"Pas supérieur à 50");

Attention, pour optimiser java réutilise un objet String s'il voit qu'il existe déjà :

String a="abc"; String b="abc"; System.out.println(a==b); //renvoie true


Par contre, si on demande spécifiquement la création d'un objet string :

String a=new String("abc"); String b="abc"; System.out.println(a==b); //renvoie false




Incrémentation :
Attention au sens de l'incrémentation.

int a=1; int b=a++; //b vaut a (1) puis a est incrémenté à 2.



int a=1; int b=++a; //a est incrémenté à 2 puis b vaut a (2).


Dans le même esprit, on peut aussi utilise le symbole "ou" avec un égal :


for (HeureDTO heureDTO : listHeureTO) { heureOk |= checkHeureValide(heureAtester); //finalement c'est l'équivalent à if(checkHeureValide){heureOk = true; break;} (en +joli et en faisant des tours de boucle inutile...) }


Opérateur logiques : ! (non), & (et), | (ou), ^ (ou exclusif). && stoppe l'évaluation de la condition dès qu'elle est fausse (ça évite de tester 50 & inutiles si la première condition est fausse). De la même façon, || stoppe l'évaluation dès qu'elle est vraie.


Tableaux : un regroupement de variable d'un même type. On peut avoir des tableaux multidimensionnels. Attention, un tableau a un nombre de cases fixe, qui ne peut pas changer après qu'on l'ai déclaré (si on a absolument besoin de redimensionner un tableau, utiliser Arraylist).


//classique int[] tableau3b = new int[10]; //pré-rempli int tableau[]={1,2,3,4,5,6,7,8,9,10}; //2 dimensions, attention à la virgule entre les accolades int nombres[][] = { {0,2},{1,3} }; int nombresAutreExemple[][] = { {0,2,4}, {1,3,9}, {1,3,5} }; System.out.println(nombres[1][1]); //2 dimensions sans préciser le contenu int[][] multi = new int[5][10]; //une Arraylist qui accepte n'importe quoi ArrayList tableau2 = new ArrayList(); //une Arraylist qui n'accepte que des objets étudiants ArrayList<Etudiant> listeEtudiants = new ArrayList<Etudiant> (); //Initialiser l'arrayList et lui donner des valeurs en même temps //Soluce 1, notez que Arrays.asList est contenu dans newArrayList() : sinon on va rencontrer UnsupportedOperationException en essayant d'ajouter/supprimer des éléments. List<String> maBelleArrayList = new ArrayList(Arrays.asList("Ma belle valeur", "Autre valeur", "Encore une autre")); //Soluce 2 (Java 8) List<String> maBelleArrayList = Stream.of("Ma belle valeur", "Autre valeur", "Encore une autre").collect(Collectors.toList());


Il existe aussi depuis java 5 une méthode particulière permettant de parcourir les tableaux for(type_du_tableau valeur_de_la_case_du_tableau : nom_dutableau) :


String tab[] = {"a", "b", "c", "d", "e"}; for(String valeur : tab){ System.out.print(valeur); } //Affiche abcde


Avec un tableau d'entier :

int tab2[] = {1, 2, 3, 4, 5}; for(int valeur : tab2){nouvelleEspece System.out.print(valeur); } //Affiche 12345



Opérations sur les bits



Opérateurs bits à bits : utilisé dans des domaines très spécifiques. Permettent d'effectuer diverses comparaisons sur 2 le format binaire de 2 nombres et de renvoyer un résultat.

& (si les bits comparés valent 1 tout les 2, 1 est renvoyé).
| (si un des bits comparés vaut 1, 1 est renvoyé).
^ (si les bits sont différents, 1 est renvoyé).

Dans l'exemple ci-dessous on compare 15 (1111 en binaire) et 10 (1010 en binaire).

Exemple :

System.out.println("On renvoie le résultat de la comparaison bit à bit & (si les 2 bits valent 1->1) de "+a+" et "+b+", càd :"); System.out.println(a+" en binaire : "+Integer.toString(a,2)); System.out.println(b+" en binaire : "+Integer.toString(b,2)); System.out.println("Résultat en binaire : "+Integer.toString(a&b,2)+", résultat en décimal :"+(a&b)); System.out.println("-----------------------------------------------------------------------------------"); System.out.println("On renvoie le résultat de la comparaison en OR inclusif | (si un des 2 bits est à 1->1) de "+a+" et "+b+", càd :"); System.out.println(a+" en binaire : "+Integer.toString(a,2)); System.out.println(b+" en binaire : "+Integer.toString(b,2)); System.out.println("Résultat en bits : "+Integer.toString(a|b,2)+", résultat en décimal :"+(a|b)); System.out.println("-----------------------------------------------------------------------------------"); System.out.println("On renvoie le résultat de la comparaison en OR exclusif ^ (2 bits différents->1) de "+a+" et "+b+", càd :"); System.out.println(a+" en binaire : "+Integer.toString(a,2)); System.out.println(b+" en binaire : "+Integer.toString(b,2)); System.out.println("Résultat en bits : "+Integer.toString(a^b,2)+", résultat en décimal :"+(a^b));



On renvoie le résultat de la comparaison bit à bit & (si les 2 bits valent 1->1) de 15 et 10, càd : 15 en binaire : 1111 10 en binaire : 1010 Résultat en binaire : 1010, résultat en décimal :10 ----------------------------------------------------------------------------------- On renvoie le résultat de la comparaison en OR inclusif | (si un des 2 bits est à 1->1) de 15 et 10, càd : 15 en binaire : 1111 10 en binaire : 1010 Résultat en bits : 1111, résultat en décimal :15 ----------------------------------------------------------------------------------- On renvoie le résultat de la comparaison en OR exclusif ^ (2 bits différents->1) de 15 et 10, càd : 15 en binaire : 1111 10 en binaire : 1010 Résultat en bits : 101, résultat en décimal :5


Décalage de bits : l'opérateur << permet de décaler les bits à droite, >> à gauche. >>> ne prend pas en compte le signe (positif ou négatif).

Exemple :

int a=15,b=0; b=a<<2; System.out.println("c vaut "+b);//renvoie "c vaut 60" car 1111 est devenu 111100 (décalage de 2 bits à gauche)


Complémentation : inverse tous les bits.

int a = 15, b = 0; b = ~a; System.out.println("a vaut " + a+" soit "+Integer.toString(a, 2)+" en binaire"); System.out.println("b vaut " + b+" soit "+Integer.toString(b, 2)+" en binaire");



a vaut 15 soit 1111 en binaire b vaut -16 soit -10000 en binaire -10000


Complément à deux : une méthode pour inverser le signe d'un nombre.

int a = 15, b = 0; b=~a+1; System.out.println("b vaut " + b);



Objets et classes


Pourquoi utiliser des objets et non pas des fonctions ?
On pourrait faire en procédural, un tableau voiture puis une fonction afficher_marque(voiture) au lieu d'une classevoiture puis voiture.afficherMarque(), mais le premier cas pose plusieurs problèmes :

-afficher_marque() est une fonction distincte de l'entité voiture. On a d'abord, par exemple, un tableau voiture avec plusieurs informations puis plus tard cette fonction. En orienté objet, on peut tout regrouper au sein d'une même classe, ce qui permet de voir en un coup d'oeil quelles fonctions sont associé à la voiture. Le code est plus facilement réutilisable, le travail plus facilement divisable (plusieurs développeurs écrivent différents objets avec leurs fonctions).

-dans l'exemple procédural, on pourrait mal déclarer le tableau et se retrouver avec une erreur en appelant afficher_marque. En orienté objet, on peut obliger à déclarer l'objet correctement. L'objet contrôle les éléments qui lui sont propres.

-on peut facilement autoriser ou interdire l'accès à certains objets à des parties du programme (avec public/private). Par exemple, en mettant un attribut d'un objet en private, on peut empêcher de le modifier directement (par exemple, si un objet a un attribut "nom", pour empêcher de faire objet.nom="AAA" à l'extérieur de l'objet). S'il y a besoin de modifier le nom de l'objet, on va plutôt donner des méthodes ("modifierNom" par exemple, qui pourra s'assurer que le nom respecte certaines formes, ou bien vérifier les autorisations de l'utilisateur...). Cette idée de protection des attributs des objets s'appelle l'encapsulation.
En général on a une seule classe par fichier mais on peut écrire plusieurs classes dans un seul fichier à condition qu'il n'y ait 1)qu'une seule classe public 2)que le fichier porte le nom de cette classe.

Une classe décrit une famille d'objets. C'est en quelque sorte le plan d'un objet. D'une classe va naitre des objets qu'on dit "instances" de cette classe.


Il existe des variables d'instances (définissant des propriétés des objets nomobjet.propriete), des variables de classe (propriétés communes à toutes les instances de la classe Classe.propriete), variables locales (qui ne sortiront pas de l'objet, utilisées dans les méthodes par exemple).

En java une classe doit commencer par une majuscule.

Ci-dessous une classe oiseau :

package exosSDZ; public class Oiseau { //ci dessous les "attributs" de l'objet //(en gros, des variables qui seront propres à chaque objet oiseau créés à partir de cette classe) //private pour pas qu'on puisse modifier directement oiseau.chant à l'extérieur de la classe private String chant; private String espece; private int taille; //on pourrait initialiser une taille par défaut ici mais ce n'est pas propre //un attribut statique, qui compte le nombre d'oiseaux //il sera partagé entre tous les oiseaux public static int nbrOiseaux=0; public Oiseau(){ //Le CONSTRUCTEUR. En initialisant l'objet, que va-t-il se passer ? Il ne renvoie rien, même pas Void //il a des attributs par défaut System.out.println("Creation d'un oiseau..."); chant="Cui cui !"; espece="oiseau générique"; taille=15; nbrOiseaux=nbrOiseaux+1; } //sauf si on a saisi des attributs public Oiseau(String saisieEspece, String saisieChant, int saisieTaille){ System.out.println("Creation d'un oiseau..."); chant=saisieChant; espece=saisieEspece; taille=saisieTaille; nbrOiseaux=nbrOiseaux+1; } public void chante(){ System.out.println(chant); } //Ci dessous un ACCESSEUR, une méthode pour ACCEDER à des variables d'instance (getter) //Retourne l'espèce public String donneEspece() { return espece; } //Ci dessous un MUTATEUR, une méthode pour MODIFIER des variables d'instance (setter) //Retourne l'espèce public void changeEspece(String nouvelleEspece) { espece=nouvelleEspece; } }


Qu'on peut appeler de la façon suivante :

package exosSDZ; public class classePrincipaleExosSDZ { public static void main(String[] args) { //ici on instancie un objet Oiseau (avec new), qu'on met dans un identifiant d'objet de type Oiseau. //oiseau1 est une sorte de lien qui permet d'accéder à cet objet Oiseau oiseau1=new Oiseau(); oiseau1.chante(); //oiseau2 est une sorte de lien qui permet d'accéder à cet objet Oiseau oiseau2=new Oiseau("hirondelle","Piouuu ! Piouuu !", 20); oiseau2.chante(); System.out.println(oiseau2.donneEspece()); oiseau2.changeEspece("colombe"); System.out.print(Oiseau.nbrOiseaux); } }


Ce qui affiche :

Creation d'un oiseau... Cui cui ! Creation d'un oiseau... Piouuu ! Piouuu ! hirondelle 2


Avec Eclipse, on peut générer automatiquement les accésseur (getters) et mutateurs (setters) en faisant lic droit dans la classe->source->generate getters and setters.

On peut faire :

package exosSDZ; public class classePrincipaleExosSDZ { public static void main(String[] args) { Oiseau oiseau1=new Oiseau(); oiseau1.chante(); //oiseau2 est une sorte de lien qui permet d'accéder à cet objet Oiseau oiseau2=new Oiseau("hirondelle","Piouuu ! Piouuu !", 20); oiseau2.chante(); oiseau2=oiseau1; //oiseau2 pointe maintenant vers le même objet que oiseau1 (oiseau par défaut). //l'hirondelle est innaccessible } }


This


this est une référence à l'objet en cours d'utilisation. Il est implicite, mais parfois on doit l'utiliser :
-quand un argument a le même nom qu'un attribut et qu'il faut les différencier :


private int taille; private String nomDonne; public MonObjet(int taille, String nomDonne){ this.taille=taille; //this obligatoire pour différencier nom=nomDonne; //this pas obligatoire car l'argument nomDonne et l'attribut nom n'ont pas le même nom. }


-quand on va renvoyer l'objet, il faut faire un return this;


Classe enveloppant les primitifs


Parfois certaines méthodes veulent en entrée des objets et pas des primitifs. Il existe des classes qui servent à "envelopper" le primitif.

Grosso modo elles ressemblent à ça :


public class Boolean { //enveloppé dans une classe.. private boolean valeur; //...on retrouve le type primitf //suivent les accesseurs, des méthodes }


Il existe ce genre de classe pour tous les primitifs :
byte -> Byte
short -> Short
int -> Integer
long -> Long
boolean -> Boolean
char -> Character
float -> Float
double -> Double

Attention, en utilisant un double égal on compare les références.

Si on veut comparer la valeurs d e2 integers il faut utiliser "equals".

Integer a; Integer b; a=7; b=7; System.out.println(a==b); //true, jusqu'à 128 ça marche car Java "prépare" des Integer jusqu'à 128 a=700; b=700; System.out.println(a==b); //false, ça ne marche pas après


Héritage


Une classe peut hériter des propriétés et des méthodes d'une autre. Par exemple, on pourrait avoir une classe animaux avec une propriété taille, puis une classe oiseau déclarée avec public class Oiseau extends Animaux{ . Attention, comme tous les autres éléments extérieurs à Animaux, Oiseau ne peut pas avoir accès au variables/méthodes "private". Pour qu'on autorise des classes héritées à utiliser ces variables, il faut les mettre en "protected".
Protected est considéré comme public dans le paquet et dans l'héritage, sinon c'est privé. C'est à dire que 2 classes dans le même package voient les attributs/méthodes "protected" l'une de l'autre.
On peut mettre private/protected/public sur des attributs, méthodes et classes.


package exosSDZ; public class Animal { protected int taille; protected String mange(){ return "Miam miam."; } }


On ne peut pas hériter de 2 classes en même temps (extends Mamifère, Animaux) mais les classes peuvent s’enchaîner.


On peut initialiser le constructeur de la classe mère en utilisant super().
De façon générale super permet d'appeler la classe mère, pour référencer une méthode de la classe supérieure.

On peut donc trouver ça dans la classe Oiseau :

public String mange(){ return super.mange()+", l'oiseau avait faim."; }


"mange()" est une méthode polymorphe, car elle est employée dans Animal et des Oiseau. Ce n'est pas la même chose qu'une méthode surchargée (deux méthodes du même noms différenciées par leurs arguments), ici, on différencie les méthodes selon l'objet. Dans l'exemple plus haut, c'est la méthode dans Oiseau qui est invoquée (appelant elle-même la méthode dans Animal).

Tous les objets héritent de la classe Object de Java. Ainsi, la méthode toString() ou la méthode equals() existent pour tous les objets. On peut bien sûr les redéfinir (polymorphisme) et faire en sorte que Oiseau.equals compare la race, Animal.equals compare l'espèce... equals(), toString() et hashCode() sont des méthodes souvent redéfinies pour bien correspondre à ce qu'on attend des objets. Il faut souvent redéfinir hashCode pour que notre comparaison personnalisée avec equals fonctionne.

Il est possible de stocker des Oiseau dans des Animal car les Oiseaux sont des animaux : c'est la covariance des variables.
On peut faire des tableaux d'animaux et y mettre des Oiseaux (ou autre animaux).


package exosSDZ; public class classePrincipaleExosSDZ { public static void main(String[] args) { Animal[] tabOiseaux= new Animal[2]; Oiseau oiseau1=new Oiseau(); tabOiseaux[0]=oiseau1; } }


Par contre, si on veut le faire chanter, il faut utiliser le transtypage. On peut mettre un objet Oiseau dans un tableau d'Animaux, mais il faut transtyper l'animal en oiseau pour le faire chanter :

package exosSDZ; public class classePrincipaleExosSDZ { public static void main(String[] args) { Animal[] tabOiseaux= new Animal[2]; Oiseau oiseau1=new Oiseau(); tabOiseaux[0]=oiseau1; ((Oiseau)tabOiseaux[0]).chante(); } }


Même si un objet est stocké dans une variable de type différent, ce sera la méthode de l'objet qui sera appelé. C'est ça qu'on appelle l'override.

Ci-dessous des objets stockés dans un tableau d'animaux :

public class Chien extends Animal { @Override public void pousserUnCri() { System.out.println("Ouaf ouaf"); } }


abstract class Animal { public void pousserUnCri() { System.out.println("Cri par défaut."); } }


public class Main { public static void main(String[] args) { Animal[] maJolieFerme={new Chien(), new Chat(), new Lapin()}; for (Animal animal:maJolieFerme) { animal.pousserUnCri(); //même stockés dans une var type Animal, les animaux pousseront leur propre cri. } } }


Résultat :

Ouaf ouaf Miaou miaou Gnif gnif


L'override est un peu opposition par rapport à une variable ou une méthode statique qui serait "hidden" et dépendente de la variable :

class Caninide { String nom = "canidé"; } public class Chien extends Caninide { String nom = "chien"; public static void main(String[] args) { Chien c = new Chien(); System.out.print(c.nom); //affiche "chien" Caninide c2 = c; System.out.print(c2.nom); //affiche "canidé" MEME OBJET MAIS LE RESULTAT DEPEND DU TYPE DE LA VARIABLE } }



Dans chaque classe, java créé une classe publique "Class" que l'on peut consulter pour trouver les méthodes (String.class.getMethods()), la superclasse String.class.getSuperclass()...

A noter également hashCode qui renvoit un identifiant unique pour un objet selon les éléments de comparaisons mis en place avec equals. System.out.print(oiseau1.hashCode()); renvoie 1808253012.

Un autre mot clef, final qui empêche une méthode d'être redéfinie ou qui empêche un objet d'avoir des enfants.
public final string methode(){

Classe abstraite


On peut faire en sorte qu'une classe ne puisse pas être instanciée (c'est à dire interdire de créer un objet directement à partir de cette classe) avec le mot clef abstract. On parle de "classe abstraite".

Ainsi, pour éviter de donner le droit de faire Animal animal1=new Animal(); il faut définir la classe comme ceci :abstract class Animal {. On pourra toujours avoir des classe qui héritent de Animal, et des objets créés à partir de ces classes-ci.

On peut mettre des méthodes abstraites dans les classes abstraites : abstract void methode();. Des méthodes qu'il faudra nécessairement redéfinir dans les classes enfants : c'est une obligation, on a ainsi l'assurance que les classes enfants pourront réaliser les méthodes abstraites de leur mère.

Redéfinition / Override


C'est quand une classe, qui hérite d'une autre, a la même signature (=nom de la méthod+type des arguments d'entrée) qu'une méthode de la classe mère. On ne peut pas baisser le niveau de visibilité d'une méthode quand elle redéfinie une autre.

Si on met le mot clef final à une méthode, cela empêche sa redéfinition. public final void afficher()
Si on le met à un attribut, cela signifie que c'est une constante.

Ordre de création d'une classe



class Arthropod { static { System.out.println("0"); } //b)au chargement de cette classe, tout de suite on initialize les blocs statiques { System.out.println("3"); } //f)on initilise les blocs d'instance de la classe parente public Arthropod() { System.out.println("4"); //g) on initialise le constructeur de la classe parente } } public class Spider extends Arthropod { //a)Départ ici car c'est là qu'est main. Avant de s'occuper de Spider, il faut initialiser Arthropod static { System.out.println("1"); } //d)on a fini d'initialiser la class athropod, on s'occupe d'initialiser celle-ci { System.out.println("5"); } //h) on initialise le constructeur de la classe enfant public Spider(){ super(); //e)on arrive dans le constructeur, qui nous dit d'aller voir la classe parente System.out.println("6"); //i)Voilà, on a terminé l'inialisation de cette instance et de ses parents } public static void main(String[] args) { Spider a; //c)Ici ce serait un chargement de Spider (et ses classes parente) si on ne les avait pas déjà chargée a = new Spider(); //d)on va maintenant créer une instance } }

Interface


Une interface ressemble à une classe abstraite, mais ses attributs seront forcément statiques+finaux et (jusqu'à java 8) n'avait pas d'implémentation de méthodes (que des méthodes abstraites).

On dit parfois qu'une interface est un contrat, car utiliser cette interface oblige à implémenter les méthodes listées par l'interface. Par contre, elle ne force pas à utiliser des attributs. Si on met un attribut dans une interface, celui-ci devient une constante pour toutes les classes l'utilisant.
On peut y mettre des méthodes statiques.

C'est très utile pour le travail en équipe, où une personne va écrire une interface avec des méthodes et où une seconde personne devra réaliser une classe en incluant obligatoirement ces méthodes.


Une classe peut avoir plusieurs interfaces : public class ClasseA extends ClasseB implements interface1,interface2 {

Exemple avec une classe perroquet liée avec l'interface ComportementPerroquet :

package exosSDZ; public interface ComportementPerroquet { void parle(); }



package exosSDZ; public class Perroquet extends Oiseau implements ComportementPerroquet { //obligatoire d'avoir cette méthode car elle est dans l'interface public void parle(){ System.out.print("Coco veut un gateau"); } public Perroquet(){ super(); } public Perroquet(String saisieEspece, String saisieChant, int saisieTaille){ super(saisieEspece,saisieChant,saisieTaille); } }



package exosSDZ; public class classePrincipaleExosSDZ { public static void main(String[] args) { Perroquet ara1=new Perroquet("Ara", "Kaww kaww",15); ara1.chante(); ara1.parle(); } }


Si on met un attribut dans une interface, il devient une constante partagée par toutes les instances des objets utilisant cette interface.


Depuis java 8, on peut mettre des méthodes dans les interfaces.

Une interface peut aussi être utilisé dans les fonctions.

On peut ainsi avoir une classe mamifère avec une interface animal public class Mamifere extends Animal.

Puis : predateur.manger(Animal)

C'est très pratique, cela donne des sortes de "types" à nos objets.

On pourra également mettre le mamifere dans un objet animal : Animal a=new Mamifere();

Depuis Java 8, les interfaces peuvent fournir des méthodes par défaut, qui seront utilisables directement par la classe qui implémente l'interface :

public interface Grogneur { default public void grogner(){ System.out.println("grr"); } }


Si une classe hérite de deux interfaces, comportant chacune une méthode du même nom, la classe ne saura pas quelle implémentation par défaut utiliser : il faudra donc redéfinir la méthode dans la classe.

super.maMethode() ne fonctionnera pas car il ne saura pas quelle méthode utiliser (les deux interfaces implémentés ont toutes les deux une méthode de ce nom). Dans la classe fille, il faudra faire Grogneur.super.grogner(); pour indiquer qu'on veut utiliser l'implémentation de la classe Grogneur.

Exposer des fonctions


Le nom "interface" vient du fait qu'elle sert originellement à exposer des fonctions. C'est un point d'entrée... une interface.

Exemple :

public interface MaBelleInterface { void maMethodeQuOnVeutExposer(); }



public class MaBelleClasse implements MaBelleInterface { public void maMethodeQuOnVeutExposer(){ System.out.println("coucou"); } public void autreMethodePasDocumentee(){ System.out.println("blah"); } }



Lorsqu'on va déclarer une variable, on utilisera le type "MaBelleInterface".
On fera donc MaBelleInterface mbi=new MaBelleClasse() et on ne pourra accéder qu'à la méthode "maMethodeQuOnVeutExposer" (ou alors, il faudra caster).




Exceptions



Ce sont des objets de la classe Exception, spécialisés dans le traitement des erreurs. Soit ce sera la JVM qui va générer automatiquement ces objets, soit c'est le développeur qui va générer un objet de type exception.

La clase Exception hérite de Throwable. Il y a différentes sous-classes d'Exception comme InterruptedException, ArithmeticException, NullPointerException... (diagramme)



Il faudra l'attraper avec un "catch" pour faire des traitements personnalisés, sinon, le programme s'arrêtera. On peut catch une Exception, une ArithmeticException...
Si on catch "Exception", on attrapera toutes les exceptions possibles. Si on essaie d'attraper une sous exception spécifique A, on peut oublier d'attraper l'exception B, ce qui fera planter le programme si B est levée. Il est donc plus sécurisant d'attraper "Exception", mais on ne sait pas trop ce qu'on attrape.


try { //instruction qu'on essaye d'exécuter } catch (ArithmeticException e) { //erreur sur laquelle on veut réagir (e contient le code de l'erreur) System.out.println(e); //on affiche le code de l'erreur } finally{ //action effectuée dans tous les cas } //le programme continue


Il est obligatoire de catcher certaines exceptions ("checked exceptions") : le code refusera de compiler si on a pas mis de bloc try/catch.
Les RunTimeException (et ses classes hérités), elles, ne sont pas détectées à la compilation. Elles n'ont pas besoin d'être dans un bloc try catch obligatoirement ("unchecked exception").

Par exemple, une NullPointerException (variable absente) peut se produire à peu près n'importe où dans le code. Ca ne serait pas pratique d'être obligé de mettre un try/catch partout. C'est donc une RunTimeException.
Par contre, un bout de code qui va lire un fichier a de grande risques de provoquer une erreur (fichier innaccessible, pas de permission, etc) et ce n'est pas très courant. Il a donc été décidé que "bufferedReader.readLine()" lèverait une checked exception.



On peut également propager l'exception d'un bout de code à l'autre avec "Throws". Ci-dessous la classe main appelle une méthode saisiId qui propage une exception (throws Exception) :


package testExceptions; import java.util.Scanner; public class ClassePrincipale { static int b=0; public static void main(String[] args) { int id; try { id=saisiId(); } catch (Exception e) { // TODO Auto-generated catch block System.out.println("Erreur : merci d'écrire un nombre."); } } public static int saisiId() throws Exception{ //on va propager l'exception générée par la JVM Scanner s=new Scanner(System.in); int id=s.nextInt(); return id; }


Une runtime exception remonte toute seule comme une grande, il n'y a même pas besoin de la throw.

Une bonne pratique consiste à propager ses exceptions jusqu'au plus haut niveau possible et de toutes les traiter au même endroit. On peut mettre plusieurs catch à la suite pour attraper différentes exceptions.
Exemple :


/* Appelle le menu et récupère les erreurs. * Se relance lui-même en cas d'exception. */ public void lancementMenu(){ try{ this.menu(); } catch (StackOverflowError e){ System.out.println(Message.ErreurDepassementPile); this.lancementMenu(); } catch (NullPointerException e){ System.out.println(Message.ErreurObjetInexistant); this.lancementMenu(); } catch (PersistenceException e){ System.out.println(Message.ErreurDeConnectionBase); this.lancementMenu(); } catch (NumberFormatException IME){ //un parseInt qui s'est mal passé System.out.println(Message.ErreurMerciEntrerChiffre); this.lancementMenu(); } catch(Exception e){ System.out.println("*ERREUR NON PRISE EN CHARGE*"); e.printStackTrace(); this.lancementMenu(); //System.out.println("J'ai attrapé une exception !"); } }



On peut générer nous même des exceptions. Par exemple, si on demandeà l'utilisateur de saisir un nombre entre 1 et 10 mais qu'il saisi 11, la JVM ne pourra pas détecter qu'il y a une Exception, c'est à nous de la lever en écrivant throw new Exception(); :

public static void saisiIntervale() throws Exception{ //on va propager l'exception Scanner s=new Scanner(System.in); int id=s.nextInt(); if(id<0||id>10){ throw new Exception("lol"); //créer une nouvelle exception } }



Bien penser à : utiliser des exceptions pour la gestion des erreurs (sauf si ce sont des erreurs métiers, par exemple retirer de l'argent à un compte qui est à 0 ne pose pas de problème au point de vu informatique mais pose problème au niveau d'une banque donc erreur métier), attraper un maximum d'exception pour éviter les crashs, centraliser la gestions des exceptions avec des throws qui les remontent, réfléchir à la cause dans le catch (attraper Exception ou une sous-exception ?).

On peut également créer des classes d'exceptions personnalisées qu'on va réutiliser d'un programme à l'autre.


package testExceptions; public class ExceptionPerso extends Exception{ @Override public String getMessage(){ return super.getMessage()+" mon message perso"; } }


Enumération



Une énumération est une sorte de classe où on construit directement les objets. Elle doivent être dans leur propre fichier, comme une classe ou une interface. A utiliser lorsqu'on a besoin d'énumérer une petite liste de constante (à la différence d'un tableau, on ne risque pas de les modifier).

Un exemple simple (printemps, ete automne et hiver sont donc des objets, instances de :

package exosSDZ; public enum Saison { printemps, ete, automne, hiver; }


Puis, pour l'utiliser :

package exosSDZ; public class classePrincipaleExosSDZ { public static void main(String[] args) { System.out.print(Saison.automne); } }


On peut aussi mettre des attributs, des méthodes et des constructeurs dans les enums. Ci-dessous un exemple un peu plus complexe pour gérer le texte des erreurs dans une seule classe :

package traitement; public enum Message { ErreurDeConnectionBase ("*ERREUR :* Problème de connexion à la base de donnée."), //ici l'Enum se construit toute seule ErreurEntrezChoix ("*ERREUR :* Merci d'entrer un chiffre correspondant à un des choix proposés."), ErreurAucuneBanque ("*ERREUR :* Il n'existe aucune banque dans la base de donnée."), ErreurMerciEntrerChiffre ("*ERREUR :* Merci d'entrer un chiffre."), ErreurBanqueIntrouvable ("*ERREUR :* Cette banque est introuvable."); private String texte = ""; //on en a besoin pour stocker les Strings //Constructeur Message(String texte){ this.texte = texte; } public String toString(){ return texte; } }


On l'appelle :

package traitement; public class classePrincipaleExosSDZ { public static void main(String[] args) { try{ Interface.saisie(); } catch (NumberFormatException IME){ //un parseInt qui s'est mal passé System.out.println(Message.ErreurMerciEntrerChiffre); this.lancementMenu(); } } }


Ce qui affiche "*ERREUR :* Merci d'entrer un chiffre correspondant à un des choix proposés." en résultat.

Chaque élément de l'enum est une variable statique vers un objet instancié.

Par exemple, la ligne deviendra ErreurDeConnectionBase ("*ERREUR :* Problème de connexion à la base de donnée.") équivaut à public static final Message ErreurDeConnectionBase = new Message("*ERREUR :* Problème de connexion à la base de donnée.");.


Comme une classe, on peut instancier une Enum :

public enum Animal { Vache("Meuh","Herbivore"), Chat("Miaou","Carnivore"); private String cri; private String regime; public void crier() { System.out.println(cri); } Animal(String cri, String regime) { this.cri = cri; this.regime = regime; } }


Instanciation :

Animal a=Animal.Chat; a.crier();


Finalement, c'est juste une classe avec des constructeurs pré-remplis :-)

A noter qu'on peut lancer des instructions à l'initialisation d'une enum avec le bloc static {} :

public enum MonEnum { PREMIER_CODE (123, "Echec de l'import du volume standard : un ou plusieurs paramètres sont incorrects."), DEUXIEME_CODE (456, "Echec de l'import du volume standard : la requête n'est pas autorisée."); private String texte; private Integer code; private static Map<Integer, MonEnum> MAP_DE_MON_ENUM = new HashMap<Integer, MonEnum>(); //on créé une hashmap avec numéro<->objet static { for (MonEnum monEnum : MonEnum.values()) { MAP_DE_MON_ENUM.put(monEnum.numero, monEnum); } } //constructeurs, to string, et surtout getter dans la hashmap pour que "456" renvoie DEUXIEME_CODE


L'ordre de déclaration des Enums est important : les premières prioritaires sur les suivantes. On peut les comparer entre-elles avec compareTo (laquelle est supérieur à quelle autre).

Pour tester l'égalité entre enum, on peut utiliser un double égal ou equals (mais ce dernier peut faire un nullpointer si on fait maVarNulle.equals(autreVar) et en plus le double égal a l'avantage de vérifier qu'on compare les mêmes enums lors de la compilation).

Bonne refs sur les enums :
http://www.kdgregory.com/index.php?page=java.enum
http://blog.xebia.fr/2008/07/23/enumerations-utilisation-avancee/
http://www.jmdoudoux.fr/java/dej/chap-jdk1.5.htm#jdk1.5-8


Méthodes


Equivalent aux "fonctions" dans les autres langages.
Exemple avec une méthode accessible à tous (publique) qui ne retourne rien (donc void) pour afficher le contenu d'un tableau, appelée 3 fois :

package exosSDZ; public class classePrincipaleExosSDZ { public static void main(String[] args) { int tab1[]={1,2,3,4}; afficherContenu(tab1); } public static void afficherContenu(int tab[]){ for(int contenuCase:tab){ System.out.print(contenuCase); } } }


Il est possible d'avoir 2 méthodes du même nom pour traiter différents types, par exemple ici on peut traiter des tableau d'entier et de String (on appelle ça "surcharger une méthode") :


package exosSDZ; public class classePrincipaleExosSDZ { public static void main(String[] args) { int tab1[]={1,2,3,4}; String tab2[]={"aa","bb"}; afficherContenu(tab1); afficherContenu(tab2); } public static void afficherContenu(int tab[]){ for(int contenuCase:tab){ System.out.print(contenuCase); } } public static void afficherContenu(String tab[]){ for(String contenuCase:tab){ System.out.print(contenuCase); } } }



On parle de surcharge quand il y a 2 méthodes de noms identiques dans la même classe avec des paramètres différents. On peut surcharger les constructeurs ou les méthodes.

Attention à ne pas confondre surcharge et redéfinition.

On peut aussi utiliser des ellipses ("variadic") si on ne sait pas combien d'arguments on va envoyer :

public static void additionner(String maBelleString, int... tabEntiers) { int resultat=0; for (int chiffre:tabEntiers) { resultat=resultat+chiffre; } System.out.println(resultat); }

Passage d'argument



Certains langages comme C# ou php permettent d'avoir des mots clefs (ref et &) pour passer par référence.

Exemple en php :

<?php $nombre = 1; essayerDeModifier($nombre); echo $nombre; // affiche 2, la modification a marché function essayerDeModifier(&$nombreParam){ //notez le & $nombreParam=$nombreParam*2; //à cause du &, si on modifie $nombreParam on modifiera aussi $nombre } ?>


Du coup, est-ce que Java passe par référence ou par valeur ? Java passe par valeur : ce sont des nouvelles variables contenant une copie de valeur qui sont passés en argument. Dans certaines de ces copies de variables on a des emplacements mémoire d'objets, et cet objet là sera visé par les modification sur ses attributs. Mais on peut très bien vider la copie sans que ça influence la variable originale.

Exemple en php et un passage par référence :

<?php $chien = new Animal(); $chien->nom = "Medor"; essayerDeModifier($chien); //peut-on modifier, dans une méthode, vers où pointe la variable $chien ? echo $chien->nom; //quand on passe par référence, oui ! Affichera "anonyme" function essayerDeModifier(&$animalParam){ //notez le & $animalParam = new Animal(); //tiens on décide qu'il y aura un nouvel animal, on lui donne pas de nom } class Animal { var $nom="anonyme"; } ?>


Le même exemple en Java :

public class Main{ public static void main (String [] args) { Animal animal = new Animal(); animal.nom= "Medor"; essayerDeModifier(animal); //Peut-on modifier, dans une méthode, vers où pointe cette variable ? System.out.println (animal.nom); //On ne peut pas ! (affiche Medor) } private static void essayerDeModifier(Animal animalParam) { //création d'une nouvelle var pointant vers l'emplacement mémoire précédent animalParam = new Animal(); //tiens on décide de faire un nouvel animal } //quand la méthode se termine, le nouvel animal n'est plus pointé par rien, il sera effacé par le ramasse miette } class Animal{ public String nom = "anonyme"; }


Ci-dessous divers exemple.

Quand une méthode prend en paramètre un objet, c'est une copie contenant la valeur d'un emplacement mémoire donc on peut modifier des attributs de cet emplacement :

public class Main{ public static void main (String [] args) { Animal animal = new Animal(); //une nouvelle variable qui pointe vers un emplacement mémoire animal.nom= "Medor"; modifierNom(animal); //On va tenter de modifier le nom de l'animal dans une méthode... System.out.println (animal.nom); //Ca fonctionne ! (affiche Fido) } private static void modifierNom(Animal animalParam) { //création d'une nouvelle var vers l'emplacement mémoire précédent animalParam.nom = "Fido"; //on va chercher l'objet dans l'emplacement mémoire et on modifie son nom } } class Animal{ public String nom; }


Par contre comme déjà vu on ne peut pas modifier ce vers quoi pointe la variable originale :

public class Main{ public static void main (String [] args) { Animal animal = new Animal(); //une nouvelle variable qui pointe vers un emplacement mémoire animal.nom= "Medor"; essayerDeModifier(animal); //Peut-on, dans une méthode, modifier vers où pointe cette variable ? System.out.println (animal.nom); //On ne peut pas ! (affiche Medor) } private static void essayerDeModifier(Animal animalParam) { //création d'une nouvelle var pointant vers l'emplacement mémoire précédent animalParam = new Animal(); //ah, animalParam pointe maintenant vers un nouvel emplacement mémoire animalParam.nom = "Fido"; //on va chercher l'objet dans l'emplacement mémoire et on modifie son nom } //quand la méthode se termine, l'animal appelé "Fido" n'est plus pointé par rien, il sera effacé par le ramasse miette } class Animal{ public String nom; }


Si vous avez un objet immutable comme un Integer ou un String, vous n'avez pas le choix que de créer un nouvel objet bien sûr.


Les primitifs sont clairement passés par copie (attention aux tableaux de primitifs voir plus bas) :

public class Main{ public static void main (String [] args) { int nombre = 1; essayerDeModifier(nombre); //On va tenter de modifier le nombre... System.out.println (nombre); //...la modif ne marchera pas. 1 sera affiché. } private static void essayerDeModifier(int nombreParam) { //creation d'une nouvelle var nombreParam nombreParam++; //on peut l'incrémenter... nombreParam = 9; //...ou bien lui changer sa valeur, peu importe car c'est une copie } }


*ATTENTION*, Les tableaux de primitif sont traités comme des objets (il existe en effet une classe int[].class par exemple) :

public class Main{ public static void main (String [] args) { int[] monTableau={1}; //creation d'une var monTableau qui pointe vers un nouvel objet dans un nouvel emplacement mémoire essayerDeModifier(monTableau); System.out.println (monTableau[0]); //affichera 99 (et pas 1) } private static void essayerDeModifier(int[] monTableauParam) { //creation d'une var monTableauParam qui pointe vers l'emplacement mémoire sus-cité monTableauParam[0] = 99; //on modifie le premier emplacement de l'objet } }


Autre exemple où cette fois-ci on ne modifie pas l'objet de départ mais un nouveau :

public class Main{ public static void main (String [] args) { int[] monTableau={1}; //creation d'une var monTableau qui pointe vers un nouvel objet dans un nouvel emplacement mémoire essayerDeModifier(monTableau); System.out.println (monTableau[0]); //affichera 1 (car l'objet n'a pas été modifié) } private static void essayerDeModifier(int[] monTableauParam) { //creation d'une var monTableauParam qui pointe vers l'emplacement mémoire sus-cité monTableauParam = new int[1]; //on change d'avis ! on fait pointer monTableauParam vers un nouveau tableau vers un nouvel emplacement mémoire monTableauParam[0] = 99; //on modifie le premier emplacement de ce nouvel objet } }



Package



En java les class, interfaces et enum sont regroupées dans des packages, qui sont des sortes de dossiers, nommés en minuscule. Un package facilite la réutilisation de plusieurs classes sur d'autres programmes.
A noter que si on utilise pas le mot clef public (ni private, ni protected, ce qui revient à utiliser default), les variables et les méthodes ne sont visibles qu'à l'intérieur du package.

C'est assez utile pour cibler des choses, par exemple on peut demander à une librairie de n'afficher des logs/de faire des analyses/de ne considérer que les classes dans com.machin.truc mais com.machin.bidule.

Et puis ça permet d'avoir des classes de même nom dans l'appli, vu qu'elles sont dans des packages différents.

Imports


"Import" permet de récupérer des classes venant d'autres packages. import mon.package.maClasse;

Import static permet d'utiliser des fonctions sans devoir taper à chaque fois le nom de la classe :

import static java.lang.Math.*; //..plus bas dans le code... System.out.println(PI); //au lieu de System.out.println(Math.PI);


Collection d'objets


On peut mettre les objets dans des tableaux (à condition de respecter le type d'objet) ou dans des structures spécialement adaptées pour ça qui sont plus lentes mais plus souple. Par exemple, un ArrayList peut être redimensionné après sa création (ce qui prend beaucoup de mémoire) et permet de stocker n'importe quel type d'objet.

On peut utiliser une boucle ordinaire avec i puis monArrayList.get(i)

Pour remplacer un élément par un autre dans un ArrayList: MonArrayList.set(MonArrayList.indexOf(employe1),employe2);

On peut aussi utiliser un itérateur pour parcourir les collections qui sont des :

package exosSDZ; import java.util.ArrayList; import java.util.ListIterator; public class classePrincipaleExosSDZ { public static void main(String[] args) { ArrayList demoArrayList = new ArrayList(); demoArrayList.add(356); demoArrayList.add("Chaîne de caractère."); //ITERATEUR ListIterator interateur = demoArrayList.listIterator(); while(interateur.hasNext()){ System.out.println(interateur.next()); } } }


Autre exemple :

Iterator<Integer> monIterateur=maBelleListe.iterator(); while(monIterateur.hasNext()) { System.out.println("-> "+monIterateur.next()); } System.out.println("Entrez maintenant le chiffre à supprimer :"); saisie=s.nextInt(); monIterateur=maBelleListe.iterator(); while(monIterateur.hasNext()) { if(saisie==monIterateur.next()) { monIterateur.remove(); } }


Depuis java 5, on peut utiliser une boucle de type "for each" qui revient au même que d'utiliser un itérateur :

package exosSDZ; import java.util.ArrayList; import java.util.ListIterator; public class classePrincipaleExosSDZ { public static void main(String[] args) { ArrayList demoArrayList = new ArrayList(); demoArrayList.add(356); demoArrayList.add("Chaîne de caractère."); for(Object valeur : demoArrayList){ System.out.println(valeur); } }


Il est beaucoup plus rapide d'utiliser un "for each" comme ci-dessus que de chercher ce que contient tableau[1], puis tableau[2]... avec une boucle avec un "i", mais par contre c'est moins polyvalent, par exemple on ne sait pas la place dans la liste de l'élément qu'on modifie.

Outre l'ArrayList, il existe aussi les LinkedList organisées de façon différentes : chaque élément de la LinkedList pointe vers l'élément suivant.Ainsi, il est plus lent d'ajouter un élément vers le milieu d'une LinkedList que pour une ArrayList, mais par contre il sera plus rapide d'ajouter des éléments au début ou à la fin d'une LinkedList.

Si on stocke toujours le même type de donnée et qu'on a pas besoin de redimensionner le tableau après sa déclaration (on peut faire une déclaration avec une variable : tableau[var]), privilégier les tableaux pour des raisons de performance.

Il existe également des Set, ne stockant qu'une fois un objet dans toute la collection (HashSet demoArrayList = new HashSet();)

Enfin, pour les tableaux associatif (clef renvoie valeur), il existe les map (Hashtable et HashMap).


package exosSDZ; import java.util.Hashtable; public class classePrincipaleExosSDZ { public static void main(String[] args) { Hashtable demoHashTable = new Hashtable(); demoHashTable.put("taille", 45); demoHashTable.put("EffetsVisuels",true); demoHashTable.put("PageDemarrage", "http://www.site.com"); System.out.println(demoHashTable.get("taille")); System.out.println(demoHashTable.get("EffetsVisuels")); System.out.println(demoHashTable.get("PageDemarrage")); } }


Lambdas


Nouvelle façon d'écrire une classe anonyme. A la base, on a une "interface fonctionnel", c'est à dire une interface qui contient une "SAM" (single abstract method), une seule méthode abstraite. Ensuite, on peut écrire des impleméntations de cette interface avec des lambdas :


package lambda1; interface MaBelleInterfaceFonctionelle { // Interface Fonctionelle int traiteChiffre(Integer i); // Single Abstract Method, on sait pas trop ce qu'elle fait pour le moment } public class TestLambda { public static void main(String[] args) { MaBelleInterfaceFonctionelle m = monChiffre -> monChiffre*2; // implémentation qui mutiplie le chiffre donné par 2 System.out.println(m.traiteChiffre(4)); // affiche 8 MaBelleInterfaceFonctionelle m2 = monChiffre -> monChiffre+100; // implémentation qui rajoute 100 au chiffre donné System.out.println(m2.traiteChiffre(4)); // affiche 104 } }



Exemple :


interface MaBelleInterfaceAnimale { // interface dite "fonctionnelle" : "qui ne contient qu'une seule méthode abstraite". String direBonjour(String utilisateur); } public static void main(String args[]) { // Autrefois, si on voulait implémenter vite fait une interface on utilisait des classes anonymes comme ceci MaBelleInterfaceAnimale humain = new MaBelleInterfaceAnimale() { @Override public String direBonjour(String utilisateur) { return "Bonjour " + utilisateur; } }; // Maintenant, à condition que l'interface n'ai qu'une seule méthode abstraite, on peut simplifier. // Ci-dessous le compilateur sait qu'on veut implémenter la fonction "direBonjour" (car c'est la seule de l'interface). // Il sait déjà quels sont les types en entrées/sorties car définies dans l'interface. MaBelleInterfaceAnimale chien = (utilisateur) -> { return "ouaf"; }; //On peut simplifier encore plus. Parenthèses, accolades et valeur de //retour sont facultatives tant que le compilateur peut les déduire. MaBelleInterfaceAnimale chat = utilisateur -> "miaou"; System.out.println(humain.direBonjour("Billy")); //"bonjour Billy" System.out.println(chien.direBonjour("Billy")); //"ouaf" System.out.println(chat.direBonjour("Billy")); //"miaou" }


C'est pratique quand par exemple on veut appeler une fonction sur chaque élément d'une liste, d'une façon qui rappelle fortement les fonctions fléchées javascript :

maBelleListeDElements.forEach( monElement -> monService.maMethode(monElement) );


Comme ce genre de fonction appelé sur chaque élément est souvent utilisée, il existe la notation raccourcie maBelleListeDElements.forEach(monService::maMethode); et on peut appeler des méthodes statiques de la même façon MonService::maMethodeStatique et même un constructeur MonService::new ! Tout cela s'appelle "référence de méthode".

Et si on a besoin de plusieurs ligne, utiliser des {} :

maBelleListeDElements.forEach( monElement -> { logger.info("appel"); monService.maMethode(monElement); } );


Enfin, si on a pas besoin de paramètre d'entrée, utiliser (), () -> logger.info("coucou en passant"); et si on a plusieurs param, on les mets entre parenthèses (a, b) -> logger.info(a+b);.

Dans les exemples de lambdas ci-dessus forEach ne renvoie rien. C'est utile pour parcourir une collection et faire un truc dessus mais on ne peut pas la transformer, ni la réutiliser.

Les 4 cas où il est possible d'utiliser des références de méthode :

interface MaBelleInterfacePourString { String traiteString(String s); } public class TestLambda2 { public static void main(String[] args) { // CAS 1 METHODE STATIQUE, on passe le param envoyé dans methode statique MaBelleInterfacePourString maBelleInterfacePourStringImpl = String::valueOf; //Equivalent de s -> s.valueOf(s) System.out.println(maBelleInterfacePourStringImpl.traiteString(null)); //renvoie "null" // CAS 2 METHODE D'INSTANCE, on passe le param dans une méthode de l'instance "hey" MaBelleInterfacePourString maBelleInterfacePourStringImpl2 = "hey"::concat; // Equivalent de s -> "hey".concat(s); System.out.println(maBelleInterfacePourStringImpl2.traiteString("lol")); //renvoie "heylol" // CAS 3 METHODE D'INSTANCE DU PARAM, méthode d'instance appartenant au propre paramètre qui est envoyé MaBelleInterfacePourString maBelleInterfacePourStringImpl3 = String::trim; // Equivalent de s -> s.trim(); System.out.println(maBelleInterfacePourStringImpl3.traiteString(" coucou ")); //renvoie "coucou" // CAS 4 CONSTRUCTEUR MaBelleInterfacePourString maBelleInterfacePourStringImpl4 = String::new; // Equivalent de s -> new String(s); System.out.println(maBelleInterfacePourStringImpl4.traiteString("yeah")); //renvoie "yeah" } }


Un exemple avec 2 paramètres :

package lambda1; interface MaBelleInterfacePourString { boolean traiteString(String s, String test); } public class TestLambda2 { public static void main(String[] args) { // CAS 2 METHODE D'INSTANCE, cette fois ci avec 2 paramètres MaBelleInterfacePourString maBelleInterfacePourStringImpl = String::startsWith; // Equivalent de (s, p) -> s.startsWith(p); System.out.println(maBelleInterfacePourStringImpl.traiteString("toto", "to")); // true System.out.println(maBelleInterfacePourStringImpl.traiteString("toto", "ta")); // false } }



API Stream


Depuis Java 8 il existe l'API Stream, qui change un peu la façon d'écrire du Java car elle travaille sur des "flux". Par exemple, on peut créer un flux de données infini comme ceci : Stream.generate(Math::random); Mais le stream est lazy loading, feignant, et il ne se passera rien tant qu'on aura pas expliqué ce qu'on veut qu'il se passe à la fin du stream.
En effet il existe des opérations intermédiaires (map, flatMap, filter...) sur les Stream et des opérations finales (count, collect, reduce...).


Stream //la classe Stream .generate(Math::random) //génère des données à l'infini avec la fonction Math::random .limit(5) //une opération intermédiaire : on limit à 5 élément .forEach(System.out::println); //une opération finale, on affiche chaque résultat




Map permet de convertir un objet en un autre / d'appeler une fonction, par exemple, mise en majuscule :

public static void main(String[] args) { List<String> mesBellesLettresMinuscules = new ArrayList<>(Arrays.asList("a", "b","c")); List<String> mesBellesLettresMajuscules = mesBellesLettresMinuscules.stream() //on parcourt la liste .map(String::toUpperCase) //on met en majuscule avec la méthode statique toUpperCase //.map(a -> a.toUpperCase()) //autre façon qu'on aurait pu utiliser pour mettre en majuscule .collect(Collectors.toList()); //on récupère sous forme de liste System.out.println(mesBellesLettresMajuscules); }


Comparaison sans/avec API stream pour afficher "[A, B, C]" :

List<String> mesBellesLettresMinuscules = Arrays.asList("a", "b", "c"); //sans API stream, on est forcé de déclarer une seconde liste List<String> mesBellesLettresMaj1 = new ArrayList(); mesBellesLettresMinuscules.forEach(t -> mesBellesLettresMaj1.add(t.toUpperCase())); System.out.println(mesBellesLettresMaj1 ); //avec l'API stream, une seule ligne, pas de déclaration System.out.println(mesBellesLettresMinuscules.stream().map(String::toUpperCase).collect(Collectors.toList()));


Plus on fait de transformation sur l'objet, plus l'api stream est pratique par contre ça devient rapidement illisible.

Récupération d'un attribut d'un objet et mise dans une liste :

List<MonDTO> maListeDeDTOs = monService.renvoyerUnelistedeDTOs; List<LesIdentifiantsDeMesDTOS> orderGroupDTOs.stream().map(unDTO -> unDTO.getIdentifiant()).collect(Collectors.toList());


Autre exemple :

List<Fiches> fiches = Arrays.asList( new Fiche(), new Fiche() ); //la méthode ci-dessous a besoin de FicheDTO, pas de fiches... on peut les convertir directement avec les streams monBeauService.analyserLesFichesDTO( fiches.stream().map(monMapper.INSTANCE::entityToDto).collect(Collectors.toList()) );


FlatMap est souvent utilisé pour combiner 2 listes :

List<String> mesBellesVoyellesMinuscules = new ArrayList<>(Arrays.asList("a", "e","i", "o", "u")); List<String> mesBellesconsonnesMinuscules = new ArrayList<>(Arrays.asList("b", "c", "d", "f")); List<List<String>> maListeDeListe = new ArrayList<>(); maListeDeListe.add(mesBellesVoyellesMinuscules); maListeDeListe.add(mesBellesconsonnesMinuscules); List<String> maBelleListeCombinee = maListeDeListe.stream() .flatMap(Collection::stream) .map(a -> a.toUpperCase()) .collect(Collectors.toList()); System.out.println(maBelleListeCombinee); //renvoie [A, E, I, O, U, B, C, D, F]


Autre exemple, un programme qui renvoie les utilisateurs d'une salle :

public static void main(String[] args) { //on veut les utilisateurs des salles 101 et 103 List<Integer> mesNumerosDeSalleSelectionnes = new ArrayList<>(Arrays.asList(101, 103)); List<String> utilisateurs = mesNumerosDeSalleSelectionnes.stream() //la fonction appelée dans flatMap doit impérativement retourner un objet de classe "Stream" .flatMap(numero -> recupererUtilisateursByNumeroDeSalle(numero)) .collect(Collectors.toList()); System.out.println(utilisateurs); //renvoie [Bob, Paul, Billy, Marty, Genn] } static Stream<String> recupererUtilisateursByNumeroDeSalle(Integer numero){ switch(numero){ case 101: return new ArrayList<>(Arrays.asList("Bob", "Paul", "Billy")).stream(); case 102: return new ArrayList<>(Arrays.asList("Josie", "Lucie", "Marie")).stream(); case 103: return new ArrayList<>(Arrays.asList("Marty","Genn")).stream(); default: return new ArrayList<String>().stream(); } }


Ci-dessus les utilisateurs sont combinés dans une seule liste alors qu'on a donné une liste de salle en entrée. Mais on aurait pu faire en sorte que recupererUtilisateurByNumeroDeSalle renvoie par exemple Stream<List<String>> en modifiant la fonction et avoir Bob, Paul, Billy], [Marty, Genn. Donc c'est faut de dire que flatMap ne fait "qu'aplatir la map", on fait un peu ce qu'on veut (à condition de renvoyer un stream).

A retenir
Map : Une valeur d'entrée = une valeur de retour.
FlatMap : Une valeur d'entrée = 0, 1, ou plusieurs valeurs de retour.


filter permet de ne garder que certains des éléments du stream.
Ci-dessous on parcourt chaque groupe contenu dans mesBeauxgroupes et on le supprime si sa liste est vide :

//renverra une liste de groupes ne contenant pas de groupe où maBelleListeDeGroupe serait vide : mesBeauxgroupes=mesBeauxgroupes.stream().filter(monGroupe -> !monGroupe.getMaBelleListeDeGroupe().isEmpty()).collect(Collectors.toList());


Ci dessous une comparaison entre du code sans et du code avec stream :

package streams; import java.util.*; public class Names { public static void main(String[] args){ // On veut afficher les 2 premiers noms (par ordre alphabétique) qui font 4 caractères var list = List.of("Bob", "Paul", "Zachary", "Bill", "Zeph", "Lucas"); //liste immutable List<String> copieListe = new ArrayList<String>(list); //c'est immutable donc on bosse sur une copie de la liste Collections.sort(copieListe); //classement par ordre alphabétique selon le compareTo de String var nbrNomsTrouves = 0; // comme on ne veut que les 2 premiers noms, cette var va compter à combien on est for (int i=0;i<copieListe.size();i++) { if(nbrNomsTrouves==2) { //si on en a déjà trouvé 2 on sort de la boucle break; } if(copieListe.get(i).length() == 4){ System.out.println(copieListe.get(i)); //on affiche le nom nbrNomsTrouves++; } } System.out.println("---------"); //Equivalent en stream list .stream() //transforme la liste en stream pour pouvoir utiliser les opérateurs de stream .sorted() //classée via le CompareTo dans String .filter(n -> n.length() == 4) //Implementation d'un Predicate pour ne garder que les noms qui valent 4 .limit(2) //on arrête le stream après 2 éléments .forEach(System.out::println); //Opérateur terminal, notation abrégée équivalente à s -> System.out.println(s) } }


Le stream est beaucoup plus lisible : list.stream().sorted().filter(n -> n.length() == 4).limit(2).forEach(System.out::println);. La sortie est :

Bill Paul --------- Bill Paul


A la fin d'un stream, on peut utiliser différentes opérations terminales comme "collect" pour le transformer en autre chose.

Collect permet aussi de diviser le Stream en 2, par exemple ici on compte les éléments ET on fait une moyenne, sur le même Stream, car il a été divisé en 2 avec la méthode teeing :

Stream.of(1,2,3).collect( Collectors.teeing( Collectors.counting(), Collectors.averagingInt(t ->t), (resultCollector1,resultCollector2)-> { System.out.println("Nbr Elements : " + resultCollector1); System.out.println("Moyenne : " + resultCollector2); return null;} ));






Réflexivité


La réflexion/réfléxivité, c'est la capacité à lire et modifier un objet à l'exécution. En java, on ne peut que lire (pas possible de modifier l'implémentation des méthodes ou le nom des attributs pendant l'exécution du code).
monObjet.getClass() donne un objet de type class contenant les métadonnées (constructeurs disponibles pour cet objet, attributs, méthodes, type....).

Le code pour récupérer les méthodes de la classe Lon pourrait ressembler à ça :

Lion l=new Lion(); Class donnees=l.getClass(); Methode[] methods=donnees.getMethods();


On peut lancer une méthode ainsi récupérée avec evoke().


Généricité




Imaginons une classe "Box", avec comme attribut un objet :

public class Box { private Object object; public void set(Object object) { this.object = object; } public Object get() { return object; } }


On pourrait stocker n'importe quel objet :

Box b=new Box(); b.set("test"); //on stocke une string System.out.println(b.get()); b.set(19); //on stock un Integer System.out.println(b.get());


La généricité nous permettrait de limiter à un seul type d'objet par instance :

Box<String> b=new Box<>(); //on ne stockera que des Strings b.set("test"); System.out.println(b.get()); b.set(19); //interdit, on a dit qu'on ne stockait que des strings dans cette instance ! System.out.println(b.get());



Le code de Box deviendra :

public class Box<T> { private T object; public void set(T object) { this.object = object; } public Object get() { return object; } }


Cette classe accepte n'importe quel type :

package com.monpackage; public class PasGenerique { private Object monAttribut; public PasGenerique() { this.monAttribut = null; } public void setMonAttribut(Object monAttribut) { this.monAttribut = monAttribut; } }


Elle n'est pas générique. Elle accepte tout et n'importe quoi mais n'est pas générique :

PasGenerique pasGen=new PasGenerique(); pasGen.setMonAttribut("ma string"); //on met une chaîne pasGen.setMonAttribut(3); //on peut aussi y mettre un integer



Ci-dessous, le même genre de classe mais générique cette fois :

package com.monpackage; public class Generic<T> { private T monAttribut; public Generic() { this.monAttribut = null; } public Generic(T monAttribut) { this.monAttribut = monAttribut; } public T getMonAttribut() { return monAttribut; } public void setMonAttribut(T monAttribut) { this.monAttribut = monAttribut; } }


Là, on ne peut plus mettre n'importe quoi :

Generic<String> g=new Generic<>("coucou"); //"<String>" indique qu'on ne veut que des Strings System.out.println(g.getMonAttribut()); g.setMonAttribut(2); //ne marchera pas ! On a dit qu'on ne veut que des Strings !


Ca permet donc de verrouiller ce qu'on va mettre dans une de nos classes.

Autre exemple :

package exosSDZ; public class TestGenericite<a,b>{ private a valeur; private b valeur2; public TestGenericite(a varA, b varB){ valeur=varA; } public a retourneValeurA(){ return valeur; } }


Puis :

package exosSDZ; import java.util.Hashtable; public class classePrincipaleExosSDZ { public static void main(String[] args) { //la classe marche avec un string et un entier, ATTENTION il faut préciser "integer" et non pas écrire "int" TestGenericite<String,Integer> ObjetTestGenericite=new TestGenericite<String,Integer>("chaine",2); System.out.println(ObjetTestGenericite.retourneValeurA()); //également avec un entier et un float TestGenericite<Integer,Double> ObjetTestGenericite2=new TestGenericite<Integer,Double>(2,2.3); System.out.print(ObjetTestGenericite2.retourneValeurA()); } }


Les classes s'occupant de collection d'objets (ArrayList...) utilisent ce principe. Ainsi, on peut forcer une collection à ne contenir que des integers :

ArrayList<Integer> demoArrayList = new ArrayList<Integer>(); demoArrayList.add(356); demoArrayList.add(2);


Fonctionne (on précise qu'on veut des animaux mais on stocke des oiseaux) :

package exosSDZ; import java.util.ArrayList; public class classePrincipaleExosSDZ { public static void main(String[] args) { ArrayList<Animal> demoArrayList = new ArrayList<Animal>(); Oiseau oiseau1=new Oiseau(); demoArrayList.add(oiseau1); } }


Fonctionne :


package exosSDZ; import java.io.File; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Hashtable; public class classePrincipaleExosSDZ { public static void main(String[] args) { ArrayList<Animal> demoArrayList = new ArrayList<Animal>(); ArrayList<Animal> demoArrayList2 = new ArrayList<Animal>(); //les 2 tableaux deviennt les mêmes (???!!) demoArrayList=demoArrayList2; Oiseau oiseau1=new Oiseau(); demoArrayList.add(oiseau1); for(Object valeur : demoArrayList2){ System.out.println(valeur); } } }


Mais on ne pourrait pas faire demoArrayList=demoArrayList2; si demoArrayList et demoArrayList2 n'étaient pas du même type, même des types enfants :

package exosSDZ; import java.io.File; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Hashtable; public class classePrincipaleExosSDZ { public static void main(String[] args) { ArrayList<Animal> demoArrayList = new ArrayList<Animal>(); ArrayList<Oiseau> demoArrayList2 = new ArrayList<Oiseau>(); demoArrayList=demoArrayList2; //ne FONCTIONNE PAS } }


A moins de faire ArrayList demoArrayList = new ArrayList();, ou bien en utilisant "?" ArrayList<?> demoArrayList = new ArrayList<Animal>(); pour accepter tous les types ou bien en acceptant les types "héritant de..." avec ArrayList<? extends Animal> demoArrayList = new ArrayList<Animal>();. On peut aussi utiliser ArrayList<? extends Animal> demoArrayList = new ArrayList<Animal>();
On peut aussi faire l'inverse : au lieu d'autoriser les classes hérités, on peut autoriser les classes parents (superclasses) avec le mot clef "super" : ArrayList<? super Oiseau> demoArrayList



Bon tutoriel ici.

Autre exemple au cas où le précédent n'est pas clair, on ne pourra pas faire :

private static void testMethod(List<Object> params) { //on n'accepte que des objets pour cette méthode. for (Object o : params) { System.out.println(o); } public static void main(String[] args) { List<String> ls = new ArrayList<String>(); //une liste de string testMethod(ls); //cette methode ne fonctionnera pas, vu qu'on a dit qu'elle n'accepte que des objets }


Attention également à ne pas confondre l'ajout d'élément/le remplacement d'une liste par une autre.


//ne fonctionne pas car on veut mettre direct une List<Enfant> dans une List<Parent> List<Parent> activationList = methodRenvoyantArrayListDEnfants(); //fonctionne car on met des enfants dans une List<Parent> List<Parent> activationList = new ArrayList<>(); activationList.addAll(methodRenvoyantArrayListDEnfants());


Il faudrait private static void testMethod(List<?> params) { (on appelle "?" un wildcard (jocker).

Attention, en utilisant "?" ou "extends", la collection passe en lecture seule.

Méthode Générique


Putôt que de déclarer le générique au niveau de la classe on peut le déclarer au niveau d'une méthode.
L'avantage c'est que ça permet d'utiliser la généricité dans des méthodes statiques.
Car en effet un générique déclaré au niveau de la classe dépend de l'instance (La classe List peut avoir plusieurs instance qui prendront des String, des Integers etc) et donc n'est pas utilisable dans les méthodes statiques.

Mais en déclarant le générique au niveau de la classe, pas de souci, notez le <T> après le private :

for (Categorie categorie : siNullRenvoyerListeVide(monObjet.getCategories(), Categorie.class)) { for (AutreObjetContenuDansUneCategorie monAutreObjetCDUC : siNullListeVide(categorie.getObjetsCOntenus(), AutreObjetContenuDansUneCategorie.class)) { monAutreObjetCDUC.setAttribut(1); } } public static <T> List<T> siNullRenvoyerListeVide(List<T> categories, Class<T> tClass){ if(categories != null ){ return categories; }else{ return Collections.emptyList(); } }


Méthode qui prend n'importe quel argument et renvoie n'importe quoi (vue dans ReflectionTestUtils.invokeMethod):

public <T> T methodeQuiRenvoieCeQueVousVoulez( Object... args) { }


Notez que public <T> void maMethode(T monObjet) {, finalement c'est pareil que public void maMethode(Object monObjet) { à cause du "type erasure" (=à la compilation Java efface les types).

Generique et wildcard


On peut utiliser le jocker "?" en déclarant les générique.

"?" permet de mettre n'importe quoi. Exemple :

List<?> maListeGeneriqueAvecJoker = new ArrayList<String>(); maListeGeneriqueAvecJoker = new ArrayList<Integer>(); maListeGeneriqueAvecJoker = new ArrayList<Chien>();


Attention les listes deviennent immutable.

Dans java.util.List il y a un exemple avec une méthode : boolean containsAll(Collection c); On peut passer une collection de n'importe quoi dans containsAll.

List<Cat> catList= new ArrayList<Cat>(); List<String> stringList = new ArrayList<String>(); catList.containsAll(stringList); // un Cat et un String n'ont rien à voir pourtant on peut utiliser la liste de String dans le containsAll


? extends MaBelleClasse permet de mettre uniquement des objets qui étendent MaBelleClasse. Exemple :

List<? extends Animal> maListeQuiPrendDesAnimaux = new ArrayList<Chien>(); maListeQuiPrendDesAnimaux = new ArrayList<Chat>(); maListeQuiPrendDesAnimaux = new ArrayList<Lapin>();


Je ne vois pas trop l'intérêt car on peut juste faire List<Animal> maListe2QuiPrendDesAnimaux = new ArrayList<>(); mais bon.

En outre la liste est immutable, impossible de faire :

List<? extends Animal> maListeQuiPrendDesAnimaux = new ArrayList<Cat>(); maListeQuiPrendDesAnimaux.add(new Cat()); //ne compile pas


Exemple dans java.util.List : boolean addAll(Collection c); On peut passe tout ce qui étend E :

List<MaBelleClasse> maBelleClasseListe = new ArrayList<>(); // ici E est défini comme "MaBelleClasse" List<LeFilsDeLaBelleClasse> leFilsDeLaBelleClasseListe = new ArrayList<>(); maBelleClasseListe.addAll(leFilsDeLaBelleClasseListe); // on peut mettre leFilsDeLaBelleClasseListe car il étend E.


? super MaBelleClasse permet de mettre les parents de la classe.

List<? super String> maListeDeParentsDeString = new ArrayList<CharSequence>(); maListeDeParentsDeString = new ArrayList<Object>(); maListeDeParentsDeString = new ArrayList<String>();


Exemple avec des animaux :

List<? super Chien> maListeDeChienOuParents = new ArrayList<Animal>(); maListeDeChienOuParents = new ArrayList<Object>(); maListeDeChienOuParents = new ArrayList<Chien>();


Néanmoins on ne peut pas ajouter de superclasse :

List<? super Dog> maListeDeChienOuParents = new ArrayList<Animal>(); maListeDeChienOuParents.add(new Animal()); //il ne veut que des Dog


Je ne vois pas non plus trop l'intérêt.


Attention, on ne peut pas se servir des jockers à droite :

List<?> maListe = new ArrayList<?>(); //NE COMPILE PAS List<?> maListe = new ArrayList<? super Animal>(); //NE COMPILE PAS List<?> maListe = new ArrayList<? extends Animal>(); //NE COMPILE PAS

Threads


Dans une application, on peut avoir plusieurs processus qui sont exécutés sans se bloquer entre eux. On appelle ça des threads. Par exemple, on pourra avoir un thread qui charge la base de donnée et un qui réagit aux actions de l'utilisateur.
La méthode "main" est un thread. Tous les Threads implémentent l'interface "Runnable".

Plusieurs façons de faire :
-méthode moderne avec un executorService ExecutorService executorService = Executors.newSingleThreadExecutor(); qui permet de lancer une classe qui possède un Runnable ou un Callable (qui renverra un Future). Garder en tête qu'une InterruptedException ou bien deux interrupt enlève le flag demandant l'interuption du thread.
-Ou alors "MonThread extends Thread" (par contre il ne peut pas hériter d'autre chose).Pour le lancer il suffira de faire MonTread monThread=new MonTread(); puis MonThread.start();. On implémente la classe runnable indirectement (car on hérite de Thread qui l'implémente lui même).
-Ou alors "MonTread implements runnable", ce qui nous laisse la possibilité de le faire hériter d'autre chose mais un peu plus compliqué car pour le lancer il faudra faire :
Thread t=new Thread(new monThread());.

On trouvera tout le temps une méthode run(), contenant le traitement quand le code est actif. Pour lancer le run, il faudra faire maClasseImplementantRunnable.start().
On utilisait autrefois MonThread.sleep(), MonThread.resume() etc, mais ces méthodes sont dépréciées. Préférer Thread.interrupt, wait, notify.

On peut passer des paramètre dans un thread lancé via class anonyme comme ceci :

new Thread((new Runnable() { String maBelleChaine = ""; @Override public void run() { EjbCristalAccess.getInstance().traiterPostCristal(maBelleChaine); } private Runnable startAvecParam(String maBelleChaine){ this.maBelleChaine = maBelleChaine; return this; } }).startAvecParam(postCristal)).start(); }


Un problème très courant avec les threads : quand plusieurs processus vont accéder au même objet, ils risquent de se gêner les uns les autres. Il faut un mécanisme de gestion de la concurrence avec l'utilisation du mot réservé "synchronized" couplé à un système de gestion de la file d'attente (pour éviter que les threads essaient d'accéder en permanence à la ressource).
Plus d'info sur synchronized : https://rom.developpez.com/java-synchronisation/


En swing il n'y a pas depolitique de sécurité vis à vis des threads. Par contre en Android, un thread ne peut pas manipuler la vue directement, sauf le thread déclaré comme contrôleur de l'activité. On aura donc vue->controleur activity<-handler<-thread.

Voir exercice "le diner de philosophe" pour s'entraîner à l'utilisation des threads.

Exemple un peu simpliste pour illustrer le problème de "la section critique" :
On considère un serveur web partagé par plusieurs clients (C1 et C2). Il a un attribut "nombreDeVisiteurs". On a la mauvaise idée d'utiliser js chez le client pour récupérer la valeur du serveur, ajouter 1 à ce nombre puis envoyer ce nombre vers le serveur.
C1 va récupérer la valeur 0.
C2 va récupérer la valeur 0.
C1 va ajouter 1 et envoyer 1.
C2 va ajouter 1 et envoyer 1.

Le nombre de visiteur est de 2, mais le serveur aura récupéré 1...

Plusieurs solutions pour résoudre ça en java : sémaphores (on peut le voir sous linux avec "ipcs"), moniteurs Java, annotation


Connexion BDD


On va être dans l'utilisation de librairie fournies en standard.
JDBC : Java database connectivity. Une API qui va utiliser des drivers pour se connecter à un SGBD, constituée de 2 packages java.sql et javax.sql. On va y trouver notamment les classes Driver, Connection, Statement, ResultSet...

Le schéma suivant peut être utilisé :
Appli---JDBC(interface pour se connecter à la base)---Driver JDBC (fourni par éditeur SGBD)---SGBD


Un exemple de connexion très basique utilisant uniquement JDBC (clic droit sur projet, build path, add library, Connectivity Driver et prendre le driver de votre BDD, par exemple mysql-connector-java-5.1.47-bin.jar ) :

package testJDBCsimple; import java.sql.* public class ClassePrincipale { public static void main(String[] args) { Class.forName("com.mysql.jdbc.Driver"); //on charge le pilote pour connexion à mysql... facultatif en java8. Connection con; try { con = DriverManager.getConnection("jdbc:mysql://localhost:3306/mabase?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC", "root", "monmotdepasse"); //lecture des infos : Statement stmt = con.createStatement(); ResultSet rset = stmt.executeQuery("SELECT * FROM MATABLE"); while ( rset.next() ) { System.out.println(rset.getString("MONCHAMP")); } //pour de l'insertion : String sql = "INSERT INTO EMPLOYE VALUES (22, 'Zara', 'Ali', 'C17',14000,NULL,NULL,NULL)"; stmt.executeUpdate(sql); rset.close(); stmt.close(); con.close(); } catch (Exception ex) { ex.printStackTrace(); } } }


?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC" est facultatif, il permet d'éviter une erreur de TimeZone qui se produit si aucun fuseau horaire n'a pas été défini dans MySQL.


Problèmes :
-on utilise directement le langage du SGBD avec lequel on travaille, ce qui implique qu'on devra changer le code si on change de SGBD.
-on reçoit des collections non-typées, ce qui implique reconstruction des objets dans l'appli, ce qui demande pas mal de traitements.

JPA


Pour résoudre ces problèmes on peut ajouter une librairie intermédiaire de type JPA (Java Persistence API), qui fait parti d'une catégorie de produits proposant une technique de programmation "ORM" (Object Relational Mapping - on travaille sur sa base de données en utilisant des objets qui seront convertis par l'ORM).
Un ORM nous permet d'éviter d'écrire directement des requêtes en SQL : l'idée est d'écrire du code de manipulation d'objets qui sera retranscris en requête par l'ORM.

Il existe différentes implémentations de JPA (qui est un ensemble de spécifications), notamment "Eclipse Link" pour Eclipse. Il existe aussi Hibernate,Toplink... ce sont des frameworks qui permettent de travailler en ORM.

Le schéma peut ressembler à :
Appli---JPA (EclipseLink ou Hibernate pour générer des entités model)---JDBC---Driver---SGBD

JPA va renvoyer des objets à notre application, ce qui permet de les traiter plus facilement.

Une application JPA fonctionne avec un fichier persistence.xml qui contient tout le paramétrage pour accéder à la base.

Une entité, une représentation sous forme de classes des éléments BDD, est du code JAVA précédé par des annotations (par ex, si on a une table client, on aura @entity pour indiquer que c'est une table, @primary pour la clef primaire...). On peut les écrire ou les générer. On aura un objet de type Entity Manager. L'entity manager nous donne des méthodes java pour utiliser la base. Par exemple, une methode.persist(object) pourra être équivalent à un "insert". C'est souvent la solution la plus pertinente, mais d'autres existent.

On peut aussi :
-mélanger le code java et le SQL (mauvaise idée car le code n'est pas portable d'une SGBD à l'autre).
-utiliser du code java et du JPQL(une variante orienté objet de SQL pour Java, simplifiant beaucoup les requêtes, exemple listant les noms des clients chez qui a eu lieu une intervention sur un produit supérieur à 300e : select i.codeclt.nomclt FROM Intervention i where i.produit.prix>300e, au lieu de faire plusieurs join on utilise l'objet pour récupérer l'attribut prix de l'objet produit de l'objet intervention).
-utiliser API criteria, du code Java se rapprochant très fortement de SQL (avec des méthodes pour faire select, where... les lignes de codes sont parfois très longues et difficile à lire avec cette techniques).

Configuration d'Eclipse pour JPA (implémentation Eclipselink)


Vous aurez besoin d'Eclipse JEE, du driver JPA pour votre SGBD, d'une connexion internet pour récupérer Eclipse Link.
Il existe dans Eclipse JEE (absent dans la version standard) un système de "facets" pour configurer son projet et notamment mettre en place JPA.

-Créer un projet Java (et non pas un projet JPA).
-Clic droit sur le projet à gauche, propriétés, selectionner "Project Facets".
-Cocher JPA dans la liste, puis, cliquer sur le lien en dessous "Further Configuration Available".
-(Si cette étape n'a pas été faite dans Eclipse/ si Eclipse Link n'est pas coché :) choisissez plateforme Eclipse Link puis cliquez sur le bouton télécharger (une disquette avec une flèche vers le bas).
-Puis add connection, choisissez votre SGBD et entrez un nom qui identifiera votre connexion dans Eclipse.
-(Si cette étape n'a pas été faite dans Eclipse) Tout en haut de la fenêtre indiquant "Specify a profile", on vous demandera un driver. Cliquer sur le + pour l'ajouter et allez chercher le .jar avec votre driver (mysql-connector-java-5.1.39-bin.jar par exemple), supprimer éventuellement les chemins non-valides, confirmez.
-Mettez le nom de votre base et son chemin (remplacer "database").
-Cliquez sur "test connection" qui devra être au vert, puis fermer cette fenêtre en appuyant sur finish.
-Dans la fenêtre "Modify JPA Faceted Project" où vous avez téléchargé Eclipse, ne pas oublier de cocher "Add driver library to build path", puis "ok" 2 fois.

Vous devez maintenant avoir plusieurs éléments dans le dossier de votre projet (à gauche) :EcliseLink, le driver JDBC et, dans le dossier src/META-INF, un fichier persistence.xml qui contient les informations de configuration.
Double cliquez sur persistence.xml.

-Dans l'onglet "connection", la transaction type est à changer pour "Resource Local" si vous n'utilisez pas de serveur d'application.
-Cliquez sur "Populate from connection" et choisissez la connexion appropriée.

Vous pouvez gérer les connections créées dans Eclipse dans la fenêtre "Data Source Explorer".


Hibernate



Framework/ORM open source gérant la persistance des objets d'une base de donnée relationnelle. Il va générer et exécuter le code SQL, ce qui a plusieurs avantages : pas besoin pour le dev de connaitre SQL et pas besoin de modifier le code en cas de changement de SGBD.
(Un framework impose sa façon de travailler, une librairie est juste utilisée : on est "en dessous" du framework (forcé de se conformer à elle) et "au dessus" de la librairie (on va chercher ce qui nous intéresse).)

Permet de transformer facilement les tables en classes (par ex tables client avec champs nom, prenom... deviendra une classe Client avec attribut nom et prénom) via un document de mappage *hbm.xml ou des annotations (@Entity, @Column pour faire le lien avec la colonne de la table, @Id pour définir la clef primaire).

Classe persistante : l'élève, le client... l'entité qui sera stockée dans la base de donnée. Elles ont besoin d'un constructeur sans paramètre, des getters et des setters. Doivent avoir un attribut qui sera un identifiant de l'objet par rapport aux autres de la même classe (en gros la clef primaire dans la table).
Session factory : une abstraction de notre base.
Session : une abstraction de la connexion à la base. Fabrique des objets Transactions. Créé à partir d'un ensemble de paramètres contenus dans le fichier hibernate.cfg.xml.
Transaction : Une interface qui gère les transactions.
L'interface transaction utilise JTA, une API présente dans la spec JEE, un protocole de commit à deux phases pour éviter deadlocks, lectures fantômes... Très efficace pour transactions courtes mais moins sur les transaction longues, il prend du temps à lever le second commit.
ConnectionProvider : facultatif, fabrique de pool de connexion.

Hibernate suit la spécification JPA mais peut aussi la simplifier : en JPA on a DataSource+PersistenceUnit+EntityManager+TransactionManager, en Hibernate on pourra avoir DataSource+SessionManager(fusion de PU et EM)+TransactionManager.

Installation sous Eclipse


Elipse utilise EclipseLink mais on peut également y installer hibernate.

Dans Eclipse -> Add->Software entrer le nom "Hibernate tools" et l'url http://download.jboss.org/jbosstools/photon/stable/updates (photon désigne votre version d'Eclipse ! Ca pourrait être http://download.jboss.org/jbosstools/oxygen/stable/updates)

Déployer "JBoss application Development" et cocher "Hibernate tools", "next", "finish".

New Dynamic web project.

Copier/coller dans WEB-INF->lib les jar suivants :
-antlr-2.7.7.jar
-byte-buddy-1.8.17.jar
-classmate-1.3.4.jar
-dom4j-1.6.1.jar
-hibernate-commons-annotations-5.0.4.Final.jar
-hibernate-core-5.3.6.Final.jar
-jandex-2.0.5.Final.jar
-javassist-3.23.1-GA.jar
-javax.activation-api-1.2.0.jar
-javax.persistence-api-2.2.jar
-jboss-logging-3.3.2.Final.jar
-jboss-transaction-api_1.2_spec-1.1.1.Final.jar
-mysql-connector-java-5.1.23-bin.jar

Puis clic droit sur le projet, properties->Java Build Path->Libraries->Add JAR et on va chercher les JAR mis précédemment.

A droite, dans le project explorer, clic droit sur le dossier "Java resources" et créer le dossier "resources".
Dans le menu d'Eclipse, file->new->other->hibernate configuration file.
Les champs à remplir absolument (les autres peuvent être laissés vides) :
Database dialect : Mysql
Driver class : com.mysql.jdbc.Driver
Connection url : jdbc:mysql://localhost:3306/base_exemple
username et password.

La version d'hibernate 5.3 semble poser des problèmes avec TomCat, prendre la version 5.1.

Une fois les champs renseignés le fichier de config hibernate.cfg.xml est généré par Eclipse (ongletsource en dessous):

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.password">password</property> <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/base_exemple</property> <property name="hibernate.connection.username">nomutilisateur</property> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> </session-factory> </hibernate-configuration>


Windows->Perspective->Open perspective->hibernate (si rien ne se passe on peut faire "reset perspective"). Il y a un petit + en haut de la perspective hibernate. On charge son projet et son fichier de configuration, puis OK. Les tables devraient apparaitre si on déroule à gauche.

En cas d'erreur "Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]", vérifiez que mysql est lancé, que l'adresse de connexion à votre base est correcte, allez dans le détail des erreurs (peut être un problème de timezone demande de rajouter un paramêtre derrière l'url).

Il faut maintenant faire le "mapping", c'est à dire définir le lien entre les tables et les classes java.

Eclipse peut le générer automatiquement. Run->Hibernate code generation->Hibernate Code Generation Configuration, double cliquez sur Hibernate Code generation à gauche. Le output directory doit être votreprojet/src. Cochez reverse engineer from JDBC Connection. Ne touchez à rien d'autre, dans l'onglet Exporters cochez .java et .hbm.xml, puis apply et run. Des classes java et des fichiers xml doivent être générés (vous pouvez générer des annotations au lieu de xml en choisissant annotations en haut de cette fenêtre).

Exemple :

package com.model; // Generated Oct 3, 2018 1:22:21 PM by Hibernate Tools 5.1.8.Final import java.math.BigDecimal; import java.util.HashSet; import java.util.Set; /** * Employe generated by hbm2java */ public class Employe implements java.io.Serializable { private int numemp; private Projet projet; private String nomemp; private String prenomemp; private String poste; private BigDecimal salaire; private BigDecimal prime; private Integer superieur; private Set inscrits = new HashSet(0); public Employe() { } public Employe(int numemp, String nomemp, String prenomemp, String poste, BigDecimal salaire) { this.numemp = numemp; this.nomemp = nomemp; this.prenomemp = prenomemp; this.poste = poste; this.salaire = salaire; } public Employe(int numemp, Projet projet, String nomemp, String prenomemp, String poste, BigDecimal salaire, BigDecimal prime, Integer superieur, Set inscrits) { this.numemp = numemp; this.projet = projet; this.nomemp = nomemp; this.prenomemp = prenomemp; this.poste = poste; this.salaire = salaire; this.prime = prime; this.superieur = superieur; this.inscrits = inscrits; } //ci dessous getters et setters


Le fichier xml qui fait entre autres le lien entre la classe et la base :

<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!-- Generated Oct 3, 2018 1:22:21 PM by Hibernate Tools 5.1.8.Final --><!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping auto-import="true" default-access="property" default-cascade="none" default-lazy="true"> <class catalog="base_exemple" dynamic-insert="false" dynamic-update="false" mutable="true" name="com.model.Employe" optimistic-lock="version" polymorphism="implicit" select-before-update="false" table="EMPLOYE"> <id name="numemp" type="int"> <column name="NUMEMP"/> <generator class="assigned"/> </id> <many-to-one class="com.model.Projet" embed-xml="true" fetch="select" insert="true" name="projet" not-found="exception" optimistic-lock="true" unique="false" update="true"> <column length="4" name="CODEPROJET"/> </many-to-one> <property generated="never" lazy="false" name="nomemp" optimistic-lock="true" type="string" unique="false"> <column length="25" name="NOMEMP" not-null="true"/> </property> <property generated="never" lazy="false" name="prenomemp" optimistic-lock="true" type="string" unique="false"> <column length="25" name="PRENOMEMP" not-null="true"/> </property> <property generated="never" lazy="false" name="poste" optimistic-lock="true" type="string" unique="false"> <column length="20" name="POSTE" not-null="true"/> </property> <property generated="never" lazy="false" name="salaire" optimistic-lock="true" type="big_decimal" unique="false"> <column name="SALAIRE" not-null="true" precision="8"/> </property> <property generated="never" lazy="false" name="prime" optimistic-lock="true" type="big_decimal" unique="false"> <column name="PRIME" precision="7"/> </property> <property generated="never" lazy="false" name="superieur" optimistic-lock="true" type="java.lang.Integer" unique="false"> <column name="SUPERIEUR"/> </property> <set embed-xml="true" fetch="select" inverse="true" lazy="true" mutable="true" name="inscrits" optimistic-lock="true" sort="unsorted" table="INSCRIT"> <key on-delete="noaction"> <column name="NUMEMP" not-null="true"/> </key> <one-to-many class="com.model.Inscrit" embed-xml="true" not-found="exception"/> </set> </class> </hibernate-mapping>


Puis, dans le fichier de config Hibernate (hibernate.cfg.xml), dans la balise session-factory, il va falloir référencer les fichiers de mapping créés :

<mapping resource="mon/beau/package/Employe.hbm.xml"/> <mapping resource="mon/beau/package/Seminaire.hbm.xml"/>

Pour tester, vous pouvez faire un main et y taper :

package com.persistence; import java.util.List; import javax.persistence.Query; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; import com.fasterxml.classmate.AnnotationConfiguration; import com.model.Employe; public class Main { public static void main(String[] args) { SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory(); Session session = sessionFactory.openSession(); session.beginTransaction(); Query query = session.createQuery("from Employe"); List listeEmploye = query.getResultList(); session.getTransaction().commit(); session.close(); System.out.println("Taille de la liste de résultat" + listeEmploye.size()); for(Object employe : listeEmploye){ System.out.println(((Employe)employe).getNomemp()); } } }



Si on veut utiliser une session factory :

//Dans main/resources/persistence.xml <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0"> <persistence-unit name="maPersistenceUnit" transaction-type="RESOURCE_LOCAL"> <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> <properties> <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" /> <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/QUIZ2" /> <property name="javax.persistence.jdbc.user" value="mleveque" /> <property name="javax.persistence.jdbc.password" value="password" /> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" /> <property name="hibernate.show_sql" value="true" /> <!-- <property name="hibernate.hbm2ddl.auto" value="create" /> ATTENTION SUPPRIME LES TABLES --> </properties> </persistence-unit> </persistence>


Dans un controleur appelé par une JSP par exemple :

package controler; import java.io.Serializable; import javax.annotation.PostConstruct; import javax.faces.bean.ManagedBean; import javax.faces.bean.RequestScoped; import javax.faces.bean.SessionScoped; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import com.model.Stagiaire; @ManagedBean @RequestScoped public class ControlIndex implements Serializable { private int nombreTest=1; @PostConstruct public void onLoad(){ System.out.println("Création..."); nombreTest=999; EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("maPersistenceUnit"); EntityManager em = entityManagerFactory.createEntityManager(); System.out.println("LE STAGIAIRE "+em.find(Stagiaire.class, 1)); } public int getNombreTest() { return nombreTest; } public void setNombreTest(int nombreTest) { this.nombreTest = nombreTest; } }


Petit exemple avec des annotations




@Entity public class Etudiant implements Serializable { @Id @GeneratedValue private Long idEtudiant; private String nom; private String prenom; private Date dateNaissance; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "idSection", nullable = false) private Section section; //chaque étudiant n'appartient qu'à une seule section //suivi d'un constructeur vide, d'un constructeur complet, des getters et setters



@Entity public class Section implements Serializable { @Id @GeneratedValue private Long id; private String libelle; @JsonIgnore //pour éviter la récursion infinie... On peut aussi utiliser @JsonBackReference / @JsonManagedReference @OneToMany(fetch = FetchType.LAZY, mappedBy = "section") private List<Etudiant> etudiants; //chaque section a une liste d'étudiants //suivi d'un constructeur vide, d'un constructeur complet, des getters et setters




Connexion à une BDD via Netbeans+Glassfish


3 éléments: dans le menu "service" (windows->services) on doit avoir une connexion de netbeans à la base, le serveur d'application (glassfish) doit être connecté à la base, le fichier persistence.xml doit être correct pour que notre appli puisse s'y connecter. Attention, Glassfish n'acceptera pas un utilisateur sans mot de passe.
Dans Netbeans, menu service, clic droit sur database, new connection, choisir le connecteur pour mysql, entrez le nom de la base, l'utilisateur, le mot de passe. Notez l'url de connexion (exemple : jdbc:mysql://localhost:3306/maBaseDeDonnees), elle sera utile par la suite.
Créez un nouveau projet Java web en sélectionnant le serveur d'application que vous souhaitez utiliser (glassfish, payara...) et vos packages (controleur, data access, modele par exemple...). Clic droit sur le package modele, new other, persistence, entity class from database. Data source, new data source, sélectionnez la connection créée précédemment, donnez le nom JNDI de votre choix et ajoutez les tables. Dans la fenêtre suivante vous pouvez décocher "Generate JAXB Annotations". Dans la fenêtre suivant celle-ci, Collection Type peut être mis en java.util.List (il y a 2 autres choix proposés : java.util.collection, la classe mère, qui n'ordonne pas les collections et ne propose donc pas d'accéder directement à l'élément n°X directement et java.util.set qui interdit les doublons).

Un fichier glassfish-resources doit s'être créé, contenant les informations qu'utilisera glassfish pour se connecter à la base. N'oubliez pas de récupérer le nom de l'unité de persistence pour votre EntityManagerFactory dans le fichier persistence.xml qui s'est également créé.

Exemple de fichier glassfish-resources :

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE resources PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN" "http://glassfish.org/dtds/glassfish-resources_1_5.dtd"> <resources> <jdbc-connection-pool allow-non-component-callers="false" associate-with-thread="false" connection-creation-retry-attempts="0" connection-creation-retry-interval-in-seconds="10" connection-leak-reclaim="false" connection-leak-timeout-in-seconds="0" connection-validation-method="auto-commit" datasource-classname="com.mysql.jdbc.jdbc2.optional.MysqlDataSource" fail-all-connections="false" idle-timeout-in-seconds="300" is-connection-validation-required="false" is-isolation-level-guaranteed="true" lazy-connection-association="false" lazy-connection-enlistment="false" match-connections="false" max-connection-usage-count="0" max-pool-size="32" max-wait-time-in-millis="60000" name="mysql_examJavaEEBourse_rootPool" non-transactional-connections="false" pool-resize-quantity="2" res-type="javax.sql.DataSource" statement-timeout-in-seconds="-1" steady-pool-size="8" validate-atmost-once-period-in-seconds="0" wrap-jdbc-objects="false"> <property name="serverName" value="localhost"/> <property name="portNumber" value="3306"/> <property name="databaseName" value="examJavaEEBourse"/> <property name="User" value="root"/> <property name="Password" value="motdepasse"/> <property name="URL" value="jdbc:mysql://localhost:3306/examJavaEEBourse?zeroDateTimeBehavior=convertToNull"/> <property name="driverClass" value="com.mysql.jdbc.Driver"/> </jdbc-connection-pool> <jdbc-resource enabled="true" jndi-name="java:app/examJavaEEBourse" object-type="user" pool-name="mysql_examJavaEEBourse_rootPool"/> </resources>


Avec l'unité de persistence :

<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name="examJavaEEBoursePU" transaction-type="JTA"> <jta-data-source>java:app/examJavaEEBourse</jta-data-source> <exclude-unlisted-classes>false</exclude-unlisted-classes> <properties/> </persistence-unit> </persistence>


A partir d'ici, le projet est fonctionnel mais n'est pas enregistré dans glassfih. A chaque déploiement de l'appli, le fichier glassfish-resources seralu par le serveur pour créer un pool de connexion (servent à diminuer la charge sur le serveur en répartissant les requêtes entre les clients). Il va falloir configurer Glassfish pour qu'il se connecte à la base, puis nous configurerons notre projet pour qu'il passe par Glassfish.

Première chose, Glassfish aura besoin du driver pour mysql.Vous devrez mettre mysql-connector-java-5.1.39-bin.jar dans /home/aka/glassfish-4.1.1/glassfish/domains/domain1/lib.

Dans l'onglet services, servers, redémarrez Glassfish et connectez vous sur l'interface d'admin (localhost:4848) pour accéder à l'interface d'administration. Ressources, JDBC connection pool, new, donnez le nom que vous voulez, ressource type java.sql.Driver, database MySql.
Puis next, ping enabled pour tester la connexion, et tout en bas renseignez les champs user,password et url avec l'url récupérée plus haut (sinon, clic droit propriété sur votre connexion dans Netbeans pour la récupérer). On doit avoir un ping succeded à la fin. Si le ping échoue, vérifiez que le serveur mysql a été mis au bon endroit (essayez un autre domaine/lib). Vérifier que le driver classname est bien com.mysql.jdbc.Driver, que votre serveur mysql soit démarré, que les autorisations soient bonnes.
On ne peut pas encore accéder au pool, il faut lui donner un nom de ressource. JDBC ressources, new. Le nom doit être de forme jdbc/nomdemabase et sélectionnez le pool que vous venez de créer.

Dans le persistence.xml de votre projet, cochez "use Java transaction API", et le nom de la ressource que vous venez de créer. Il faudra avoir dans le xml "transaction-type="JTA" et "jdbc/votreBase".

Si vous avez un plantage dans la console d'administration de glassfish (un bug connu empêche la création de bases sur glassfish 4.1.1),vous pouvez installer dans netbeans un serveur alternatif à glassfish comme Paraya (clicdroit sur servers, add new, glassfish et sélectionner le jar de paraya).


Exemple application DAO


DAO : Direct Access Object.

Un patron de conception avec 2 aspects importants : une classe abstraite supportant la généricité et souvent une interface pour verrouiller les méthodes. On aura forcément une classe mère abstraite (DAO), un héritage et la classe mère supportera la généricité.

DAO1Banque---->(Abstract)DAO (peut implémenter une interface)<-----DAOClient


On va construire une application dans 3packages ; modèle (qui contiendra les entités), accès (qui contiendra les méthodes pour communiquer avec la BDD) et traitement (qui contiendra les méthodes d'interactions -le menu où l'utilisateur va saisir les opérations etc).

Dans le modèle, on va avoir des clsses qui vont représenter la structure de la base. Par exemple, si on a une table client et une banque banque, on aura une classe Banque et une Client.
On peut générer le code des entités : clic droit sur le projet->JPA Tools->Generate entities from tables (cliquer sur l'icône connexion pour charger les entités si besoin)->next->next->Key Generator->identity.

Des fichiers .java seront générés avec des annotations : @Entity pour indiquer qu'il s'agit d'une table, @Id pour indiquer la clef primaire, @ManyToOne indique la relation, @JoinColumn(name="id_compte_debite") pour indiquer la colonne de la clef étrangère... Il faudra pour un modèle au moins un constructeur sans argument et des getters et setters sur l'ensemble des attributs. les noms des attributs correspondent aux champs de la table (ils peuvent être changés si besoin avec une annotation). Ces entités seront gérées par un objet EntityManager.

Si on souhaite donner à une class une variable qui ne sera pas persistée en base, on peut utiliser l'annotation "@Transient".

Dans la couche access, créer la classe DAO, abstract, la rendre générique en mettant entre chevrons une lettre qui va correspondre à une type d'objet (ne pas mettre un type déjà existant). On va créer un entity manager qui va gérer les entités, pour le créer, on aura besoin d'un objet EntityManagerFactory.

Puis, on va créer des méthodes permettant de manipuler les données de l'entité qu'on va envoyer à l'entity manager.


package acces; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; public abstract class DAO <T>{ protected EntityManager em; //pour gérer les objets public DAO(){ //on ne peut pas directement créer un entity manager, il faut une factory EntityManagerFactory emf=Persistence.createEntityManagerFactory("exoCDI_banqueJPA"); //on trouve le nom dans le xml, <persistence-unit name="exoCDI_banqueJPA".... em=emf.createEntityManager(); //création de l'entity manager } public void ecrire(T objetEntite) //utilisation de la généricité (pour récupérer des entités client, banque, compte... etc. { em.getTransaction().begin(); //debute la transaction, càd verrouille l'enregistrement em.persist(objetEntite); //enverra une commande "insert into" em.getTransaction().commit();//valide la transaction (enlève le verrou sur l'enregistrement en base) } public void maj(T objetEntite) { em.getTransaction().begin(); em.merge(objetEntite); em.getTransaction().commit(); } public void supprimer(T objetEntite) { em.getTransaction().begin(); try { T objettrouve=em.merge(objetEntite); //il met à jour l'objet et le retourne em.remove(objettrouve); //supprime l'objet de la base em.getTransaction().commit(); } catch (Exception e) { em.getTransaction().rollback(); //erreur, annulation de la transaction } } }


em.getTransaction().begin(); sert à verrouiller l'enregistrement dans la BDD pour qu'on soit le seul à pouvoir le modifier. En Java EE, on n'a pas à gérer les transactions, c'est souvent fait par le serveur d'application.

On va maintenant faire une classe DAOclient et une DAObanque, vu que DAO est une classe abstraite, c'est le seul moyen d'avoir accès à un Entity Manager et aux méthodes créées précédemment :


package acces; import java.util.List; import modele.Banque; //ne pas oublier pour pouvoir lire ailleurs que dans notre package "acces" public class DaoBanque extends Dao<Banque> { //une méthode pour renvoyer toutes les banques dans une liste public List<Banque> liretous() { return em.createNativeQuery("select * from banque",Client.class).getResultList(); } public Banque rechercherparId(int id) //cherche avec la clef { return em.find(Banque.class, id); } }


Puis, dans la classe demo :

package traitement; import acces.*; import modele.*; public class Demo { public static void main(String args[]){ Banque b=new Banque(); b.setNom("Banque de Paris"); DaoBanque daoBanque=new DaoBanque(); daoBanque.ecrire(b); } }


Exemple de DAO générique (on lui donne la classe à renvoyer donc plus besoin de créer des classes qui en héritent) :

package modele.access; import java.lang.reflect.ParameterizedType; import java.util.List; import java.util.Observable; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import javax.persistence.Query; public class Dao<T> extends Observable{ //utilisation de la généricité private EntityManager em; //pour gérer les objets private Class classeARenvoyer; //pour savoir quelle classe doit être renvoyée public Dao(Class classeARenvoyer){ //on ne peut pas directement créer un entity manager, il faut une factory EntityManagerFactory emf=Persistence.createEntityManagerFactory("examJavaPU"); //on trouve le nom dans le fichier persistence.xml em=emf.createEntityManager(); //création de l'entity manager this.classeARenvoyer=classeARenvoyer; //assignation de la classe que l'on va renvoyer } public void ecrire(T objetEntite) { em.getTransaction().begin(); //debute la transaction, càd verrouille l'enregistrement em.persist(objetEntite); //enverra une commande SQL "insert into..." em.getTransaction().commit();//valide la transaction (enlève le verrou sur l'enregistrement en base) } public void maj(T objetEntite) { em.getTransaction().begin(); //debute la transaction, càd verrouille l'enregistrement em.merge(objetEntite); em.getTransaction().commit();//valide la transaction (enlève le verrou sur l'enregistrement en base) this.setChanged(); this.notifyObservers(objetEntite); } public void rafraichir(T objetEntite) //par exemple, on a ajouté un enseignant à un objet Classe //il faut rafraichir l'enseignant pour que la classe apparaisse dans sa liste de classe { em.getTransaction().begin(); //debute la transaction, càd verrouille l'enregistrement em.refresh(objetEntite); em.getTransaction().commit();//valide la transaction (enlève le verrou sur l'enregistrement en base) } public T rechercherparId(int id) //cherche avec la clef { return (T)em.find(classeARenvoyer, id); } public List<T> liretous() { return em.createNativeQuery("select * from "+classeARenvoyer.getSimpleName().toLowerCase(),classeARenvoyer).getResultList(); } public List<T> renvoyerListeRechercheParChamp(String champ, String valeur){ return em.createNativeQuery("SELECT * FROM "+classeARenvoyer.getSimpleName().toLowerCase()+" where "+champ+" LIKE \"%"+valeur+"%\"", classeARenvoyer).getResultList(); } public void supprimer(T objetEntite) { em.getTransaction().begin(); try { T objettrouve=em.merge(objetEntite); //il met à jour l'objet et le retourne em.remove(objettrouve); //supprime l'objet de la base em.getTransaction().commit(); } catch (Exception e) { em.getTransaction().rollback(); //erreur, annulation de la transaction } } }

Modele-Vue-Controleur



MVC (modèle vue controleur) est un patron de conception qui peut être appliqué à java, php...

3 pakages :

-Modele : On y trouve des modèle (les "entités", par exemple des objets "étudiants"), qui représentent les données internes et effectue des calculs internes sur leurs attributs (une entité "commande" pourrait avoir une méthode pour calculer le prix total). Par exemple "nom/prenom/id" pour une classe étudiant, "mesure1/mesure2/taux" pour une classe convertisseur...
C'est le modèle qui va donner l'ordre automatiquement à la vue de se rafraichir en cas de modification (méthode notifyObservers). Hérite de "Observable".
On y inclue un sous-package accès avec les DAO.
On doit y ajouter un observer : mon_modele.addObserver(ce_qui_observe);
En général c'est en lien avec une base de donnée, mais il peut aussi être lié à un webservice par exemple.

-Vue : L'affichage, on a généralement une classe fenêtre qui initialise une JFrame, l'affiche, positionne les boutons.
Elle observe le modèle en implémentant "Observer", avec une méthode update qui récupère les infos du modèle et met à jour l'affichage.
Elle envoie des infos au contrôleur avec une méthode addActionListener (positionnée par exemple sur un bouton bouton.addActionListener(controleur)).

-Controleur : s'occupe des traitements, implémentant ActionListener pour qu'il puisse réagir à des actions dans la vue.
Modifie le modèle (avec des xxxx.set) et la vue (en changeant par exemple les panels).

Il faut faire des associations entre ces 3 packages :
-la vue doit avoir en attribut l'objet principal du controleur et celui du modèle.
-le controleur doit avoir en attribut l'objet de la vue (la fenêtre).

Il y a deux philosophie : la fenetre envoie les infos au controlleur (push) ou bien le controlleur récupère des infos de la fenêtre (pull).


En MVC la vue connaît le modèle, en MVP (Modèle vue présentation, comme dans Primefaces) non.

Exemples divers




package exoCDI_login; import java.util.Scanner; public class classeExoCDI_login { public static void main(String[] args) { Scanner scan_saisie = new Scanner (System.in); System.out.println("Entrez votre mail :"); String mail = scan_saisie.next(); //s'il y a bien un @ au moins en 2e lettre && que le dernier point est après le dernier arobas if(mail.indexOf("@")>2 && mail.lastIndexOf(".")>6 && mail.length()>9){ System.out.println("Votre adresse mail est juste !"); }else{ System.out.println("Votre adresse mail est fausse !"); } } }


En regex :

package exoCDI_loginEreg; import java.util.Scanner; public class ExoCDI_loginEregClasse { public static void main(String[] args) { Scanner scan_saisie = new Scanner (System.in); System.out.println("Entrez votre mail :"); String mail = scan_saisie.next(); //s'il y a bien un @ au moins en 2e lettre && que le dernier point est après le dernier arobas if(mail.matches("\\w{2,}\\w+\\@\\w{2,}[.].*")){ System.out.println("Votre adresse mail est juste !"); }else{ System.out.println("Votre adresse mail est fausse !"); } } }


Convertisseur Celcius Fahrenheit :

package exoCDI_convertisseurCelciusFahrenheit; import java.util.Scanner; public class ClassePrincipaleConvertisseurCF { public static void main(String[] args) { int a=1; int b=++a; System.out.println(a+","+b); boolean quitter=false; while (quitter==false) { System.out.println("===================================="); System.out.println( "Entrez la température suivie de son unité (ex : 25.5C) pour la convertir (ou q pour quitter)."); Scanner scan_saisie = new Scanner(System.in); String temp = scan_saisie.next(); if(temp.contains(",")){ temp=temp.replace(",", "."); }for(i=1;i<=9;i++){ if (temp.endsWith("C") || temp.endsWith("c")) { float celcius = Float.parseFloat(temp.substring(0, temp.length() - 1)); System.out.println(" " + celcius + " Celcius fait "+(celcius * 9 / 5 + 32)+" Fahrenheit."); } else if (temp.endsWith("F") || temp.endsWith("f")) { float Fahrenheit = Float.parseFloat(temp.substring(0, temp.length() - 1)); System.out.println(" " + Fahrenheit + " Celcius fait "+((Fahrenheit - 32) * 5 / 9)+" Celcius."); } else if (temp.endsWith("Q") || temp.endsWith("q")) { quitter=true; } else { System.out.println( "ERREUR : Tapez la température suivie de C pour celcius et F pour Fahrenheit (exemples : 23C ou 33F)"); } } } }


Morpion 2 joueurs :

package exoCDI_morpion; import java.util.Scanner; public class ClassePrincipale_exoCDImorpion { public static void main(String[] args) { int[] casesPlateau = new int[10]; boolean finDuJeu=false; int i=0; int numJoueur=1; int positionDemandee=0; for(i=1;i<=9;i++){ casesPlateau[i]=0; } while (finDuJeu==false) { System.out.println("|_|A|B|C|"); for(i=1;i<=9;i++){ if(i==1){ System.out.print("|1"); } System.out.print("|"); if(casesPlateau[i]==1){ System.out.print("X"); }else if(casesPlateau[i]==2){ System.out.print("O"); }else{ System.out.print(" "); } if(i==3){ System.out.println("|"); System.out.print("|2"); } if(i==6){ System.out.println("|"); System.out.print("|3"); } } System.out.println("|"); positionDemandee=1; System.out.println("JOUEUR "+numJoueur+" : entrez les coordonnées désirée (ex : A1)."); Scanner scan_saisie = new Scanner(System.in); String saisie = scan_saisie.next(); //A=65, B=66... positionDemandee+=((int)saisie.charAt(0)-65); if(saisie.substring(1,2).equals("1")){ //c'est bon }else if(saisie.substring(1,2).equals("2")){ positionDemandee+=3; }else if(saisie.substring(1,2).equals("3")){ positionDemandee+=6; }else{ System.out.println("Erreur : Merci de saisir un chiffre entre 1 et 3."); } if(casesPlateau[positionDemandee]==0){ casesPlateau[positionDemandee]=numJoueur; numJoueur = (numJoueur==1)?2:1; }else{ System.out.println("Erreur : Cette case est déjà occupée !"); } for(i=1;i<=2;i++){ //on teste pour les deux joueurs if(casesPlateau[1]==i && casesPlateau[2]==i && casesPlateau[3]==i || casesPlateau[4]==i && casesPlateau[5]==i && casesPlateau[6]==i || casesPlateau[7]==i && casesPlateau[8]==i && casesPlateau[9]==i){ System.out.println("**************************\r\nVictoire du joueur "+i+" !"); finDuJeu=true; } if(casesPlateau[1]==i && casesPlateau[4]==i && casesPlateau[7]==i || casesPlateau[2]==i && casesPlateau[5]==i && casesPlateau[8]==i || casesPlateau[3]==i && casesPlateau[6]==i && casesPlateau[9]==i){ System.out.println("**************************\r\nVictoire du joueur "+i+" !"); finDuJeu=true; } if(casesPlateau[1]==i && casesPlateau[5]==i && casesPlateau[9]==i || casesPlateau[3]==i && casesPlateau[5]==i && casesPlateau[7]==i){ System.out.println("**************************\r\nVictoire du joueur "+i+" !"); finDuJeu=true; } } } } }



//factorielle d'un nombre package exoParistech1; public class Main { public static void main(String[] args) { int factorielle=1; int i; //pour chaque argument... for(String argument:args){ factorielle=1; //on essaie... try{ //..de multiplier en boucle l'argument en descendant d'un chiffre for(i=Integer.parseInt(argument);i>1;i=i-1){ factorielle*=i; } } catch(NumberFormatException e){ System.out.println("Merci d'entrer un nombre."); System.exit(0); } System.out.println("Factorielle de "+argument+"="+factorielle); } } }


Un bout de code pour faire continuer l'utilisateur s'il appuie sur entrée :

System.out.println("Appuyez sur entrée pour continuer...."); try{System.in.read();} catch(Exception e){}


Création d'une fenêtre Swing :


package testBoutonsSDZ; import javax.swing.JFrame; import javax.swing.JPanel; public class ClassePrincipale { public static void main(String[] args) { JFrame fenetrePrincipale=new JFrame(); fenetrePrincipale.setTitle(""); fenetrePrincipale.setSize(400, 300); fenetrePrincipale.setLocationRelativeTo(null); //Termine le programme si on ferme fenetrePrincipale.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //Il faut créer un JPanelpour manipuler des éléments à l'intérieur JPanel panneau=new JPanel(); //on associe le JPanel à notre fenêtre fenetrePrincipale.setContentPane(panneau); //on affiche la fenêtre (à la fin pour que son contenu soit rafraichit fenetrePrincipale.setVisible(true); } }


Avec des éléments (voir http://circe.univ-fcomte.fr/docs/java/tutorial.20060804/ui/features/components.html pour les éléments):

package testSwing; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JSlider; import javax.swing.KeyStroke; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.Arrays; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JTable; import javax.swing.JOptionPane; import javax.swing.table.DefaultTableModel; public class ClassePrincipale { public static void main(String[] args) { //1 - Créé la fenêtre JFrame fenetrePrincipale=new JFrame(); fenetrePrincipale.setTitle(""); fenetrePrincipale.setSize(400, 300); fenetrePrincipale.setLocationRelativeTo(null); fenetrePrincipale.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//termine le programme si on ferme //2 - Il faut créer un JPanelpour manipuler des éléments à l'intérieur JPanel panneau=new JPanel(); //3 - on associe le JPanel à notre fenêtre fenetrePrincipale.setContentPane(panneau); //4 - ajout d'un layout au Jpanel FlowLayout disposition = new FlowLayout(); panneau.setLayout(disposition); //5 - créé et positionne les éléments dans le Jpanel JLabel texte = new JLabel("Très impressionant"); panneau.add(texte); int dialogResult = JOptionPane.showConfirmDialog (fenetre, "Voulez-vous vraiment SUPPRIMER ce client ?","information",JOptionPane.YES_NO_OPTION); if(dialogResult == JOptionPane.YES_OPTION){ daoClient.supprimer(fenetre.getPanneauModifClient().getClient()); fenetre.afficheSelectionClient(); } JButton bouton = new JButton("Cliquez-moi"); panneau.add(bouton); JCheckBox caseACocher=new JCheckBox("Cochez-moi"); panneau.add(caseACocher); ArrayList<String> liste=new ArrayList<String>(Arrays.asList("Choisissez-moi !","Non, moi !", "Moi moi moi !")); JComboBox menuDeroulant=new JComboBox(liste.toArray()); panneau.add(menuDeroulant); JMenuBar menuBar = new JMenuBar(); JMenu menu = new JMenu("Un Menu"); menu.setMnemonic(KeyEvent.VK_A); menu.getAccessibleContext().setAccessibleDescription( "Description de ce menu"); menuBar.add(menu); JMenuItem menuItem = new JMenuItem("Un sous menu texte",KeyEvent.VK_T); menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_1, ActionEvent.ALT_MASK)); menuItem.getAccessibleContext().setAccessibleDescription("Description de ce sous menu"); menu.add(menuItem); fenetrePrincipale.setJMenuBar(menuBar); //message d'alerte JOptionPane.showMessageDialog(null, "Erreur de saisie"); JList elementListe=new JList(liste.toArray()); panneau.add(elementListe); JRadioButton boutonRadio=new JRadioButton("Tickez-moi"); JRadioButton boutonRadio2=new JRadioButton("Et moi en même temps !"); ButtonGroup group = new ButtonGroup(); group.add(boutonRadio); group.add(boutonRadio2); panneau.add(boutonRadio); panneau.add(boutonRadio2); JSlider curseurDefilement=new JSlider(1,0,10,5); panneau.add(curseurDefilement); DefaultTableModel modeleTable; modeleTable = new DefaultTableModel(); JTableNotes = new JTable(modeleTable); JTableNotes.setModel(modeleTable);//puis quandon aura les données modeleTable.addRow(new Object[]{... String produitRecherche = JOptionPane.showInputDialog(fenetre, "Entrez l'id du produit"); //popup avec champ de texte //6 - affiche la fenêtre fenetrePrincipale.setVisible(true); } }


A priori, une JTable stocke ce qu'elle affiche (des Strings, des Integers...), on ne peut pas stocker des objets tels que des étudiants, des produits...

Dans la première colonne de la table, on aura par exemple un identifiant de l'objet :

int numLigneSelectionnee = fenetre.getPanneauListeCategorie().getjTableProduits().getSelectedRow(); int numColonneselectionnee = fenetre.getPanneauListeCategorie().getjTableProduits().getSelectedColumn(); String numDeLObjet=String.valueOf(fenetre.getPanneauListeCategorie().getjTableProduits().getModel().getValueAt(0, numLigneSelectionnee));



Une classe héritante de JPanel isolée (bonne pratique), qui utilise la librairie design grid layout, facilitant la création d'interface :

package vue; import java.awt.GridLayout; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import modele.UnitesMesures; import net.java.dev.designgridlayout.DesignGridLayout; public class PanelResultat extends JPanel{ private Fenetre fenetre; private JLabel labelResultat; private JButton boutonRetour; public PanelResultat(Fenetre fenetre){ this.fenetre=fenetre; //4 - ajout d'un layout au Jpanel DesignGridLayout layout = new DesignGridLayout(this); //5 - créé et positionne les éléments dans le Jpanel labelResultat=new JLabel("ee"); boutonRetour=new JButton("Retour"); layout.row().grid().add(labelResultat); layout.row().grid().empty().add(boutonRetour); } }


Avec un JScrollPane :

package vue; import java.awt.Dimension; import java.util.ArrayList; import java.util.Arrays; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import net.java.dev.designgridlayout.DesignGridLayout; public class PanneauSelectionBanque extends JPanel { Fenetre fenetre; public PanneauSelectionBanque(Fenetre f){ this.fenetre=f; DesignGridLayout layout = new DesignGridLayout(this); //on liste les banques disponibles JList listeDeroulanteBanques=new JList(fenetre.getListBanques().toArray()); JScrollPane panelDeroulant=new JScrollPane(listeDeroulanteBanques); panelDeroulant.setPreferredSize(new Dimension(40, 0)); layout.row().grid().add(panelDeroulant); JButton boutonSelectionnerBanque = new JButton("Selectionner"); JButton boutonNouvelleBanque = new JButton("Créer une nouvelle banque"); layout.row().grid().add(boutonSelectionnerBanque); layout.row().grid().empty(); layout.row().grid().add(boutonNouvelleBanque); }


Si on a besoin de mettre à jour la JList, on peut utiliser un DefaultListModel (lorsqu'on mettra le modèle à jour la JList se mettra à jour aussi) :

listeBanque=new DefaultListModel(); for (int i=0; i<fenetre.getListBanques().toArray().length; i++) { listeBanque.addElement((Banque)fenetre.getListBanques().get(i)); } listeDeroulanteBanques=new JList<Banque>(listeBanque); listeDeroulanteBanques.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); JScrollPane panelDeroulant=new JScrollPane(listeDeroulanteBanques); panelDeroulant.setPreferredSize(new Dimension(40, 0));


C'est une bonne pratique d'avoir une fenêtre principale en tant que JFrame (public class FenetrePrincipale extends JFrame) et plusieurs panels différents qu'on peut changer de la façon suivante:

public Fenetre(Controleur controleur) { //code du constructeurs, différentes méthods etc... public void affichePanneauResultat(){ this.add(panneauResultat); this.revalidate(); this.repaint(); } public void afficheFormulaireDebut(){ this.add(panneauSaisie); this.revalidate(); this.repaint(); }


Ou même créer une fonction qui supprime le dernier panel utilisé et revalidate/repaint :

public void utilisePaneau(JPanel panneau){ if(dernierPanelUtilise!=null){ this.remove(dernierPanelUtilise); this.add(panneau); this.revalidate(); this.repaint(); dernierPanelUtilise=panneau; }else{ this.add(panneau); this.revalidate(); this.repaint(); dernierPanelUtilise=panneau; }



Pour l'aléatoire :

import java.util.Random; Random random = new Random(); random.nextInt(max - min + 1) + min


Timer:

package minuteur; import java.util.Timer; import java.util.TimerTask; //vu sur http://www.journaldunet.com/developpeur/tutoriel/jav/050623-java-repetition-timer-timertask.shtml public class RepetAction { Timer t; public RepetAction() { t = new Timer(); t.schedule(new MonAction(), 0, 1*1000); } class MonAction extends TimerTask { int nbrRepetitions = 3; public void run() { if (nbrRepetitions > 0) { System.out.println("Ca bosse dur!"); nbrRepetitions--; } else { System.out.println("Terminé!"); t.cancel(); } } } }


Puis évidemment dans le main : RepetAction verifHeureMin=new RepetAction();

Récupérer l'heure :

Calendar tempsActuel = Calendar.getInstance(); int heureActuelle = tempsActuel.get(Calendar.HOUR_OF_DAY);


Récupérer et formater la date (ancienne méthode) :

import java.util.Date; //tout en haut du code, avec les imports import java.text.DateFormat; import java.text.SimpleDateFormat; //plus bas, dans la classe Date dateDuJour = new Date(); //renvoie la date du jour DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); //permet de donner un format à la date reçue dateFormat.format(dateDuJour); //renvoie un String de lafaçon donton aformaté plus haut (2016/10/08 14:34:25 par ex)


Faire l'inverse, trouver la date depuis une chaine de caractère :

String maDateEnstring = "2017-12-13T13:00:00Z"; DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'"); try { Date date = format.parse(maDateEnstring); //on récupère en tant que Date } catch (ParseException e) {


Conversion UTC en heure de Paris :

Date dateUTC = new Date(1585440000000L); //la date est formée à partir timestamp linux en millisecondes, elle n'a pas de notion de timezone ! Par contre si on fait un toString, elle essaiera d'afficher dans la TimeZone de la machine. Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); //on veut une instance de calendar qui travaille en UTC calendar.setTime(dateUTC); //on lui donne le timestamp en milliseconde System.out.println(calendar.get(Calendar.HOUR_OF_DAY) + "h" + calendar.get(Calendar.MINUTE) + "min"); //affiche "0h0min" System.out.println(Long.toString(calendar.getTimeInMillis())); //"1585440000000L" aucun changement calendar.setTimeZone(TimeZone.getTimeZone("Europe/Paris")); //on change la timezone System.out.println(Long.toString(calendar.getTimeInMillis())); //toujours "1585440000000L" System.out.println(calendar.get(Calendar.HOUR_OF_DAY) + "h" + calendar.get(Calendar.MINUTE) + "min"); //affiche "1h0min" = calendar nous donne l'heure en décalant selon la nouvelle timezone }


Si vous avez juste besoin d'afficher la date en String dans une autre timezone, donner la timezone au SimpleDateFormat : simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));


Exemple pour créer une date au format UTC :

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); //on a mis "Z" mais il s'en fiche car Z est entre guillemets simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); //donc on doit lui dire que lorsqu'il va parser, il doit convertir en UTC Date dateFinJSuivant = simpleDateFormat.parse("2019-01-31T00:00:00Z");


Une date en string qu'on veut en string dans une autre timezone :

DateFormat format = new SimpleDateFormat(yyyy-MM-dd'T'HH:mm:ss'Z'); format.setTimeZone(TimeZone.getTimeZone("UTC")); try { Date dateTemp = format.parse(dateEnString); //on convertit le string en date selon la timezone UTC format = new SimpleDateFormat(yyyy-MM-dd'T'HH:mm:ss);//on voudra un nouveau format format.setTimeZone(TimeZone.getTimeZone("Europe/Paris"));//on le voudra en Europe/Paris return format.format(dateTemp); //on utilise de nouveau format pour convertir la date précédente } catch (ParseException e) { logger.error("Erreur lors de la conversion de la date."); }


Pour les dates, une nouvelle façon en Java 8, beaucoup plus simple pour ajouter des jours, calculer des intervalles etc :

//Ajouter 40 jours à une date : LocalDate.parse("2016-08-01").plusDays(40); //Formater la date : DateTimeFormatter formatter= DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm"); //import java.time.format.DateTimeFormatter, pas JodaTime ! String date="20/09/2018 15:30"; LocalDateTime localDateTime=LocalDateTime.parse(date,formatter); System.out.println(localDateTime); System.out.println(formatter.format(localDateTime)); //Calculer un âge : System.out.println("Age : "+Period.between(LocalDate.parse("2000-01-01"), LocalDate.now()).getYears());


Parcourir une hashmap :

import java.util.Map.Entry; //import à faire en haut du code ! for(Entry<Reference, StockArticle> entry : listeArticles.entrySet()) { //on peut utiliser entry.getValue() et //entry.getKey() }


Nouvelle méthode Java 8 :

items.forEach((k, v) -> { System.out.format("key: %s, value: %d%n", k, v); });


2e méthode sans import :

Iterator iterateurHashmapString=maBelleHashMapPourPrenoms.values().iterator(); while(iterateurHashmapString.hasNext()) { System.out.println(iterateurHashmapString.next()); }


Pour utiliser Collections.sort(maCollection) afin de trier un objet personnalisé, il faut implémenter Comparable et surcharger la méthode compareTo :

public class Piece implements Comparable<Piece> //... //... public int compareTo(Piece p) { //tri par ordre décroissant if(this.getValeur()>p.getValeur()){ return -1; } if(this.getValeur()<p.getValeur()){ return 1; } return 0; }


Concernant les valeurs -1, 1 et 0, elles permettent de savoir si l'objet est plus petit, plus grand ou égal : monObjet.compareTo(monAutreObjet) retournera un négatif si on veut que monObjet < monAutreObjet, un positif si monObjet > monAutreObjet, 0 sinon.
C'est extrèmement pratique, par exemple, si on a un objet avec plusieurs attributs et que certains attributs combinés rendent un objet prioritaire à un autre, il suffit d'implémenter comparable puis Collections.sort(maCollectionDObjets) pour les trier (attention, par défaut c'est trié du plus petit au plus grand -ou autrement dit du moins important au plus important... ça dépend de ce qui est trié).

Ne pas oublier qu'on peut comparer une date directement avec dateCommentaire.compareTo(o.dateCommentaire);

On peut aussi faire une "classe anonyme" (classe directement insérée dans une méthode) :

Collections.sort(listeClassement, new Comparator<Classement>() { //on classe la liste "listeClassement" //méthode anonyme @Override public int compare(Classement cl1, Classement cl2) { return cl2.getMoyenne().compareTo(cl1.getMoyenne()); //compareTo retourne 1, 0 ou -1 } });


Petite particularité, une classe anonyme n'a pas de constructeur. Mais il existe néanmoins un moyen de lancer quelque chose à l'initialisation de la classe anonyme, utiliser des "Initializer Block" (blocs initialiseurs) :

MaBelleClasseAnonyme maBelleInstance = new MaBelleClasseAnonyme() { List<String> maBelleListe = new ArrayList<String>(); { //pour remplir la map, on peut faire ça tm.put("maClef","maValeur"); } };



Pour les tableaux, il suffit de faire Arrays.sort(monTableau);. Pour faire une copie : System.arraycopy(tableau1,0,copieTableau1,0,3);.

Exemple api criteria :

public List<Eleve> rechercherparNomAPICriteria(String nom) //cherche avec la clef { CriteriaBuilder constructeurRequete = em.getCriteriaBuilder(); CriteriaQuery<Eleve> requeteCriteria = constructeurRequete.createQuery(Eleve.class); Root<Eleve> c = requeteCriteria.from(Eleve.class); ParameterExpression<String> p = constructeurRequete.parameter(String.class); requeteCriteria.select(c).where(constructeurRequete.equal(c.get("nom"), nom)); Query requete = em.createQuery(requeteCriteria); return requete.getResultList(); }



En java 8 on peut faire :
maListe.sort((o1, o2) -> o1.getDateDebut().compareTo(o2.getDateDebut()));

Ou même :
ctrsoDuringSidre.sort(Comparator.comparing(CtrsoEntity::getDateDebut));

JPQL


Java Persistent Query Language. Point commun avec SQL : la structure de la requête est similaire, elsmots réservés aussi (select, from where, group by, having...) à tel point qu'il est facile de les confondre. Contrairement à SQL, JPQL ne fait pas de requête sur la structure de la base mais sur le modèle objet.

Avantages de JPQL : il est universel (compatible toutes bases) et on garde une logique objet.

Un exemple select p from Produit p, où l'on voit que la majuscule est obligatoire car on interroge la classe.


package acces; import java.util.List; import model.Appartement; public class DaoAppartement extends Dao { public List<Appartement> listerAppartementsGeresParCarole() { return em.createQuery("select a.ref from Appartement a where a.representant.nomrep='Palege'",Appartement.class).getResultList(); } public List<Appartement> listerAppartementsGeresParCaroleAVillefranche() { return em.createQuery("select a.ref from Appartement a where a.representant.nomrep='Palege' AND a.client.villeclt='Villefranche'",Appartement.class).getResultList(); } public List<Object[]> renvoyerMoyennePrixDesAppartementsParSecteur(){ return em.createQuery("select a.secteur,AVG(a.pxvente) from Appartement a GROUP BY a.secteur",Object[].class).getResultList(); } public Long renvoyerNombreAppartementsALaSuperficieSupA80(){ return em.createQuery("select count(a) from Appartement a where a.superficie>80",java.lang.Long.class).getSingleResult(); } }


Pour récupérer les résultats d'un "group by", étant donné qu'on a pas de classe, on va avoir un tableau d'objets :

List<Object[]> lo=daoAppartement.renvoyerMoyennePrixDesAppartementsParSecteur(); for (Object[] tabObjet : lo) { System.out.println("Objet -->"+tabObjet[0]); System.out.println("Objet -->"+tabObjet[1]); }


Autre exemple de requêtes :
select p.designation,AVG(p.prix) from Produit p GROUP BY p.designation HAVING AVG(p.prix)>300

Convertir un BigDecimal en Double : java.math.BigDecimal bd1 = new java.math.BigDecimal(totalNotesGenerales/ListeResultatsEtudiant.size()); ou c.setMoyenneGenerale(BigDecimal.valueOf(totalNotesGenerales/ListeResultatsEtudiant.size()));.

MD5 :

//on vérifie le mot de passe String plaintext = motDePasse; MessageDigest m = null; try { m = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException ex) { Logger.getLogger(ControlConnexion.class.getName()).log(Level.SEVERE, null, ex); } m.reset(); m.update(plaintext.getBytes()); byte[] digest = m.digest(); BigInteger bigInt = new BigInteger(1,digest); String hashtext = bigInt.toString(16); // Now we need to zero pad it if you actually want the full 32 chars. while(hashtext.length() < 32 ){ hashtext = "0"+hashtext; }



Annotation


Des "metadonnées", informations en plus du code source qui peuvent servir à donner des informations au compilateur, à un framework, un collègue...

Par exemple @Override permet d'indiquer qu'une méthode fille remplace une méthode mère (générera une erreur à l'écriture/compilation si ce n'est pas le cas).
@FonctionnalInterface indique qu'une interface ne peut avoir qu'une seule méthode abstraite.
@SuppressWarning cache certaines erreurs @SuppressWarnings({"rawtypes","unchecked"}) par exemple.
On peut mettre @Deprecated sur une de nos classes/entités pour indiquer qu'elle est déprécié et qu'on devrait cesser de l'utiliser.

Il est possible de créer ses propres annotations, et d'autres annotations permettent de leur définir des comportements. Par exemple, @Target permet de préciser sur quels éléments une annotation sera utilisable.

Par exemple :

import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE}) //Cette annotation s'appliquera sur les classes @Retention(RetentionPolicy.RUNTIME) public @interface SmartPhone { String os() default "Nougat"; //l'annotation aura 2 paramêtres, avec Nougat comme valeur par défaut int version() default 1; //et 1 }


On peut maintenant utiliser cette annotation sur une classe :

@SmartPhone (os="KitKat",version=7) //on définit des valeurs (si non, ça aurait été les valeurs par défaut qui auraient été mises) public class Telephone { String modele; int taille; public Telephone(String modele, int taille) { this.modele = modele; this.taille = taille; } }



Puis, pour récupérer les éléments de l'annotation, c'est un peu compliqué :

Telephone t = new Telephone("Xperia", 1); Class maBelleClasse = t.getClass(); //on récupère la classe de l'objet Telephone Annotation an = maBelleClasse.getAnnotation(SmartPhone.class); //on a besoin de cette classe pour récupérer l'annotation de SmartPhone.class SmartPhone s=(SmartPhone)an; //on caste l'annotation reçue dans le type particulier de notre annotation (Smartphone) System.out.println(s.os()); //pour pouvoir utiliser les méthodes de notre annotation. Renverra "KitKat".


Exemple MVC



import vue.Fenetre; public class Demarrage { public static void main(String[] args){ Fenetre fenetre=new Fenetre(); } }



package vue; import java.awt.BorderLayout; import java.awt.FlowLayout; import java.awt.GridLayout; import java.util.Observable; import java.util.Observer; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTextField; import controleur.ControleFenetre; import modele.Convertisseur; public class Fenetre implements Observer { private JFrame fenetrePrincipale; private JLabel labelKm, labelMiles; private JTextField champTexteKm; private JTextField champTexteMiles; private JButton boutonreset, boutonConvertir; private ControleFenetre controleFenetre; private Convertisseur convertisseur; public Fenetre(){ convertisseur=new Convertisseur(); controleFenetre=new ControleFenetre(this,convertisseur); //association entre vue et controleur //1 - Créé la fenêtre JFrame fenetrePrincipale=new JFrame(); fenetrePrincipale.setTitle(""); fenetrePrincipale.setSize(400, 300); fenetrePrincipale.setLocationRelativeTo(null); fenetrePrincipale.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //2 - Il faut créer un JPanelpour manipuler des éléments à l'intérieur JPanel panneau=new JPanel(); //3 - on associe le JPanel à notre fenêtre fenetrePrincipale.setContentPane(panneau); //4 - ajout d'un layout au Jpanel BorderLayout dispositionNSEO = new BorderLayout(); panneau.setLayout(dispositionNSEO); //5 - créé et positionne les éléments dans le Jpanel JPanel JPanelCentre=new JPanel(); panneau.add(JPanelCentre,dispositionNSEO.CENTER); FlowLayout dispositionGrille = new FlowLayout(); JPanelCentre.setLayout(dispositionGrille); labelKm=new JLabel("Km "); JPanelCentre.add(labelKm); champTexteKm=new JTextField(5); JPanelCentre.add(champTexteKm); labelMiles=new JLabel("Miles "); JPanelCentre.add(labelMiles); champTexteMiles=new JTextField(5); JPanelCentre.add(champTexteMiles); JPanel JPanelSud=new JPanel(); panneau.add(JPanelSud,dispositionNSEO.SOUTH); JButton boutonReset=new JButton("RESET"); JPanelSud.add(boutonReset); JButton boutonConvertir=new JButton("CONVERTIR"); JPanelSud.add(boutonConvertir); convertisseur.addObserver(this); fenetrePrincipale.setVisible(true); boutonConvertir.addActionListener(controleFenetre); boutonReset.addActionListener(controleFenetre); } @Override public void update(Observable o, Object arg) { //réagit à un changement annoncé dans Observable o //arg argument éventuellement transmis this.lireModeleEtAppliqueModifDansAffichage(); } public void remiseAZeroChamps(){ champTexteKm.setText(""); champTexteMiles.setText(""); } public JTextField getChampTexteKm() { return champTexteKm; } public void lireModeleEtAppliqueModifDansAffichage(){ //on récupère la valeur de km et met à jour l'affichage //au cas où il y ait eu un reset champTexteKm.setText(String.valueOf((double)Math.round(convertisseur.getKm()*100)/100)); //on met à jour la valeur du champ miles champTexteMiles.setText(String.valueOf((double)Math.round(convertisseur.getMiles()*100)/100)); } public void afficheErreurSaisie(){ JOptionPane.showMessageDialog(null, "Erreur de saisie"); } }



package controleur; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JFrame; import controleur.ControleFenetre; import modele.Convertisseur; import vue.Fenetre; public class ControleFenetre implements ActionListener{ private Convertisseur convertisseur; private Fenetre fenetre; public ControleFenetre(Fenetre f, Convertisseur c){ this.fenetre=f; //association entre vue et controleur this.convertisseur=c; //association entre modele et controleur } @Override public void actionPerformed(ActionEvent e) { //throws Exception marche pas ici if(e.getActionCommand().equals("RESET")){ //le controleur reset les valeurs l'objet convertisseur (puis cet objet notifiera la vue) convertisseur.setKm(0); convertisseur.setMiles(0); } if(e.getActionCommand().equals("CONVERTIR")){ try { //change l'attribut miles de l'objet convertisseur par la valeur du champ km*0.62 convertisseur.convertir(Double.parseDouble(fenetre.getChampTexteKm().getText())); } catch (NumberFormatException e1) { // TODO Auto-generated catch block fenetre.afficheErreurSaisie(); } } } }



package modele; import java.util.Observable; public class Convertisseur extends Observable{ private double km; private double miles; public void setMiles(double miles) { this.miles = miles; this.setChanged(); //un boolean passe de true à false dans observable this.notifyObservers(); //on dit à la fenêtre qu'on a changé } public void convertir(double km){ this.setMiles(km*0.62); this.setKm(km); } public double getKm() { return km; } public void setKm(double km) { this.km=km; this.setChanged(); //un boolean passe de true à false dans observable this.notifyObservers(); //on dit à la fenêtre qu'on a changé } public double getMiles() { return miles; } }


Un message d'erreur qui se ferme tout seul après X secondes :

public void genererMsgBoxErreurFermetureAuto(String message){ JOptionPane pane = new JOptionPane(message, JOptionPane.ERROR_MESSAGE); MsgBoxErreurFermetureAuto = pane.createDialog(this, "Erreur"); ComponentAdapter adapteurPourFermeture=new ComponentAdapter() { @Override public void componentShown(ComponentEvent e) { super.componentShown(e); t = new Timer(10000,controleur); t.setActionCommand("finTimerMsgBoxErreur"); t.start(); } }; MsgBoxErreurFermetureAuto.addComponentListener(adapteurPourFermeture); MsgBoxErreurFermetureAuto.setVisible(true); }


Pareil mais avec des choix :

JButton boutonChangerMdp=new JButton("Changer le mot de passe"); boutonChangerMdp.setActionCommand("demandeChgrMdp"); boutonChangerMdp.addActionListener(controleur); JButton boutonFermer=new JButton("Fermer le programme"); boutonFermer.setActionCommand("demandeFermeture"); boutonFermer.addActionListener(controleur); JButton[] boutonsChoix = {boutonChangerMdp,boutonFermer}; JLabel messagePopUpProposeChangementMdp=new JLabel("Le serveur refuse la connexion avec les informations fournies dans config.properties. Essayez de changer le mot de passe. Sinon, supprimez le fichier config.properties et relancez l'application."); JLabel messageFermeturePopUpProposeChangmntMdp=new JLabel("(Fermeture automatique dans quelques secondes...)"); JLabel[] labels={messagePopUpProposeChangementMdp,messageFermeturePopUpProposeChangmntMdp}; JOptionPane msg = new JOptionPane(labels, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_OPTION,null,boutonsChoix,boutonsChoix[1]); dialogPopUpProposeChangementMdp = msg.createDialog("Accès refusé par le serveur SQL"); dialogPopUpProposeChangementMdp.setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE); ComponentAdapter adapteurPourFermeture=new ComponentAdapter() { @Override public void componentShown(ComponentEvent e) { super.componentShown(e); t = new Timer(10000,controleur); t.setActionCommand("fenetreDemandantMdpArriveFinTimer"); t.start(); } }; dialogPopUpProposeChangementMdp.addComponentListener(adapteurPourFermeture); dialogPopUpProposeChangementMdp.setVisible(true);



public String afficherFenetreChangementRetournerMdp(){ timerAvantFermeture.stop(); JPasswordField pf = new JPasswordField(); int okCxl = JOptionPane.showConfirmDialog(null, pf, controleur.getGp().getUtilisateur()+"@"+controleur.getGp().getCheminbase(), JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); String mdp=""; if (okCxl == JOptionPane.OK_OPTION) { mdp = new String(pf.getPassword()); }else{ System.out.println("clic sur croix"); } return mdp; }


Passer un string d'un encodage à l'autre :

String maChaine=new String(monAutreChaine.getBytes(StandardCharsets.ISO-8859-1),StandardCharsets.UTF_8);



JUnit


Sous intelliJ, alt+enter sur la classe que l'on veut tester puis "create test". Pour lancer le test, se mettre au début de la classe, un petit bouton va apparaitre pour lancer les tests.

Exemple avec un test qui teste deux méthodes de la classe Traitement :

package com.test; import com.monpackage.Traitement; import static org.junit.jupiter.api.Assertions.*; class TraitementTest { Traitement test; @org.junit.jupiter.api.BeforeEach //sera lancé avant chaque test void setUp() { test=new Traitement(); //l'objet traitement sera instancié avant chaque test } @org.junit.jupiter.api.Test void concatener() { String resultat=test.concatener("a","a"); assertEquals("aa", resultat); //vérifie si ce qu'on attend est identique à la variable résultat } @org.junit.jupiter.api.Test void multiplier() { int resultat=test.multiplier(2,2); assertEquals(4, resultat); //vérifie si ce qu'on attend est identique à la variable résultat } }


Au niveau de l'arborescence, les tests se positionnent "en miroir" par rapport aux classes à tester. Par exemple, pour tester src/main/java/com/monappli/MaBelleClasse on mettra le test dans test/main/java/com/monappli/MaBelleClasse. Il doit donc y avoir 2 dossiers resources (un sous src/main et un sous test/main).

Mockito


Une librairie pour générer des fausses données qui seront testées avec JUnit.

Exemple de tests avec Spring boot :

package mon.beau.package; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.when; import static org.mockito.ArgumentMatchers.any; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import java.util.Optional; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.beans.factory.annotation.Autowired; import ch.qos.logback.core.net.server.Client; //autres imports pour les beans du projet... @RunWith(MockitoJUnitRunner.class) public class AccountRestServiceTest { @InjectMocks private AccountRestService accountRestService; @Mock private AccountRepository accountRepository; @Mock private CurrentAccountRepository currentAccountRepository; @Mock private SavingsAccountRepository savingAccountRepository; @Mock private CustomerRepository customerRepository; @Before public void init() { MockitoAnnotations.initMocks(this); System.out.println("-------nouveau test--------"); } @Test(expected = LExceptionQueJattends.class) public void createCurrentAccountWithNoClient() throws ApplicationException { // GIVEN Customer client = new Customer(); client.setFirstName("Toto"); client.setLastName("Toto"); client.setId(1l); CurrentAccount cc = new CurrentAccount(); cc.setCustomer(client); cc.setBalance(100d); cc.setEntitled("Mon compte"); cc.setOverdraftLimit(300d); when(customerRepository.findById(1l)).thenReturn(Optional.empty()); //WHEN accountRestService.createCurrentAccount(cc); //THEN fail("Mon test est KO"); } @Test public void createCurrentAccountClient() throws ApplicationException { // GIVEN Customer client = new Customer(); client.setFirstName("Toto"); client.setLastName("Toto"); client.setId(1l); client.setAccounts(new ArrayList<>()); CurrentAccount cc = new CurrentAccount(); cc.setCustomer(client); cc.setBalance(100d); cc.setEntitled("Mon compte"); cc.setOverdraftLimit(300d); when(customerRepository.findById(1l)).thenReturn(Optional.of(client)); when(accountRepository.save(any())).thenReturn(cc); //WHEN Account ccRetour = accountRestService.createCurrentAccount(cc); //THEN assertTrue(ccRetour != null); assertTrue(ccRetour.getEntitled().equals(cc.getEntitled())); } }


Il existe également @MockBean sous Springboot (il sera injecté comme bean dans les classes qui en ont besoin), mais attention il ne va pas mocker les méthodes qui ne sont pas liées au bean (méthodes utilitaires privates dans la classe par ex, le corps des méthodes sera null). Ci-dessous un exemple de test unitaire REST :

@RunWith(SpringRunner.class) @AutoConfigureMockMvc @SpringBootTest public class Ma_belle_classe_de_test { @Autowired private MockMvc mvc; //utile pour tester les endpoints @MockBean private BeanAppeleParMonControllerRest beanAppeleParMonControllerRest; //il sera injecté dans le controleur rest @Test public void should_call_ma_belle_fonction() throws Exception { //GIVEN when(BeanAppeleParMonControllerRest.interroBDD(any())).thenReturn(false); //si interro de la BDD, le mocker //WHEN ResultActions result = mvc.perform(get("/monBeauEndPoint") //le controleur sera appelé par là //on pourrait enchainer plein de chose genre .with(user("monBeluser").authorities(new SimpleGrantedAuthority("ceQuiAEteMisDansLeChampPreAuthorize"))) .param("monParam", "laValeurDeCeParam")); //ça marche avec des string, des int //THEN Mockito.verify(beanAppeleParMonControllerRest, times(1)).maBelleFonction( laValeurDeCeParam //on peut faire un New Quelquechose() ici si on utilise des @ModelAttribute ); result.andExpect(status().isOk()); } }


Il existe aussi org.springframework.test.util.ReflectionTestUtils pour invoquer des méthodes privées ou remplir des attributs privés avec Spring (sinon... PowerMock).

En cas de java.lang.IllegalArgumentException: Name for argument type [int] not available, and parameter name information not found in class file either., mettez bien lesparam dans l'annotation PathVariable de votre controleur (@PathVariable("monBeauParam") int monBeauParam).

On peut vérifier ce que fait le verify. Par exemple, si on a un repository qui saveAll et qu'on veut vérifier ce qui est sauvé :

ArgumentCaptor<List<MonEntity>> argument = ArgumentCaptor.forClass(List.class); //sera alimenté lors du saveAll verify(ctrsoRepository).saveAll(argument.capture()); List<MonEntity> maListeDEntitiesQuiAEteSauvee = argument.getValue(); //il existe aussi getAllValues si le saveAll est appelé plein de fois


Autre exemple :

//Given ArgumentCaptor<String> argumentString = ArgumentCaptor.forClass(String.class); //on prépare la capture de l'url ArgumentCaptor<Map<String, String>> argumentMap = ArgumentCaptor.forClass(Map.class); //on prépare la capture de la map de param //When serviceQuiFaitUnPut.faireUnPut(); //à l'interieur de la méthode il va appeler une url avec une map de param //on vérifie ce que la méthode a appelé quand elle a fait "http.put(url, null, paramètres)" Mockito.verify(http, Mockito.times(1)).put(argumentString.capture(), ArgumentMatchers.isNull(), argumentMap.capture()); Map<String, String> paramsCaptures = argumentMap.getValue(); assertEquals(urlCaptures, maBelleUrl); assertEquals(paramsCaptures.get("maBelleClefDeMapQuOnEnvoieSurLUrl"), "monBeauParam");


Ancienne façon de faire des tests :

package com.test; import com.monpackage.Traitement; import org.junit.jupiter.api.*; import org.mockito.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; class TraitementTest { Traitement test; @BeforeEach //sera lancé avant chaque test² void setUp() { test=mock(Traitement.class); //ici on créé une fausse instance d'un objet Traitement when(test.concatener("a","a")).thenReturn("aa"); //ici on fait semblant d'utiliser la méthode concatener when(test.multiplier(2,2)).thenReturn(4); //pareil ici, on simule l'utilisation de la méthode multiplier } @Test void concatener() { String resultat=test.concatener("a","a"); assertEquals("aa", resultat); //vérifie si ce qu'on attend est identique à la variable résultat } @Test void multiplier() { int resultat=test.multiplier(2,2); assertEquals(5, resultat); //vérifie si ce qu'on attend est identique à la variable résultat } }


Si vous avez besoin du contenu d'un fichier de properties, mettez le dans test/resources.

On peut donner de fausses valeurs à un bundle (https://stackoverflow.com/questions/18377160) :

//plutôt que de lire fichier.properties, on va donner un faux bundle final ResourceBundle bundleMock = new ResourceBundle() { TreeMap<String, String> tm = new TreeMap<String, String>(); { //pas de constructeur dans les classes anonymes mais des euh, blocs initialiseurs tm.put("maclef","mavaleur"); } protected void setParent( ResourceBundle parent ) { // doit être overwritté sinon ResourceBundle.getBundle(String) fait une boucle infinie à priori } @Override protected Object handleGetObject( String key ) { return tm.get( key ); } @Override public Enumeration<String> getKeys(){ return Collections.enumeration( tm.keySet() ); } }; //il faut un control qui... euh... sert je crois à définir quel bundle est lu ResourceBundle.Control controlMock = new ResourceBundle.Control() { public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload ) { return bundleMock; } }; //Là on va charger le fichier avec notre control perso. Les valeurs sont gardées en cache. ResourceBundle.getBundle( "fichier", controlMock);


Avec Spring on peut aussi overrider un seul param :@SpringBootTest(properties = { "param.dans.fichier.de.properties=valeur" })

Pour tester un log le plus simple est de lui mettre un appender custom qui va enregistrer tous les logs qui passent :

public static class TestAppender extends AppenderSkeleton { public List<String> messages = new ArrayList<String>(); public void close() { } public boolean requiresLayout() { return false; } @Override public void append(LoggingEvent event) { messages.add(event.getMessage().toString()); } }


DBUnit


Pour tester des DAO qui vont chercher des infos en BDD par exemple.

Il me semble que DBUnit va alimenter la vraie BDD d'après un XML, puis laisser le code java y accéder etc, puis remettre les anciennes données (?).

Pour exporter les données de sa BDD en xml :

Class.forName("oracle.jdbc.OracleDriver"); Connection jdbcConnection = DriverManager.getConnection( "jdbc:oracle:thin:@localhost:1521/MABDD", "MonUser", "MonMdp"); IDatabaseConnection connection = new DatabaseConnection(jdbcConnection); QueryDataSet partialDataSet = new QueryDataSet(connection); partialDataSet.addTable("MaPremiereTable"); partialDataSet.addTable("MaSecondeTable"); FlatXmlDataSet.write(partialDataSet, new FileOutputStream("mesBellesDonnees.xml"));


Charger les données. Dans l'exemple ci-dessous on remplace un pattern de notre xml par un objet java (en l'occurence une date).

DataSourceDatabaseTester dataSourceDatabaseTester = new DataSourceDatabaseTester(dataSource); IDataSet dataSet = new FlatXmlDataSetBuilder().build(new FileInputStream(getClass().getResource("/datasets/mesBellesDonnees.xml").getPath())); ReplacementDataSet rDataSet = new ReplacementDataSet(dataSet); rDataSet.addReplacementObject("LaChaineQuonVeutRemplacerParDate", new Date()); dataSourceDatabaseTester.setDataSet(rDataSet); dataSourceDatabaseTester.onSetup();


Tester des méthodes privées


Devoir tester des méthodes privées est souvent révélateur d'une mauvaise organisation du code : plus le code sera facilement testable, moins il sera monolothique et plus il sera facile de le faire évoluer.

Néanmoins, pour les vieilles applications, on ne peut pas se permettre de refactorer toute l'appli pour tester car on risque d'introduire des regressions. A ce moment là, ça me parait normal de vouloir tester de façon unitaire les méthodes privées.
Ci dessous un exemple avec Powermock, il accède aux méthodes privées avec Whitebox.invokeMethod(new MaClasseATester(), "maMethodePrivee", sonArgument);.


package mon.beau.package; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.reflect.Whitebox; import static org.junit.Assert.assertEquals; import static org.powermock.api.mockito.PowerMockito.mock; import static org.powermock.api.mockito.PowerMockito.when; @RunWith(PowerMockRunner.class) public class MaClasseDeTestTest { @Test public void should_do_something() throws Exception { //GIVEN MaClasseResultat resultatAttendu = new MaClasseResultat(); //WHEN MaClasseResultat vraiResultat = Whitebox.invokeMethod(new MaClasseATester(), "maMethodePrivee", "ArgumentString"); //THEN assertEquals(vraiResultat, resultatAttendu); }


Au niveau des dépendances :

<dependency> <groupId>junit</groupId> <artifactId>junit-dep</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>1.5.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito</artifactId> <version>1.5.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>3.1.0</version> <scope>test</scope> </dependency>


Mocker des méthodes statiques


Autre signe révélateur d'une mauvaise organisation du code, mais parfois on a pas le choix sur des vieilles applis.

Un exemple ci-dessous. PowerMockito doit "préparer" la classe statique (@PrepareForTest) avant de pouvoir la mocker :

package mon.beau.package; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.reflect.Whitebox; import static org.junit.Assert.assertEquals; import static org.powermock.api.mockito.PowerMockito.mock; import static org.powermock.api.mockito.PowerMockito.when; @RunWith(PowerMockRunner.class) @PrepareForTest({UneClasseStatique.class, UneAutreClasseStatique.class})//ici on met les classes à mocker et les classes que l'on veut observer public class ModeleMetierRemplissageTest { @Test public void should_do_something() throws Exception { //GIVEN PowerMockito.mockStatic(UneClasseStatique.class); when(UneClasseStatique.maMethodeStatique()).thenReturn(new ObjetRetourne()); PowerMockito.mockStatic(UneAutreClasseStatique.class); when(UneAutreClasseStatique.maMethodeStatique()).thenReturn(new AutreObjetRetourne()); MaClasseATester maClasseATester = new MaClasseATester(); //WHEN //cette classe utilise les 2 méthodes statiques vues plus haut ! C'est pour ça qu'il a fallu les mocker. MaClasseResultat result = maClasseATester.action(); //THEN assertEquals(vraiResultat, resultatAttendu); }


Détection si un "new" a été appelé :

import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Mockito.mock; import static org.powermock.api.mockito.PowerMockito.whenNew; import static org.mockito.Mockito.verify; @RunWith(PowerMockRunner.class) @PrepareForTest(ClasseDontOnveutVerifierSiNewAppele.class) public class PowerMockDemoTest { @Test public void testMockNew() throws Exception { LaClasseATester instanceDeLaClasseATester = new LaClasseATester(); ClasseDontOnveutVerifierSiNewAppele instanceVerif = mock(ClasseDontOnveutVerifierSiNewAppele.class); whenNew(ClasseDontOnveutVerifierSiNewAppele.class).withAnyArguments().thenReturn(instanceVerif); verify(instanceVerif,times(1)).methodeAppeleeParLaClasseNew(); //ici on vérifie si la méthode x de la classe new est appelée, par exemple un setter //La façon de tester dépendra du code que vous êtes en train de tester, de ce que fait la classe après avoir été invoquée (Une méthode appelée ? Elle est retournée ? Un champ modifié ?) } }


Détecter si une méthode statique a été appelée (avec ses arguments), cf https://stackoverflow.com/a/50157861/8253373 :

import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Mockito.mock; import static org.powermock.api.mockito.PowerMockito.whenNew; import static org.mockito.Mockito.verify; @RunWith(PowerMockRunner.class) @PrepareForTest(ClasseDontOnveutVerifierSiMethodeStatiqueAppele.class) public class PowerMockDemoTest { @Test public void testMockNew() throws Exception { PowerMockito.mockStatic(ClasseDontOnveutVerifierSiMethodeStatiqueAppele.class); //appeler le vrai code qui appelle la méthode statique PowerMockito.verifyStatic(); //bizarrement ça se fait en 2 lignes, celle là et celle du dessous ClasseDontOnveutVerifierSiMethodeStatiqueAppele.methodeStatique(argumentEventuel);//si le vrai code n'a pas appelé notre classe statique avec les vrais arguments notre test echouera là } }


Assurez-vous que le when" qui vous utilisez soit bien le "when" de powermock et pas celui de mockito.
Si vous avez des OverlappingFileLockException avec Powermock+jacoco, vous pouvez essayer @PowerMockIgnore("org.jacoco.agent.rt.*") au dessus de vos tests. Il est également souvent intéressant d'ignorer "javax.net.ssl.*".

En cas de MockClassLoader cannot access jdk/internal/reflect superclass jdk.internal.reflect.MagicAccessorImpl, essayer @PowerMockIgnore("jdk.internal.reflect.*") au niveau de la classe ou même :

@RunWith(PowerMockRunner.class) @PowerMockIgnore({"javax.management.", "com.sun.org.apache.xerces.", "javax.xml.", "org.xml.", "org.w3c.dom.", "com.sun.org.apache.xalan.", "javax.activation.*"}) public class PowerMockitoBaseRunner { }


Java FX


Successeur de Swing pour les interfaces graphiques, autrefois intégré au JRE mais devenu une biblio à part depuis Java 11.

Une API, qui s'organise de façon moderne avec des fichiers XML (FXML) et CSS, rafraichit automatiquement la vue, meilleures performances graphiques.
Peut s'interfacer avec Swing mais ne partagent pas le même thread.

Dans Eclipse, Help->Install new software->chercher et installer "e(fx)clipse".

Scene Graph


Un arbre de nodes (ayant chacun une ID, un style et un volume). A la base, on trouve le node "root". On peut rajouter des effets sur chacun des nodes, par exemple une ombre portée.
Dans chaque node on va mettre du texte, des images, des boutons, des navigateurs intégrés, etc.

Dans un programme Java FX on aura un stage("théatre"), dans lequel se dérouleront des scènes où seront les nodes.


Exemples



package application; import javafx.application.Application; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.TextField; import javafx.scene.layout.GridPane; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.scene.text.Text; import javafx.stage.Stage; public class Main extends Application { @Override public void start(Stage primaryStage) { StackPane root=new StackPane(); GridPane grid= new GridPane(); grid.setAlignment(Pos.CENTER); grid.setHgap(10); grid.setVgap(10); grid.setPadding(new Insets(25,25,25,25)); Text textLogin=new Text("Login :"); textLogin.getStyleClass().add("login-text"); TextField textField=new TextField(); grid.add(textLogin, 0, 0, 2, 1); grid.add(textField, 1,1); Rectangle rec=new Rectangle(125,500,100,20); rec.setFill(Color.DARKBLUE); root.getChildren().add(rec); Button btn=new Button(); btn.setText("cliquez-moi"); //cette ligne est équivalente à : /* btn.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent arg0) { System.out.println("coucou"); } }); */ btn.setOnAction(event->System.out.println("coucou")); root.getChildren().add(grid); Scene scene=new Scene (root, 300, 250); scene.getStylesheets().add(Main.class.getResource("style.css").toExternalForm()); //fichier css dans le package directement primaryStage.setTitle("on beau titre de fenêtre!"); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String[] args) { launch(args); } }


Avec dans le fichier css :

.root { -fx-background-image: url("backgroundstylevista.png"); } .login-text{ -fx-text-fill:red; -fx-fill: white; }



Exemple avec un fichier fxml, un clic sur le bouton affiche un nouveau texte dans le textField :

<?xml version="1.0" encoding="UTF-8"?> <?import javafx.lang.*?> <?import javafx.util.*?> <?import javafx.scene.*?> <?import javafx.scene.layout.*?> <?import javafx.scene.text.*?> <?import javafx.scene.control.*?> <GridPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.SampleController" alignment="CENTER" hgap="10" vgap="10"> <Text fx:id="monTexte" text="Message aléatoire" GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.columnSpan="2"/> <TextField fx:id="textFieldPif" GridPane.columnIndex="1" GridPane.rowIndex="1" GridPane.columnSpan="10"/> <Button fx:id="boutonGenerer" text="Cliquez pour générer un message aléatoire" GridPane.columnIndex="1" GridPane.rowIndex="2" onAction="#clicSurLogin"/> </GridPane>


SampleController.java :

package application; import java.util.Random; import javafx.fxml.FXML; import javafx.scene.Node; import javafx.scene.control.Button; import javafx.scene.control.TextField; import javafx.scene.effect.DropShadow; import javafx.scene.effect.Reflection; import javafx.scene.input.MouseEvent; import javafx.scene.text.Text; public class SampleController { Random random = new Random(); @FXML private TextField textFieldPif ; //l'attribut doit avoir le même nom que son fx:id @FXML private Text monTexte ; //l'attribut doit avoir le même nom que son fx:id @FXML private Button boutonGenerer; @FXML public void initialize() { //Lorsque le controlleur sera initialisé DropShadow ombrePortee=new DropShadow(); //on applique un effet ombre portée si la souris est sur le bouton boutonGenerer.addEventHandler( MouseEvent.MOUSE_ENTERED, (MouseEvent e)->{ boutonGenerer.setEffect(ombrePortee); }); boutonGenerer.addEventHandler( MouseEvent.MOUSE_EXITED, (MouseEvent e)->{ boutonGenerer.setEffect(null); }); } @FXML private void clicSurLogin() { String[] strings= {"lol","lul","lil"}; textFieldPif.setText(strings[random.nextInt(2 - 0 + 1)]); Reflection r=new Reflection(); r.setFraction(0.9); textFieldPif.setEffect(r); } }


Main :

package application; import javafx.application.Application; import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene.layout.BorderPane; import javafx.scene.layout.GridPane; import javafx.fxml.FXMLLoader; public class Main extends Application { @Override public void start(Stage primaryStage) { try { GridPane root = (GridPane)FXMLLoader.load(getClass().getResource("Sample.fxml")); Scene scene = new Scene(root,400,400); scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm()); primaryStage.setScene(scene); primaryStage.show(); } catch(Exception e) { e.printStackTrace(); } } public static void main(String[] args) { launch(args); } }


Layout


Comme en Swing, différent layouts combinables existent pour organiser nos éléments.
-BorderPane : Offre des zones "top/bottom/right/left/center" pour y mettre les éléments.
-HBox/VBox : arrangement horizontal/Vertical des éléments (attention, empêchent la fenêtre d'être redimensionnée si ça cache les élements).
StackPane : Empile les éléments.
-GridPane : Une grille.
-FlowPane : Align les éléments horizontalement ou verticalement avec un retour à la ligne.
-AnchorPane : Ancre l bord d'un élément sur un côté ou au centre.

Exemple de menu



package application; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.scene.control.Menu; import javafx.scene.control.MenuBar; import javafx.scene.control.MenuItem; import javafx.scene.layout.BorderPane; import javafx.stage.Stage; public class Main extends Application { @Override public void start(Stage primaryStage) { try { MenuBar menuBar=new MenuBar(); Menu fichierMenu = new Menu("Fichier"); MenuItem nouveauMI=new MenuItem("Nouveau"); Menu recentMI=new Menu("Récent"); MenuItem dernierFichierMI=new MenuItem("Mon dernier fichier"); MenuItem avantDernierFichierMI=new MenuItem("Mon avant-dernier fichier"); recentMI.getItems().addAll(dernierFichierMI,avantDernierFichierMI); Menu quitterMenu=new Menu("Quitter"); fichierMenu.getItems().addAll(nouveauMI, recentMI); menuBar.getMenus().addAll(fichierMenu, quitterMenu); BorderPane root = (BorderPane)FXMLLoader.load(getClass().getResource("Sample.fxml")); root.setTop(menuBar); Scene scene = new Scene(root,400,400); scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm()); primaryStage.setScene(scene); primaryStage.show(); } catch(Exception e) { e.printStackTrace(); } } public static void main(String[] args) { launch(args); } }


Liens


http://perso.telecom-paristech.fr/~hudry/coursJava/exercices/

Java EE


Java Enterprise Edition (ou récemment "Jakarta EE"), un ensemble de spécifications de composants pour développer des applications destinées aux entreprises (applications sur serveur, distribuées, à grande échelle). Permet notamment de développer des pages web dynamiques.

On aura un client qui communiquera avec un serveur web (apache par exemple) qui communiquera avec un serveur d'application (glassfish ou JBoss) fonctionnant avec du Java.

Dans le serveur d'application se trouve le servlet, un objet qui récupère les requètes et renvoie une réponse (ou redirige les requêtes vers la vue).
La vue en JSF est un fichier xhtml constitué d'un mélange de HTML, de JSTL (Java server page Standard Tag Library) et d'EL (Expression language). On va utiliser Primefaces, qui permet de faire plus de choses comme utiliser un gestionnaire de placement et donne des composants comme un calendrier, une galerie photos, un menu déroulant....

Plusieurs frameworks disponibles pour la vue : JSP (le premier, obsolète, où on avait des bouts de java dans la vue), JSF (recommandé) et des frameworks tiers comme Struts (abandonné depuis l'apparition de JSF), seam, spring.

Exemple :<b><h:outputText id="affichageVar1" value="#{controleur.var1}"/></b>
Les balises html sont <b>, "h:outputText" est du JSTL, "#{controlJeu.nom}" du EL.
La vue est gérée par un conteneur web.

Notion de Bean : le controleur, l'EJB et les entités doivent respecter la spécification "Bean" en respectant des règles.
-Tous les attributs doivent être privés.
-Il doit exister au moins un constructeur sans argument (car les beans sont instanciés automatiquement).
-Il doit y avoir des getters et setters sur l'ensemble des attributs.

Le controleur est un bean de type CDI (Contexts and Dependency Injection). On écrit la classe de cette objet, mais il sera créé créé par le serveur d'application (si notre classe s'appelle ControleurPage, un objet "controleurPage" sera créé au lancement de l'appli).

Par dessus ça se trouve la couche accès, fonctionnant avec des EJB (Entreprise Java Bean, ressemble beaucoup aux CDI mais en plus évolués, fait transaction, remoting, asynchrone, Timer -appel d'une fonction à intervale régulier, Security), DAO et encore au dessus les modèles avec les entités JPA (géré par glassfish). Les EJB sont géréspar un conteneur d'EJB.

Outre les applications web, une implémentation de Java EE doit également être capable de gérer 4 types de composants (Applet web -le navigateur du client sera le conteneur d'applet, Applications -applications java exécutée chez le client, et des Applications d'Entreprise exécutée dans le conteneur d'EJB).



JSP/Servlets avec Tomcat et Eclipse


1)Télécharger et dézipper les binaires tomcat sur https://tomcat.apache.org/download-90.cgi.
2)Dans Eclipse, Windows->Preferences->Server->Runtime Environnement, add et sélectionnez votre dossier.
3)New, Dynamic Web Project en demandant un serveur local et sélectionnant le serveur tout juste installé. Demandez la création de web.xml. Si vous préférez passez par Maven, il y a un archetype (maven-archetype-webapp) qui va générer les dossiers et les dépendances maven nécessaires pour vous.

Java beans : le modèle.
JSP (Java Server Pages) : La vue.

Servlet : le controleur. Répond à des requêtes HTTP. Fonctionne avec 2 objets en entrée : HttpServletRequest HttpServletResponse. Instancié. Géré par un moteur de servlet (tomcat, glassfish, resin). Se place dans src/monpackage/MonBeauServlet.java.
Pour informer le serveur de l'existence de ce servlet, 2 solutions : l'indiquer dans le fichier web-INF web.XML ou utiliser une annotation sur la class du servlet @WebServlet("/MonBeauServlet").


Exemple de web.xml :

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0"> <servlet> <servlet-name>MonBeauServlet</servlet-name> <servet-class>com.monpackage.MonBeauServlet</servet-class> </servlet> <servlet-mapping> <servlet-name>MonBeauServlet</servlet-name> <url-pattern>MonBeauServlet</url-pattern> </servlet-mapping> </web-app>


Exemple de servlet :

public class MonBeauServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); response.setCharacterEncoding("UTF-8"); PrintWriter out=response.getWriter(); out.println("<b>Quel beau println</b>"); response.getWriter().append("On peut aussi utiliser append avec des méthodes. Url de base du projet :").append(request.getContextPath()); } }




Ci-dessus, le code html est directement dans le servlet, ce qui n'est pas très pratique, sauf si on veut juste faire un bouchon renvoyant du JSON par exemple...
Un exemple avec un bouchon, un peu plus complexe (renvoie et accepte une liste d'objets) :


@Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); response.setHeader("Cache-Control", "no-cache"); //s'il n'y a pas de données, on remplit avec les données de l'exemple de la FFT List<MonObjet> monObjetListe = new ArrayList<>(); alertOnOffersList.add(new MonObjet()); alertOnOffersList.add(new MonObjet()); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'"); ObjectMapper mapper = new ObjectMapper(); //librarie jackson mapper.setDateFormat(df); PrintWriter out = response.getWriter(); out.append(mapper.writeValueAsString(monObjetListe)); //pour récup les parmètres d'un get on peut faire request.getParameter("monBeauParametre"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); StringBuilder buffer = new StringBuilder(); BufferedReader reader = request.getReader(); String line; while ((line = reader.readLine()) != null) { buffer.append(line); } String post = buffer.toString(); MonObjet[] monObjetTab = new ObjectMapper().readValue(post, MonObjet[].class); //librarie jackson System.out.println(monObjetTab); }




On peut découpler le html en utilisant la technologie JSP (clic droit sur le dossier webcontent, new JSP File), puis this.getServletContext().getRequestDispatcher("/mabellepagejsp.jsp").forward(request, response); pour charger la JSP (on peut utiliser include à la place de forward pour que les vues se concatènent).


Exemple de page JSP qui affiche une variable présente dans le controleur :

Dans le controleur :

String message = "Mon beau message !!"; ArrayList maListeDeString= new ArrayList (); ... request.setAttribute("monMessageAAfficher", message);


Dans le JSP (vérifiez bien que vous êtes sur l'url monprojet/maroute et pas monprojet/mapage.jsp) :

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@page import="java.util.ArrayList"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> Bonjour je suis une page JSP. Ci-dessous 3 façons d'afficher une variable : <br> <% out.println(request.getAttribute("monMessageAAfficher")); %> <br> <%= request.getAttribute("monMessageAAfficher") %> <br> ${monMessageAAfficher} <br> Exemple de boucle sur une ArrayList : <br> <% ArrayList arrayListeRecuperee=(ArrayList)request.getAttribute("maListeDeString"); for(int i=0;i<4;i++){ //on affiche les 4 premiers éléments de l'ArrayList out.println(arrayListeRecuperee.get(i)+"<br>"); } %> </body> </html>




On peut également créer ses propres tags JSP.

Pour les connexions à la base, on peut faire une connexion classique. Pour rappel :

Connection con; try { con = DriverManager.getConnection("jdbc:mysql://localhost:3306/base_exemple", "mleveque", "password"); //lecture des infos : Statement stmt = con.createStatement(); ResultSet rset = stmt.executeQuery("SELECT * FROM EMPLOYE WHERE NUMEMP=4"); while ( rset.next() ) { request.setAttribute("nom", rset.getString("NOMEMP")); request.setAttribute("prenom", rset.getString("PRENOMEMP")); } rset.close(); stmt.close(); con.close(); } catch (Exception ex) { ex.printStackTrace(); } //envoie des infos au JSP this.getServletContext().getRequestDispatcher("/afficheemploye.jsp").include(request, response);




JSF




JSF n'a qu'une seule servlet qui va aiguiller les requêtes entrantes (on appelle ça le pattern "Front Controler", lui même spécialisation du pattern "Mediateur").

On utilisera JAVA EE+EJB3+JPA2.2+JSF2.2+Primefaces.

Netbeans est livré en standard avec glassfish et l'intégration est bien faite pour le manipuler. Glassfish serveur est un serveur d'application très puissant utilisé par des gros sites.

Pour démarrer : new project -> java web -> web application. Nommer le projet, puis le server est glassfish server, java version 7. Le framework sera donc Java Server Faces et dans l'onglet components ont ajoutera primefaces. Dans le panneau de gauche, dans le dossier Web Pages/WEB-INF, rajouter un fichier vierges "beans.xml" (obligatoire pour respecter la spécification JEE7).

Deux fichiers xhtml sont créés par défaut.

Tout en haut, les infos html pour le navigateur. En dessous, les librairies utilisées (si on veut utiliser les éléments Primefaces dans une page il faudra mettre p="http://primefaces.org/ui). Les éléments sont préfixés selon la librairie. Par exemple <p:layout fullPage="true"> est un élément primefaces car il commence par "p".

Dans Eclipse, il faudra sans doute ajouter les .jar de JSF, par exemple jsf-api-2.2.9.jar et jsf-impl-2.2.9.jar.
Puis new->Dynamic web project->modifier la configuration du serveur en ajoutant JSF.


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui"> <f:view contentType="text/html"> <h:head> <f:facet name="first"> <meta content='text/html; charset=UTF-8' http-equiv="Content-Type"/> <title>PrimeFaces</title> </f:facet> </h:head> <h:body> <p:layout fullPage="true">


Ce code sera modifié en code html compréhensible par le navigateur par la suite.

Pour mettre en place un site dynamique, il faut un CDI/ Clic droit, nouveau package "controleur", nouvelle classe ControleurIndex. Il faut déclarer qu'il s'agit d'un CDI avec l'annotation "@Named" (et "import javax.inject.Named;"). On pourra alors utiliser ses attibuts (par exemple un string chaineTest qui vaudra "bonjour" avec les getters et setters appropriés) dans notre page xhtml avec l'Expression Language #{controleurIndex.chaineTest} (la première lettre du controleur en minuscule car il s'agit d'un objet).


(autres éléments, header, layout, etc...) <h:body> <p:layout fullPage="true"> <p:layoutUnit position="north" size="100" resizable="true" closable="true" collapsible="true"> Voici la valeur de l'attribut chaineTest du controleur : #{controlJeu.chaineTest} Header </p:layoutUnit> (autres éléments, balises fermantes, etc...)


Après avoir cliqué sur "clean and build project" puis sur "run project", une page html s'affichera et devra afficher l'attribut.


Le fichier web.xml définit (entre autres) quels fichiers sont interprétés par java.

Ci-dessous, n'importe quel fichier ("*") présent dans "/faces/" seront interprétés :

<servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping>


Si un de vos fichier xhtml n'est pas interprêté, vous pouvez modifier web.xml de la façon suivante :

<servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.xhtml</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.xhtml</welcome-file> </welcome-file-list>


C'est aussi dans web.xml qu'on installe des thèmes, précise la page d'accueil, les pages d'erreur, etc.

<context-param> <param-name>primefaces.THEME</param-name> <param-value>le-frog</param-value> </context-param> <welcome-file-list> <welcome-file>index.xhtml</welcome-file> </welcome-file-list> <error-page> <exception-type>javax.faces.application.ProtectedViewException</exception-type> <location>/connexion.xhtml</location> </error-page>




Les objets sont créés dès qu'ils sont appelés dans une vue (par exemple dès qu'on appellera #{controleur.variable} le contrôleur sera créé). La durée de vie des objets managés est indiqué avec une annotation.
-@RequestScoped : durée de vie plus longue, temps d'une requête.
-@ViewScoped : tant qu'on est dans la même page, l'objet n'est pas détruit (javax.faces.view.ViewScoped).
-@SessionScoped : tant qu'on ne ferme pas le navigateur l'objet n'est pas détruit (javax.enterprise.context.SessionScoped).
-@ApplicationScoped : tant que le site existe, l'objet n'est pas détruit. On peut ajouter @ManagedBean(eager=true) pour que l'objet soit créé dès le lancement de l'appli (et pas dès que l'objet est appelé).

Attention ViewScoped, SessionScoped et ApplicationScoped nécessitent un Serializable.

Exemple :

@Named @SessionScoped public class ControlConnexion implements Serializable { //reste de la classe


Pour chaque client une instance du contrôleur est dédié. Si on annote le CDI avec @Singleton, on aura un seul CDI pour tous les clients, avec une durée de vie illimitée.

Exemple de page simple (affichera l'id, le nom, le prénom de l'id renseigné quand on clique sur lire ou bien le créé si on clique sur écrire) :

<p:panel id="panel"> <h:form> <p:inputText value="#{controlIndex.etudiant.id}"/> <p:inputText value="#{controlIndex.etudiant.nom}"/> <p:inputText value="#{controlIndex.etudiant.prenom}"/> <p:commandButton value="Lire" id="ajax" update="panel" actionListener="#{controlIndex.lire()}"/> <p:commandButton value="Ajouter" update="panel" actionListener="#{controlIndex.ajouter()}"/> </h:form> </p:panel>


Notez le "update" qui indique les éléments de la page qu'on veut modifier en ajax (ici, tout le panel). On ne peut update que des éléments JSF, si vous voulez màj des éléments non-jsf le plus simple est de les encadrer dans un "h:panelGroup". On peut également faire un update @all ou @form, ou même utiliser des sélecteurs types jquery.
Attention le update fait sauter les listeners javascript. Si vous faites un update sur un bout de code javascript, ce code sera rééxécuté.

Avec son contrôleur :

@Named @RequestScoped public class ControlIndex implements Serializable { @Inject private DaoEtudiant daoEtudiant; private Etudiant etudiant; public void lire(){ etudiant=daoEtudiant.rechercherparId(etudiant.getId()); } public void ajouter(){ daoEtudiant.ecrire(etudiant); } //ajouter les getters et setters ci dessous



Exemple de page d'index un peu complexe :

<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui"> <f:view contentType="text/html"> <h:head> <f:facet name="first"> <meta content='text/html; charset=UTF-8' http-equiv="Content-Type"/> <title>PrimeFaces</title> </f:facet> </h:head> <h:body> <h:form> <p:poll interval="1" update="panneauJoueur1,panneauJoueur2,panneauJoueur3,panneauJoueur4" /> </h:form> <p:dialog header="Nouveau joueur" id="popupNouveauJoueur" modal="true" widgetVar="popupNouveauJoueur" minHeight="40"> <h:form> <h:panelGrid columns="2" cellpadding="5"> <h:outputText id="champSaisieNom" value="Nom :"/><p:inputText value="#{controlJeu.nomSaisi}"/> <h:outputText id="champSaisiePrenom" value="Prenom :"/><p:inputText value="#{controlJeu.prenomSaisi}"/> <p:commandButton value="Valider" id="boutonValiderNouveauJoueur" update=":msg,:panneauJoueur1,:panneauJoueur2,:panneauJoueur3,:panneauJoueur4" onclick="PF('popupNouveauJoueur').hide();" actionListener="#{controlJeu.createTurn()}"/> </h:panelGrid> </h:form> </p:dialog> <p:layout fullPage="true"> <p:layoutUnit position="north" size="50" resizable="true" closable="true" collapsible="true"> <p:growl id="msg" showDetail="true" /> <h:form> <p:commandButton value="Ajouter un joueur" id="boutonNouveauJoueur" onclick="PF('popupNouveauJoueur').show()"/> <h:outputText id="infosMultijoueurs" value="Votre id : #{controlJeu.getId()}" /> </h:form> <br /> </p:layoutUnit> <p:layoutUnit position="center" id="panneauxJoueurs"> <h:panelGrid columns="2"> <p:panel header="Joueur 1 : #{controlJeu.tabTurn[0].joueur.prenom} #{controlJeu.tabTurn[0].joueur.nom}" closable="#{controlJeu.testerSiBoutonAppartientAuJoueur(0)}" id="panneauJoueur1" style="margin-bottom:20px" visible="#{controlJeu.rendered[0]}"> <p:ajax event="close" listener="#{controlJeu.reagirPanelFerme}" update=":msg" /> <h:panelGrid columns="2"> <h:form> <h:panelGrid columns="3"> <p:graphicImage id="imageDe1" value="img/#{controlJeu.tabImagesDes[0][0]}" /> <p:graphicImage id="imageDe2" value="img/#{controlJeu.tabImagesDes[0][1]}" /> <p:graphicImage id="imageDe3" value="img/#{controlJeu.tabImagesDes[0][2]}" /> <p:commandButton value="Lancer dé 1" id="BoutonLancerDe1" update="imageDe1,BoutonLancerDe1" rendered="#{controlJeu.afficherBouton(0)}" actionListener="#{controlJeu.appuiSurLancer(0,0)}" disabled="#{controlJeu.tabTurn[0].getDeLance(0)}"/> <p:commandButton value="Lancer dé 2" id="BoutonLancerDe2" update="imageDe2,BoutonLancerDe2" rendered="#{controlJeu.afficherBouton(0)}" actionListener="#{controlJeu.appuiSurLancer(0,1)}" disabled="#{controlJeu.tabTurn[0].getDeLance(1)}"/> <p:commandButton value="Lancer dé 3" id="BoutonLancerDe3" update="imageDe3,BoutonLancerDe3" rendered="#{controlJeu.afficherBouton(0)}" actionListener="#{controlJeu.appuiSurLancer(0,2)}" disabled="#{controlJeu.tabTurn[0].getDeLance(2)}"/> <p:commandButton value="Choisir" id="boutonChoisir" update="panneauJoueur1,msg" rendered="#{controlJeu.afficherBouton(0)}" actionListener="#{controlJeu.appuiSurChoisir(0)}" disabled="#{controlJeu.tabTurn[0].tourFini}"/> <p:commandButton value="Valider" id="boutonValider" update="panneauJoueur1,msg" rendered="#{controlJeu.afficherBouton(0)}" actionListener="#{controlJeu.appuiSurValider(0)}" disabled="#{!(controlJeu.tabTurn[0].isJoue())}"/> </h:panelGrid> </h:form> <p:panel header="Stats J1" style="min-height:600;"> Nombre de coups : #{controlJeu.tabTurn[0].calculNombreCoups()}<br /><br /> Score : #{controlJeu.tabTurn[0].score}<br /><br /> Pourcentage : #{(controlJeu.tabTurn[0].victoires/controlJeu.tabTurn[0].calculNombreCoups())*100}%<br /><br /> Nombre de victoires : #{controlJeu.tabTurn[0].victoires}<br /> </p:panel> </h:panelGrid> </p:panel> <p:panel header="Joueur 2 : #{controlJeu.tabTurn[1].joueur.prenom} #{controlJeu.tabTurn[1].joueur.nom}" closable="#{controlJeu.testerSiBoutonAppartientAuJoueur(1)}" id="panneauJoueur2" style="margin-bottom:20px" visible="#{controlJeu.rendered[1]}"> <p:ajax event="close" listener="#{controlJeu.reagirPanelFerme}" update=":msg" /> <h:panelGrid columns="2"> <h:form> <h:panelGrid columns="3"> <p:graphicImage id="imageDe1J2" value="img/#{controlJeu.tabImagesDes[1][0]}" /> <p:graphicImage id="imageDe2J2" value="img/#{controlJeu.tabImagesDes[1][1]}" /> <p:graphicImage id="imageDe3J2" value="img/#{controlJeu.tabImagesDes[1][2]}" /> <p:commandButton value="Lancer dé 1" id="BoutonLancerDe1J2" update="imageDe1J2,BoutonLancerDe1J2" rendered="#{controlJeu.afficherBouton(1)}" actionListener="#{controlJeu.appuiSurLancer(1,0)}" disabled="#{controlJeu.tabTurn[1].getDeLance(0)}"/> <p:commandButton value="Lancer dé 2" id="BoutonLancerDe2J2" update="imageDe2J2,BoutonLancerDe2J2" rendered="#{controlJeu.afficherBouton(1)}" actionListener="#{controlJeu.appuiSurLancer(1,1)}" disabled="#{controlJeu.tabTurn[1].getDeLance(1)}"/> <p:commandButton value="Lancer dé 3" id="BoutonLancerDe3J2" update="imageDe3J2,BoutonLancerDe3J2" rendered="#{controlJeu.afficherBouton(1)}" actionListener="#{controlJeu.appuiSurLancer(1,2)}" disabled="#{controlJeu.tabTurn[1].getDeLance(2)}"/> <p:commandButton value="Choisir" id="boutonChoisirJ2" update="panneauJoueur2,msg" rendered="#{controlJeu.afficherBouton(1)}" actionListener="#{controlJeu.appuiSurChoisir(1)}" disabled="#{controlJeu.tabTurn[1].tourFini}"/> <p:commandButton value="Valider" id="boutonValiderJ2" update="panneauJoueur2,msg" rendered="#{controlJeu.afficherBouton(1)}" actionListener="#{controlJeu.appuiSurValider(1)}" disabled="#{!(controlJeu.tabTurn[1].isJoue())}"/> </h:panelGrid> </h:form> <p:panel header="Stats J2" style="min-height:600;"> Nombre de coups : #{controlJeu.tabTurn[1].calculNombreCoups()}<br /><br /> Score : #{controlJeu.tabTurn[1].score}<br /><br /> Pourcentage : #{(controlJeu.tabTurn[1].victoires/controlJeu.tabTurn[1].calculNombreCoups())*100}%<br /><br /> Nombre de victoires : #{controlJeu.tabTurn[1].victoires}<br /> </p:panel> </h:panelGrid> </p:panel> </h:panelGrid> </p:layoutUnit> </p:layout> </h:body> </f:view> </html>


Récupère le contenu d'un champ et l'utilise en argument :

<h:form id="formulaireChat" > <p:inputText id="champText" value="#{controleurUtilisateur.textTemp}"/> <p:commandButton update="chat,formulaireChat" actionListener="#{controleurChat.ajouterMessage(controleurUtilisateur.utilisateur, param['formulaireChat:champText'])}"/> </h:form>


Dans certain cas, on voudra pouvoir utiliser un input html (et non pas un p:commandButton), il faudra utiliser bundle pour intégrer la fonction :

<input type="submit" value="#{bundle['controlConnexion.connexionRealm']}" />


Pour cibler un élément, on utilise "id". Il y a une notion de portée, par exemple pour cibler un élément a dans un form b, il faut faire ":a:b".

Pour diriger vers une autre page on peut donner le nom de la page en statique <h:commandButton value="Intitulé du lien" action="nomDeLaPageDeDestination (sansXHTML)" /> (on peut ajouter "?faces-redirect=true", ou bien de façon dynamique avec une fonction ou une variable <h:commandButton value="Intitulé du lien" action="#{controleur.fonctionQuiRenvoieUnString()}" />. Il vaut mieux que ça se fasse dynamiquement du côté contrôleur car il est généralement plus simple de modifier celui-ci que toutes les pages xhtml.

On peut aussi faire une redirection dans le code du contrôleur avec FacesContext.getCurrentInstance().getExternalContext().redirect("index.xhtml");. Attention, ça manque un peu de souplesse caron gère les url à la place de Java. Parfois il rajoutera des token derrière une url, de type "panier.xhtml?javax.faces.Token=bucEhCRemJmdo8SUgg%3D%3D" si on veut par exemple accéder à une page sécurisée.

FacesContext permet également d'exécuter les scripts depuis le java. Ci-dessous on affiche un pop-up avec une méthode javascript (show) :

RequestContext requestContext = RequestContext.getCurrentInstance(); FacesContext context=FacesContext.getCurrentInstance(); requestContext.execute("PF('dlg1').show()");


Chaque client aura sa propre représentation de la vue. Dès qu'il arrivera sur la page, un numéro de session lui sera attribué qu'on peut récupérer avec :

FacesContext fCtx = FacesContext.getCurrentInstance(); HttpSession session = (HttpSession) fCtx.getExternalContext().getSession(false);


Si les clients partagent des infos sur la vue (par exemple une variable en statique partagée par tous, ou un contrôleur en singleton où sont tous connectés les clients), les changements sur une vue ne se répercuteront pas tant que la page ne sera pas actualisée. Plusieurs solutions pour actualiser la vue :
-La balise <p:poll>, qui actualise en ajax deséléments de la page toutes les x secondes. <p:poll interval="1" update="panneauJoueur1,panneauJoueur2,panneauJoueur3,panneauJoueur4" />. Très simpleà mettre en place.

-La balise avec du code java, qui enverra un message à tous les clients. Elle ne fonctionnera pas avec toutes les balises.

-En java, on récupère l'id du client et on fait des demandes de mise à jour.

Le composant One menu : un composant pour afficher une liste déroulante. 2 façons de faire, statique ou à la main. Ci-dessous, le selectItem est statique alors que le selectItems est rempli par une liste :

<p:selectOneMenu id="choixCategories" value="#{controleur.numCategorieSelectionnee}" style="width:125px" onchange="submit()" valueChangeListener="#{controleur.maMethode()}"> <f:selectItem itemLabel="-- Choisissez une catégorie --" itemValue="" /> <f:selectItems itemValue="#{controleur.renvoyerNumCategorie(i)}" itemLabel="#{controleur.renvoyerSuperbe(i)}" var="i" value="#{controleur.daoCategorie.renvoyerToutes()}" /> </p:selectOneMenu>

onchange execute du javascript côté client. Ici, la méthode submit() est appelée pour envoyer le formulaire dès que le selectOneMenu est changé. Lorsque la valeur sera changée, valueChangeListener appellera maMethode().

On peut remplir le one menu avec des strings, des int... (puis utiliser la valeur pour faire une recherche en base) mais aussi simuler le remplissage avec des objets en utilisant un "converter". On aura ainsi directement un getter/setter sur un objet de type Etudiant par exemple dans le bean controleur (attention il faut absolument que la méthode equals soit redéfinie dans Etudiant -par exempleun test d'égalité sur le num d'étudiant).


import javax.faces.convert.Converter; ... @RequestScoped @Named public class ConverterEtudiant implements Converter { @Inject private DaoEtudiant daoe; @Override public Object getAsObject(FacesContext context, UIComponent component, String value) { //retourne un objet en fonction du champ html value (du num d'étudiant) return daoe.rechercher(Integer.parseInt(value)); } @Override public String getAsString(FacesContext context, UIComponent component, Object value) { //remplit le champ "value=" du select html avec le numéro d'étudiant if(value==null){ return ""; } return String.valueOf(((Etudiant)value).getId()); } }


Puis dans le select one menu :

<p:selectOneMenu value="#{cdiControl.e}" converter="#{converterEtudiant}"> <f:selectItems var="etudiant" itemLabel="#{etudiant.getNom()}" value="#{cdiControl.daoEtudiant.getAll()}" />


Pour lancer une méthode uniquement si le selectOne est rempli, utiliser les attributs required="true" requiredMessage="merci de selectionner quelque chose", puis dans votre oncomplete oncomplete="if (!args.validationFailed) maMethodeJs();".


Autre exemple (en base on a une List "listeNomCategorie" contenant des noms de catégories type "Culture", "Histoire", "Poésie"...) :

<h:selectOneMenu id="categorieRecherche" value="#{controlRecherche.categorieRecherche}" required="true"> <f:selectItem itemLabel="Toutes catégories" itemValue="" /> <f:selectItems itemValue="#{nomCategorie}" var="nomCategorie" value="#{controlRecherche.listeNomCategorie}" /> </h:selectOneMenu>


Attention aux injections SQL ! Ci-dessous différents exemples montrant différentes façons de faire les requêtes :

package modele.access; import java.util.List; import javax.ejb.Stateless; import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.ParameterExpression; import javax.persistence.criteria.Root; import modele.Eleve; @Stateless public class DaoEleve extends Dao<Eleve> { //une méthode pour renvoyer toutes les banques dans une liste public List<Eleve> liretous() { return em.createNativeQuery("select * from eleve",Eleve.class).getResultList(); } public Eleve rechercherparId(int id) //cherche avec la clef { return em.find(Eleve.class, id); } public List<Eleve> rechercherparNom(String nom) //cherche avec la clef { //Danger d'injection SQL !! return em.createNativeQuery("select * from eleve where nom='"+nom+"'",Eleve.class).getResultList(); } // SQL natif //Avantage : connu et éprouvé //Inconvévient : Syntaxe peut varier légèrement selon le SGBD public List<Eleve> rechercherparNomParams(String nom) //cherche avec la clef { Query requete=em.createNativeQuery("select * from eleve where nom=?1",Eleve.class); requete.setParameter(1, nom); return requete.getResultList(); } // JPQL //Avantage : on reste purement en orienté objet //Inconvénient : difficile de passer des requêtes complexes public List<Eleve> rechercherparNomJPQL(String nom) //cherche avec la clef { Query requete=em.createQuery("select e from Eleve e where e.nom=:nom"); requete.setParameter("nom", nom); return requete.getResultList(); } // NamedQuery/NamedNativeQuery // -la syntaxe est légèrement vérifiée lors du démarrage de l'appli // -regroupées dans les classes les concernants public List<Eleve> rechercherparNomNamed(String nom) //cherche avec la clef { Query requete=em.createNamedQuery("Eleve.findByNom"); requete.setParameter("nom", nom); return requete.getResultList(); } // APICriteria //Avantage : les erreurs de requêtes sont vues au moment de la compilation //Inconvénient : légèrement moins lisible public List<Eleve> rechercherparNomAPICriteria(String nom) //cherche avec la clef { CriteriaBuilder constructeurRequete = em.getCriteriaBuilder(); CriteriaQuery<Eleve> requeteCriteria = constructeurRequete.createQuery(Eleve.class); Root<Eleve> c = requeteCriteria.from(Eleve.class); ParameterExpression<String> p = constructeurRequete.parameter(String.class); requeteCriteria.select(c).where(constructeurRequete.equal(c.get("nom"), nom)); Query requete = em.createQuery(requeteCriteria); return requete.getResultList(); } }


Composants JSF



<f:metadata> <f:viewAction action="#{controleur.afficherMsgConsole()}"/> </f:metadata>
pour lancer une action au chargement de la page. On peut également forcer un EJB Singleton à se lancer au démarrage de l'appli avec l'annotation @Startup.

Pour utiliser du css : <h:outputStylesheet library="css" name="style.css" />

Datatable editable :

<h:form id="mainForm"> <p:growl id="growl" /> <p:dataTable id="bookDataTable" value="#{controlIndex.listTest}" var="produit" editable="true"> <p:column headerText="ID" style="width : 200px ; font-size : 9px">#{produit.id}</p:column> <p:column headerText="Titre"> <p:cellEditor> <f:facet name="output">#{produit.modele}</f:facet> <f:facet name="input"><p:inputText value="#{produit.modele}" /></f:facet> </p:cellEditor> </p:column> <p:column headerText="Actions"><p:rowEditor /></p:column> <p:ajax event="rowEdit" update="bookDataTable :mainForm:growl" listener="#{controlEdition.onRowEdit}" /> </p:dataTable> </h:form>


Attention, si vous utilisez une méthode pour remplir la datatable (<p:dataTable value="#{controleur.maListeGeneree()}">), celle-ci pourra être appelée plusieurs fois. De manière générale, en JSF, un getter peut être appelé plusieurs fois même s'il n’apparaît qu'une seule fois sur la page.

Objets managés / Non-managés


Quand on fait un new() objet, ce dernier n'est pas managé. Par contre si on fait un em.find, c'est à dire qu'on demande à l'entity manager de créer un objet à partir d'éléments présents en base, celui-ci sera managé. Si on demande à un entity manager de manager un objet qui ne l'est pas (ou pas par lui), on peut se retrouver avec une erreur "java.lang.IllegalArgumentException: Entity not managed".
Le lazy loading ne fonctionnera que dans un état managé.

Problème, un EJB stateless (DaoProduit par exemple) s'adapte dynamiquement au nombre de clients, donc les entity managers apparaissent et disparaissent, laissant potentiellement des objets qui ne sont managés par personne. Il faudra utiliser un "merge" sur l'objet (DaoProduit.merge(produit),ce qui fusionne les attributs de l'objets avec ceux présents en base et renvoie un objet managé). Merge se base sur la clef primaire.

Pour transformer un objet managé en non managé (cas rare), utiliser "detach".

Validation en utilisant les annotations


Plusieurs types de validationpossible : en javascript (facileà déjouer), en faisant des échanges vue/contrôleur, ou dans le modèle directement.
Il faudratoujours sécuriser au niveau du modèle. C'est assez simple en JPA.

Une fois le modèle généré à partir des tables, on peut ajouter des annotations :

@Size(max = 32) @Column(name = "nom") @Pattern(regexp="^[a-zA-Z]+$",message="Champs Alphabétique") private String nom;


Lorsqu'on utilisera cette classe pour créer un objet, ses attrbuts devront respecter les annotations. Ainsi, il sera impossible de mettre un numéro dans l'attribut nom.

Faire remonter ces messages d'erreur est très simple avec Primefaces (penser à mettre à jour le formulaire pour que les messages s'affichent) :

Nom : <p:inputText id="nom" value="#{controlInscription.utilisateur.nom}" /> <p:message for="nom"/>


Si on essaie d'entrer un texte avec des chiffres dans le champ nom (ce qui va essayer d'attribuer cette valeur à l'attribut "nom" de notre utilisateur), la balise p:message affichera un message d'erreur "Champs Alphabétique" et empêchera la création de l'objet.

Sécurisation des pages


Plusieurs choix pour resttreindre l'accès à certaines pages de notre site :
-Avec du code dans la page (par eemple ViewAction, qui s'exécute avant le chargement de la page).

-ProtectedViews, très simple à mettre en oeuvre :il seraimpossible d'accéder à une page en tapant son url, mais on pourra toujours y accéder par un lien interne (un token sera généré pour nous permettre d'y accéder). Il suffit de créer un fichier faces-config.xml (clic droit, new, other...) et d'y mettre :


<?xml version='1.0' encoding='UTF-8'?> <faces-config version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"> <protected-views> <url-pattern>/protected/welcomePrimefaces.xhtml</url-pattern> </protected-views> </faces-config>


Dans web.xml, on peut mettre une page d'erreur personnalisée :

<error-page> <exception-type>javax.faces.application.ProtectedViewException</exception-type> <location>/connexion.xhtml</location> </error-page>


-Realm, qui passera par un paramétrage de l'application ET de glassfish, donc 2 niveaux de sécurité. Par contre, il s'appuie sur la base de donnée, à laquelleil faudra apporter une notion d'utilisateurs et de groupes de sécurité. En outre, il exige de respecter des spécifications précises pour les formulaires (pas plus de 2 champs...) qui peuvent être limitantes. Très bon tuto.

Il faudra au moins 2 tables : utilisateur (contenant un nom d'utilisateur et un mot de passe hashé) + une table utilisateur_groupe (contenant un nom d'utilisateur -clef étrangère vers l'utilisateur- et le nom d'un groupe). On ajoute souvent une troisième table "groupe" pour arriver au schéma "utilisateur*---*utilisateur_groupe*---*groupe".
Dans Glassfish, ajouter un realm dans server-config, security, realm. Mettre "jdbcRealm" dans JAAS Context, le chemin JNDI de votre base, le nom de la table contenant les utilisateurs, la colonne contenant le pseudo, la colonne contenant le mot de passe, la table utilisateur_groupe, dans "Group Name Column" la colonne contenant d'utilisateur_groupe contenant le nom des utilisateurs, dans "Assign Groups" la colonne contenant le nom des groupes. Dans "Assign Groups", indiquez le groupe par défaut pour tous les utilisateurs. Dans "Digest Algorithm" et "Password Encryption Algorithm" y renseigner l'algorithme utilisé pour le hash des mots de passe. Indiquez aussi le charset ("utf-8").

Il faut maintenant indiquer quels répertoires seront protégés et selon quelles conditions dans web.xml :

<login-config> <auth-method>BASIC</auth-method> <realm-name>realmTopCommerce2</realm-name> <form-login-config> </form-login-config> </login-config> <security-constraint> <web-resource-collection> <web-resource-name>Default secure pages</web-resource-name> <url-pattern>/protegeesParRealm/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>default_user_role</role-name> </auth-constraint> </security-constraint> <security-role> <role-name>default_user_role</role-name> </security-role>


Puis vous aurez besoin dans le même répertoire d'un fichier glassfish-web.xml contenant :

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd"> <glassfish-web-app error-url=""> <security-role-mapping> <role-name>default_user_role</role-name> <group-name>utilisateur</group-name> </security-role-mapping> </glassfish-web-app>


Si vous crééez un répertoire "protegeesParRealm" avec une page web, un pop up devrait s'afficher vous demandant vos infos.

On peut aussi s'identifier avec un fomulaire personnalisé en changeant "l'auth method" :

<auth-method>FORM</auth-method> <realm-name>realmTopCommerce2</realm-name> <form-login-config> <form-login-page>/connexion.xhtml</form-login-page> <form-error-page>/logonError.jsp</form-error-page> </form-login-config>


Pour récupérer l'utilisateur connecté avec le realm :
request.getRemoteUser()

Pour se connecter, il suffit d'utiliser javax.servlet.http.HttpServletRequest.login (doc oracle):

public String connexionRealm(){ System.out.println("On va chercher "+pseudo); FacesContext context = FacesContext.getCurrentInstance(); HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest(); try { request.login(pseudo, motDePasse); utilisateur=daoUtilisateur.rechercherParPseudo(pseudo); } catch (ServletException e) { System.out.println("Echec du login"); } return "index.xhtml"; }



-Filters, qui est positionné entre le contrôleur et le conteneur de servlet, parfait pour vérifier qu'il y a les infos appropriées dans la requête :
Requète -> Conteneur -> Filtre -> Contrôleur
Réponse <- Conteneur <- Filtre <- Contrôleur


Pour forcer le https :

<security-constraint> <display-name>ConstraintSSL</display-name> <web-resource-collection> <web-resource-name>protected</web-resource-name> <description/> <url-pattern>/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> <http-method>HEAD</http-method> <http-method>PUT</http-method> <http-method>OPTIONS</http-method> <http-method>TRACE</http-method> <http-method>DELETE</http-method> </web-resource-collection> <user-data-constraint> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> </security-constraint>


Pour faire un filter Spring c'est très simple :

package controller; import org.springframework.stereotype.Component; import javax.servlet.*; import java.io.IOException; @Component public class MonBeauFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("coucou du filter"); filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { } }



Connexion à une base de donnée


On parle d'architecture n tiers. On aura la couche client (xhtml), la couche logique (cdi et autres beans java) et enfin la couche d'accès aux données (JPA).

Pour pouvoir gérer les connexions à la base, Oracle amis en place des EJB (Entreprise Java Beans).
Les EJB sont capables de gérer les transactions (plus besoin de faire transaction begin/commit), sont très capables au niveau de la sécurité, ont la possibilité de se délencher automatiquement (par exemple envoyer un mail tous les jours à minuit) et gèrent la concurrence.

Sans EJB, si l'application se connectait directement à la base, tous les clients essaieraient de se connecter en même temps.

Il y a 3 principaux types d'EJB :
-Stateless, le plus utilisé. Plusieurs EJB sont générés (par exemple, pour 8 connexions simultanées, 4 objets EJB seront instanciés par le serveur). C'est très capable en matière de performance, mais les objets sont volatiles, on ne peut pas stocker d'attributs vu qu'un objet peut disparaître rapidement et que plusieurs clients peuvent se partager les EJB).

-Stateful, un EJB sera mis en place pour chaque client. Les données sont conservées, mais c'est gourmand en ressource.

-Singleton, un seul EJB pour tous les CDI. C'est le moins performant mais le plus léger en terme de ressource et un système de file d'attente doit être mis en place par le programmeur. Si le CDI est Singleton, on va mettre l'EJB dans ce mode.

Il en existe d'autre comme EJB timer (pour exécuter les méthodes en différé, très simple, il suffit d'ajouter @Schedule(hour = "11", minute = "28") pour déclencher une méthode à 11h28), EJB web services pour se connecter à des services.

Au niveau du contrôleur, il aura besoin d'un objet DAO, mais on ne peut pas faire "new DAO" comme auparavant vu qu'il est généré automatiquement : il faudra utiliser l'annotation @Inject pour indiquer qu'il s'agit d'un objet managé que l'on veut injecter. Attention, l'injection se fait une fois l'objet construit. Si on essaie d'appeler un objet injecté dans le constructeur, ça ne marchera pas, il faut le faire après la construction. On peut appeler une méthode dès que la construction est terminée avec @PostConstruct.


Exemples :

package dataAccess; import java.io.Serializable; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; //Serializable car le Controleur est en SessionScoped //si on peut sauvegarder le controleur durant toute une session, il faut pouvoir sauver les Dao public class Dao<T> implements Serializable{ //on ne créé pas d'entitymanagerfactory @PersistenceContext(unitName="testConnexionMysqlServeurAppliPU") protected EntityManager em; //pour que les autres classes dao puissent y accéder public void ecrire(T objetEntite) { //pas besoin de em.getTransaction().begin(); em.persist(objetEntite); } public void maj(T objetEntite) { em.merge(objetEntite); } public void supprimer(T objetEntite) { T objettrouve=em.merge(objetEntite); //il met à jour l'objet et le retourne em.remove(objettrouve); //supprime l'objet de la base } }



package dataAccess; import javax.ejb.Stateless; import modele.Etudiant; @Stateless //on doit déclarer qu'il s'agit d'un EJB, stateless en l'occurence NE PAS OUBLIER OU ERREUR " javax.persistence.TransactionRequiredException" public class DaoEtudiant extends Dao { public Etudiant rechercherparId(int id) //cherche avec la clef { return em.find(Etudiant.class, id); } }



package controleur; import dataAccess.DaoEtudiant; import java.io.Serializable; import javax.enterprise.context.SessionScoped; import javax.inject.Inject; import javax.inject.Named; import modele.Etudiant; @SessionScoped @Named public class Controleur implements Serializable{ private Etudiant e; @Inject //permet de récupérer le Dao créé automatiquement (on nous l'injecte) private DaoEtudiant daoEtudiant; public Controleur(){ //Attention on peut pas demander directement au dao //e=daoEtudiant.rechercherparId(1); } public Etudiant getE() { return e; } public void setE(Etudiant e) { this.e = e; } public DaoEtudiant getDaoEtudiant() { return daoEtudiant; } public void setDaoEtudiant(DaoEtudiant daoEtudiant) { this.daoEtudiant = daoEtudiant; } public Etudiant getEtudiantParId(int id){ return daoEtudiant.rechercherparId(id); } }


Envoi de mail


Avec une apli java on utilise l'api JavaMail pour en envoyer. En Java EE, on va utiliser le serveur d'application,c'est à dire Glassfish+JavaMail. On va paramétrer Glassfish pour le faire pointer vers un serveur mail (pour les tests, on peut utiliser "fake smtp server"). Dans la console d'amin de Glassfish (http://localhost:4848/), menu de gauche, javamail session. Entrez le nom jndi de votre choix, l'adresse du serveur (localhost si local), le nom et l'adresse de l'expéditeur. Il faudra ajouter une propriété additionnelle en bas : mail-smtp-port (ou mail.smtp.port) et y indiquer le port de votre serveur smtp.
Selon les serveurs, il peut y avoir plus ou moins de propriétés additionnelles (par exemple, il y en aurait plus d'une dizaine à ajouter si on voulait utiliser gmail).

Pour envoyer le mail, dans le managed bean, les imports :

import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage;


L'attribut javax.mail.Session où on injecte la ressource créée dans glassfish :

//Adresse JNDI créée dans glassfish qui pointe vers notre serveur SMTP @Resource(name="jmail/mailcommande") private Session session;


Enfin, le code pour envoyer le mail :

MimeMessage message = new MimeMessage(session); try { message.setFrom(new InternetAddress(session.getProperty("mail.from"))); InternetAddress[] address = {new InternetAddress("bob@bob.com")}; message.setRecipients(Message.RecipientType.TO, address); message.setSubject("SUJET DU MAIL"); message.setSentDate(new Date()); message.setText("TEXTE DU MAIL"); Transport.send(message); } catch (MessagingException ex) { ex.printStackTrace(); }


EJB envoyant un message à l'heure voulue :

@Named @Singleton @Startup public class Controleur3 { @Resource TimerService timerService; @PostConstruct public void init(){ ScheduleExpression heureMessage = new ScheduleExpression().hour("16").minute("46"); timerService.createCalendarTimer(heureMessage, new TimerConfig()); } @Timeout public void afficherMessage(Timer timer) { System.out.println("il est 16h40"); } }


Listeners JPA



Se rapprochent des triggers PLSQL. Ne marchent que dans un contexte JavaEE avec un serveur d'application. Une classe entité est écoutée par une classe listener qui va enclencher des traitements selon les évènements. Exemple : une entité stock avec un champ quantité où le listener va afficher un message en fonction de la quantité (on pourrait avoir ce genre de comportement avec des annotations @min(0) pour que la quantité ne soit jamais mise à zéro.
Les listeners sont là pour des contraintes complexes, par exemple, supprimer en base l'article dont la quantité en stock est négative. Pour ajouter un listener on utilise, juste sous l'annotation @Entity, l'annotation @EntityListeners(MaClasseListener.class) ou @EntityListeners({1.class,2.class,3.class}) si on en a plusieurs. Ensuite, on va préciser par annotation le déclenchement de ces méthodes.

Exemple :

public class EtudiantListener { @Resource private SessionContext ctx; // sessionContext permet d'acceder à la transaction ejb, on pourra faire un ctx.setRollbackOnly(); dessus par exemple pour la forcer à ne faire qu'un rollback @PostPersist //indique que la méthode suivante se déclenchera après l'insertion en base public void checknomprenom(Etudiant e) //l'objet en cours de traitement est injecté ici automatiquement { FacesContext context=FacesContext.getCurrentInstance(); context.addMessage(null, new FacesMessage("Etudiant écrit")); //affichage d'un message } }


@PrePersist (se déclenche avant un em.persist), @PreRemove (avant em.remove), @PreUpdate (avant update en base),@PostPersist(après insert en base), @PostRemove (après em.remove), @PostUpdate (après update en base), @PostLoad (après un select ou un refresh).

On peut également se passer d'une classe supplémentaire et directement écrire les annotations dans le modèle.

Héritage JPA



Dans la table utilisateur, on aura un champ "typeUser" qui prendracomme valeur "non-admin" ou "admin".

La classe utilisateur :

@Entity @Inheritance(strategy=InheritanceType.SINGLE_TABLE) //definition du type de stratégie d'héritage @DiscriminatorColumn(name="typeuser",discriminatorType=DiscriminatorType.STRING) //definition de la colonne discriminante et du type de discriminant içi une chaine. @DiscriminatorValue("non-admin") // valeur du discriminant ("non-admin") @Table(name = "utilisateur") @NamedQueries({ .......//les NamedQueries public class Utilisateur implements Serializable {


Dans la classe admin, héritée d'utilisateur, on aura les infos suivantes :

@Entity @DiscriminatorValue("admin") public class Admin extends Utilisateur{


Si on créé un objet de type utilisateur, le champ typeuser sera à la valeur "non-admin" par défaut. Si on créé un objet admin, ce champ sera à "admin".

Ainsi, on retrouvera ces valeurs dans la base :

+----+---------+-----------+ | id | nom | typeuser | +----+---------+-----------+ | 13 | Morane | non-admin | | 14 | Adhmine | admin | | 17 | padmin | non-admin | +----+---------+-----------+


Il y a donc différentes stratégies d'héritages :
@Inheritance(strategy=InheritanceType.SINGLE_TABLE) : on met l'objet parent et les objets hérités dans une seule table, différenciés par un champ (par exemple "typeuser"). Si les objets hérités ont des attributs en plus, des colonnes seront ajoutés. Par exemple, si on a un objet voiture qui hérite de véhicule, on aura un champ "nombre_de_portes" qui ne sera renseigné que pour les voitures et NULL pour les motos).

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) : chaque classe et classe hérité aura sa table. Une table véhicule avec un champ "marque", une table voiture avec un champ "marque" et "nombre_de_portes"...

@Inheritance(strategy=InheritanceType.JOINED) : chaque class aura sa table mais on aura une table pour les propriétés communes.

Spring


Un framework et un conteneur d'objets permettant l'inversion de contrôle, orienté aspect.

Rod Johnson proposa en 2002 dans un livre du code qui deviendra la base du Spring en 2004. Supporte les annotations 2007 avec la version 2.5, a été racheté par VMware en 2013.
Beaucoup de sous projet Spring : Spring MVC, Spring Web Flow, Spring Security, Spring Batch... La base étant Spring Core.

Programmation orienté aspect




Injection


Trois méthodes d'injection : xml (pour les classes qui risquent souvent de changer, tout ce qui concerne l'infrastructure), annotation (directement dans le code java, pour les classes qui n'auront pas beaucoup de modification), code java.

Permet de résoudre le problème du couplage fort : une classe A ayant comme attribut la classe B. Une seule classe B pour la classe A, on parle de couplage fort car A est très fortement lié à B (si on modifie le type de B il faut modifier le code source de A). Pour éviter le couplage, on peut utiliser une interface : A n'aura plus un objet de type B mais de type "interface_implementant_B". Par contre, on sera toujours obligé de faire new B().... Sauf si on utilisait Class.forName, qui permet d'instancier une classe sans préciser son nom. On pourrait imaginer que le nom des classes serait dans un fichier séparé.


Class classeObjet = Class.forName ("MaJolieClasse"); //ici je met un string directement mais on pourrait le choper dans un fichier texte à part MonInterface monBelObjetBienType= (MonInterface)objet.newInstance();


On peut également invoquer des méthodes avec "invoke".

Injection XML


C'est le concept original de Spring : utiliser un fichier XML pour définir les objets que l'on veut instancier.
Ci-dessous on définit un bean "dao" qu'on va injecter en tant qu'attribut attributDao dans la classe ClasseMetier :

<?xml version = "1.0" encoding = "UTF-8"?> <beans xmlns = "http://www.springframework.org/schema/beans" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id = "dao" class = "mon.paquet.ClasseImplementantDao"></bean> <bean id = "metier" class = "mon.paquet.ClasseMetier"> <property name = "attributDao" ref = "dao"/> </bean> </beans>


Les objets du type ClassPathXmlApplicationContext (ou FileSystemXmlApplicationContext qui permet de charger plusieurs fichiers à la fois) récupèrent et parsent le xml. context.getBean(metier) va générer un objet de type métier où sera injecté le dao.

Exemple concret (avec les librairies spring-core et spring-context) :

//dans src/main/resources/applicationContext.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.monpackage" /> <bean id="beanClasseQuiDitCoucou" class="com.monpackage.ClasseQuiDitCoucou"> <property name="coucou" value="Coucou tout le monde !"></property> <!-- l'attribut String coucou prendra la valeur "Coucou tout le monde !". Outre les strings on peut injecter des integer, des lists, etc.--> </bean> </beans>



//cette classe sera injectée et sa propriété "coucou" sera modifiée package com.monpackage; public class ClasseQuiDitCoucou { private String coucou; public String getCoucou() { return coucou; } public void setCoucou(String coucou) { this.coucou = coucou; } }




package com.monpackage; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * Hello world! * */ public class App { static String monAttribut; public static void main( String[] args ) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //on a besoin du xml pour savoir quel classe injecter. On génère en même temps un "contexte", une sorte d'environnement là seront exécutés nos beans. ClasseQuiDitCoucou instanceDeLaClasseQuiDitCoucou = (ClasseQuiDitCoucou) context.getBean("beanClasseQuiDitCoucou"); //instanciation de ce qui est référence par beanClasseQuiDitCoucou System.out.println(instanceDeLaClasseQuiDitCoucou.getCoucou()); } }


On peut injecter via les getters ou les setters mais également via le constructeur (constructor-arg dans le xml) et même sans aucun getter/setter.



Autre exemple qui illustre le découplage avec les interfaces, cette fois-ci ce sont des beans qui sont injectées :

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.monpackage" /> <bean id="ClasseQuiDitCoucou" class="com.monpackage.ClasseQuiDitCoucou"> <property name="coucou" value="Coucou tout le monde !"></property> </bean> <bean id="bdao" class="com.monpackage.DaoImpl"> </bean> <bean id="metier" class="com.monpackage.MetierImpl"> <property name="dao" ref="bdao"></property> <!-- ici on fait une reférence au bean déclaré plus haut mais on pourrait aussi directement déclarer le bean ici --> </bean> </beans>




package com.monpackage; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { static String monAttribut; public static void main( String[] args ) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Metier instanceMetier = (Metier) context.getBean("metier"); System.out.println(instanceMetier.calcul()); } }



package com.monpackage; public interface Metier { double calcul(); }



package com.monpackage; public class MetierImpl implements Metier { private IDao dao; public double calcul() { double nb=dao.getValue(); return 2*nb; } public IDao getDao() { return dao; } public void setDao(IDao dao) { this.dao = dao; } }



package com.monpackage; public interface IDao { double getValue(); }



package com.monpackage; public class DaoImpl implements IDao { public double getValue() { return 5; } }


On peut aussi fait hériter des beans XML les uns les autres.


<bean id="dao" abstract="true"> <!-- ne peut pas être instancié --> <property name="attribut" ref="valeur"/> </bean> <bean id="fooDao" class="daoEnfant" parent="dao"> <!-- il aura "attribut" --> </bean>


Une bonne pratique est de diviser les fichiers XML : un pour ce qui concerne le métier et un qui concerne l'application.

Depuis Spring 3.1 il existe un notion de profil : une annotation @Profile("Development") sera mise sur un bean pour l'activer ou non. On aura donc dans applications.properties : spring.profiles.active=dev par exemple. Doc spring

On peut récupérer des infos facilement dans le fichier properties avec

@Value("${ma.propriete}") private String maPropriete;


Ca peut aussi servir pour injecter des ressources lorsqu'on fait un test avec @RunWith(SpringRunner.class) :

@Value("classpath:mesxml/monxml.xml") private Resource xml;



Injection par annotation


Pour éviter de travailler sur les XML, on peut décorer nos classes avec les annotations : Spring va chercher dans notre projet les classes correspondantes aux annotations. C'est plus simple que le XML mais ne convient pas à tous les beans (plutôt utile pour les bean métiers -des classes concernant le métier, qui changent peu souvent que les beans). On pourra quand même avoir un XML pour définir le contexte et indiquer qu'on veut utiliser les annotations, qui devra contenir la balise context:component-scan. Cette balise indique les packages où Spring doit aller chercher des annotations (car ce serait une perte de temps de scanner TOUT le projet, par ex si on a 90% de classes métier et 10% de Spring...).
@Autowired permettra d'injecter.

Définition des packages à scanner dans le XML :

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.monpackage, com.monautrepackage" /> </beans>


Le main :

package com.monpackage; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import java.io.File; public class Main { static String monAttribut; public static void main( String[] args ) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Metier instanceMetier = (Metier) context.getBean("Metier"); System.out.println(instanceMetier.calcul()); } }




package com.monpackage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component("Metier") public class MetierImpl implements Metier { @Autowired private IDao dao; public double calcul() { double nb=dao.getValue(); return 2*nb; } public IDao getDao() { return dao; } public void setDao(IDao dao) { this.dao = dao; } }



Si on voulait ne pas utiliser de XML, on pourrait tout faire par annotation :

AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(); //nouveau contexte context.scan("com.monpackage"); //ici on indique les classe à scanner pour trouver les annotations context.refresh(); context.close();


On peut faire des "classes de configuration", qui seront annotées avec @Configuration, souvent @EnableTransactionManagement, @ComponentScan pour indiquer les classes à scanner...

Les classes de configuration peuvent définir des méthodes en temps que bean : les beans seront les objets renvoyés.

Exemple :

package com.monpackage.configuration; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.core.env.Environment; import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.orm.hibernate4.HibernateTransactionManager; import org.springframework.orm.hibernate4.LocalSessionFactoryBean; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource; import java.util.Properties; @Configuration @EnableTransactionManagement @ComponentScan({"com.monpackage"}) @PropertySource(value ={"classpath:application.properties"}) public class HibernateConfiguration { @Autowired private Environment environnement; @Bean public LocalSessionFactoryBean sessionFactoryBean(){ LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean(); sessionFactory.setDataSource(dataSource()); sessionFactory.setPackagesToScan(new String[] {"com.monpackage"}); sessionFactory.setHibernateProperties(hibernateProperties()); return sessionFactory; } @Bean public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(environnement.getRequiredProperty("jdbc.driverClassName")); dataSource.setUrl(environnement.getRequiredProperty("jdbc.url")); dataSource.setUsername(environnement.getRequiredProperty("jdbc.username")); dataSource.setPassword(environnement.getRequiredProperty("jdbc.password")); return dataSource; } private Properties hibernateProperties(){ Properties properties=new Properties(); properties.put("hibernate.dialect", environnement.getRequiredProperty("hibernate.dialect")); properties.put("hibernate.show_sql", environnement.getRequiredProperty("hibernate.show_sql")); properties.put("hibernate.format_sql", environnement.getRequiredProperty("hibernate.format_sql")); properties.put("hibernate.hbm2ddl.auto", environnement.getRequiredProperty("hibernate.hbm2ddl.auto")); return properties; } @Bean public HibernateTransactionManager transactionManager(SessionFactory s){ HibernateTransactionManager txManager=new HibernateTransactionManager(); txManager.setSessionFactory(s); return txManager; } }


Injection par code java


Permet de faire la même chose que le XML mais via le code java. Il existe notamment BeanFactoryPostProcessor pour modifier la configuration des beans.

Fichiers properties


Il est possible de stocker des propriétés en dehors du xml puis de les charger à l'intérieur :

<bean id="mailProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:monBeauFichier.properties" /> </bean>



<bean id="mailSender" class="mon.paquet.MonBean"> <property name="attributAChanger" value="${smtp.host}" /> </bean>


Forcer des paramètres via ligne de commande




-Dspring.datasource.url="jdbc:blablamadatasource" -Dspring.datasource.username="monuser" -Dspring.datasource.password="monpass" -Dspring.config.location="/chemin/vers/mon/fichier.properties"


Scope des beans


Les beans sont instanciés une seule fois par contexte Spring (scope=Singleton par défaut) et dès le lancement de l'application. Les variables sont partagés entre les threads. L'annotation @Transactional permet de faire en sorte qu'un bean ne soit accessible que par un seul thread au maximum.

Si on a besoin de plusieurs instances du même bean (par exemple il contient des données utilisateurs), on peut utiliser : session, request (du début de la requête HTTP jusqu'à la fin), prototype, flow... Soit dans le XML avec l'attribut scope, soit par annotation @Scope.

Exemple, dans le bean :

package com.monpackage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.stereotype.Component; @Scope("singleton") @Component("Metier") public class MetierImpl implements Metier { static int nbrVisiteur=0; @Autowired private IDao dao; public MetierImpl() { System.out.println("Lancement de mon bean"); } }



//Dans le main Metier instanceMetier = (Metier) context.getBean("Metier"); //on appelle le bean une fois, "Lancement de mon bean" doit s'afficher instanceMetier = (Metier) context.getBean("Metier"); //on l'appelle une deuxième fois, , "Lancement de mon bean" ne doit PAS s'afficher si on est en singleton. Il DEVRAIT s'afficher une seconde fois si on avais mis en @Scope("prototype")


Des problèmes de posent avec les scopes : par exemple B1 (singleton) a une référence à B2 (request). Comment B1 peut-il maintenir un lien avec B2 vu que ce dernier va changer tout le temps ? Il faut une entité intelligente pour faire le lien entre les 2 : le proxy. Il règle les problèmes de différences de cycles de vie entre les beans. B1 n'a pas une référence directe à B2, il passe donc par un proxy qui maintient ces liens. Il faudra utiliser l'annotation scoped-proxy sur le bean qui va apparaître/disparaître.
Le proxy est une enveloppe : quand on va appeler une méthode, on passe par cette enveloppe qui va effectuer le véritable appel. Une sorte de classe passe-plat.

Attention si une classe implémente une interface Spring va créer un proxy pour l'interface pour éviter ça on peut utiliser l'annotation EnableAspectJAutoProxy(proxyTargetClass = true) sur une classe de configuration.


Instanciation et injection




Spring lit le XML -> Utilise l'api reflexion de Java pour instancier les beans -> crée les proxys.

Une fois ce travail fait, les attributs injectés, on peut utiliser les annotations @PostConstruct (méthode recommandée), ou bien init-method dans le XML ou une interface (InitializingBean). Il existe le pendant pour la destruction, "PreDestroy" (ou destroy-method dans le xml, ou utiliser l'interface DisposableBean).

On peut demander à Spring de ne pas charger les beans tout de suite, avec le lazy loading. "lazy-init" dans le XML.

Si on est dans un serveur web comme Tomcat par exemple, on peut lancer spring via le fichier web.xml :

<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext*.xml</param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener>


On peut stopper Spring en utilisant : applicationContext.close();

Spring security permet de sécuriser les échanges back et front.

Spring Retry



Pour relancer des méthodes en cas d'exception. C'est un peu lourd à utiliser je trouve :
-dépendance spring-retry et spring-boot-starter-aop
-la méthode doit être annotée @EnableRetry dans une classe à part de notre controller (https://stackoverflow.com/questions/36417493)
-cette classe doit être un bean (donc soit appelé avec une méthode @Bean dans une classe de @Configuration ou bien simplement annotée avec @Component/@Service@Repository).
-si on veut faire des relances sur des erreurs 500 par exemple il faut définir des RetryTemplate avec des RetryPolicy (https://stackoverflow.com/questions/27236216/)

Spring MVC


Framework Java web. Il faudra des fichiers xml, dans WEB-INF au moins web XML pour indiquer où est le servlet :

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> <!-- equivalent de ClassPathXmlApplicationContext.... --> </listener> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <!-- on lui dit de consulter dispatcher-servlet.xml si l'url commence par slash --> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>


dispatcher-servlet.xml :

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- On indique quel package scanner pour trouver les annotations--> <context:component-scan base-package="com.monpackage" /> <!-- Ajoute le support des annotations --> <mvc:annotation-driven/> <!-- Indique où seront situées les vues --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/view/" /> <property name="suffix" value=".jsp" /> </bean> <!-- <mvc:resources..../> pour les pages statiques--> <mvc:resources mapping="/resources/**" location="/WEB-INF/resources/"/> </beans>


Le fichier applicationContext.xml est nécessaire si on utilise pas les annotations :

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="dao" class="com.monpackage.dao.CatalogueDaoImpl"></bean> <!-- définition d'un bean --> <bean id="metier" class="com.monpackage.metier.CatalogueMetierImpl"> <property name="dao" ref="dao"></property> <!-- injection d'un bean --> </bean> </beans>





Installation sous Eclipse


Help->Eclipse Marketplace->Spring Tools. Puis créer un nouveau projet Spring Legacy, puis String MVC project.

Installation sous Netbeans


File, new project, Spring, Spring MVC.

Exemple d'un CRUD simple (pas de BDD)



package com.monpackage.dao; import org.jboss.logging.Logger; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.*; @Component //indique que ça peut être injecté public class CatalogueDaoImpl implements ICatalogueDao { private Map<String,Produit> produits=new HashMap<String,Produit>(); //pour l'exercice, on utilise pas hibernate mais une hashmap où chaque produit est lié à sa ref Logger logger= Logger.getLogger(CatalogueDaoImpl.class); @Override public void addProduit(Produit p){ produits.put(p.getReference(),p); } @Override public Produit getProduit(String ref){ return produits.get(ref); } @Override public List<Produit> getAllProduits(){ Collection<Produit> prods = produits.values(); return new ArrayList<Produit>(prods); } @Override public List<Produit> getProduits(String motCherche){ //On récupère chaque produit et hashmap et on renvoie une liste List<Produit> prods = new ArrayList<>(); for(Produit p: produits.values()){ if(p.getDesignation().indexOf(motCherche)>=0){ //...a condition que la string entrée existe dans la désignation. prods.add(p); } } return prods; } @Override public void updateProduit(Produit p){ produits.put(p.getReference(), p); } @Override public void deleteProduit(String reference){ //on supprime de la hashmap en se basant sur la référence produits.remove(reference); } @Override @PostConstruct //se déclenchera dès que ce bean sera appelé public void init(){ this.addProduit(new Produit("HP675","Ordinateur",100,10)); this.addProduit(new Produit("AEP65","Ordinateur",100,10)); this.addProduit(new Produit("AT980","Ordinateur",100,10)); } }



package com.monpackage.dao; import java.util.List; public interface ICatalogueDao { //on utilise une interface pour découpler le code void addProduit(Produit p); List<Produit> getProduits(String mc); void deleteProduit(String reference); void init(); List<Produit> getAllProduits(); void updateProduit(Produit p); Produit getProduit(String ref); }



package com.monpackage.dao; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.Positive; import java.io.Serializable; public class Produit implements Serializable { //le modèle @NotEmpty //on force reference à contenir quelque chose private String reference; private String designation; @Positive //le prix doit être positif private double prix; private int quantite; public Produit() { //il faut un constructeur vide pour pouvoir instancier un nouveau produit vide (qu'on va modifier puis insérer dans la liste) } public Produit(@NotEmpty String reference, String designation, double prix, int quantite) { this.reference = reference; this.designation = designation; this.prix = prix; this.quantite = quantite; } public String getReference() { return reference; } public void setReference(String reference) { this.reference = reference; } public String getDesignation() { return designation; } public void setDesignation(String designation) { this.designation = designation; } public double getPrix() { return prix; } public void setPrix(double prix) { this.prix = prix; } public int getQuantite() { return quantite; } public void setQuantite(int quantite) { this.quantite = quantite; } }



package com.monpackage.metier; import com.monpackage.dao.ICatalogueDao; import com.monpackage.dao.Produit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; @Component public class CatalogueMetierImpl implements ICatalogueMetier { @Autowired //Spring va chercher un objet de type ICatalogueDao, l'instancier et l'injecter private ICatalogueDao dao; public ICatalogueDao getDao() { return dao; } public void setDao(ICatalogueDao dao) { this.dao = dao; } @Override public void addProduit(Produit p){ dao.addProduit(p); } @Override public Produit getProduit(String mc){ return dao.getProduit(mc); } @Override public List<Produit> getAllProduits(){ return dao.getAllProduits(); } @Override public List<Produit> getProduits(String mc){ return dao.getProduits(mc); } @Override public void deleteProduits(String mc){ dao.deleteProduit(mc); } @Override public void updateProduit(Produit p){ dao.updateProduit(p); } }



package com.monpackage.metier; import com.monpackage.dao.Produit; import org.springframework.stereotype.Component; import java.util.List; public interface ICatalogueMetier { void addProduit(Produit p); Produit getProduit(String mc); List<Produit> getAllProduits(); List<Produit> getProduits(String mc); public void deleteProduits(String mc); public void updateProduit(Produit p); }



package com.monpackage.web; import com.monpackage.Produit; import com.monpackage.ICatalogueMetier; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import javax.validation.Valid; @Controller public class CatalogueController { @Autowired private ICatalogueMetier metier; @RequestMapping("/index") //si l'url correspond à /index public String index(Model model) { model.addAttribute("produit", new Produit()); //on va créer un nouveau produit et l'envoyer au modèle model.addAttribute("produits", metier.getAllProduits()); //on récupère la liste des produits et on l'envoie au modèle System.out.println("il faut get les produits !"+metier.getAllProduits()); return "produits"; } @RequestMapping("/saveProduit") public String save(@Valid Produit p, BindingResult bindingResult, Model model) { //bindingResult return faux si ya des erreurs sur les contraintes if(bindingResult.hasErrors()){ model.addAttribute("produits", metier.getAllProduits()); return "produits"; } metier.addProduit(p); model.addAttribute("produit", new Produit()); model.addAttribute("produits", metier.getAllProduits()); return "produits"; } @RequestMapping("/deleteProduit") public String delete(@RequestParam String ref, Model model){ metier.deleteProduits(ref); model.addAttribute("produit", new Produit()); model.addAttribute("produits", metier.getAllProduits()); return "produits"; } @RequestMapping("/editProduit") public String edit(@RequestParam String ref, Model model){ model.addAttribute("produit", metier.getProduit(ref)); model.addAttribute("produits", metier.getAllProduits()); return "produits"; } }


Le JSP :

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://www.springframework.org/tags/form" prefix="f" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Catalogue de Produits</title> </head> <body> <div> <f:form modelAttribute="produit" method="post" action="saveProduit"> <table> <tr> <td>Reference:</td> <td><f:input path="reference"/></td> <td><f:errors path="reference" cssClass="error"/></td> </tr> <tr> <td>Désignation:</td> <td><f:input path="designation"/></td> <td><f:errors path="designation" cssClass="error"/></td> </tr> <tr> <td>Prix :</td> <td><f:input path="prix"/></td> <td><f:errors path="prix" cssClass="error"/></td> </tr> <tr> <td>Quantité :</td> <td><f:input path="quantite"/></td> <td><f:errors path="quantite" cssClass="error"/></td> </tr> </table> <input type="submit" value="Save"/> </f:form> </div> <div> <p>${ produits }</p> <table> <tr> <th>REF</th> <th>DESIGNATION</th> <th>PRIX</th> <th>QUANTITE</th> </tr> <c:forEach items="${ produits }" var="p" varStatus="loop"> <tr> <td>${p.reference}</td> <td>${p.designation}</td> <td>${p.prix}</td> <td>${p.quantite}</td> <td><a href="deleteProduit?ref=${ p.reference }">Delete</a></td> <td><a href="editProduit?ref=${ p.reference }">Edit</a></td> </tr> </c:forEach> </table> </div> </body> </html>


Spring + Spring MVC + Hibernate


Cet exemple illustre le minimum possible pour faire marcher les 3 ensemble.

Création de la base


Lancez la base de données, puis on va créer une seule table avec 2 champs :

DROP DATABASE IF EXISTS SPRINGMVCTEST; CREATE DATABASE SPRINGMVCTEST CHARACTER SET utf8 COLLATE utf8_general_ci; USE SPRINGMVCTEST; CREATE TABLE utilisateur ( id INT(6) auto_increment UNIQUE NOT NULL, nom VARCHAR(64) NOT NULL, constraint PK_utilisateur PRIMARY KEY (id) );


Préparation du projet


Votre projet doit intégrer Spring, Spring MVC, hibernate, MySQLConnectorJ. Voici les librairies que j'avais sur mon projet :

//Tout ce qui concerne Hibernate 5 : antlr-2.7.7.jar byte-buddy-1.8.17.jar classmate-1.3.4.jar dom4j-1.6.1.jar hibernate-commons-annotations-5.0.4.Final.jar hibernate-core-5.3.6.Final.jar jandex-2.0.5.Final.jar javassist-3.23.1-GA.jar javax.activation-api-1.2.0.jar javax.persistence-api-2.2.jar jboss-logging-3.3.2.Final.jar jboss-transaction-api_1.2_spec-1.1.1.Final.jar //diver mysql mysql-connector-java-5.1.47.jar //spring mvc spring-web-4.3.18.RELEASE.jar spring-webmvc-4.3.18.RELEASE.jar spring-webmvc-portlet-4.3.18.RELEASE.jar spring-websocket-4.3.18.RELEASE.jar //spring aopalliance-1.0.jar commons-logging-1.2.jar spring-aop-4.3.18.RELEASE.jar spring-aspects-4.3.18.RELEASE.jar spring-beans-4.3.18.RELEASE.jar spring-context-4.3.18.RELEASE.jar spring-context-support-4.3.18.RELEASE.jar spring-core-4.3.18.RELEASE.jar spring-expression-4.3.18.RELEASE.jar spring-instrument-4.3.18.RELEASE.jar spring-instrument-tomcat-4.3.18.RELEASE.jar spring-jdbc-4.3.18.RELEASE.jar spring-jms-4.3.18.RELEASE.jar spring-messaging-4.3.18.RELEASE.jar spring-orm-4.3.18.RELEASE.jar spring-oxm-4.3.18.RELEASE.jar spring-test-4.3.18.RELEASE.jar spring-tx-4.3.18.RELEASE.jar



Pour ajouter une librairie dans intelliJ : file->project structure->librairie->petit + sur la colonne au milieu à gauche->from maven->tapez une chaine de caractère présente dans le nom de la librairie ("mysql-connector" par exemple) puis cliquez sur la loupe.


Puis, allez sur l'onglet "problems" à droite et cliquez sur "fix" et "add", cela ajoutera les librairies du serveur.


Création de la classe modèle


Cette classe sert à de notre table en base de donnée : notre table "utilisateur" va se transformer en objet java de cette classe.


package mon.beau.packag; import javax.persistence.*; import java.io.Serializable; import static javax.persistence.GenerationType.IDENTITY; @Entity @Table(name="utilisateur") public class Utilisateur implements Serializable { //le modèle @GeneratedValue(strategy = IDENTITY) @Id @Column(name="utilisateur") private int id; @Column(name="nom") private String nom; public Utilisateur() { } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } }


Utilisation de la classe HibernateTemplate



Cette classe est très pratique, elle contient des méthodes pour récupérer des données dans une classe (template.get), enregistrer (template.save), charger tout une liste (template.loadAll).


package mon.beau.packag; import org.springframework.orm.hibernate5.HibernateTemplate; import java.util.*; public class AccesBase { //C'est cet objet qui va nous permettre de manipuler notre base ! HibernateTemplate template; public HibernateTemplate getTemplate() { return template; } public void setTemplate(HibernateTemplate template) { this.template = template; } }


Création du controleur


Sera appelé quand on sera sur "http://localhost:8080/insert" :

package mon.beau.packag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class IndexController { @Autowired private AccesBase accesBase; @RequestMapping("/insert") public String index(Model model) { System.out.println(accesBase.getTemplate().get(Utilisateur.class,1).getNom()); //on récupère un utilisateur en base... Vous devez en avoir un bien sûr //pour créer un utilisateur Utilisateur u=new Utilisateur(); u.setNom("john"); accesBase.getTemplate().save(u); //on met l'utilisateur en bases return "index"; } }


Injection des beans et configuration d'hibernate



<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="hibernateDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/SPRINGMVCTEST" /> <property name="username" value="mleveque" /> <property name="password" value="password" /> </bean> <bean id="mysessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"> <property name="dataSource" ref="hibernateDataSource"></property> <property name="annotatedClasses"> <list> <value>mon.beau.packag.Utilisateur</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop> <prop key="hibernate.show_sql">true</prop> <!-- <prop key="hibernate.hbm2ddl.auto">create</prop> --> <!-- si on veut créer la base lors du lancement du programme, à enlever en prod... --> <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop> </props> </property> </bean> <bean id="template" class="org.springframework.orm.hibernate5.HibernateTemplate"> <property name="sessionFactory" ref="mysessionFactory"></property> <property name="checkWriteOperations" value="false"/> <!-- pour autoriser l'insertion de données --> </bean> <bean id="dao" class="mon.beau.packag.AccesBase"> <property name="template" ref="template"></property> </bean> </beans>


Voilà, c'est tout !

Autres fichiers



Pour info voici mon fichier dispatcher-servlet :

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <context:component-scan base-package="mon.beau.packag" /> <mvc:annotation-driven/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/view/" /> <property name="suffix" value=".jsp" /> </bean> </beans>


Et web.xml :

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>


Bon tuto ici : http://www.opentuto.com/integration-framework-spring-hibernate/

Spring boot


Permet de mettre en place des projets rapidement au prix d'une certaine rigidité. Architecture monolithique, avec en général Tomcat et Spring Boot IOC, des Rest Controllers, Dispatcher Servlets (qui doivent connaitre les Rest Controllers), nos entités, Spring repository (qui va dialoguer avec JPA), JPA (Hibernate en général).

Dans intelliJ, créez un projet "Spring Initializr" en sélectionnant ce dont vous avez besoin.

Exemeple avec les composants Web, JPA, MySQL :

package com.monpackage.coursspringboot.dao; import com.monpackage.coursspringboot.entities.Etudiant; import org.springframework.data.jpa.repository.JpaRepository; public interface EtudiantRepository extends JpaRepository<Etudiant,Long> {}



package com.monpackage.coursspringboot.controllers; import com.monpackage.coursspringboot.dao.EtudiantRepository; import com.monpackage.coursspringboot.entities.Etudiant; import com.monpackage.coursspringboot.entities.Section; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.web.bind.annotation.*; //c'est pas très conseilleé d'avoir le service directement en restController, il vaudrait mieux découpler : un restController qui appelle un service derrière. Cela permettrait d'avoir la vérification du code métier dans le service+comme chaque service lance une transaction, les transactions seraient isolées. @RestController public class EtudiantRestService { @Autowired private EtudiantRepository etudiantRepository; @RequestMapping(value="/saveEtudiant", method= RequestMethod.POST) public Etudiant saveEtudiant(@RequestBody Etudiant etudiant){ return etudiantRepository.save(etudiant); } @RequestMapping(value="/listEtudiant", method= RequestMethod.GET) public Page<Etudiant> listerEtudiant(int page, int size){ return etudiantRepository.findAll(new PageRequest(page,size)); } @RequestMapping(value="/getEtudiant/{id}") public Etudiant getSection(@PathVariable("id") Long id){ return etudiantRepository.findById(id).get(); } }


Pour créer un étudiant, on devra poster (la date est en timestamp mais il existe un autre format "2012-04-23T18:25:43.511Z") :

{ "nom":"Crasby", "prenom":"Bob", "dateNaissance":1540559249 }



package com.monpackage.coursspringboot; import com.monpackage.coursspringboot.dao.EtudiantRepository; import com.monpackage.coursspringboot.entities.Etudiant; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.List; @SpringBootApplication public class CoursspringbootApplication { public static void main(String[] args) { try { ApplicationContext ctx = SpringApplication.run(CoursspringbootApplication.class, args); EtudiantRepository etudiantRepository = ctx.getBean(EtudiantRepository.class); DateFormat df = new SimpleDateFormat("yyyy-mm-dd"); etudiantRepository.save(new Etudiant("Bob", "Johnson", df.parse("1999-11-11"))); etudiantRepository.save(new Etudiant("Bill", "Johnson", df.parse("1999-11-11"))); List<Etudiant> etds = etudiantRepository.findAll(); for (Etudiant e : etds) { } }catch(Exception e){} } }



package com.monpackage.coursspringboot.entities; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import java.io.Serializable; import java.util.Date; @Entity public class Etudiant implements Serializable { @Id @GeneratedValue private Long idEtudiant; private String nom; private String prenom; private Date dateNaissance; public Etudiant() { } public Etudiant(String nom, String prenom, Date dateNaissance) { this.nom = nom; this.prenom = prenom; this.dateNaissance = dateNaissance; } //getters et setters }


Dans l'application.properties :

spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/springsec spring.datasource.username=mleveque spring.datasource.password=password spring.jooq.sql-dialect=org.hibernate.dialect.MySQL5InnoDBDialect sping.jpa.show-sql=false spring.jpa.hibernate.ddl-auto=none spring.jackson.serialization.FAIL_ON_EMPTY_BEANS=false


Voir aussi l'annotation @ModelAttribute si on veut un objet paramètre :

@ApiOperation(httpMethod = "GET", value = "Mon beau webservice.", response = MonBeauDTO.class, responseContainer = "List") @ApiResponses(value = { @ApiResponse(code = HttpServletResponse.SC_OK, message = "OK") }) @RequestMapping(value = "/std", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public List<MonBeauDTO> maBelleMethode(@ModelAttribute MesBeauxParametres mesBeauxParametres) throws MaBelleException { if (mesBeauxParametres.getMonPremierParam() != null) { //faire quelque chose selon ce param }else if (mesBeauxParametres.getMonDeuxiemeParam() != null){ //faire quelque chose selon ce param }


REST


Representational State Transfert : Un style d'architecture qui normalise le webservice côté serveur et certain aspects côté client.

Web = http + format d'adresse + html.

Webservice = du web mis à la disposition de machine (elles utilisent le format d'adresse et le protocole http, en général on ne renvoie pas du html aux machines mais du json, du xml, etc).

Client ---- Webservice ---- Base de donnée

Le webservice est à peu près l'équivalent du DAO (il aura ses propres entity manager,EJB...). Il va fournir des donnés à la demande en respectant un cadre normalisé par REST. On ne se connecte pas directement à la base (ce n'est pas une bonne pratique de laisser la base accessible directement), on se connecte en http au webservice qui va se connecter à la base.

1)Des URL sont utilisées pour se connecter à la base. On pourrait ouvrir un navigateur et taper htp://monserveur/livres afin de récupérer la liste des livres en JSON ou en XML.

2)Les opérations sur la base correspondent aux requêtes HTTP : GET (lecture), POST (écriture), PU (màj), DELETE. Si le webservice détecte un get, il va renvoyer telle ou telle chose; pour un POST, le serveur renverra un code HTTP 201 (created)+l'objet créé (format JSON généralement), ce qui va nous permettre de connaître son ID dans la base ou de connaître le résultat de calculs. Pour que le client sache quels verbes/quelles url utiliser, il y a des normes comme Open API (récent), WADL ou SWAGGER.

3)On peut échanger les infos avec différents formats de fichier : JSON, XML... JSON est à préférer car plus compact et dévoile moins d'informations que le XML où les balises décrivent chaque attribut et est directement interprété par javascript.

4)Des mesures de sécurité peuvent être prises, par exemple en ajoutant un token dans l'url ou dans les entêtes http.

En JavaEE, on va se baser sur la bibliothèque JAX-RS qui est simple et fait un recours massif aux annotations. Dans Netbeans, une fois un projet JavaEE web créé et une connection vers notre basede donnée effectuée, clic droit à la racine du projet et new, other, webservices, restful web service from entity classes.
Un package service sera généré, se basant sur les entités de notre package "modele".
On y trouvera AbstractFacade qui est l'équivalent de la classe abstrait DAO, une classe ApplicationConfig, avec une annotation @javax.ws.rs.ApplicationPath("webresources") indiquant le chemin de l'url (http://monsite/webresources/modele.commande). On trouvera aussi private void addRestResourceClasses(Set<Class<?>> resources) { qui listera les entités.

Enfin, les entités :

@Stateless //indique qu'il s'agit d'un EJB @Path("modele.produit") //le chemin pour y accéder (http://monsite/webresources/modele.produit) public class ProduitFacadeREST extends AbstractFacade<Produit> { @PersistenceContext(unitName = "exoCDI_topCommercePartie2PU") //pour la création de l'entity manager private EntityManager em; public ProduitFacadeREST() { super(Produit.class); } @GET //que se passera-t-il si on fait un HTTP GET (càd si le navigateur appelle une page) @Path("{id}") @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) //les formats de fichier qui seront générés public Produit find(@PathParam("id") Integer id) { return super.find(id); } @DELETE @Path("{id}") public void remove(@PathParam("id") Integer id,@HeaderParam("key") String clef) { if(clef=="clefDeSuppression"){ super.remove(super.find(id)); }else{ System.out.println("la clef n'est pas bonne."); } }


On peut créer nous même des méthodes qui seront appelées selon les paramètres de l'URL, ci-dessous une méthode pour lister les produits selon une catégorie donnée, si on tape l'url "http://monsite/webresources/modele.produit/listerProduitsCategories" :

@GET @Path("{id}/listeProduits") @Produces({MediaType.APPLICATION_JSON}) public List<Produit> listerProduitsCategories(@PathParam("id") Integer id) { return em.createNativeQuery("select * from produit where idCategorie="+id,Produit.class).getResultList(); }


En cas d'erreur "BEGIN_ARRAY but was STRING" c'est peut être que de l'XML est renvoyé à la place du JSON. Enlever MediaType.APPLICATION_XML pour ne garder que @Produces({MediaType.APPLICATION_JSON}.

Le travail du côté client est de générer l'URL, de récupérer les informations concernant l'objet et de le régénérer.

Ci-dessous un exemple du côté de l'application cliente, qui va se connecter à un webservice et récupérer une liste de "catégorie". On utilise les librairies httpclient, httpcore, commons-logging de chez Apache pour faire la requête HTTP puis GSON (librairie google) pour parser le fichier json.


CloseableHttpClient client = HttpClientBuilder.create().build(); HttpGet get = new HttpGet("http://localhost:8080/exoCDI_topCommercePartie2/webresources/modele.categorie"); // creation d'une requete HttpResponse response = null; try { response = client.execute(get); //execution de la requete } catch (IOException ex) { Logger.getLogger(ExoCDI_connexionATopCommerce.class.getName()).log(Level.SEVERE, null, ex); } StatusLine statusLine = response.getStatusLine(); System.out.println("Code réponse du serveur :"+statusLine.getStatusCode()); if(statusLine.getStatusCode() == 200) { // si réponse positive du serveur HttpEntity entity = response.getEntity(); //recuperation sous forme httpentity InputStream content = null; try { content = entity.getContent(); //passage du contenu dans inputStream } catch (IOException ex) { Logger.getLogger(ExoCDI_connexionATopCommerce.class.getName()).log(Level.SEVERE, null, ex); } catch (UnsupportedOperationException ex) { Logger.getLogger(ExoCDI_connexionATopCommerce.class.getName()).log(Level.SEVERE, null, ex); } Reader reader = new InputStreamReader(content); //objet de type reader pour reconstruire l'objet GsonBuilder gsonBuilder = new GsonBuilder(); //objet factory à objet json Gson gson = gsonBuilder.create(); //creation du objet gson listeCategories = new ArrayList<Categorie>(); listeCategories = Arrays.asList(gson.fromJson(reader, Categorie[].class)); //gson fromJson resconstruit des objets à partir d'un inputstream et des métadonées de type Class try { content.close(); //finalisation on ferme le flux } catch (IOException ex) { Logger.getLogger(Controleur.class.getName()).log(Level.SEVERE, null, ex); } System.out.println(listeCategories.get(0).getLibelle()); //on affiche la 1ere categorie }



REST va découpler la partie client de la partie serveur. Il enverra au client uniquement des données modèle, pas d'information sur la structure de la page. Il n'y a pas de notion de session.

On peut avoir besoin de renvoyer des objets qui n'ont rien à voir avec nos entités. Par exemple : on a une entité prof avec âge n° de tel... mais on veut renvoyer une entité sans ces données + ajouté un champ "nom_matieres" où serait ajouté les matières enseignées par le prof. On va donc créer un objet dont le seul but sera d'être retourné en JSON, un DTO (Data transfer object).
Le DTO évite donc de renvoyer l'entité brut.

SOAP



On parle d'architecture orientée service. On aura un annuaire de webservices, chaque webservice enverra une description de ses méthodes ou attributs à l'annuaire (au format wsdl), puis l'adresse sera envoyé au client pour qu'il communique directement avec le webservice via le protocole SOAP.
WSDN : un contrat d'interface pour indiquer quelles sont les données échangées.


Android


La majorité des applications Android sont écrites en Java. Elles ne fonctionnent pas avec la machine virtuelle d'oracle : soit elles utilisent la machine virtuel Dalvik (avant android 5), soit elles sont recompilés pour devenir du code natif Android. Ca reste du Java, tout ce qu'il est possible de faire en JavaSE se retrouve sur android. Par contre l'interface graphique n'a rien à voir (plus de Swing)et autre grosse différence, on doit gérer le multitâche lors du développement. Android a également ses propres règles de sécurité. Il faut également tenir compte des caractéristiques propres à un appareil mobile comme le mode d'affichage (portrait/paysage), la taille de l'écran variable,lesoutils comme le gyroscope, la caméra, etc. C'est le multitâche qui reste le plus dur à comprendre et mettre en place.

Dans un projet Android, on trouve un dossier "src" avec les packages java (doivent être nommés mot1.mot2). Le dossier "Gen" (invisible sous Android Studio) contient lui tous les fichiers générés (on y touche pas), on y trouvera la class "R" qui est généré à chaque compilation. Le dossier "Manifest" contient "Android.Manifest", un fichier où on indiquera les classes de l'application, son titre, le type de SDK utilisé, les thèmes et autres réglages.
Dans "res", on trouvera plusieurs choses importantes. D'abord, les ressources images ("drawable") avec des dossiers pour les différentes tailles d'images selon la résolution de l'appareil sur lequel elles seront affichées. Ensuite, le dossier "layout" qui contient les vues (en xml). Enfin, le dossier "values" contenant des XML où sont renseignés les constantes (par exemple les textes affichés sur les boutons).
La liaison entre les vues en xml et le code java est faite avec la classe "R" dans le dossier "gen", une référence vers chaque élément ajouté dans le dossier RES sera ajouté automatiquement à cette classe R pour qu'on puisse y accéder par la suite (par exemple, pour afficher une vue, on fera setContentView(R.layout.ma_jolie_vue);, pour trouver un boutton déclaré dans le xml d'une vue, ce sera findViewById(R.id.MonJoliBouton);).

Le contrôleur est une "activité", il hérite de la classe "activity" (et ces activités devront être déclarées dans Android.Manifest). On redéfinira la classe "onCreate" pour indiquer ce qui se passera au lancement de l'activité (affichage d'une vue, initialisation des variables, création des listeners...). Il y a plusieurs activités pour une appli, quand on passe de l'une à l'autre, l'activité est mise en pause par défaut (mais on pourrait la laisser tourner en arrière plan en redéfinissant des méthodes). Tous les états possibles d'une application sont des méthodes d'activity : onCreate, onStart, onRestart, onResume,onPause, onStop, onDestroy (il faut la stopper avant). Il suffit de faire super.onPause() pour mettre en pause. En cas de long chargement (grosse BDD par exemple), on pourra avoir besoin de redéfinir ces états pour éviter d'interrompre le chargement.

Pour les interactions, il y a 2 façons de faire :
-soit du côté vue, dans le xml, en mettant un onClick sur un bouton :

<Button android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="Cliquez-moi" android:onClick="MaSuperbeMethode" />

C'est très simple mais la vue n'est pas réutilisable et on n'est plus vraiment dans un modèle MVC.

-soit du côté contrôleur, en ajoutant un listener sur un bouton.

Exemple du cours :

//Un listener pour table View.OnClickListener clickButtonTable = new View.OnClickListener() { @Override public void onClick(View actuelView) { // traitement déclenché par le clic sur le bouton Intent intent = new Intent(MainActivity.this, TableConversions.class); startActivity(intent); } }; //on l'attribue au bouton table buttonTable.setOnClickListener(clickButtonTable);


Exemple plus simple sur la doc android :

button = (Button) findViewById(R.id.button_id); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // Perform action on click } });


Ca respecte mieux le modèle MVC car la vue est indépendante du contrôleur par contre le code est moins lisible (utilisation de classes anonymes).

On va potentiellement avoir beaucoup de code dans la classe anonyme si on a plusieurs boutons dans la vue. Si on est gêné par ces classes anonymes, on peut faire une classe à part avec tous les listeners dedans.

Fragments


Un fragment est un peu l'équivalent des panels en swing, c'est à dire un système de container qui contient des élements d'interface (boutons, listViews, images, etc). Ils pourront avoir les mêmes layout que les activités (en xml). On peut disposer les fragments un peu comme en veut de façon avec différents types de périphériques. Comme une activité, un fragment possède un cycle de vie.
2 manières de faire des fragments, de façon statique (en dur, avec une référence vers la classe de notre fragment dans le xml de l'activité, facile à mettre en place mais on ne pourra pas retirer le fragment plus tard lors du cycle de vie de l'appli) ou dynamique (en code java, on peut remplacer les fragments en temps réel).
Les Fragments ont un cycle de vie différent des applications cf. L'activity qui a appelé le fragment est passée en paramètre lors du onAttach. On peut la récupérer avec un getActivity.

Pour les fragments dynamiques, ils ne sont pas ajoutés manuellement au "backstack" (la liste d'applis quand on appuie sur "back"), il faut le demander explicitement avec la méthode "addToBackstack".




Exemple minimum de fragment statique (associé à un "fragment_chrono.xml") :

package com.example.aka.exocdi_chronometre; import android.app.Fragment; import android.os.Bundle; import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class FragmentChrono extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate le layout (càd récupère le XML et l'utilise comme vue) return inflater.inflate(R.layout.fragment_chrono, container, false); } }


Pour utiliser ce fragment dans la vue (si le chemin vers la classe, ici "com.example.aka.exocdi_chronometre.FragmentChrono" n'est pas trouvé dans android studio, l'écrire puis faire alt+enter pour qu'il créé et reconnaisse la classe fragment automatiquement) :

<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:name="com.example.aka.exocdi_chronometre.FragmentChrono" android:id="@+id/headlines_fragment" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout>


En dynamique, les fragments seront appelés depuis le code java.

Ci-dessous un exemple fonctionnel tiré de la doc android :

package com.example.aka.exocdi_chronometre; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.support.v7.app.AppCompatActivity; import android.view.View; public class MainActivity extends FragmentActivity { //attention, si on extends FragmentActivity, il faudra "import android.support.v4.app.Fragment;" dans la classe du fragment @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Check that the activity is using the layout version with // the fragment_container FrameLayout if (findViewById(R.id.layoutQuiContientLeFragment) != null) { // However, if we're being restored from a previous state, // then we don't need to do anything and should return or else // we could end up with overlapping fragments. if (savedInstanceState != null) { return; } // Create a new Fragment to be placed in the activity layout FragmentChrono fragmentChrono = new FragmentChrono(); // In case this activity was started with special instructions from an // Intent, pass the Intent's extras to the fragment as arguments fragmentChrono.setArguments(getIntent().getExtras()); // Add the fragment to the 'fragment_container' FrameLayout getSupportFragmentManager().beginTransaction() .add(R.id.layoutQuiContientLeFragment, fragmentChrono).commit(); } }


Et le fragment :

package com.example.aka.exocdi_chronometre; import android.support.v4.app.Fragment; //ATTENTION, l'import a changé comparé au fragment statique import android.os.Bundle; import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class FragmentChrono extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_chrono, container, false); } }


Pour changer de fragment, on peut en ajouter un devant, ou cacher l'ancien avec "hide", ou utiliser replace (attention replace stoppe l'ancien fragment.
Il faut passer par un objet fragmentTransaction car plusieurs fragments ont le risque d'être manipulés par plusieurs objets à la fois.


FragmentHistorique fragmentHistorique = new FragmentHistorique(); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); // On ajoute le fragment appelé "fragmentHistorique" devant l'ancien fragment transaction.add(R.id.layoutQuiContientLeFragment, fragmentHistorique); //ajoute la transaction au "backstack" -ce qui nous permet de retrouver le précédent fragment avec back transaction.addToBackStack(null); //on peut donner un nom à cet ajout au backstack pour le récupérer plus tard d'une autre façon qu'en appuyant sur back //Effectue la transaction transaction.commit();




Installation sur Linux



Dans tous les cas, il faudra installer le SDK Android. Il peut être téléchargé tout en bas de la page https://developer.android.com/studio/index.html (lien direct vers la version 24.4.1).

(Ce processus d'installation n'a été testé qu'une seule fois étalé sur plusieurs mois : ) Une fois téléchargé, direction le dossier "tools", lancez une console et exécuter ./android update sdk. Installer Android SDK Tools, Android SDK Platform-tools, AndroiD SDK Build-tools + les éléments selon votre la version d'android avec laquelle vous voulez travailler (tout le dossier "Android 7.0 API 24" par exemple).

Pour créer un téléphone émulé, lancer ./android avd.

Netbeans


Il faut utiliser le plugin NBAndroid avec Netbeans 8.1 (la version de novembre 2015 n'a pas fonctionné sur 8.2).

Instructions d'installation.

Dans Netbeans, tools plugin, setting, add "http://nbandroid.org/release81/updates/updates.xml". Puis available plugin, cocher Android, redémarrer netbeans. Puis tools, options, miscellaneous, renseigner le dossier du SDK android. Créer un nouveau projet Android (le nom du package doit être sous la formemot1.mot2). Au démarrage, vous serez invité à choisir un de vos "AVD".

Mieux vaut tout de même utiliser Android Studio.

OrmLite


Un ORM (Object Relational Mapping) léger, utilisé pour fournir un accès à une base de donnée pour une application.

A la différence de JPA, il ne gère pas les transactions, il n'y a pas de gestion de la concurrence (vu qu'il n'y a qu'une seule application qui utilise la base), il n'y a pas de fichier de config -tout se fait depuis le code java, il est "model driven" (=on écrit les models PUIS la base et les tables seront générés automatiquement alors que pour JPA on créé les tables puis on importait depuis MySQL).
Cette approche "model driven" est possible aussi en JPA ("generate tables from entities") mais n'est pas faite en général.

Comme avec JPA, on aura du code java (de type monDao.queryBuilder().where().eq("prenom",prenom).query();, interprété par l'ORM, qu'il transformera en requête SQL. Les deux travaillent avec des annotations :

@DatabaseField(id = true private String name; @DatabaseField(columnName = "passwd", canBeNull = false) private String password;


On ne peut pas avoir de clef concaténée en OrmLite, il faut la créer soit même par concaténation, dans le constructeur surchargé et le setter : clef1Clef2=clef1.toString()+clef2.toString();


Il faudra importer les packages OrmLite dans le projet.

Exemples



Partie serveur d'un client/serveur :

package com.monpackage; import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.*; public class Main { public static void main(String[] args) { try { ServerSocket ss = new ServerSocket(8821); Socket s = ss.accept(); DataInputStream dis=new DataInputStream(s.getInputStream()); String str=(String)dis.readUTF(); System.out.println("Message="+str); ss.close(); System.out.println("Bye."); }catch(Exception e) { System.out.println(e.getMessage()); } } }


Partie client d'un client/serveur :

package com.monpackage; import java.io.*; import java.net.Socket; public class Main { public static void main(String[] args) { try{ Socket s=new Socket("localhost",8821); DataOutputStream dout=new DataOutputStream(s.getOutputStream()); dout.writeUTF("Hello Server"); dout.flush(); dout.close(); s.close(); }catch(Exception e){System.out.println(e);} } }


Ecrire dans un fichier :

try { PrintWriter sortie = new PrintWriter(new FileOutputStream("Etudiant.txt")); sortie.print(prenomEtudiant); sortie.print(nomEtudiant); sortie.print(note); sortie.close (); } catch (Exception e) { System.out.println(e.getMessage()); }


Exemple d'application Android (convertisseur euros/francs):

package afpa.cdi.euroFrancs; import android.app.Activity; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { private EditText francsEditText; private EditText eurosEditText; /** Méthode appelée lors de la création de l'activité. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //on indique qu'on utilise ressources/values/strings.xml setContentView(R.layout.main); // On récupère les EditText pour les modifier plus tard francsEditText = (EditText) findViewById(R.id.francsEditText); eurosEditText = (EditText) findViewById(R.id.eurosEditText); // On créé un listener qui va surveiller les changements sur le champ du texte TextWatcher surveilleModifTexteEuros = new TextWatcher() //une classe anonyme avec ses méthodes obligatoires { public void beforeTextChanged(CharSequence s, int start, int count, int after) { } public void onTextChanged(CharSequence s, int start, int before, int count) { //on converti l'autre champ if(eurosEditText.hasFocus()){ if(!eurosEditText.getText().toString().equals("") && !eurosEditText.getText().toString().equals(".")){ Long nombreConverti=Math.round(Double.valueOf(eurosEditText.getText().toString())*655); francsEditText.setText(String.valueOf(nombreConverti.doubleValue()/100)); }else{ //si le champ euros est complètement effacé, il faut aussi effacer le champ francs francsEditText.setText(""); } } } public void afterTextChanged(Editable s) { } }; TextWatcher surveilleModifTexteFrancs = new TextWatcher() { public void beforeTextChanged(CharSequence s, int start, int count, int after) { } public void onTextChanged(CharSequence s, int start, int before, int count) { if(francsEditText.hasFocus()){ if(!francsEditText.getText().toString().equals("") && !francsEditText.getText().toString().equals(".")){ Long nombreConverti=Math.round(Double.valueOf(francsEditText.getText().toString())*655); eurosEditText.setText(String.valueOf(nombreConverti.doubleValue()/100)); }else{ //si le champ francs est complètement effacé, il faut aussi effacer le champ euros eurosEditText.setText(""); } } } public void afterTextChanged(Editable s) { } }; // On lui attribue une méthode si il reçoit le focus eurosEditText.addTextChangedListener(surveilleModifTexteEuros); francsEditText.addTextChangedListener(surveilleModifTexteFrancs); //BOUTON RESET // On implémente l’interface OnClickListener OnClickListener clickButton = new OnClickListener() { @Override public void onClick(View actuelView) { // traitement déclenché par le clic sur le bouton // par exemple affichage d’une boite popup Toast.makeText(getBaseContext(), "Reset", Toast.LENGTH_LONG).show(); francsEditText.setText(""); eurosEditText.setText(""); } }; // On récupère le bouton cliqué Button bouton = (Button) findViewById(R.id.boutonReset); // On appelle sa méthode setOnClickListener() bouton.setOnClickListener(clickButton); } }


Le layout main.xml :

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/euros" /> <EditText android:id="@+id/eurosEditText" android:layout_width="fill_parent" android:layout_height="40dip" android:ems="20" android:layout_marginLeft="36dip" android:inputType="numberDecimal" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/francs" /> <EditText android:id="@+id/francsEditText" android:layout_width="fill_parent" android:layout_height="40dip" android:ems="20" android:layout_marginLeft="36dip" android:inputType="numberDecimal" /> <Button android:id="@+id/boutonReset" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/reset" /> </LinearLayout>


Les activités sont des classes qui hérite d'Activity, elle doivent être déclarés dans AndroidManifest.xml.

Pour passer d'une activité à une autre, on utilise des intents :

//cecodepeut être dans un OnClickListener par exemple Intent intent = new Intent(MainActivity.this, Resultat.class); intent.putExtra("nomPourRecupererObjetEnvoye",objetEnvoye); //on peut envoyer des paramètres de la façon suivante startActivity(intent);


Pour récupérer des objets envoyés d'une activité à l'autre, on peut écrire dans l'activité de destination : getIntent().getExtras().getString("nomPourRecupererObjetEnvoye");

Certains objets doivent être alimentés par des "adapters", qui sont l'équivalent des DefaultListModel en swing. Ces adapters vont faire l'intermédiaire entre un tableau et un objet (une liste, une combobox) qui utilisera ce tableau. Lorsque le tableau sera mis à jour, l'adapter mettra à jour l'objet dynamiquement.

Ci-dessous, un adapter pour un spinner :
ArrayAdapter<String> adaptateurPourMonObjet = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, monTableau);


Un adapteur pour une listeView :

super.onCreate(savedInstanceState); setContentView(R.layout.activite_principale); //la liste de choses qui seront affichées dans la liste view listTextes=new ArrayList(); //on récupère la liste view maListView = (ListView) findViewById(R.id.maListeView); //on ajoute les éléments voulus listTextes.add("texte1"); listTextes.add("texte2"); listTextes.add("texte3"); //il faut utiliser un adapter final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, listTextes); //on met l'adapter sur la listeview maListView.setAdapter(adapter);


On ne peut pas avoir de tableau à 2 dimensions dans le XML.

On pourrait faire ([http://stackoverflow.com/questions/6571477/is-it-possible-to-access-a-string-array-item-in-android|exemple]) :

<array name="Unite1"> <item name="nom">Euros</item> <item name="valeurEnEuro">1</item> </array> <array name="Unite2"> <item name="nom">Francs</item> <item name="valeurEnEuro">0.1524</item> </array> <array name="unitesUtilisees"> <item>@array/Unite1</item> <item>@array/Unite2</item> </array>


Puis :

//on récupère le contenu du tableau listant les taux et les unites TypedArray unitesUtilisees = getResources().obtainTypedArray(R.array.unitesUtilisees); tabNomUnites = new String[unitesUtilisees.length()]; tabTaux = new String[unitesUtilisees.length()]; for (int i = 0; i < unitesUtilisees.length(); ++i) { //on récupère l'id de la ressource stockée à la position i du tableau unitesUtilisees //c'est à dire un tableau de string System.out.println("contenu de unitesUtilisees ?"+String.valueOf(getResources().obtainTypedArray(unitesUtilisees.getResourceId(i, 0)))); System.out.println("Quelle unite ?"+String.valueOf(getResources().getResourceEntryName(unitesUtilisees.getResourceId(i, 0)))); TypedArray soustabUnitesUtilisees=getResources().obtainTypedArray(unitesUtilisees.getResourceId(i, 0)); System.out.println("Le contenu de soustabUnitesUtilisees :"+soustabUnitesUtilisees.toString()); //on parcourt le tableau qui vient d'être renvoyé for (int j = 0; j < soustabUnitesUtilisees.length(); ++j) { System.out.println("Nom du champ :"+soustabUnitesUtilisees.getResourceId(j,99)); System.out.println("Contenu du champ :"+String.valueOf((soustabUnitesUtilisees.getString(j)))); } }


Renvoie

I/System.out: contenu de unitesUtilisees ?[3, 5, 2, 0, 0, 0, 16, 1, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0] I/System.out: Quelle unite ?Unite1 I/System.out: Le contenu de soustabUnitesUtilisees :[3, 5, 2, 0, 0, 0, 16, 1, 2, 0, 0, 0] I/System.out: Nom du champ :99 I/System.out: Contenu du champ :Euros I/System.out: Nom du champ :99 I/System.out: Contenu du champ :1


MAIS impossible de récupérer l'attribu "name" dans <item name="nom">Euros</item>. On est donc forcé de respecter un sens (nom de la devise en premier, nom de la valeur en bas).

Donc :

<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="Unite1"> <item>Euros</item> <item>1</item> </string-array> <string-array name="Unite2"> <item>Francs</item> <item>0.1524</item> </string-array> <array name="unitesUtilisees"> <item>@array/Unite1</item> <item>@array/Unite2</item> </array> </resources>


TypedArray unitesUtilisees = getResources().obtainTypedArray(R.array.unitesUtilisees); tabNomUnites = new String[unitesUtilisees.length()]; tabTaux = new String[unitesUtilisees.length()]; for (int i = 0; i < unitesUtilisees.length(); ++i) { tabNomUnites[i]=getResources().getStringArray(unitesUtilisees.getResourceId(i, 0))[0]; tabTaux[i]=getResources().getStringArray(unitesUtilisees.getResourceId(i, 0))[1]; }


Un exemple de Handler ([https://guides.codepath.com/android/Repeating-Periodic-Tasks#scheduledthreadpoolexecutor|bon tuto]) :

package com.example.aka.exocdi_chronometre; import android.os.Bundle; import android.os.Handler; import android.os.Message; public class HandlerThreadChrono extends Handler { private MainActivity mainActivity; public HandlerThreadChrono(MainActivity ma) { mainActivity=ma; } @Override public void handleMessage(Message msg) { mainActivity.mettreAJourChrono(); } }


Un exemple de thread qui communique avec le handler plus haut :

package com.example.aka.exocdi_chronometre; import android.os.Bundle; import android.os.Message; public class ThreadChrono implements Runnable { private HandlerThreadChrono handlerThreadChrono; private int i; public ThreadChrono(HandlerThreadChrono h) { handlerThreadChrono=h; } public void run(){ //on contacte le chrono pour lui dire d'updater la vue handlerThreadChrono.sendEmptyMessage(0); // System.out.println("ThreadChrono, tour "+(i++)); //on relance dans xxx millisecondes handlerThreadChrono.postDelayed(this,100); } }


Pour faire fonctionner les thèmes dans Android Studio, changer la valeur de theme dans le manifest par android:theme="@style/Theme.AppCompat.

Pager


Le système utilisé en android pour faire défiler des fragments en glissant avec le doigt. Il n'y a pas besoin de fragmentManager si on utilise un pager.

Layout du pager :

<?xml version="1.0" encoding="utf-8"?> <!-- activity_screen_slide.xml --> <android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/idDeMonPager" android:layout_width="match_parent" android:layout_height="match_parent" />


Il faudra une classe pour le pager, héritant de FragmentStatePagerAdapter (ou PagerAdapter selon les cas) :

import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentStatePagerAdapter; public class MaClassePager extends FragmentStatePagerAdapter { private FragmentChrono fragmentChrono; private FragmentHistorique fragmentHistorique; public MaClassePager(FragmentManager fm) { super(fm); System.out.println("Constructeur du fragment"); fragmentChrono=new FragmentChrono(); fragmentHistorique=new FragmentHistorique(); } @Override public Fragment getItem(int position) { //à la position X, on retourne un fragment ou un autre System.out.println("getItem dans le pager"); if(position==0) { System.out.println("On retourne"+fragmentChrono); return fragmentChrono; } return fragmentHistorique; } @Override public int getCount() { return 2; //le nombre de pages dans le fragment } @Override public void notifyDataSetChanged() { //cette méthode est appelée quand on veut rafraichir les fragments super.notifyDataSetChanged(); System.out.println("NotifyDatasetchanged dans le pager"); fragmentHistorique.actualiserHistorique(); } }


Puis dans le MainActivity :

private ViewPager viewPager; private MaClassePager monInstanceDepager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_du_pager);//on utilise le layout du pager viewPager=(ViewPager) findViewById(R.id.idDeMonPager); monInstanceDepager=new MaClassePager(getSupportFragmentManager()); viewPager.setAdapter(monInstanceDepager);



Autre exemple simplifié à l'extrême et fonctionnel :
La class "fragment 1" :

package com.example.aka.testpager; import android.support.v4.app.Fragment; //ATTENTION, l'import a changé comparé au fragment statique import android.os.Bundle; import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class Fragment1 extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment1, container, false); } }


La classe fragment 2 est identique.

Le pager :

package com.example.aka.testpager; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentStatePagerAdapter; public class MaClassePager extends FragmentStatePagerAdapter { public MaClassePager(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { //à la position X, on retourne un fragment ou un autre System.out.println("Position : "+position); switch (position) { case 0: return new Fragment1(); case 1: return new Fragment2(); default: return new Fragment1(); //il ne faut pas retourner null sinon crash } } @Override public int getCount() { return 2; //le nombre de pages dans le fragment } @Override public void notifyDataSetChanged() { //cette méthode est appelée quand on veut rafraichir les fragments super.notifyDataSetChanged(); } }


La mainActivity :

package com.example.aka.testpager; import android.support.v4.view.ViewPager; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { private ViewPager viewPager; private MaClassePager monInstanceDepager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_du_pager); viewPager=(ViewPager) findViewById(R.id.idDeMonPager); monInstanceDepager=new MaClassePager(getSupportFragmentManager()); viewPager.setAdapter(monInstanceDepager); } }


Web


Pour donner l'autorisation à l'application d'aller sur internet, dans le manifest, juste avant l'ouverture de la balise "application", ajouter <uses-permission android:name="android.permission.INTERNET" />.

Application utilisant Apache httpclient


Pour utiliser les librairies apache, mettre dans le fichier build.gradle useLibrary 'org.apache.http.legacy' :

android { ... buildTypes { ... } ... useLibrary 'org.apache.http.legacy' }

Il faudra ensuite utiliser un Thread pour les connexions (si on se connecte à internet dans la classe "main", on risque d'obtenir une erreur "NetworkOnMainThreadException").

Ci-dessous 2 classes d'une application fonctionnelle (si on veut que le thread communique avec le min, il faudra rajouter un handler) :

La classe main, qui lance le thread :

package com.example.aka.testrest; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Thread t=new Thread(new ThreadConnexion()); t.start(); } }



La classe ThreadConnexion qui se connecte au serveur et récupère les infos :

package com.example.aka.testrest; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; public class ThreadConnexion implements Runnable { @Override public void run() { System.out.println("Le threadConnexion run..."); DefaultHttpClient httpclient = new DefaultHttpClient(); try { HttpHost target = new HttpHost("192.168.122.1", 8080, "http"); HttpGet getRequest = new HttpGet("/testWebServiceAndroid"); System.out.println("On se connecte à " + target); HttpResponse httpResponse = httpclient.execute(target, getRequest); HttpEntity entity = httpResponse.getEntity(); System.out.println("----------------------------------------"); System.out.println(httpResponse.getStatusLine()); System.out.println("----------------------------------------"); if (entity != null) { System.out.println(EntityUtils.toString(entity)); } } catch (Exception e) { e.printStackTrace(); } finally { httpclient.getConnectionManager().shutdown(); } } }


Rest


Exemple d'une application REST avec Android. Attention, cette façon de faire est dépréciée depuis le sdk 21.

Du côté serveur :
Créer la base de donnée, créer le projet web java EE, ajoutez une connexion vers votre base, puis ajouter unpackage modele dans le projet (on pourra le supprimer plus tard, il servira juste à générer le Rest), clic droit new "entity classes from database" , ajouter la connexion vers votre base à la liste, générer les classes, puis clic droit sur le projet, new "restful web service from entities classes".

Du côté client (android) :
Tout d'abord, indiquer dans le fichier gradle les librairies que l'on souhaite utiliser (ici gson et org.apache.http.legacy) :

android { ... } buildTypes { ... } useLibrary 'org.apache.http.legacy' } dependencies { ... compile 'com.google.code.gson:gson:2.4' ... }



Ensuite, il faudra l faudra instancier le thread dans la classe main :

package com.example.aka.testrest; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Thread t=new Thread(new ThreadConnexion()); t.start(); } }


Pour l'exemple, on va récupérer un objet de type Etudiant, il nous faut donc une classe Etudiant pour que GSON puisse le construire à partir des infos reçues du serveur :

package com.example.aka.testrest; public class Etudiant { private int id; private String nom; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } }


Enfin, le thread qui va se connecter :

package com.example.aka.testrest; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class ThreadConnexion implements Runnable { @Override public void run() { System.out.println("Le threadConnexion run..."); DefaultHttpClient httpclient = new DefaultHttpClient(); try { HttpHost target = new HttpHost("192.168.122.1", 8080, "http"); HttpGet getRequest = new HttpGet("/testWebServiceAndroid/webresources/modele.etudiant"); System.out.println("On se connecte à " + target); HttpResponse httpResponse = httpclient.execute(target, getRequest); HttpEntity entity = httpResponse.getEntity(); InputStream content = null; try { content = entity.getContent(); //passage du contenu dans inputStream } catch (IOException ex) { } catch (UnsupportedOperationException ex) { } Reader reader = new InputStreamReader(content); //objet de type reader pour reconstruire l'objet GsonBuilder gsonBuilder = new GsonBuilder(); //objet factory à objet json Gson gson = gsonBuilder.create(); //creation du objet gson List listeEtudiants = new ArrayList<Etudiant>(); listeEtudiants = Arrays.asList(gson.fromJson(reader, Etudiant[].class)); //gson fromJson resconstruit des objets à partir d'un inputstream et des métadonées de type Class try { content.close(); //finalisation on ferme le flux } catch (IOException ex) { } System.out.println("Mon étudiant joli :"+((Etudiant)listeEtudiants.get(0)).getNom()); } catch (Exception e) { e.printStackTrace(); } finally { httpclient.getConnectionManager().shutdown(); } } }


Résultat:

I/System.out: Le threadConnexion run... I/System.out: On se connecte à http://192.168.122.1:8080 D/NetworkSecurityConfig: No Network Security Config specified, using platform default I/OpenGLRenderer: Initialized EGL, version 1.4 D/OpenGLRenderer: Swap behavior 1 E/EGL_emulation: tid 19823: eglSurfaceAttrib(1174): error 0x3009 (EGL_BAD_MATCH) W/OpenGLRenderer: Failed to set EGL_SWAP_BEHAVIOR on surface 0xa24d0840, error=EGL_BAD_MATCH I/System.out: !!!!!!!!!!!!!! Content :org.apache.http.conn.EofSensorInputStream@860a409 I/System.out: Mon étudiant joli :Bob



Autre exemple qui n'est pas deprecated au moment d'écrire ces lignes :

package com.example.aka.webservicemeteo.access; import android.os.Message; import android.util.Log; import com.example.aka.webservicemeteo.modele.WeatherObservation; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonObject; import org.json.JSONObject; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.Arrays; import java.util.List; public class ThreadGetMeteo implements Runnable { private MonHandler monHandler; public ThreadGetMeteo(MonHandler monHandler) { this.monHandler = monHandler; } @Override public void run() { try { Log.i("suivi","Dans le ThreadGetMeteo, ouverture de l'url pour récupérer l'observation météo..."); URL url = new URL("http://api.geonames.org/findNearByWeatherJSON?lat=47.316667&lng=5.016667&username=levmc"); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); BufferedReader contenuWebRecu = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); //Log.i("suivi","STREAM RECU" + contenuWebRecu.readLine()); String json = contenuWebRecu.readLine(); Gson gson = new Gson(); //on ne peut pas directement reconstruire un objet WeatherObservation car le webservice //renvoie un json avec WeatherObservation à la racine, c'est ballot //on va donc reconstruire dans une classe intermédiaire JsonObject jsonObject = gson.fromJson(json, JsonObject.class); //puis, depuis cette classe intermédiaire, on va récupérer tous les enfants de "weatherObservation" WeatherObservation weatherObservation = gson.fromJson(((JsonObject)jsonObject.get("weatherObservation")), WeatherObservation.class); Log.i("suivi","Reconstruction de l'observation météo à partir du JSON reçu..."+weatherObservation); contenuWebRecu.close(); //finalisation on ferme le flux Log.i("suivi","Exemple de weatherobservation :"+weatherObservation); Log.i("suivi","Ville :"+weatherObservation.getStationName()); Log.i("suivi","Température :"+weatherObservation.getTemperature()); Message message=new Message(); //on met l'objet weatherObservation dans le message à destination du handler message.obj=weatherObservation; //on envoie le message au handler monHandler.sendMessage(message); } catch (Exception e) { //il faudrait plutôt catcher différentes exceptions ici //"pas de réseau ?" "pas de réponse ?" "mauvaise réponse ?" Message message=new Message(); message.obj=e.getLocalizedMessage().toString(); //on envoie le message au handler monHandler.sendMessage(message); Log.i("suivi","Exception :"+e.getMessage()); }finally { Log.i("suivi","Terminé !"); } } }


Avec son handler :

package com.example.aka.testpersothreads; import android.os.Bundle; import android.os.Handler; import android.os.Message; public class MonHandler extends Handler { private ActivitePrincipale activitePrincipale; public MonHandler(ActivitePrincipale ma) { activitePrincipale=ma; System.out.println("Creation du handler"); } @Override public void handleMessage(Message msg) { System.out.println("Un message a été reçu : "+msg.toString()); activitePrincipale.modifInterface(); } }


Et le main :

package com.example.aka.webservicemeteo; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; import com.example.aka.webservicemeteo.access.MonHandler; import com.example.aka.webservicemeteo.access.ThreadGetMeteo; public class ActivitePrincipale extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_activite_principale); MonHandler monHandler=new MonHandler(this); Thread t=new Thread(new ThreadGetMeteo(monHandler)); t.start(); } public void changerTexteCentralPar(String nouveauTexte) { TextView texteCentral= (TextView) findViewById(R.id.texteCentral); texteCentral.setText(nouveauTexte); } }


Rx Java



Résumé


Une librairie qui fonctionne avec des objets "observateurs" (Observer, Subscriber...) et des objets "observés" (de type Flowable, Observable...). Elle permet d'appliquer des modificateurs (fusion, création de thread) et les transformations de notre choix (par ex mettre en majuscule, fusionner, récupérer un attribut précis...) en chaine sur les objets observés.

Par exemple, on pourra interroger 2 API en parallèle, puis utiliser Rx Java pour observer le retour, combiner les 2 réponses ("observables") et créer un nouvel objet en une seule ligne.

Fonctionnement observable / observé


Les observateurs s'abonnent à un observé. L'observé appelle la fonction onNext() de son abonné pour lui envoyer des données. En cas d'erreur, l'observé appelle le onError() de son abonné. Et à la fin, il lui appelle onCompleted().

Un exemple qui montre très bien ces concepts (rxJava v2, fonctionne quasi-pareil en v3) :

package petitTestRxJava; import io.reactivex.Observable; import io.reactivex.ObservableEmitter; import io.reactivex.ObservableOnSubscribe; import io.reactivex.Observer; import io.reactivex.disposables.Disposable; import io.reactivex.internal.operators.observable.ObservableCreate; public class maBelleClassePrincipale { public static void main(String[] args) { System.out.println("1) Début du programme, création de l'observable"); Observable<String> observable = new ObservableCreate<String>(new ObservableOnSubscribe<String>() { @Override public void subscribe(ObservableEmitter<String> emitter) throws Exception { emitter.onNext("3) Envoi d'un message"); emitter.onComplete(); } }); System.out.println("2) Milieu du programme, création d'un observer"); Observer<String> observer = new Observer<String>() { // //il lui faut forcément les méthodes onNext, onError, onComplete car c'est ce que l'observable va appeler @Override public void onNext(String s) { System.out.println("onNext " + s); } @Override public void onError(Throwable e) { } @Override public void onComplete() { System.out.println("onComplete"); } @Override public void onSubscribe(Disposable d) { System.out.println("onSubscribe"); } }; observable.subscribe(observer); //on donne cet obsrvateur à l'observable, ce qui va déclencher l'envoi du message } }


En sortie :

1) Début du programme 2) Milieu du programme onNext 3) Envoi d'un message onComplete



Heureusement on peut faire plus court (fonctionne en V2/V3) malheureusement la création et l'appel à l'objet qui observe sont bien cachés dans les tréfonds du code:

public static void main(String[] args) { System.out.println("1) Début du programme"); Observable<String> observable = Observable.create( //Création d'un observable. new ObservableOnSubscribe<String>(){ public void subscribe(ObservableEmitter<String> observableEmitteur){ observableEmitteur.onNext("3) La belle data !"); //sous le capot, appelle en fait le onNext du "observer" } } ); System.out.println("2) Milieu du programme"); observable.subscribe(laChaine-> System.out.println(laChaine)); //sous le capot, créé un "observer" en lui donnant la callback "laChaine-> System.out.println(laChaine)" }

Dans le code ci-dessus les concepts sont invisibilisés mais sont toujours là !
En creusant, on voit que le onNext appelle un "downStream.onNext()" et que "downStream" est un observer : https://github.com/ReactiveX/RxJava/blob/v3.1.5/src/main/java/io/reactivex/rxjava3/internal/operators/observable/ObservableGenerate.java
On voit également que le subscribe créé un observeur : https://github.com/ReactiveX/RxJava/blob/3.x/src/main/java/io/reactivex/rxjava3/core/Observable.java

Pour appeler les méthodes onError et onComplete, rien de sorcier :

public void subscribe(ObservableEmitter<String> monBeauSouscripteur){ ... monBeauSouscripteur.onError(new Exception()); //appelle du onError du subscriber ... monBeauSouscripteur.onComplete(); //envoi


Pour traiter les appels, on peut faire ça :

System.out.println("2) Milieu du programme"); observable .doOnError(t -> {System.out.println("Exception reçue : " + t);}) .doOnComplete(() -> {System.out.println("Fin du programme");}) .subscribe( a-> System.out.println(a));


Ou, encore plus concis, ça :

observable.subscribe( //utilisation d'une méthode alternative pour lui filer les codes du... a-> System.out.println(a), //...onNext() t -> {System.out.println("Exception reçue : " + t);}, //...onError() () -> {System.out.println("Fin du programme");} //...onSuccess() );


Créer un observable, façon simple


Beaucoup plus facile que d'utiliser "new ObservableCreate" comme vu ci-dessus, il existe des méthodes statiques (just, fromIterable, range...) dans la classe Observable pour créer des observables avec toutes sortes de comportements :

package petitTestRxJava; import io.reactivex.rxjava3.core.Observable; public class maBelleClassePrincipale { public static void main(String[] args) { Observable.just("La belle data !").subscribe( obj -> System.out.println(obj)); //Affiche "La belle data !" Observable.just("a", "b", "c").subscribe( obj-> System.out.println(obj)); //Affiche a, b puis c Observable.fromIterable(new ArrayList<>(Arrays.asList(1,2,3))).subscribe( a-> System.out.println(a)); //Affiche 1, 2 puis 3 Observable.range(1, 1_000_000).subscribe( obj-> System.out.println(obj)); //Affiche de un à un million } }


Back pressure


Quand un observable envoie des données trop vite pour que l'observé ait le temps de les traiter.

Opérateurs


Un opérateur est une fonction qui prend en entrée et donne en retour un observable. Pour chacun élément émit par l'observable d'entrée, une fonction sera appliquée dessus et le résultat sera émit dans l'observable de retour.

Map


Permet d'appeler une fonction pour l'objet émis. Cette fonction doit renvoyer un observable.

Exemple :

List<String> mesBellesLettres = Arrays.asList("a", "b", "c"); Observable.fromIterable(mesBellesLettres) //pour chaque élément on renvoi un observable avec une fonction appliquée sur l'émission .map(obj -> obj.toUpperCase()) //on pourrait aussi utiliser la méthode statique comme ceci : .map(String::toUpperCase) .subscribe( a-> System.out.println(a)); //(renvoie A, B puis C)


Si on voulait écrire un log à chaque convertion :

List<String> mesBellesLettres = Arrays.asList("a", "b", "c"); Observable.fromIterable(mesBellesLettres) .map(string -> { System.out.println("Transformation en majuscule !"); string.toUpperCase(); return string; //on renvoie un String et la fonction map() s'occupe de l'encapsuler dans un observable }) .subscribe( a-> System.out.println(a)); //cet observable on peut y souscrire ici }


flatMap


Permet d'appeler une fonction pour l'objet émis. Cette fonction doit renvoyer un ou plusieurs observables.

C'est très pratique quand on veut renvoyer un observable qui n'est pas du même type, ci-dessous ça commence avec observable émettant un tableau de String puis à la fin on veut un Integer :

public static void main(String[] args) { String[] loginInfo = {"utilisateur", "mot_de_passe"}; Observable.just(loginInfo) .flatMap(obj -> seConnecter(obj)) .subscribe(obj-> System.out.println("code de retour : " + obj)); } static Observable<Integer> seConnecter(String[] obj) { System.out.println("LOG : Connexion avec " + obj[0] + " " + obj[1]); //....imaginons qu'on appelle une fonction de connexion qui renvoie un numero (1 si réussie par exemple) int retourResultatConnexion = 1; return Observable.just(retourResultatConnexion); } //Affiche : //LOG : Connexion avec utilisateur mot_de_passe //code de retour : 1


Un map au lieu du flatmap aurait encapsulé le code retour. Le code de retour ne s'afficherait pas, ce serait à la place le toString de l'observable encapsulant.

LOG : Connexion avec utilisateur mot_de_passe Code de retour io.reactivex.rxjava3.internal.operators.observable.ObservableJust@60c6f5b


Pour utiliser map dans cette situation il faudrait donc 1)s'abonner à l'observable renvoyant le String, puis 2)s'abonner à l'observable renvoyant le Integer, bref, c'est une usine à gaz :

.subscribe(obj-> { obj.subscribe(obj2 -> System.out.println("Code de retour" + obj2)); });


Autre exemple, flatmap qui renvoie plusieurs éléments alors qu'il n'y a qu'un seul élément en entrée :

static Observable<String> listerAchats(int numCommande) { String[] objetsDeLaCommande ={"fraises", "fromage blanc", "boudoirs", "papier toilette"}; return Observable.fromArray(objetsDeLaCommande); } public static void main(String[] args) { int numCommande = 123; System.out.println("Liste de la commande 123 :"); Observable.just(numCommande) .flatMap(obj -> listerAchats(obj)) .subscribe(obj-> System.out.println("-" + obj)); } //Affiche : //Liste de la commande 123 : //-fraises //-fromage blanc //-boudoirs //-papier toilette



zip


Permet d'utiliser une fonction s'appliquant sur plusieurs observables en même temps en entrée. Renvoie un observable en sortie.

Ci-dessous, on donne 2 observable ainsi qu'une fonction prenant en entrée les émissions de ces observables :

Observable<String> titreFilmObservable = Observable.just("Star Trek", "Ip Man"); Observable<String> genreFilmObservable = Observable.just("Science Fiction", "Action"); Observable<String> resultObservable = Observable.zip(titreFilmObservable, genreFilmObservable, (titre, genre) -> titre + "=" + genre); resultObservable.subscribe( str -> System.out.println(str)); //Affiche : //Star Trek=Science Fiction //Ip Man=Action


Delay


Fait une pause.

subscribeOn


Spécifie le scheduler qu'on veut utiliser, en pratique indique quel genre de thread on veut.

Exemples :
Schedulers.newThread() (un nouveau thread pour chaqsue observable), Schedulers.single() (un seul thread commun à tous les observables), Schedulers.io() (pratique pour les lectures disques/appels réseau créé des thread selon un pool dynamique, en réutilise etc), Schedulers.computation() (pareil que l'autre sauf qu'il se base sur le nombre de coeurs cpu), AndroidSchedulers.mainThread() (pour Android, utile pour la modification de l'UI).

toBlocking


La différence est qu'on peut se désinscrire d'un observable non bloquant à tout moment. observable.toBlocking() pour convertir en bloquant.


System.out.println("Debut"); Observable.just("La belle donnée !") .subscribeOn(Schedulers.io()) //creation d'un nouveau thread .delay(1000, TimeUnit.MILLISECONDS) //attente d'une seconde //.toBlocking() //bloque le thread principal .subscribe( obj -> System.out.println("La donnée : " + obj)); //sans toBlocking() on ne verra jamais ça, car le thread principal s'arrête avant //Thread.sleep(2000); //si on ne veut pas de toBlocking() on peut attendre manuellement que le thread Schedulers.io() se termine System.out.println("Fin");





Divers


Compiler et exécuter


L'utilitaire javac permet de compilet un fichier .java en fichier .class.

Par exemple : javac MaClasseJava.java

Un fichier MaClasseJava.class sera généré, impossible à lire avec un éditeur de texte (c'est du code compilé !).

Pour le lancer, il faut utiliser java : java MaClasseJava



JMS


Service d'envoi de message de Java, dépassé, remplacé depuis par du Rest, RabbitMQ... selon les besoins.
Les messages peuvent être envoyés sur une "queue" (le message atterri dans une boîte aux lettres et est pris par quelqu'un) ou sur un "topic" (le message est dupliqué à tous les abonné au topic). Les queues sont pratiques pour des clusters car ils permettent ainsi de diviser la charge (server A prend le premier message, puis B le 2e, puis A le 3e etc). Les topics sont pratiques quand on a un serveur qui doit notifié des choses en broadcast à ses clients.
En termes simplifiés : queue = client vers serveur, topic = serveur vers clients.

Exemple pour publier sur un topic :

import javax.jms.* //dans un init() par exemple, récupération du contexte : Hashtable<String, String> ht = new Hashtable<String, String>(); ht.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.enterprise.naming.SerialInitContextFactory"); //ca dépend de votre serveur, weblogic, apache... ht.put(Context.PROVIDER_URL, "monbeauserver:5000"); ht.put(Context.SECURITY_PRINCIPAL, "monlogin"); ht.put(Context.SECURITY_CREDENTIALS, "monpass"); try { ctx = new InitialContext(ht); } catch (NamingException e) { e.printStackTrace(); } //puis envoi de message au topic TopicConnectionFactory factory = (TopicConnectionFactory) ctx.lookup("MaConnectionFactory"); //le nom JNDI connection = factory.createTopicConnection("monlogin", "monpass"); session = connection.createTopicSession(false, javax.jms.Session.AUTO_ACKNOWLEDGE); Message message = session.createTextMessage("coucou"); TopicPublisher topicPublisher = session.createPublisher(((Topic)ctx.lookup("jndi/jms/remote/montopic"))); topicPublisher.send(message); connection.close();


Pour lister les noms JNDI d'un serveur, on peut faire :

NamingEnumeration<NameClassPair> list = ctx.list(""); while (list.hasMore()) { System.out.println("-------->" + list.next().getName()); }


Jenkins


Sous utilisé sur les projets Java, sert surtout à faire de la livraison.
En lui fournissant un script groovy (qui peut être directement dans le projet Java), Jenkins peut typiquement :
1. Faire un clone/pull/checkout de la branche désirée.
2. Changer la version (en modifiant “snapshot” des poms.xml).
3. Poser un tag (une étiquette pour revenir en arrière sur git) avec le n° de version.
4. Executer les scripts de build Maven (ce qui va générer un zip qui sera envoyé au client par exemple).
5. Modifier les POM en incrémentant de 1+"SNAPSHOT"
6. Pousser sur la branche désirée.

Parfois on demande à Jenkins de faire un build à chaque push sur la branche Master. Jenkins offre une interface simplifiée pour que même un non-développeur puisse faire une livraison au client.

Maven


Maven est un outil qui permet entre autre de gérer les dépendances à distance, en les récupérant sur un serveur distant. Au lieu d'intégrer les librairies manuellement (importation de jar), on va donner l'URL d'un dépôt Maven où seront récupérés les fichiers.

Maven est souvent intégré aux IDE mais on peut aussi télécharger un zip contenant maven et fair epointer sur IDE dessus si on a besoin d'une version particulière de Maven.

Il permet également de packager son projet selon différents profils (par exemple le fichier de config sera différent entre le profil "dev" et le profil "integ"), et de lancer toute sorte de plugins pour faire différentes choses (par exemple, convertir une doc ascii-doc en HTML avec le plugin asciidoctor et l'intégrer dans un zip qui sera livré au client avec maven-assembly-plugin).

On peut lancer un plugin directement (clean javafx:jlink par ex) ou bien attacher le plugin à une "phase". En effet Maven se déroule selon plusieurs phase : clean (nettoyer le dossier target), compile, test, package, instal, deploy...
Maven se configure avec des fichiers XML, souvent un dossier "pom.xml" à la racine du projet.
Ci-dessous le plugin javafx-maven-plugin sera lancé pendant la phase d'install (on décide nous même de la phase mais il faut rester logique, si vous avez un plugin qui dépend du résultat d'un autre il faudra le lancer après) :

<plugin> <groupId>org.openjfx</groupId> <artifactId>javafx-maven-plugin</artifactId> <version>0.0.8</version> <id>linux-build</id> <phase>install</phase> <goals> <goal>jlink</goal> </goals> <configuration> <stripDebug>true</stripDebug>



Pour créer un projet Maven: new project, Maven, java application. Entrer le nom du projet, éventuellement le numéro de version et un group id. Un projet va se créer, il faudra modifier le fichier pom.xml pour ajouter des librairies.

Exemple de pom.xml où a été rajouté la librairie DesignGridLayout :

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany</groupId> <artifactId>mavenproject2</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <!-- https://mvnrepository.com/artifact/net.java.dev.designgridlayout/designgridlayout --> <dependencies> <dependency> <groupId>net.java.dev.designgridlayout</groupId> <!-- Identifie le groupe qui a créé designgridlayout. Pas obligé d'avoir un nom de groupe avec des points (junit ne le fait pas) mais en général on le fait --> <artifactId>designgridlayout</artifactId> <!-- le nom de la librairie --> <version>1.5</version> <!-- le groupid+le nom de la libraie permettent d'identifier une libraire mais il y a plusieurs versions, ici on précise celle qu'on veut --> </dependency> </dependencies> </project>


Une fois le fichier sauvé, DesignGridLayout sera téléchargé et placé dans le dossier dependency et utilisable tout de suite (dans certains IDE, il y aura un bouton "import" à cliquer). Bien sûr, une librairie pourra elle même dépendre d'une autre librairie mais maven gère ça tout seul. Si vous avez maven d'installé en ligne de commande mvn dependency:tree fera un graph des inter dépendances. Pour éviter d'alourdir l'appli, on peut demander à maven de ne pas inclure les librairies (par exemple, elles sont déjà dans le serveur d'application) avec l'indication "scope" :

<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-rs-client</artifactId> <version>3.0.0</version> <scope>provided</scope> </dependency>


On pourra ausi avec un scope "test" (librairie qui ne sera autilisée que lors des tests), ou "runtime" (utilisé lors du lancement mais pas pour la compilation) etc.

Attention l'ordre de déclaration des dépendances est important !

(...) this determines what version of a dependency will be used when multiple versions of an artifact are encountered. (...) You can always guarantee a version by declaring it explicitly in your project's POM. (...) since Maven 2.0.9 it's the order in the declaration that counts: the first declaration wins.


Par exemple, les libs spring-hibernate3 et spring-tx utilisent tous les 2 spring-dao, mais celui de spring-tx est plus récent donc il faut le mettre avant.


Au lieu de prendre les dépendances sur maven repository, on peut avoir des dépôts à soi. Cela permet par exemple à une entreprise de développer ses propres librairies et d'utiliser maven pour les distribuer sans pour autant devoir les mettre sur internet. Il y a également un déport local : dans le dossier caché "m2" sur sa propre machine de dev, on aura un repository. Enfin, si on veut utiliser une librairie que maven ne propose pas sur son site, on peut donner directement l'adresse du jar avec un scope "system" de la façon suivante :

<dependency> <groupId>idDuGroupe</groupId> <artifactId>maBelleLib</artifactId> <version>1.6</version> <scope>system</scope> <systemPath>${project.basedir}/libs/maBelleLib.1.6.jar</systemPath> </dependency>


Maven peut faire des builds et des installation. "clean package" pour nettoyer son environnement de travail en virant les vieux fichiers générés / générer un jar. "Mvn deploy" va déployer vers un serveur nexus.

Autre exemple de fichier Maven, pour un projet Java EE, qui génèrera un .war quand on va lancer l'action "clean & verify" dans Maven :

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>MonProjetJavaEE</groupId> <artifactId>MonProjetJavaEE</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency> </dependencies> <build> <finalName>MonProjetJavaEE</finalName> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> </plugins> </pluginManagement> </build> </project>


Maven fonctionne avec des "lifecycles", des "phases" et des "goals" (//maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html#Lifecycle_Reference). Par exemple le lifecyle "clean" comporte 3 phases : pre-clean, clean, post-clean. Le lifecycle appelé "défaut" comporte 23 phases. En executant une phase, les phases associées au même lifecycle sont rejouées : par exemple, lancer post-clean lancera forcément les 2 phases précédentes. Pour lancer une phase, taper mvn nomDeLaPhase.

Par exemple, mvn clean install signifie qu'on veut lancer la phase clean (ce qui lancera églement pre-clean) et la phase install (ce qui lancera également les 19 phases précédentes).

Les "goals" sont des tâches (des actions diverses, par exemple analyser, packager, renommer, etc) lancées par les plugins : on peut les lancer directement en saisissant une commande ou bien les associer à une phases. Il est possible de lister les plugins associées aux phases avec mvn help:describe -Dcmd=nomDeLaPhase. Pour lancer un goal, mvn nomDuPlugin:nomDuGoal.

On peut donc dire que chaque lifecycle comporte plusieurs phases qui comportent elles-mêmes 0 ou plusieurs goals.

Les plugins peuvent lancer différents goals différents (et les goals s'attacheront à différentes phases).

La command mvn utilisée seule nous invitera à spécifier une phase ou un goal :

[ERROR] No goals have been specified for this build. You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy, pre-clean, clean, post-clean, pre-site, site, post-site, site-deploy.


super tuto

Il y a des tas de plugins. Par exemple, on peut avoir dans les fichiers de son projet une doc en asciidoctor et on utilisera le plugin asciidoctor-maven-plugin pour convertir ça en html et l'inclure dans son livrable.

Autre exemple, plutôt que de créer le livrable à la main pour le client en faisant un zip contenant le jar, les fichiers de config, des pdf, que sais-je, on pourra utiliser maven-assembly-plugin qui mettra tout automatiquement.

Pour voir l'arbre des dépendances : mvn dependency:tree

Pour monter les versions d'un POM et tous ses enfants : mvn versions:set -DnewVersion=01.02.03.04-SNAPSHOT -DprocessAllModules -DupdateMatchingVersions=true

Découpage de listes



private List<List<String>> decouper(List<String> listeADecouper, int taille) { List<List<String>> listeDeListeDecoupees = new ArrayList<>(); for (int i = 0; i <= listeADecouper.size(); i += taille) {//tant que i est plus petit que le nombre d'objets, on incrémente de i+la taille max //on fait une nouvelle liste en prenant les objets depuis le ième jusqu'à : soit i+taillemax, soit jusqu'au dernier //donc avec 15à éléments et taille à 100 : ième jusqu'à i+taillemax = de 0 à 100 //ième jusqu'au dernier = de 100 à 150 listeDeListeDecoupees.add(listeADecouper.subList(i, Math.min(i + taille, listeADecouper.size()))); } return listeDeListeDecoupees; }


Formatage de String


On demande d'afficher une string pour un integer ("d") sur 2 caractères avec un zéro : String.format("%02d",localDate.getDayOfMonth())



Un compteur qui va jusqu'à 1000 et reset


counter = (++counter)%1000;

Une fonction récursive qui calcule les factorielles



public static long factoriserRecursivement(long n){ if(n>1){ return factoriserRecursivement(n-1)*n; } return 1; }





public static void trierTableau(int[] tableauATrier) { int tampon=0; int tailleTour=tableauATrier.length-1; int nombreDeBoucle=0; //pour chaque élément du tableau for (int j = 0; j < tableauATrier.length-1; j++) { nombreDeBoucle++; System.out.println("-----"); for (int i = 0; i < tailleTour; i++) { //on pourrait faire "for (int i = 0; i < ((tableauATrier.length-1)-j); i++) {" pour se passer de tailleTour nombreDeBoucle++; System.out.println(tableauATrier.length-1 + " vient-il après " + tableauATrier[i + 1] + " ?"); System.out.println("Faut tester " + tableauATrier[i] + " par rapport à " + tableauATrier[i + 1]); if (tableauATrier[i] > tableauATrier[i + 1]) { System.out.println("Oui ! On échange les 2 cases"); tampon = tableauATrier[i]; tableauATrier[i] = tableauATrier[i + 1]; tableauATrier[i + 1] = tampon; }else { System.out.println("Non, on touche à rien."); } afficherTableau(tableauATrier); } tailleTour=tailleTour-1; //le plus grand élément vient d'être mis au bout du tableau, on a plus besoin de le parcourir entièrement (on pourrait mais on perdrait du temps) } System.out.println("Nombre de boucle : "+nombreDeBoucle); afficherTableau(tableauATrier); }


Des outils de monitoring intégrés à java



Dans le dossier des binaires, par exemple /usr/lib/jvm/jdk1.8.0_171/bin, on trouve JConsole et jvisualvm. On peut voir al consommation de mémoire en temps réel par exemple.

Enchainer des get à l'aveugle



Si vous faites :
String nom = monObjet.getAutreObjet().getEncoreAutreObjet().getNom()
...vous avez des risques de NullPointerException à chaque get.

On pourrait faire, mais c'est un peu long :
if(monObjet != null && monObjet.getAutreObjet() != null && monObjet.getAutreObjet().getEncoreAutreObjet() != null...

Mais en Java 8 on peut faire :

String nom = Optional.ofNullable(monObjet) .map(MonObjet::getMonAutreObjet) .map(MonAutreObjet::getNom) .orElse("");


Dans tous les cas ce n'est pas très bien car on viole la loi de Déméter.

Optional peut aussi faire les test directement, exemple :

Optional<String> op = Optional.of("kikoo"); op.filter(s->s.contains("a")).ifPresentOrElse(System.out::println, () -> System.out.println("La chaine ne contiens pas la lettre a"));



Tracer une erreur


Si on ne peut pas mettre des points d'arrêt pour une raison X ou Y on peut générer une exception : (new Exception()).printStackTrace();

Ca ne gênera pas la déroulement du programme et on verra s'afficher une stacktrace indiquant qui a fait les appels.


Equivalence des lettres accentuées dans les fichiers de properties




Lettre Unicode HTML à \u00e0 &agrave; â \u00e2 &acirc; ä \u00e4 &auml; è \u00e8 &egrave; é \u00e9 &eacute; ê \u00ea &ecirc; ë \u00eb &euml; î \u00ee &icirc; ï \u00ef &iuml; ô \u00f4 &ocirc; ö \u00f6 &ouml; ù \u00f9 &ugrave; û \u00fb &ucirc; ü \u00fc &uuml; ç \u00e7 &ccedil; ' \u2019

Source : http://www.eteks.com/tips/tip3.html

L'utilitaire native2ascii dans le JDK permet d'effectuer la transposition : echo vive le théâtre | native2ascii renverra vive le th\u00e9\u00e2tre.


Lecture d'un fichier texte



Si le fichier est dans le dossier resources :

File file = new File(getClass().getResource("/json/syntaxe_incorrecte.json").getFile()); String content = new String(Files.readAllBytes(file.toPath())); System.out.println(content);




S'il est externe :

String contenuDuFichierTexte = ""; FileInputStream file = new FileInputStream("chemin/relatif"); //ou /chemin/absolu try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(file, "UTF-8"));) { String line = null; while ((line = bufferedReader.readLine()) != null) { contenuDuFichierTexte += line; } } catch (IOException e) { LOGGER.error(String.format("Cannot read file \"%s\"", file), e); }





Deserialisation custom avec Jackson


Pratique si on veut une vérif des champs par exemple.

Ci-dessous on a un objet de type :

{ "attribut": "blah", "deuxiemeAttribut": "blabla", "tableau":[ { "valeurDuTableau": "valeur" }, { "valeurDuTableau": "encoreUneValeur" } ] }



package mon.beau.package; import ...... public class MonBeauDeserializer extends StdDeserializer<MonObjet> { public MonBeauDeserializer() { this(null); } public MonBeauDeserializer(Class<?> vc) { super(vc); } @Override public MonObjet deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { JsonNode root = jp.getCodec().readTree(jp); MonObjet monObjet = new MonObjet(); monObjet.setMonAttribut(root.get("attribut").asText()); monObjet.setMonDeuxiemeAttribut(root.get("deuxiemeAttribut").asText()); for (JsonNode leTableau : root.get("tableau")) { System.out.println(leTableau.get("valeurDuTableau").asText()); } return monObjet; } }


Ensuite, pour utiliser notre deserializer :

ObjectMapper mapper = new ObjectMapper(); //mapper ordinaire de jackson SimpleModule module = new SimpleModule(); //un module qu'on va ajouter à ce mapper module.addDeserializer(MonObjet.class, new MonBeauDeserializer()); //ajout de deserializer comme module mapper.registerModule(module); //enregistrement du module auprès du mapper //et, enfin pour récupérer l'objet : MonObjet monObjet = mapper.readValue(contenuPage, MonObjet.class);


Si le premier objet du JSON est un tableau, on peut faire MonDeserializer extends StdDeserializer<MonObjet[]> et autres MonObjet[].class.

Autre astuce concernant Jackson : il n'a pas besoin de setters. A moins qu'on utilises des annotations il utilise déjà la reflexion pour trouver le nom des champs et peut donc mapper directement même si le champ est privé !

Log4j


Permet d'écrire des logs. On définit des "loggeurs" et des "appenders" en xml ou dans un fichier properties puis on appelle en java comme ceci :


final static Logger logger = Logger.getLogger("monBeauLoggerDefiniDansLeXML"); //on peut aussi passer la classe //plus tard dans le code : logger.error("Une erreur s'est produite.", e);



Exmeple de fichier XML avant 1.2 :

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <!-- Là où seront envoyés les logs : --> <!-- Dans la console --> <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender"> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d %p [%t] %C (%F:%L) - %m%n"/> </layout> </appender> <!-- Dans un fichier --> <appender name="RF" class="org.apache.log4j.RollingFileAppender"> <param name="File" value="/chemin/du/log.log"/> <param name="MaxFileSize" value="3000KB"/> <param name="MaxBackupIndex" value="5"/> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d %p [%t] %C (%F:%L) - %m%n"/> </layout> </appender> <!-- Dans un autre fichier --> <appender name="mon2eAppender" class="org.apache.log4j.RollingFileAppender"> <param name="maxFileSize" value="10MB" /> <param name="maxBackupIndex" value="5" /> <param name="File" value="/autre/chemin/pour/le/log.log"/> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n" /> </layout> </appender> <!-- par défaut les logs iront là --> <root> <priority value="debug"/> <appender-ref ref="STDOUT"/> </root> <!-- on créé un logger --> <category name="nom.du.logger"> <!-- on met souvent le nom d'un package car ya une notion d'héritage mais pas obligé. --> <priority value="debug"/> <!-- Il écrira seulement debug et valeurs supérieures (info, warn, error, fatal, all) --> <appender-ref ref="RF"/> </category> <category name="nom.du.logger.enfant"> <!-- Il y a un héritage, tout ce qui sera écrit ici sera AUSSI écrit dans RF --> <priority value="debug"/> <appender-ref ref="mon2eAppender"/> <!-- on écrit dans le 2e appender --> </category> <category name="nom.du.logger.enfantegoiste" additivity="false"> <!-- additivity élimine l'héritage --> <priority value="debug"/> <appender-ref ref="mon2eAppender"/> <!-- on écrit dans le 2e appender --> </category>


Après 1.2 c'est quasi pareil, sauf que "category" devient "logger" et "priority" devient "level".

Point d'entrée du programme


La méthode "main" ("principale" en anglais) est le point d'entrée du programme. Elle doit être nommée "main", de type public (pour qu'on puisse y accéder !) et static (pas besoin de l'instancier !), admettant en paramètre un tableau string (les arguments si on lance notre programme en ligne de commande) et renvoyant void (ne renvoie rien).


public class MaBelleClasse { public static void main(String[] args) { //on peut accéder à cette classe depuis l'extérieur ! } }

Il peut y avoir plusieurs class main dans un seul programme.

Entrer dans la méthode main


-Dans l'IDE, une option est proposée dans la structure du projet pour savoir où se trouve la méthode main :


-Lors du lancement d'un jar, on peut choisir quel main lancer :par exemple, java -cp MyJar.jar mon.beau.package.MaClasseAvecMethodeMain1 ou java -cp MyJar.jar mon.beau.package.MaClasseAvecMethodeMain2.

-Dans le fichier MANIFEST.MF d'un jar, on peut indiquer la class main (Main-Class: MaBelleClasse) puis lancer le jar via java -jar jarfilename. jar.

-Certains IDE détectent le code correspondant à une méthode main et affichent un bouton pour la lancer :


Méthodes utilitaires sympa


StringUtils.capitalize("machaine") pour mettre la première lettre en majuscule ("Machaine").

Fichier MANIFEST.MF


Pour les archives java executables (JAR). Se trouve dans le dossier "META-INF". Le manifest peut indiquer le point d'entrée de l'application (Main Class), où se trouvent les librairies (class path), mettre en place un système de permission etc.

Exemple de fichier MANIFEST.MF :

Manifest-Version: 1.0 Archiver-Version: Plexus Archiver Built-By: leCreateurDuManifest Class-Path: conf/ lib/MaTresJolieLibrairieJava.1.2.3.4.jar lib/monAutr eTresJolieLibJava02.jar Created-By: Apache Maven 3.3.9 Build-Jdk: 1.8.0_171 Main-Class: mon.beau.package.MaBelleClasse


Le saut de ligne à la fin est important.
La méthode main se trouve dans mon.beau.package.MaBelleClasse, il y a un dossier de conf (contenant par exemple un fichier properties) et un dossier lib contenant les librairies.

Plus d'info :
https://docs.oracle.com/javase/tutorial/deployment/jar/manifestindex.html

Dupliquer un serveur Tomcat


-Dans /bin/setenv.sh, changez le chemin de CATALINA_BASE pour correspondre au nouveau chemin.
-Dans /conf/server.xml, dans la balise Parameter à l'intérieur de , changez la value pour correspondre au chemin désiré.
-Dans /conf/catalina.properties, changez les ports :

connectorPort=23020 shutdownPort=23025 ajpPort=23029


Ajouter un certificat / un keystore


1)Télécharger le certif par exemple avec Firefox (cliquer sur l'icône du cadenas https, more information, view certificate, "Download PEM (cert)").
2)Faire keytool -import -alias *alias* -keystore mesCertifs -file /home/monLogin/certificat.pem
3)Ensuite ajouter l'argument java : -Djavax.net.ssl.keyStore=/home/monLogin/mesCertifs -Djavax.net.ssl.keyStorePassword=password


Sécurité



-XXE attack : dans un fichier XML, appeler un autre fichier XML et pointer vers un fichier du serveur (par ex /etc/passwd). Penser à supprimer le traitement des XML EXternal Entities dans la lib qui parse le XML / voir carrément supprimer le traitement du DOCTYPE.

-Injection SQL : utiliser les prepared statements.

-ID de session : Si vous générez vous-même vos ID de session, utilisez SecureRandom. Donnez un nouvel ID de session une fois l'utilisateur loggué (?).

-Reflected XSS / Non-persistent XSS : quand on tape "mot" dans la barre de recherche et que le site répond "mot" n'existe pas, on peut mettre du javascript dans "mot". Spécialement problématique si l'url est genre "monsite.fr/mot" car il suffit de faire cliquer quelqu'un sur le lien "monsite.fr/mot<javascript>alert()</javascript>" ou un truc du genre. En JSP utiliser c:out tag ou fn:escapeXml() pour afficher une chaine saisie par l'utilisateur.

-Stored (persistent) XSS : Injection de JS sur un livre d'or, un message d'un forum etc. donc ça touche les gens juste en affichant le message.

-DOM XSS : Certains sites manipulent l'url directement, genre bonjour.fr#bob le site récpère "bob" et l'affiche dans la page. L'attaquant peut remplacer "bob" par du code js : bonjour.fr#bo<script>alert()</script>.

-Directory Traversal / Path Traversal : Dans le code on ouvre "/monRepertoire/$maVariable", l'attaquant peut donner à $maVariable la valeur "../" pour remonter d'un rep. Pour éviter ça, il faut vérif avant d'ouvrir le fichier qu'on est bien dans le rep voulu (recup de la variable, concaténation du chemin, vérification du chemin "direct" vers le fichier avec getcanonicalpath).

-Les couples mdp/logins feraient mieux d'être envoyés par POST et pas par GET car GET laisse beaucoup de trace par exemple dans les logs qui loggueraient l'url entière. Pareil pour les ID de session, eux ils pourraient même être enregistrés dans l'historique du navigateur.

-Sur la page de reste du password, ne pas marquer "email inconnu" si l'email n'est pas connu car ça donne des infos aux pirates.

-Cross Site Request Forgery : depuis un site X on créé un formulaire qui va faire un POST ver sle site Y. On demande à l'utilisateur de se connecter au site X et d'y faire un post. Il faut mettre en place un système de token dans chaque formulaire et vérifier qu'il est correct. Le site X ne connaitra pas le token (synchronizer token pattern). Mauvaise pratique que d'utiliser GET pour faire du CRUD car yaura le token dedans.

-Clickjacking : sur notre site X on met une frame vers le site Y au dessus et transparente. On met des faux boutons alignés aux vrais (invisibles). Quand l'utilisateur clique sur notre faux bouton ça clique en fait sur le vrai. Il faut que Y utilise frame-ancestors pour éviter de se faire framer par le site X.

-Insecure Object Deserialization : lors d'une deserialisation XML, du code malicieux peut être exécuté. Il faut ajouter une liste blanche à Xstream avec NoTypePermission.NONE puis ExplicitTypePermission où on renseigne les classes qu'on veut que XStream déserialise.


Questions posées lors d'entretiens



Super Nombre


Pour un nombre en texte ("123"), le dupliquer par x ("123123123"), additionner tous les chiffres du nombre entre eux, répéter jusqu'à être sous 0 :

private static long getSuperDigit(String digit, int k) { //pour chaque caractere du string on convertit sa representation ("4") en int puis multiplie tout ensemble long superDigitTentative = digit.chars().mapToLong(Character::getNumericValue).sum(); //additionner le resultat ((1+2+3)*3) revient au meme que dupliquer (1+2+3+1+2+3+1+2+3) superDigitTentative*=k; //mais moins consommateur en resources if(superDigitTentative>9) { superDigitTentative = getSuperDigit(""+superDigitTentative, 1); } return superDigitTentative; }


FizzBuzz


Pas de piège. Pour chaque nombre entre 1 et le nombre donné, si mutiple de 3 ET 5 (càd reste de la division par 3 ou 5 égal à 0) afficher FizzBuzz, mutiple de 3 afficher fizz, multiple de 5 afficher buzz, sinon afficher le nombre :

public static void fizzBuzz(int n) { // Write your code here for(int i=1;i<=n;i++) { if (i%3==0 && i%5==0) { System.out.println("FizzBuzz"); }else if (i%3==0) { System.out.println("Fizz"); }else if (i%5==0) { System.out.println("Buzz"); }else { System.out.println(i); } } }


Plus Minus



Avec un tableau de nombres positifs/negatifs/ou à zéro, calculer le ration d'éléments négatifs, positifs, 0. Forcer le formant en sortie à être x.xxxxxx.
Rien de sorcier, pour compter on peut utiliser arr.stream().filter(n -> n>0).count() qui donne un Long qu'on va caster en String.

public static void plusMinus(List<Integer> arr) { int positiveNumbers = (int) arr.stream().filter(n -> n>0).count(); int negativeNumbers = (int) arr.stream().filter(n -> n<0).count(); int zeroNumbers = (int) arr.stream().filter(n -> n==0).count(); System.out.println(String.format("%.6f", (float)positiveNumbers/arr.size())); System.out.println(String.format("%.6f", (float)negativeNumbers/arr.size())); System.out.println(String.format("%.6f", (float)zeroNumbers/arr.size())); }


Mini-Max Sum


Dans un tableau "arr" de 5 entier, renvoyer la somme des 4 plus grands et la somme des 4 plus petits.
Simple, on trie le tableau de grand a petit et on additionne en sautant le premier, puis de petit à grand et on additionne en sautant le premier.


ArrayList<Integer> bigToSmall = new ArrayList<>(arr); bigToSmall.sort((a,b) -> b-a); System.out.print(bigToSmall.stream().skip(1L).collect(Collectors.summingLong(a -> a))); //saute le + grandm additionne les 4 petits System.out.print(" "); ArrayList<Integer> smallToBig = new ArrayList<>(arr); smallToBig.sort((a,b) -> a-b); System.out.print(smallToBig.stream().skip(1L).collect(Collectors.summingLong(a -> a))); //additionne les 4 grands


Conversion format horaire


Convertir 01:00:00PM en 13:00:00, il faut juste avoir les patterns de DateTimeFormatter :

public static String timeConversion(String s) { LocalTime ldt = LocalTime.parse(s, DateTimeFormatter.ofPattern("hh:mm:ssa")); return ldt.format(DateTimeFormatter.ofPattern("HH:mm:ss")); }


Inverser 2 variables


Pour inverser 2 variables le plus simple est d'utiliser une variable intermédiaire, temporaire :

int a=1, b=9; System.out.println("a vaut " + a + ", b vaut " + b); int tmp; //la var temporaire tmp=a; a=b; b=tmp; System.out.println("a vaut " + a + ", b vaut " + b);


Mais on peut aussi s'en sortir avec des soustractions :

int a=1, b=9; System.out.println("a vaut " + a + ", b vaut " + b); a=b-a; //9-1=8 b=b-a; //9-8=1 a=a+b; //8+1=9 System.out.println("a vaut " + a + ", b vaut " + b); //a vaut 1, b vaut 9 //a vaut 9, b vaut 1


Ca marche tout le temps :

int a=11, b=9; System.out.println("a vaut " + a + ", b vaut " + b); a=b-a; //9-11=-2 b=b-a; //9--2=11 a=a+b; //-2+11=9 System.out.println("a vaut " + a + ", b vaut " + b); //a vaut 11, b vaut 9 //a vaut 9, b vaut 11


Lonely Integer


Trouver l'entier seul dans un tableau où les autres sont en double, exemple [2,6,3,3,2,1,6] 1 est seul.

Une soluce simple consiste à transformer en arrayList et utiliser lastIndexOf :

int[] monTableau = {2,6,3,3,2,9,6}; List<Integer> maListe = Arrays.stream(monTableau).mapToObj(a->a).collect(Collectors.toList()); //.boxed() au lieu de mapToObj(a->a) marche aussi for(int i=0;i<monTableau.length;i++) { if(maListe.indexOf(maListe.get(i)) == maListe.lastIndexOf(maListe.get(i))) { System.out.println("Celui qui est seul" + maListe.get(i)); break; } }


On peut aussi trier la liste, la parcourir et vérifier que le nombre d'après ne soit pas égal au nombre en cours mais bon....


public static int lonelyinteger(List<Integer> a) { if(a.size()==1) { return a.get(0); } Integer toReturn = null; a.sort((j,k) -> j-k); for(int i=0;i<a.size()-1;i++) { System.out.println("test de "+ a.get(i)); if(i>0 && a.get(i) != a.get(i-1) && a.get(i) != a.get(i+1)) { toReturn = a.get(i); System.out.println("vu !!!" + toReturn); break; } } if(toReturn==null) { toReturn=a.get(a.size()-1); } return toReturn; } }


Inverser un tableau



Assez simple, on parcourt le tableau dans un sens avec l'index i ET par soustraction avec la taille du tableau cela nous permet de le parcourir aussi dans l'autre sens. On inverse les 2 valeurs avec une variable temp.


package littleTests; import java.util.Arrays; class Solution { public static void main(String[] args) { int[] monTableau = {1,2,3,4,5,6,7}; System.out.println("Mon tableau est " + Arrays.toString(monTableau)); for(int i=0;i<monTableau.length/2;i++) { //depuis l'index 0, jusqu'à la moitiée du tableau (car sinon on réinverserait après la moitié !!) int temp=monTableau[i];//on sauve pos0, puis pos1, puis pos2.... //(monTableau.length-1)-i sera egal à (8-1-0)=7, puis 6, puis 5.... monTableau[i] = monTableau[(monTableau.length-1)-i]; //pos0 prend la valeur de pos7, puis pos1=pos6... monTableau[(monTableau.length-1)-i] = temp; //pos7 prend la valeur de pos0, puis pos6 la valeur de pos1... System.out.println("Mon tableau devient " + Arrays.toString(monTableau)); } } } /* Mon tableau est [1, 2, 3, 4, 5, 6, 7] Mon tableau devient [7, 2, 3, 4, 5, 6, 1] Mon tableau devient [7, 6, 3, 4, 5, 2, 1] Mon tableau devient [7, 6, 5, 4, 3, 2, 1] */


Flipping the matrix



Soluces : https://medium.com/@subhanusroy/flipping-the-matrix-hackerrank-optimised-solution-in-c-java-python-with-explanation-a3dad70149ee, https://www.youtube.com/watch?v=4rin1enhuQQ

Une question ""facile"" d'HackerRank (ils ont tort). John donne un tableau de 2n*2n. Le nombre n est variable, par exemple pour n=2 on aura un tableau de 2*2 * 2*2 donc 4*4.

a b c d e f g h i j k l m n o p


On peut inverser les lignes ou les colonnes. Il faut trouver la valeur maximum qu'on puisse avoir dans la sous matrix n*n en haut `a gauche (autrement dit abef).
HackerRank nous donne 20min pour résoudre ça, autant dire que c'est impossible si on ne connait pas la soluce.

La soluce c'est qu'il faut prendre la plus grande valeur parmi admp, bcno, eihl, fgjk, car ce sont les seules permutations possibles vu qu'on inverse juste (on ne fait pas glisser !!). Donc la soluce sera par exemple aclj.


New Year Chaos


Imaginer une file de personnes avec un ticket. Chaque personne peut demander à la personne devant d'échanger sa place avec lui.
Dans cette liste [2,1,5,3,4], le 2 a sauté une place, le 5 a sauté deux places.

Compter le nombre de saut mais egalement afficher "Too chaotic"si le nombre de saut est supérieur à 2.

Cidessous soluce de https://www.youtube.com/watch?v=LgszjFykAbE
Elle consiste à prédire la prochaine place qu'on devrait trouver, en donnant 3 possibilités. 1,2,3 pour commencer.

Si on a eu 1 en position 0, aucun problème.

Mais si on a eu 2, alors on s'attend à trouver 1 à la prochaine position, ou bien 3 s'il y a eu un pot de vin, ou bien 4. Mais au delà serait révélateur d'un double pot de vin (pas sûr de comprendre pourquoi....).

Si on a eu 3, alors on s'attend à trouver 1 à la prochaine position, ou bien 2 s'il y a eu un pot de vin, ou bien 4. Mais au delà serait révélateur d'un double pot de vin (pas sûr de comprendre pourquoi....).

import java.io.*; import java.math.*; import java.security.*; import java.text.*; import java.util.*; import java.util.concurrent.*; import java.util.function.*; import java.util.regex.*; import java.util.stream.*; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; class Result { /* * Complete the 'minimumBribes' function below. * * The function accepts INTEGER_ARRAY q as parameter. */ public static void minimumBribes(List<Integer> q) { int totalBribes=0; int posPrevue=1; // le chiffre qu'on s'attend a trouver `a ce stade de parcours du tableau // ce n'est pas forcement i ! Si i=0 mais qu'on vient juste // de voir 2, on s'attendra a trouver 3 derriere int prevSiDecalageDeUn=2; // le chiffre qu'on s'attend a trouver s'il y a eu un decalage de un int prevSiDecalageDe2=3; //....un decalage de 2. for(int i=0;i<q.size();i++) { System.out.println("On teste la pos " + i + ", qui devrait etre " + posPrevue); if (posPrevue==q.get(i)) { System.out.println("Sa position semble coherente, on avance" + i); System.out.println("posPrevue passe de " + posPrevue + "a " + prevSiDecalageDeUn); posPrevue = prevSiDecalageDeUn; System.out.println("prevSiDecalageDeUn passe de " + prevSiDecalageDeUn + "a " + prevSiDecalageDe2); prevSiDecalageDeUn = prevSiDecalageDe2; System.out.println("prevSiDecalageDe2 ++"); prevSiDecalageDe2++; } else if (prevSiDecalageDeUn==q.get(i)) { System.out.println("Ce n'est pas le cas, pot de vin detecte"); totalBribes++; System.out.println("prevSiDecalageDeUn passe de " + prevSiDecalageDeUn + "a " + prevSiDecalageDe2); prevSiDecalageDeUn = prevSiDecalageDe2; System.out.println("prevSiDecalageDe2 ++"); prevSiDecalageDe2++; } else if (prevSiDecalageDe2==q.get(i)) { System.out.println("Ce n'est pas le cas, double pot de vin detecte"); totalBribes+=2; System.out.println("prevSiDecalageDe2 ++"); prevSiDecalageDe2++; } else{ System.out.println( "Too chaotic"); return; } } System.out.println(totalBribes); } } public class Solution { public static void main(String[] args) throws IOException { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); int t = Integer.parseInt(bufferedReader.readLine().trim()); IntStream.range(0, t).forEach(tItr -> { try { int n = Integer.parseInt(bufferedReader.readLine().trim()); List<Integer> q = Stream.of(bufferedReader.readLine().replaceAll("\\s+$", "").split(" ")) .map(Integer::parseInt) .collect(toList()); Result.minimumBribes(q); } catch (IOException ex) { throw new RuntimeException(ex); } }); bufferedReader.close(); } }



Entrée:

1 5 2 1 5 3 4


Supprimer élement


2 entrées : un chiffre qu'on veut supprimer et un tableau. Mettre les valeurs désirées au debut du tableau. Par ex [3,2,2,3], 3 n'est pas désiré, le tableau devient [2,2,3,3]. Il faut aussi renvoyer le nombre d'éléments désirés.

Ma solution lente et verbeuse :

class Solution { public int removeElement(int[] nums, int val) { int maxLengthToCheck = nums.length-1; for(int i=0;i<maxLengthToCheck;i++) { System.out.println("Checking pos " + i + ", there is " + nums[i] + " here."); if (nums[i] == val) { //le nombre courant est non-voulu System.out.println("Moving " + nums[i] + "..."); int tmp; // on inverse le nombre courant (non-voulu) avec le dernier du tableau tmp=nums[i]; nums[i]=nums[maxLengthToCheck]; // attention ce dernier est peut etre non-voulu aussi, faudra verif nums[maxLengthToCheck]=tmp; // on sait que le dernier est non desire donc on s'arretera desormais a celui d'avant maxLengthToCheck--; //on reverif le nombre a la pos qu'on vient de verif, car le nombre de fin de tableau peut etre non-voulu lui aussi i--; } } System.out.println(Arrays.toString(nums)); //bon la c'est juste une petite boucle pour compter les nombres voulus int numberOfWanted=0; for(int i=0;i<nums.length;i++) { if (nums[i] != val) { numberOfWanted++; } } System.out.println("numberOfWanted="+numberOfWanted); return numberOfWanted; } }


Une solution courte, lui transforme le tableau [3,2,2,3] en [2,2,2,3]. Il fait un peu l'inverse en detectant le nombre voulu et remplaçant petit à petit le tableau :

class Solution { public int removeElement(int[] nums, int val) { int indexPourRemplacerTableau=0; for(int i=0;i<nums.length;i++){ if(nums[i]!=val){ System.out.println("il y a un nombre voulu a l'index " + i); System.out.println("le tableau etait " + Arrays.toString(nums)); //3, 2, 2, 3 nums[indexPourRemplacerTableau]=nums[i]; System.out.println("le tableau devient " + Arrays.toString(nums)); //2, 2, 2, 3 indexPourRemplacerTableau++; } } return indexPourRemplacerTableau; } }


Supprimer elements en doublon d'un tableau ordonné


Si on a [0,0,1,1,1,2,2,3,3,4] on veut [0,1,2,3,4 et peu importe le reste du tableau].
Même technique que précedemment : on a un index qui permet de remplacer le tableau petit à petit par les nombres voulus. Cette fois-ci le nombre non-voulu n'est pas fixe, c'est simplement le nombre précédent (vu aue le tableau est trié).


class Solution { public int removeDuplicates(int[] nums) { int compteurIndexNombresVoulus=1; int dernierNombre=nums[0]; for(int i=1;i<nums.length;i++) { System.out.println(Arrays.toString(nums)); System.out.println("on travaille sur " + nums[i]); System.out.println("le dernier nombre était " + dernierNombre + ", actuellement on a " + nums[i] ); if (nums[i] != dernierNombre) { System.out.println("on prend !"); nums[compteurIndexNombresVoulus] = nums[i]; compteurIndexNombresVoulus++; } else { System.out.println("on n'en veut pas."); } dernierNombre = nums[i]; } //System.out.println(Arrays.toString(nums)); return compteurIndexNombresVoulus; } }


On peut faire plus court et sans variable "dernier nombre". Vu que le tableau est trié, conserver le premier nombre, vérifier pour chaque nombre que le nombre d'après est plus grand, et sauvegarder ce nombre ci, bien sûr on arrête la boucle à l'avant dernier nombre :


class Solution { public int removeDuplicates(int[] nums) { //1,1,2 int compteurIndexNombresVoulus=1; //on laisse le premier nombre for(int i=0;i<nums.length-1;i++) { //jusqu'a l'avant dernier System.out.println(Arrays.toString(nums)); System.out.println("on travaille sur " + nums[i]); System.out.println(nums[i] + " est il plus petit que " + nums[i+1]); if(nums[i] < nums[i + 1]){ System.out.println("on prend !"); //1 est plus petit que 2 nums[compteurIndexNombresVoulus] = nums[i+1]; //on ajoute 2 compteurIndexNombresVoulus++; } } System.out.println(Arrays.toString(nums)); return compteurIndexNombresVoulus; } }


169. Majority Element


Trouver l'élément qui apparaît plus que la majorité du temps.

Example 1:
Input: nums = [3,2,3]
Output: 3

Example 2:
Input: nums = [2,2,1,1,1,2,2]
Output: 2


Ca marche mais c'est lent car parcourt le tableau à chaque parcours de tableau (complexité O(n^2)):

class Solution { public int majorityElement(int[] nums) { HashMap<Integer, Integer> freqHshMap = new HashMap<>(); //une map nombre, frequence for (int i=0;i<nums.length;i++) { //pour chaque element int count = 0; for (int j=0;j<nums.length;j++) { // on compte l'integralite des elements identiques (O(n^2) dommage) if (nums[j] == nums[i]) { System.out.println(nums[i] + " vu"); count++; // incremente un compteur quand on en trouve } // System.out.println(nums[i] + " apparait " + count + " fois "); freqHshMap.put(nums[i], count); //renseigne la hashmap } } // System.out.print(freqHshMap); return Collections.max(freqHshMap.entrySet(), Map.Entry.comparingByValue()).getKey(); } }


On peut s'en sortir juste avec un tour :

class Solution { public int majorityElement(int[] nums) { HashMap<Integer, Integer> freqHshMap = new HashMap<>(); //une map nombre, frequence for (int i=0;i<nums.length;i++) { //pour chaque element int value = freqHshMap.getOrDefault(nums[i], 0); //on va utiliser la valeur dans la hashmap si elle existe, zero sinon freqHshMap.put(nums[i], value + 1); //a cette valeur on rajoute 1 } System.out.println(freqHshMap); return Collections.max(freqHshMap.entrySet(), Map.Entry.comparingByValue()).getKey(); } }


Solution en 2 lignes, trier le tableau et renvoyer la moitié/2 du tableau :

class Solution { public int majorityElement(int[] nums) { Arrays.sort(nums); return nums[(nums.length/2)]; } }



121. Best Time to Buy and Sell Stock


Un tableau contenant les prix de chaque jour, trouver le meilleur profit qu'on puisse faire en achetant jour x et vendant jour y.

Une solution lente est de, pour chaque jour, comparer avec les jours suivants :

class Solution { public int maxProfit(int[] prices) { int bestDiff=0; for(int i=0;i<prices.length;i++) { // pour chaque jour for(int j=i+1;j<prices.length;j++) { // on compare avec les jours suivants int diffOfThisIteration=prices[j]-prices[i]; //System.out.println("Diff achat a " + prices[i] + " et vente a " + prices[j] + "=" + diffOfThisIteration); if(diffOfThisIteration>0 && diffOfThisIteration>bestDiff) { bestDiff = diffOfThisIteration; } } } return bestDiff; } }


Une autre solution est de garder en valeur les positions du meilleur prix, du pire prix et de la diff :

class Solution { public int maxProfit(int[] prices) { int lowestPricePos=0; //La pos ou se trouve le meilleur prix int highestPricePos=1; //La pos ou se trouve le pire prix int bestProfit = 0; //Le meilleur profit for(int i=0;i<prices.length-1;i++) { System.out.println("Day " + i); if (prices[i] < prices[lowestPricePos]) { //si le prix du jour est inferieur a celui qu'on a sauve lowestPricePos = i; //ca devient le nouveau plus bas System.out.println("New lowest price: " + prices[lowestPricePos]); highestPricePos = lowestPricePos; // le plus haut prix on peut l'oublier vu qu'on s'interesse juste au futur // on a deja le profit possible en memoire de toute facon donc on peut oublier // le jour du plus haut prix } //System.out.println("Comparing " + prices[i+1] +" and "+ prices[highestPricePos]); if (prices[i+1] >= prices[highestPricePos]) { highestPricePos = i+1; //System.out.println("New highest price: " + prices[highestPricePos]); } if (lowestPricePos<highestPricePos && (prices[highestPricePos] - prices[lowestPricePos])>bestProfit ) { profit = prices[highestPricePos] - prices[lowestPricePos]; //System.out.println("Best profit, buy day" + lowestPricePos + ", sell day " + highestPricePos + ", earn "+ bestProfit ); } } return profit; } }