Le but de ce TP est de mettre en oeuvre les éléments de base du langage Java.
Pour les enseignants, ceci est un devoir pour Github Classroom. Il montre l'utilisation des tests unitaires en Java, la notation automatique et l'insertion d'un badge pour l'affichage de la note.
Java est le nom d’une technologie mise au point par Sun Microsystems (racheté par Oracle en 2010) qui permet de produire des logiciels indépendants de toute architecture matérielle. Cette technologie s’appuie sur différents éléments qui, par abus de langage, sont souvent tous appelés Java :
- Le langage de programmation orienté objet Java ;
- Un programme compilé en bytecode Java s’exécute dans un environnement d'exécution Java (JRE) qui émule une machine virtuelle, dite machine virtuelle Java (JVM) ;
- La plate-forme Java correspond à la machine virtuelle Java à laquelle sont adjointes diverses spécifications d’API :
- Java Platform, Standard Edition (Java SE) contient les API de base et est destiné aux ordinateurs de bureau ;
- Java Platform, Enterprise Edition (Java EE) contient, en plus du précédent, des API orientées entreprise et est destiné aux serveurs ;
- Java Platform, Micro Edition (Java ME) est destiné aux appareils mobiles tels que assistants personnels ou smartphones ;
- JavaFX, une API d'interfaces utilisateurs pour Java ;
Ne pas confondre le langage de programmation de scripts JavaScript avec Java !
JRE (Java Runtime Environement) est le kit destiné au client pour pouvoir exécuter un programme Java. Il se compose essentiellement d'une machine virtuelle Java (JVM) capable d'exécuter le bytecode et les bibliothèques standard de Java.
Le kit destiné au programmeur, appelé avant JDK (Java Development Kit) et renommé depuis la version 1.2.2 en SDK (Standard Development Kit), est composé d'un JRE, d'un compilateur, de nombreux programmes utiles, d'exemples de programmes Java et des les sources de toutes les classes de l'API.
Si Java est déjà installé sur le poste, sa version peut être vérifiée en ligne de commande par :
$ java -version
$ javac -version
Sinon, il faudra l'installer.
Sous Ubuntu, il est possible d'installer :
- la version par défaut de la distribution
$ sudo apt install --assume-yes --install-recommends default-jdk
- une version version supportée par la distribution
$ sudo apt-get install openjdk-X-*
X
représente le numéro de version du JDK,11
sur une Ubuntu 20.04 LTS.
Vérification :
$ java -version
$ javac -version
Plusieurs versions de Java peuvent cohabiter, il est alors possible de configurer la version par défaut :
$ sudo update-alternatives --config java
$ sudo update-alternatives --config javac
$ java -version
$ javac -version
Il est aussi possible de télécharger des versions spécifiques de Java SE Development Kit sur le site d'Oracle. La version
17
est actuellement la dernière version disponible.
Éditez le fichier HelloWorld.java
du répertoire sequence1
dont le contenu sera le suivant :
public class HelloWorld
{
public static void main (String[] args)
{
System.out.println("Hello world!");
}
}
Cette application Java est quasiment la plus simple qu’il soit possible d’écrire. Elle consiste à afficher le message « Hello world! » sur la console.
Une application Java nécessite d’être compilée pour pouvoir être exécutée ultérieurement.
Cette opération s’effectue à l’aide de la commande javac
.
- En vous plaçant dans le répertoire
sequence1
, exécutez la commandejavac
sans paramètre pour obtenir l’aide en ligne.
$ cd sequence1
$ javac
Usage: javac <options> <source files>
...
- Compilez ensuite la classe
HelloWorld
et vérifiez qu’un fichier.class
a bien été produit dans le même répertoire.
$ javac HelloWorld.java
ls -l *.class
-rw-rw-r-- 1 tv tv 426 janv. 16 10:16 HelloWorld.class
- Vérifiez son type avec la commande
file
.
$ file HelloWorld.class
HelloWorld.class: compiled Java class data, version 55.0
- Exécutez le programme avec la commande
java
.
$ java HelloWorld
Hello world!
- Créez maintenant deux sous-répertoires dans
sequence1
que vous appellerezsrc
etbuild
. Déplacez le fichierHelloWorld.java
danssrc
et, en utilisant l’option-d
dejavac
, faites en sorte que le fichier.class
soit produit dansbuild
.
$ rm HelloWorld.class
$ mkdir ./src ./build
$ mv HelloWorld.java ./src
$ javac -d ./build ./src/HelloWorld.java
$ ls -l ./build/
-rw-rw-r-- 1 tv tv 426 janv. 16 10:18 HelloWorld.class
Il est dans certains cas utile (notamment lorsque l’on transmet l’application via le réseau) de regrouper au sein d’une archive l’ensemble de classes d’une application ou d’une librairie. Une telle archive a en Java un format normalisé et se dénomme un
jar
(java archive). Un outil éponyme (i.e. du même nom) permet d’archiver des classes.
$ cd build/
$ jar cvf hello.jar *.class
manifeste ajouté
ajout : HelloWorld.class(entrée = 426) (sortie = 289)(compression : 32 %)
$ ls -l *.jar
-rw-rw-r-- 1 tv tv 741 janv. 16 10:21 hello.jar
$ file hello.jar
hello.jar: Java archive data (JAR)
$ java -classpath hello.jar HelloWorld
Hello world!
Il existe un test unitaire noté pour ce programme :
$ gradle test
> Task :test
TestHelloWorld > testHelloWorldWorld() PASSED
Voir l'annexe Gradle si vous souhaitez exécuter les tests en local.
Prêt pour l'aventure Java : « Vers l'infini, et au-delà ! »
Cet exercice est extrait du livre « Exemples en Java in a Nutshell ».
2a. Compléter le programme Java FizzBuzz
qui, pour tous les entiers de 1 Ă 100, affiche sur la console :
1
2
3
4
5 -> Fizz
6
7 -> Buzz
8
...
97
98 -> Buzz
99
100 -> Fizz
Le code source FizzBuzz.java
à compléter est situé dans le répertoire sequence2a/src
. La méthode statique afficheFizzBuzz(int n)
affiche :
Fizz
si l’entiern
est multiple de5
,Buzz
si l’entiern
est multiple de7
,FizzBuzz
si l’entiern
est multiple de5
et de7
,- la valeur de l’entier
n
sinon.
Il existe un test unitaire noté pour ce programme :
$ gradle test
> Task :test
TestFizzBuzz > testNoneFizzBuzz() PASSED
TestFizzBuzz > testBuzz() PASSED
TestFizzBuzz > testFizz() PASSED
TestFizzBuzz > testFizzBuzz() PASSED
Il est possible de gérer la fabrication d'un programme Java avec make
. Un petit Makefile
de base :
SRC=./src
all: FizzBuzz
FizzBuzz: $(SRC)/FizzBuzz.class
@java -classpath $(SRC) FizzBuzz
$(SRC)/FizzBuzz.class: $(SRC)/FizzBuzz.java
javac -d $(SRC) $(SRC)/FizzBuzz.java
clean:
rm -f $(SRC)/*.class $(SRC)/*~
Cet exercice est extrait du livre « Exercices en Java ».
2b. Écrire un programme TableauCarre
qui crée et affiche un tableau comportant les valeurs des carrés des n
premiers nombres entiers impairs. n
sera défini sous forme d’une constante NB_VALEURS
égale à 5
.
Résultat attendu :
1 a pour carre 1
3 a pour carre 9
5 a pour carre 25
7 a pour carre 49
9 a pour carre 81
Éléments de correction :
- Lors de la déclaration d’un tableau, on ne spécifie pas la taille :
char[] tab;
ouchar tab[];
par exemple pour un tableau dechar
- Le tableau n’existera qu’après l’appel au constructeur :
tab = new char[10];
par exemple pour 10char
- Les constantes se définissent avec
final static
Il existe un test unitaire noté pour ce programme :
$ gradle test
> Task :test
TestTableauCarre > testTableauCarre() PASSED
Bonus : Adapter le fichier Makefile
pour :
- avoir une
TARGET
- fabriquer la
TARGET
dans le répertoirebuild
Le but de cette séquence est de s’exercer à la manipulation de chaînes de caractères.
3a. Inversion de chaîne
Définissez une classe StringUtils
et ajoutez-y une méthode de signature : String inverse(String s)
permettant d’inverser les caractères d’une chaîne (i.e. si la chaîne paramètre est "pipo", la méthode retourne "opip").
Écrire une classe Inverse
qui affiche la chaîne reçue en paramètre et la chaîne inversée. Le programme n'affiche rien et se termine si il ne reçoit aucune chaîne en argument.
Résultat attendu :
$ java Inverse
$ java Inverse pipo
pipo
opip
Éléments de correction :
⚠️ Il est interdit d'instancier un objet de la classeStringUtils
!⚠️ Il est interdit de réaliser une saisie dans le programme- Pour accéder à un caractère de la chaîne
s
Ă la positionpos
:s.charAt(pos)
- Autre possibilité en utilisant un
StringBuffer
(ouStringBuilder
) et sa méthodereverse()
Il existe un test unitaire noté pour ce programme :
$ gradle test
> Task :test
TestInverse > testMain1() PASSED
TestInverse > testMain2() PASSED
TestInverse > testInverse() PASSED
3b. Palyndrome
Reprendre la classe StringUtils
et ajoutez une méthode de signature : boolean estPalyndrome(String s)
permettant de tester si une chaîne de caractères
(on ignorera la présence d’espaces en début et fin de chaîne) est un palyndrome (i.e. elle se lit à l’envers comme à l’endroit, comme par exemple "o" ou "tot").
Résultat attendu :
$ java Palyndrome
Saisie : papa
papa : false
Saisie : pap
pap : true
Saisie : 0
$
Écrire une classe Palyndrome
qui prend la chaîne de caractères à tester sur l’entrée standard (stdin).
Éléments de correction :
- Utilisez la méthode
inverse()
créée à l’exercice précédent ! ⚠️ Pour tester l’égalité entre deux chaînes, il faut utiliser la méthodeequals()
de la classeString
- Pour la saisie, utilisez un
BufferedReader
et sa méthodereadLine()
ou unScanner
- La classe
BufferedReader
nécessite unInputStreamReader
et le flot d’entréeSystem.in
- Pour les classes
BufferedReader
etInputStreamReader
, il faut spécifier avec le mot-cléimport
avec le nom complet de chaque classe - L’utilisation de la méthode
readLine()
peut générer une exceptionIOException
, il faut donc soit l’intégrer dans un bloctry/catch
ou que la méthodemain()
relance les exceptions avecthrows IOException
dans sa définition
Compléter la classe Palyndrome
qui prend la chaîne de caractères à tester sur l’entrée standard jusqu’à la saisie de la valeur entière 0
. Ajouter une méthode publique statique lireInt()
qui retourne la conversion d’un String
(lu au clavier) en int
si c’est possible sinon la valeur -1
.
Éléments de correction :
- Utilisez la méthode
Integer.parseInt()
pour convertir unString
enint
- La méthode
Integer.parseInt()
génère une exceptionNumberFormatException
qu’il faudra attraper avec un bloctry/catch
Il existe un test unitaire noté pour ce programme :
$ gradle test
> Task :test
TestPalyndrome > testLireInt() PASSED
TestPalyndrome > testPalyndrome() PASSED
Le but de cette séquence est de se familiariser avec la programmation orientée objet en Java.
On désire réaliser un programme orienté objet en Java qui fournit une hiérarchie de classes destinées à mémoriser ou manipuler les propriétés de différentes figures géométriques.
Lien : l'Héritage en Java
4a. On propose de traiter au moins le cas des rectangles, cercles, triangles, carrés, sphères, parallélépipèdes rectangles, cubes, ... mais cette liste n’est pas limitative.
Chaque classe devra permettre de mémoriser les données qui permettent de définir une instance, par exemple la longueur des deux cotés d’un rectangle, le rayon de la sphère, etc. Chaque instance devra disposer quand cela a un sens :
- d’une méthode
double perimetre()
qui renvoie la valeur du périmètre de l’instance sur laquelle on l’appelle ; - d’une méthode
double aire()
qui renvoie l’aire de la surface de l’objet concerné ; - d’une méthode
double volume()
qui renvoie le volume de la forme concernée.
On considérera que lorsque ces trois notions ne sont pas définies mathématiquement, alors la valeur renvoyée sera nulle. Par exemple, le volume d’un carré sera nul, et le périmètre d’une sphère également.
On vous fournit la classe de base Figure
:
public class Figure
{
private double x;
private double y;
private double z; //coordonnées du centre
// Constructeur par défaut
public Figure()
{
this.x = 0.;
this.y = 0.;
this.z = 0.;
}
// Constructeur d'initialisation
public Figure(double x, double y, double z)
{
this.x = x;
this.y = y;
this.z = z;
}
// Accesseurs
public double getX()
{
return this.x;
}
public double getY()
{
return this.x;
}
public double getZ()
{
return this.x;
}
public double perimetre()
{
return 0.;
}
public double aire()
{
return 0.;
}
public double volume()
{
return 0.;
}
// Services
public String getDescription()
{
return "Figure";
}
@Override
public String toString()
{
return this.x + " " + this.y + " " + this.z;
}
}
On vous fournit la classe Triangle
qui hérite de la classe Figure
:
public class Triangle extends Figure
{
private double base;
private double hauteur;
// Constructeurs
public Triangle(double base, double hauteur)
{
super();
this.base = base;
this.hauteur = hauteur;
}
public Triangle(double base, double hauteur, double x, double y, double z)
{
super(x, y, z);
this.base = base;
this.hauteur = hauteur;
}
public double getBase()
{
return this.base;
}
public double getHauteur()
{
return this.hauteur;
}
@Override
public double perimetre()
{
return Math.sqrt(this.base * this.base + this.hauteur * this.hauteur) + this.base + this.hauteur;
}
@Override
public double aire()
{
return this.base * this.hauteur / 2;
}
@Override
public String getDescription()
{
return super.getDescription() + " <|--- Triangle";
}
@Override
public String toString()
{
return super.toString() + " " + this.base + " " + this.hauteur;
}
}
Remarques :
- Pour que la classe
Triangle
hérite (dérive) de la classeFigure
, il faut utiliser le mot-cléextends
, par exemple :public class Triangle extends Figure
- Si un constructeur d’une classe dérivée appelle un constructeur d’une classe de base, il doit obligatoirement s’agir de la première instruction du constructeur et ce dernier est désigné par le mot-clé
super
. - L'annotation
@Override
indique au compilateur que la méthode qui suit est la redéfinition d'une méthode de la superclasse et qu'il doit assurer cette vérification (https://docs.oracle.com/javase/7/docs/api/java/lang/Override.html). - La méthode
toString()
est définie dans la classeObject
(toutes les classes Java en héritent). La méthodetoString()
de la classeObject
renvoie par défaut le nom de la classe de l'objet concerné suivi de l'adresse de cet objet. Lorsqu'on définit une classe, il peut être très utile de redéfinir la méthodetoString()
afin de donner une description adaptée des objets de cette classe. Beaucoup de classes de l'API redéfinissent cette méthode.
La classe
Object
est la racine (la classe mère) de la hiérarchie des classes Java. Chaque classe aObject
comme superclasse. Tous les objets, y compris les tableaux, implémentent les méthodes de cette classe.
Un programme d'exemple :
public class ExempleFigures
{
public static void main (String[] args)
{
Triangle triangle = new Triangle(5, 8);
System.out.println(triangle.getDescription());
System.out.println(triangle); // appel de la méthode toString()
System.out.println("Périmetre = " + triangle.perimetre());
System.out.println("Aire = " + triangle.aire());
System.out.println("Volume = " + triangle.volume());
// etc...
}
}
On obtient :
$ java ExempleFigures
Figure <|--- Triangle
0.0 0.0 0.0 5.0 8.0
Périmetre = 22.4339811320566
Aire = 20.0
Volume = 0.0
On vous demande d’écrire les classes Carre
, Rectangle
, Cercle
, Cube
, Parallelepipede
et Sphere
et de faire fonctionner le programme de test fourni.
Mathématiquement, un carré est un rectangle et un héritage aurait pu être implémenté entre les deux classes. Mais cela pose un problème (cf. violation du principe de substitution de Liskov en Annexe) qui dépasse le cadre de ce TP.
Pour information, voici les résultats obtenus pour les calculs des périmètres, aires et volumes des différentes figures :
$ make
javac -classpath ./src -d ./src ./src/ExempleFigures.java
Figure <|--- Triangle
0.0 0.0 0.0 5.0 8.0
Périmetre = 22.4339811320566
Aire = 20.0
Volume = 0.0
Figure <|--- Carre
0.0 0.0 0.0 5.0
Périmetre = 20.0
Aire = 25.0
Volume = 0.0
Figure <|--- Rectangle
0.0 0.0 0.0 5.0 4.0
Périmetre = 18.0
Aire = 20.0
Volume = 0.0
Figure <|--- Cercle
0.0 0.0 0.0 2.0
Périmetre = 12.566370614359172
Aire = 12.566370614359172
Volume = 0.0
Figure <|--- Carre <|--- Cube
0.0 0.0 0.0 6.0
Périmetre = 0.0
Aire = 216.0
Volume = 216.0
Figure <|--- Rectangle <|--- Parallelepipede
0.0 0.0 0.0 5.0 4.0 9.0 0.0 0.0
Périmetre = 0.0
Aire = 202.0
Volume = 180.0
Figure <|--- Cercle <|--- Sphere
0.0 0.0 0.0 3.0
Périmetre = 0.0
Aire = 113.09733552923255
Volume = 113.09733552923255
...
Exemple d'utilisation d'un conteneur en Java :
import java.util.ArrayList; // Voir aussi LinkedList, HashMap, HashSet
import java.util.List; // l'interface List
public class ExempleFigures
{
public static void main (String[] args)
{
// ...
// Conteneur
List<Figure> figures = new ArrayList<Figure>();
figures.add(triangle);
figures.add(carre);
figures.add(rectangle);
figures.add(cercle);
figures.add(cube);
figures.add(parallelepipede);
figures.add(sphere);
afficherFigures(figures);
}
private static void afficherFigures(List<Figure> figures)
{
// Solution 1
for (int i = 0; i < figures.size(); i++)
{
System.out.println(figures.get(i).getDescription());
System.out.println(figures.get(i));
}
System.out.println("");
// Solution 2
for(Figure figure : figures)
{
System.out.println(figure.getDescription());
System.out.println(figure);
}
}
4b. Les classes abstraites
En programmation orientée objet (POO), une classe abstraite est une classe dont l’implémentation n’est pas complète et qui n’est donc pas instanciable (on ne peut pas créer d’objet à partir de cette classe).
Une classe abstraite sert de base à d’autres classes dérivées (héritées). Le mécanisme des classes abstraites permet de définir des comportements (méthodes) qui devront être implémentés dans les classes filles, mais sans fournir elle-même ces comportements (c’est-à -dire sans écrire de code pour cette méthode). Ainsi, on a l’assurance que les classes filles respecteront le contrat défini par la classe mère abstraite. Ce contrat est une interface de programmation.
On décide que la classe Figure
de notre exemple précédent soit une classe abstraite. Les méthodes perimetre()
, aire()
et volume()
seront aussi abstraites dans la classe Figure
.
On ne pourra plus instancier d'objets de type
Figure
! D'autre part, l'ensemble des classes qui héritent de la classeFigure
devront définir les méthodesperimetre()
,volume()
etaire()
.
Éléments de correction :
- une classe abstraite est déclarée avec le modificateur
abstract
- une méthode abstraite est déclarée avec le modificateur
abstract
: elle ne contient pas de corps, mais elle doit être implémentée dans les sous-classes non abstraites
Modifier le code source des classes produites à la séquence précédente.
En UML, une classe abstraite sera indiquée par son nom écrit en italique ainsi que toutes ses méthodes abstraites :
4c. Les interfaces
En Java, une interface est un concept de classe abstraite "poussée à l'extrème" (une expression de pure conception) où aucun élément d'implémentation n'est défini.
Une interface comporte simplement :
- Des déclarations de constantes
- Des signatures de méthodes (obligatoirement publiques)
Une interface ne peut pas comporter :
- De déclarations d' attributs
- D'implémentations de méthodes
La notion d'Interface
On dit qu'une classe implémente une interface si et seulement si elle définit l'implémentation de toutes les méthodes de l'interface. Une interface exprime un contrat.
Les méthodes doivent avoir strictement la même signature. L'annotation
@Override
peut (doit) être utilisée.
Le principe est le suivant :
public interface A
{
public void foo(Object produit);
}
public class B implements A
{
@Override
public void foo(Object o)
{
System.out.println(o);
}
}
On décide que la classe abstraite Figure
de notre exemple précédent ne possède plus les méthodes abstraites perimetre()
, aire()
et volume()
car cela oblige toutes les classes filles Ă les fournir mĂŞme si cela n'a aucun sens (le return 0
de certaines méthodes volume()
). En effet, les figures "2D" n'auront jamais de calcul de volume.
On décide de créer deux interfaces :
Calcul2D
qui déclarera les méthodesperimetre()
etaire()
Calcul3D
qui déclarera la méthodevolume()
Et donc :
- les classes
Triangle
,Carre
,Rectangle
etCercle
implémenteront l'interfaceCalcul2D
- les classes
Cube
,Parallelepipede
etSphere
implémenteront l'interfaceCalcul3D
Élément de correction :
- Une classe peut hériter d'une autre classe (
extends
) et implémenter une interface (implements
).
Modifier le code source des classes produites à la séquence précédente.
ExempleFigures.java
Le but de cette séquence est de se familiariser avec les paquetages et archive jar.
Un paquetage (package) est une unité logique renfermant un ensemble de classes ayant un lien (fonctionnel) entre elles. Il est désigné par un nom unique et défini un espace de nommage qui permet d’éviter les conflits de noms.
On utilise l’instruction package monpaquet;
en tout début d’un fichier définissant une classe pour indiquer que celle-ci fait partie du package nommé monpaquet
.
monpaquet
.
Ajouter l’instruction package figures;
en tout début de fichier pour chaque classe de cette séquence.
Créer l’arborescence suivante : sequence5/src/figures
et sequence5/build/figures
et placez les fichiers sources *.java
dans sequence5/src/figures
.
Ensuite, on compile et on exécute de la manière suivante dans ./sequence5
:
$ javac -d ./build -sourcepath ./src/figures -classpath ./build src/figures/Carre.java src/figures/Cercle.java src/figures/Cube.java src/figures/Figure.java src/figures/Parallelepipede.java src/figures/Rectangle.java src/figures/Sphere.java src/figures/Triangle.java src/figures/ExempleFigures.java
$ java -classpath ./build figures.ExempleFigures
L’option -classpath
(ou la variable d’environnement CLASSPATH
) contient une liste de chemins conduisant à des répertoires (contenant des classes ou des arborescences de paquetages) et/ou des archives jar. Lors de la compilation et de l’exécution, cette variable sert à localiser les fichiers .class
nécessaires à l’édition de lien.
On peut aussi regrouper l’ensemble des classes dans un fichier d’archive .jar
:
$ cd build
$ jar cvf figures.jar figures/*.class
$ java figures.ExempleFigures -classpath figures.jar
jar
est un outil distribué avec le JDK de Java qui permet de compresser des classes Java compilées dans une archive.
Il est également possible d’exécuter directement une application contenue dans une archive _ja_r avec l’option -jar
de la commande java
. Pour ce faire, il faut cependant avoir ajouté à l’archive un fichier de méta-données que l’on appelle manifest indiquant quelle est la classe principale de l’archive.
$ cd build
$ vim manifest
Main-Class: figures.ExempleFigures
$ jar cvmf manifest figures.jar figures/*.class
$ java -jar figures.jar
Le but de cette séquence est de se familiariser avec la bibliothèque graphique Swing.
Swing, une bibliothèque graphique pour Java, offre la possibilité de créer des interfaces graphiques identiques quel que soit le système d’exploitation.
Avec l’apparition de Java 8, JavaFX devient la bibliothèque graphique officielle du langage Java, pour toutes les sortes d’application (applications mobiles, applications sur poste de travail, applications Web), le développement de son prédécesseur Swing étant abandonné (sauf pour les corrections de bogues).
Le programme suivant est un “hello world” en Swing :
import javax.swing.JFrame;
public class TestJFrame1
{
public static void main(String[] args)
{
JFrame fenetre = new JFrame();
//Définit un titre pour notre fenêtre
fenetre.setTitle("Hello World !");
//Définit sa taille : 400 pixels de large et 100 pixels de haut
fenetre.setSize(400, 100);
// ou :
// Attribut une taille minimale Ă la fenĂŞtre
//fenetre.pack();
//Centre la fenĂŞtre
fenetre.setLocationRelativeTo(null);
//Terminera le processus lorsqu’on clique sur la croix rouge
fenetre.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Et enfin, la rendre visible
fenetre.setVisible(true);
}
}
Écrire une application Java Convertisseur qui assure la conversion euros -> francs :
Gradle est un moteur de production fonctionnant sur la plateforme Java.
C’est un outil comparable
make
, ouant
etMaven
pour Java.
Non obligatoire pour ce TP, Gradle est utilisé seulement pour fabriquer et exécuter les tests unitaires. Vous devez l'installer en local pour pouvoir tester unitairement vos exercices.
Lien : https://gradle.org/install/
Le paquet fourni par les distributions Ubuntu est trop ancien. Il ne faut pas l'installer.
- Solution 1 : installation manuelle (ici en root)
$ wget -c https://services.gradle.org/distributions/gradle-7.6-bin.zip
$ sudo mkdir /opt/gradle
$ sudo unzip -d /opt/gradle gradle-7.6-bin.zip
$ sudo ln -s /opt/gradle/gradle-7.6/bin/gradle /usr/local/bin
$ gradle -- version
- Solution 2 : installation avec
snap
$ snap install --classic gradle
$ gradle -- version
JUnit est un framework de test unitaire pour le langage de programmation Java, créé par Kent Beck et Erich Gamma.
$ cat build.gradle
plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
testImplementation('org.junit.jupiter:junit-jupiter:5.6.0')
}
sourceSets {
main {
java {
srcDirs = ['src']
}
}
test {
java {
srcDirs = ['test']
}
}
}
test {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
}
}
Exemple :
$ cd sequence1
$ gradle test
> Task :test
TestHelloWorld > testHelloWorldWorld() PASSED
Evidemment, il existe de nombreuses extensions pour Java pour Visual Studio Code.
Lien : https://code.visualstudio.com/docs/languages/java
Le pack Extension Pack for Java installera les extensions suivantes :
- Language Support for Java by Red Hat : https://marketplace.visualstudio.com/items?itemName=redhat.java
- Debugger for Java : https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-debug
- Test Runner for Java : https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-test
- Maven for Java : https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-maven
- Project Manager for Java : https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-dependency
- Visual Studio IntelliCode : https://marketplace.visualstudio.com/items?itemName=VisualStudioExptTeam.vscodeintellicode
Dans la plupart des projets, les dépendances de bibliothèques tierces en Java sont gérées traditionnellement par Maven ou Gradle. Il existe une extension pour Gradle :
Une fois les extensions Java installées, il est possible d'utiliser le raccourci Ctrl-espace
.
Il est souvent nécessaire de passer par un "bac à sable".
En informatique, le bac à sable (sandbox) est une zone d'essai permettant d'exécuter des programmes en phase de test ou dans lesquels la confiance est incertaine. C'est notamment très utilisé en sécurité informatique pour sa notion d'isolation.
Il existe de nombreux sites web qui fournissent des EDI (Environnement de Développement Intégré) en ligne pour tester du code ou des services : un espace d'apprentissage séparé. Ils permettent aussi d'échanger des exemples.
Quelques sites :
- Coding Ground For Developers : https://www.tutorialspoint.com/codingground.htm pour tout !
- Visual Studio Code Online : https://vscode.dev/
- Gitpod : https://www.gitpod.io/
- Codeanywhere (Cloud IDE) : https://codeanywhere.com/
Le principe de substitution de Liskov (Liskov substitution principle) est une définition particulière de la notion de sous-type, formulé par Barbara Liskov et Jeannette Wing.
Une instance de type T
doit pouvoir être remplacée par une instance de type G
(un sous-type de T
) sans que cela ne modifie la cohérence du programme.
« Les sous-types doivent être substituables à leur type de base. » -- Robert C. Martin
Exemple : en POO, un carré est-il un rectangle ?
- Soit une classe abstraite
Figure
- Soit une classe
Rectangle
représentant les propriétés d'un rectangle :hauteur
,largeur
. On lui associe donc des accesseurs pour accéder et modifier lahauteur
et lalargeur
librement. On définit la règle "hauteur" et "largeur" sont librement modifiable (setLargeur()
etsetLongueur()
). - Soit une classe
Carre
que l'on fait dériver (aka hériter) de la classeRectangle
. En effet, mathématiquement, un carré est un rectangle. Donc, on définit naturellement la classeCarre
comme sous-type de la classeRectangle
. On définit la règle "les 4 cotés du carré doivent être égaux" ???
On devrait pouvoir utiliser une instance de type Carre
n'importe oĂą un type Rectangle
est attendu.
Problème : Un carré ayant par définition quatre cotés égaux, il convient de restreindre la modification de la hauteur et de la largeur pour qu'elles soient toujours égales. Néanmoins, si un carré est utilisé là où, comportementalement, on s'attend à interagir avec un rectangle, des comportements incohérents peuvent subvenir : Les cotés d'un carré ne peuvent être changés indépendamment, contrairement à un rectangle. Une mauvaise solution consisterait à modifier les setter du carré pour préserver l'invariance de ce dernier. Mais ceci violerait la règle des setter du rectangle qui spécifie que l'on puisse modifier hauteur
et largeur
indépendamment.
Comment implémenter les membres de la classe Carre
?
C'est une violation du principe de substitution de Liskov : le « carré cesserait d'être un carré » si nous appelions setLargeur()
(l'attribut longueur
ne serait plus égal à l'attribut largeur
). Il faudrait donc modifier l'attribut hauteur
mais alors le « carré serait différent d'un rectangle ».
Solution : elle consiste à ne pas considérer un type Carre
comme substitut d'un type Rectangle
, et les définir comme deux types complètement indépendants. Ceci ne contredit pas le fait qu'un carré soit un rectangle. La classe Carre
est un représentant du concept "carré". La classe Rectangle
est un représentant du concept "rectangle". Or, les représentants ne partagent pas les mêmes propriétés que ce qu'ils représentent.
Thierry Vaira : thierry(dot)vaira(at)gmail(dot)com