Egalité, nullabilité, mutabilité, affichage

Livre

Dans cet exercice, on passe en revue les constructeurs d'un record.

On cherche à écrire un record Book représentant un livre avec un titre et le nom de l'auteur.

Déclarer un record Book avec les composants title et author.

Puis essayer le code suivant dans une méthode main du record Book.

  var book = new Book("Da Vinci Code", "Dan Brown");
  System.out.println(book.title + ' ' + book.author);
      
Expliquer.

Créer une classe Main (dans un fichier Main.java) et déplacer le main de la classe Book dans la classe Main.
Quel est le problème ? Comment peut-on le corriger ?

On peut remarquer que le code permet de créer des livres ayant un titre ou un auteur null.

 var weirdBook = new Book(null, "oops");
       

Comment faire pour éviter ce problème sachant qu'il existe une méthode static requireNonNull dans la classe java.util.Objects.

En fait, on peut simplifier le code que vous avez écrit à la question précédente en utilisant un constructeur compact (compact constructor). Commenter le code précédent et utiliser un constructor compact à la place.

Écrire un autre constructeur qui prend juste un titre et pas d'auteur et ajouter un code de test dans le main.
On initialisera le champ author avec "<no author>" dans ce cas.

Comment le compilateur fait-il pour savoir quel constructeur appeler ?

On souhaite maintenant pouvoir changer le titre d'un livre déjà existant en utilisant une méthode nommée withTitle qui prend en paramètre le nouveau titre.
Pourquoi le code suivant ne marche pas ?

  public void withTitle(String title) {
    this.title = title;
  }
      
Comment faire alors ? (indice comme String.toUpperCase)
Écrire le code correspondant et ajouter un code de test dans le main.

Liberté, Égalité, toString

Dans cet exercice, on s'intéresse aux méthodes automatiquement générées par le compilateur pour les records comme equals et toString.

Qu'affiche le code ci-dessous ?

    var b1 = new Book("Da Java Code", "Duke Brown");
    var b2 = b1;
    var b3 = new Book("Da Java Code", "Duke Brown");

    System.out.println(b1 == b2);
    System.out.println(b1 == b3);
      

Pourquoi ?

Comment faire pour tester si deux objets ont le même contenu ?
Écrire le code qui affiche si b1 et b2, puis b1 et b3 ont le même contenu.

Écrire une méthode isFromTheSameAuthor() qui renvoie vrai si deux livres sont du même auteur.
Et vérifier avec les deux livres suivants :

  var book1 = new Book("Da Vinci Code", "Dan Brown");
  var book2 = new Book("Angels & Demons", new String("Dan Brown"));
       

Comment faire pour que le code suivant

        var javaBook = new Book("Da Java Code", "Duke Brown");
        System.out.println(javaBook);
       
affiche
        Da Java Code by Duke Brown
       

Utiliser l'annotation @Override (java.lang.Override) sur la méthode ajoutée à Book.

A quoi sert l'annotation @Override ?

Record vs Class

Dans cet exercice, on va chercher à récrire le code d'un record en utilisant une classe. Cela n'a pas d'autre intérêt que de bien comprendre toutes les subtilités des records.

Voici le code produit par un étudiant pour une classe Book :

public class Book2 {
  private final String title;
  private final String author;

  public Book2(String title, String author) {
    this.title = title;
    this.author = author;
  }
  
  public static void main(String[] args) {
    var book1 = new Book2("Da Vinci Code", "Dan Brown");
    var book2 = new Book2("Da Vinci Code", "Dan Brown");
    System.out.println(book1.equals(book2));
  }
}
    
Malheureusement, le main n'a pas le comportement attendu.

Quel est le problème ?

Comment corriger le problème si on s'entête à utiliser une classe ?
Ne m'oubliez pas le @Override SVP !

Jeu de rôle

Dans cet exercice, on va voir comment modéliser correctement un concept en programmation orientée objet.

On part d'un code très simple qui simule les achats d'un joueur dans un jeu de rôle. Le joueur possède un certain nombre de pièces d'or et il peut acheter des potions de soins.

import java.util.Scanner;
import java.util.concurrent.ThreadLocalRandom;

public class Rogue {
    
    public static String ask(Scanner scanner){

        while(true){
            System.out.println("Type yes (to buy), no (to skip) or quit (to leave)");
            var choice = scanner.nextLine();
            if (choice.equals("yes") || choice.equals("no") || choice.equals("quit")){
                return choice;
            }
            System.out.println("Invalid choice, please try again.");
        }
    }

    public static void main(String[] args) {
        // retrieve a random number generator
        var rng = ThreadLocalRandom.current(); 
        // purse contains the amount of gold pieces the player has
        // it is initialized with a random number between 0 and 99
        var purse = rng.nextInt(100);
        var scanner = new Scanner(System.in);

        var potions = 0;
        while (true) {
            System.out.println("You have " + purse + " gold pieces.");
            var price = rng.nextInt(10);
            System.out.println("Do you want to buy a potion of healing for " + price + " gold pieces?");
            var choice = ask(scanner);
            if (choice.equals("quit")){
                break;
            }
            if (choice.equals("no")){
                continue;
            }
            // buy potion
            if (purse < price){
              System.out.println("You don't have enough gold pieces.");
              continue;
            }
            potions++;
            purse -= price;
        }
        System.out.println("You manage to acquire " + potions + " potions with " + purse + " gold pieces remaining.");
    }
}

Lire et exécuter le code ci-dessus.

On souhaite maintenant complexifier le monde du jeu. Les prix ne seront en pièces d'or mais en pièces d'or et en pièces d'argent. L'idée est qu'un pièce d'or vaut 13 pièces d'argent.
Après cette évolution, on voudrait obtenir un affichage du similaire au suivant :

You have 57g  and 8s gold pieces.
Do you want to buy a potion of healing for 0g  and 1s?
Type yes (to buy), no (to skip) or quit (to leave)
yes
You have 57g  and 7s gold pieces.
Do you want to buy a potion of healing for 1g  and 4s?
Type yes (to buy), no (to skip) or quit (to leave)
yes
You have 56g  and 3s gold pieces.
Do you want to buy a potion of healing for 3g  and 0s?
Type yes (to buy), no (to skip) or quit (to leave)
yes
You have 53g  and 3s gold pieces.
Do you want to buy a potion of healing for 3g  and 12s?
Type yes (to buy), no (to skip) or quit (to leave)
yes
You have 49g  and 4s gold pieces.
Do you want to buy a potion of healing for 6g  and 3s?
Type yes (to buy), no (to skip) or quit (to leave)
quit
You manage to acquire 4 potions with 49g  and 4s gold pieces remaining.

Pour réaliser cette évolution, on va définir un record Price qui représente un quantité de pièce d'or et d'argent. L'idée est d'équiper ce record avec toutes les méthodes nécessaires pour gérer les prix dans le jeu.
Idéalement, le code de la classe Rogue ne va être que très peu modifié. On va remplacer les prix qui étaint représentés par un entier par des objets de type Price et appeler les bonnes méthodes de Price.

Écrire le code du record Price et modifier le code de la classe Rogue en conséquence.