:: Enseignements :: ESIPE :: E4INFO :: 2025-2026 :: Java Avancé ::
[LOGO]

TP noté intermédiaire de Java Avancé 2025 - 2026


Le but de ce TP noté est d'implanter une classe BankStat qui permet d'obtenir des statistiques sur les opérations de dépôt et de retrait sur des comptes en banque.

Vos sources Java produites pendant l'examen devront être placées sous le répertoire EXAM de votre compte ($HOME) (qui est vide dans l'environnement de TP noté). Sinon, elles ne seront pas récupérées.

Tout document papier est proscrit.
La javadoc 25 est disponible à l'adresse : https://igm.univ-mlv.fr/~juge/javadoc-25/.
Les seuls documents électroniques autorisés sont les supports de cours à l'URL http://igm.univ-mlv.fr/~forax/ens/java-avance/cours/pdf/.

Vous avez le droit de lire le sujet jusqu'au bout, cela vous donnera une bonne idée de là où on veut aller !

Exercice 1 - BankStat

La classe BankStat est une classe qui permet d'ajouter des transactions (des dépôts Deposit) ou des retraits (Withdraw).
Contrairement à la vraie vie, où un dépôt possède différentes propriétés (quantité d'argent, heure, etc), pour les besoins du TP, nous avons simplifié le problème, donc un dépôt ne possède qu'un seul champ identifiant depositId de type chaîne de caractères. De même, un retrait ne possède qu'un seul champ identifiant withdrawId, également de type chaîne de caractères.
Si, par exemple, un dépôt (ou un retrait) se passe mal, alors le dépôt va être exécuté à nouveau par le système. Donc, si l'on regarde tous les dépôts et que plusieurs ont le même identifiant, alors on sait qu'il y a eu une erreur. En fait, c'est même un peu plus compliqué que cela : si un dépôt se passe mal, il est possible que le système envoie un retrait avec le même identifiant (ou vice-versa).
Ce que l'on cherche à connaître, c'est le nombre de transactions (dépôts ou retraits) valides, c'est-à-dire le nombre de transactions pour lesquelles il n'y a pas eu de dépôt/retrait ultérieur ayant le même identifiant.

La classe BankStat possède les opérations suivantes :
  • un constructeur qui permet de créer un BankStat vide,
  • la méthode addTransaction(transaction) qui permet d'ajouter un dépôt ou un retrait.
  • les méthodes depositCount() et withdrawCount qui renvoient respectivement le nombre de dépôts ajoutés et le nombre de retraits ajoutés.
  • une méthode replaceId(condition, replacement) qui remplace des identifiants de transaction.
  • une méthode containsId(id) qui permet de savoir si au moins un dépôt ou un retrait ayant l'identifiant id a été fait.
  • une méthode countById() qui renvoie une Map qui indique pour chaque id le nombre de fois qu'un dépôt ou un retrait a été fait.
  • une méthode validTransactionCount qui renvoie le nombre de transactions valides.

Des tests unitaires correspondant à l'implantation sont ici : BankStatTest.java
Note : comme on utilise les tests unitaires JUnit sans Maven, dans la configuration de votre projet, il faut ajouter la librairie JUnit 5, soit à partir du fichier BankStatTest.java, en cliquant sur l'annotation @Test et en sélectionnant le quickfix "Fixup project ...", soit en sélectionnant les "Properties" du projet (avec le bouton droit de la souris sur le projet) puis en ajoutant la librairie JUnit 5 (jupiter) au ClassPath.

  1. On cherche dans un premier temps à modéliser les dépôts (Deposit) et les retraits (Withdraw) sachant que l'on va les déclarer dans la classe BankStat. C'est plus pratique pour corriger pour nous et cela vous fait réviser les classes internes.
    Créer la classe BankStat. Puis déclarer les membres Deposit et Withdraw dans la classe BankStat sachant qu'un identifiant de dépôt ou de retrait ne peut jamais être null
    Vérifier que les tests marqués "Q1" passent.

  2. On souhaite maintenant ajouter les méthodes addTransaction, depositCount et withdrawCount.
    On peut remarquer qu'il n'est pas nécessaire de stocker les transactions dans BankStat, en effet, seuls les identifiants des transactions nous intéressent. Par conséquent, dans le but de ne pas trop utiliser de mémoire, la classe BankStat va stocker uniquement les identifiants et dans un tableau (pas dans une java.util.List !). Le tableau doit avoir une taille par défaut de 16 cases et, pour l'instant, il ne devra pas s'agrandir dynamiquement.
    On souhaite de plus que les méthodes depositCount et withdrawCount aient une complexité en pire cas de O(1).
    On peut enfin remarquer qu'il n'est pas nécessaire de stocker le nombre de transactions/identifiants que l'on a déjà vu, car c'est la somme du nombre de dépôts et du nombre de retraits.
    Écrire les méthodes addTransaction, depositCount et withdrawCount en respectant les contraintes énoncées ci-dessus.
    Vérifier que les tests marqués "Q2" passent.

  3. L'implantation que vous venez d'écrire à la limitation que l'on ne peut pas ajouter plus de 16 transactions. Modifier votre implantation pour faire grandir le tableau dynamiquement en multipliant par 2 sa taille lorsque c'est nécessaire.
    En termes d'implantation, on vous demande d'utiliser la méthode Arrays.copyOf(oldArray, newCapacity), qui prend en paramètre un tableau et une nouvelle taille, et renvoie un tableau redimensionné avec les éléments de l'ancien tableau recopiés.
    Vérifier que les tests marqués "Q3" passent.

  4. On souhaite écrire une méthode containsId(id) qui renvoie vrai si au moins un dépôt ou retrait ayant l'identifiant id a été ajouté.
    Vérifier que les tests marqués "Q4" passent.

  5. On souhaite écrire une méthode replaceId(condition, replacement) qui prend deux fonctions en paramètre, la première est appelée avec chaque identifiant et renvoie un booléen. Si ce booléen est vrai, la seconde fonction est appelée avec l'identifiant et renvoie un identifiant qui va remplacer l'identifiant existant.
    Par exemple :
             var bankStat = new BankStat();
    
             bankStat.addTransaction(new BankStat.Withdraw("W123"));
             bankStat.addTransaction(new BankStat.Deposit("D456"));
    
             bankstat.replaceId("D456"::equals, String::toLowerCase);
           
    remplace l'identifiant du dépôt "D456" par "d456".
    Écrire la méthode replaceId(condition, replacement).
    Vérifier que les tests marqués "Q5" passent.

  6. On cherche à écrire la méthode countById() qui renvoie une Map qui pour chaque identifiant renvoie le nombre de transactions (un entier) ayant cet identifiant.
    Par exemple:
               var bankStat = new BankStat();
               bankStat.addTransaction(new BankStat.Withdraw("T123"));
               bankStat.addTransaction(new BankStat.Deposit("T123"));
               bankStat.addTransaction(new BankStat.Withdraw("T456"));
    
               Map<String, Integer> result = bankStat.countById();  // {T123=2, T456=1}
           

    Écrire la méthode countById() en utilisant un Stream et l'API des collecteurs.
    Vérifier que les tests marqués "Q6" passent.
    Note : si vous n'arrivez pas à utiliser un Stream, écrivez un code avec une boucle, mais vous n'aurez pas tous les points à cette question.

  7. On souhaite maintenant écrire la méthode validTransactionCount qui renvoie le nombre de transactions valides (celles qui ont un identifiant unique).
    Écrire la méthode validTransactionCount() (attention à faire en sorte qu'elle soit efficace).
    Vérifier que les tests marqués "Q7" passent.

  8. On souhaite écrire une autre implantation de validTransactionCount() qui a probablement un complexité dans le pire cas moins bonne moins bonne que la précédente, mais qui pourrait souvent avoir des performances similaires (car l'accès à la mémoire n'est pas aléatoire).
    L'idée est la suivante : on trie le tableau des identifiants afin que les identifiants identiques soient les uns à la suite des autres.
    Puis, on fait un parcours du tableau trié et pour chaque identifiant, on compte le nombre d'identifiants identiques consécutifs. Si ce nombre est 1, alors l'identifiant est unique et donc la transaction est valide.
    En terme d'implantation, la méthode Arrays.sort permet de trier un tableau. Il vous reste à implanter comment compter les identifiants identiques consécutifs.
    Mettre en commentaire l'ancien code de validTransactionCount() et écrire le nouveau code de validTransactionCount().
    Vérifier que les tests continuent de passer.

  9. En fait, si le tableau est trié, on peut améliorer l'implantation de containsId(id) en faisant une dichotomie (binary search en anglais).
    On veut éviter de trier le tableau s'il est déjà trié, donc on va ajouter dans la classe BankStat un booléen qui indique si le tableau est trié ou non.
    Faites en sorte de vous souvenir si le tableau est trié ou non en utilisant le booléen. Mettez en commentaire l'ancien code de containsId(id) et remplacez-le par celui qui utilise le fait que le tableau peut être trié.
    Vérifier que les tests marqués "Q9" passent.
    Note : il existe déjà une méthode Arrays.binarySearch dans le JDK.