String, StringBuilder, égalité, et expressions régulières

Assignation, égalité, référence

Le but de cette exercice de comprendre le fonctionnement des chaînes de caractères String en Java.

On considère le code suivant :

      public static void main(String[] args) {
        var s = "toto";
        System.out.println(s.length());
      }
     

Quel est le type de s ?
Comment le compilateur fait-il pour calculer le type de s ?

Qu'affiche le code suivant ? Expliquer le résultat.

        var s1 = "toto";
        var s2 = s1;
        var s3 = new String(s1);
    
        System.out.println(s1 == s2);
        System.out.println(s1 == s3);
     

Quelle est la méthode à utiliser si l'on veut tester si le contenu des chaînes de caractères est le même ?

        var s4 = "toto";
        var s5 = new String(s4);
    
        System.out.println(/* comparer contenue de s4 et s5 */);
     

Qu'affiche le code suivant ? Expliquer.

        var s6 = "toto";
        var s7 = "toto";
    
        System.out.println(s6 == s7);
     

Expliquer pourquoi il est important que java.lang.String ne soit pas mutable pour pouvoir partager les chaînes de caractères.

Qu'affiche le code suivant ?

        var s8 = "hello";
        s8.toUpperCase();
        System.out.println(s8);
     
Expliquer l'affichage et modifier le code pour obtenir l'affiche de la chaîne mise en majuscule.

En morse. Stop.

Le but de cet exercice est de comprendre comment créer des chaînes de caractères en Java avec une complexité optimale.

On veut écrire une classe Morse.java qui permet, lors de son exécution, d'afficher les chaînes de caractères prises en argument sur la ligne de commande, séparées par des "Stop.". Par exemple, on voudrait obtenir l'affichage suivant :

      $ java  Morse.java ceci est drole
      ceci Stop. est Stop. drole Stop.
    

Écrire la classe Morse.java en utilisant dans un premier temps l'opérateur + qui permet la concaténation de chaînes de caractères.

A quoi sert l'objet java.lang.StringBuilder ?
Pourquoi sa méthode append(String) renvoie-t-elle l'objet de type StringBuilder sur lequel on a appelé la méthode au lieu de void ?

Ré-écrire le code en utilisant un StringBuilder.
Quel est l'avantage par rapport à la solution précédente ?

Recopier le code suivant dans une classe de Test.java :

       void main(String[] args) {
         var first = args[0];
         var second = args[1];
         var last = args[2];
         println(first + ' ' + second + ' ' + last);
       }
      
Pourquoi peut-on utiliser ' ' à la place de " " ?
Compiler le code avec javac puis utiliser la commande javap pour afficher le bytecode Java (le code intermédiaire) généré.
       javap -private -c Test.class
      
Que pouvez-vous en déduire ?

Compiler le code de la question 1, puis utiliser la commande javap pour afficher le bytecode Java généré.
Que pouvez-vous en déduire ?
Dans quel cas doit-on utiliser StringBuilder.append() plutôt que le + ?
Et pourquoi est-ce que le chargé de TD va me faire les gros yeux si j'écris un + dans un appel à la méthode append?

Méthode statique et méthode d'instance

Le but de cet exercice est d'approfondir quelques notions de programmation orientée objet : la différence entre les méthodes d'instance et les méthodes statiques, la différence accéder directement à un champ ou appeler une méthode accesseur.

On considère le code suivant :

     public record Point(double x, double y) {}
  

On veut un moyen de calculer le point qui se trouve à égale distance de deux points donnés. Il y a deux façons de faire. On peut créer une méthode statique qui prend en paramètre deux points et qui renvoie le point à égale distance. Ou bien, on peut créer une méthode d'instance qui prend en paramètre un point et qui renvoie le point à égale distance.

Ecrivez les deux méthodes dans la classe Point et dans une classe de Application.java écrivez un main qui teste ces deux méthodes.

      public class Application {
        public static void main(String[] args) {
          var p1 = new Point(0.0, 0.0);
          var p2 = new Point(1.0, 1.0);
          var p3 = // appel de la méthode statique
          System.out.println("Milieu avec la méthode statique: " + p3);
          var p4 = // appel de la méthode d'instance
          System.out.println("Milieu avec la méthode d'instance: " + p4);
        }
      }
      

On peut légitimement se demander laquelle des deux approches est préférable. Même si les deux approches sont correctes, on préfère la méthode d'instance dans la plupart des cas. En effet, elle est plus facile à utiliser, elle offre une meilleure découvrabilité par les IDE et elle permettra de profiter du polymorphisme (plus tard dans le cours).

On considère deux versions de la méthode toString:

    public record Point(double x, double y) {
        @Override
        public String toString() {
            return "Point(" + x + ", " + y + ")";
        }
    }

    public record Point(double x, double y) {
        @Override
        public String toString() {
            return "Point(" + x() + ", " + y() + ")";
        }
    }
  

Quelle est la différence entre les deux versions ? Quelle version est la plus performante ?

Dans une méthode d'instance ou statique, d'un record (ou d'une classe), on peut accéder directement aux champs de n'importe quelle instance de cette classe.
Par contre, en dehors des méthodes du record, on ne peut pas accéder directement aux champs: il faut passer par les accesseurs.

On veut maintenant convertir des coordonnées cartésiennes (x,y) en coordonnées polaires (r,theta). La conversion est donnée par les formules suivantes :

polar

Dans une classe PolarConverter.java, un camarade de classe a écrit le code suivant :

   public class PolarConverter {
       public static void main(String[] args) {
        var point = new Point(3,4);

        var theta = Math.atan2(point.y(), point.x());
        var r = Math.sqrt(point.x()*point.x() + point.y()*point.y());
        System.out.println("Coordonnées cartésiennes: " + point);
        System.out.println("Coordonnées polaires: " + "(" + r + "," + theta + ")");
       }
    } 
  
D'un point de vue mathématique, le code est correct. La fonction Math.atan2 renvoie un angle en radians entre et π.

Du point de vue de la programmation orientée objet, le code pose un problème car tous les calculs sur les coordonnées du Point sont effectués dans la classe PolarConverter et non dans la classe Point.
On est obligé d'aller chercher les coordonnées du Point en utilisant les accesseurs x() et y().

Modifier la classe Point pour qu'elle contienne les méthodes permettant de convertir les coordonnées cartésiennes en coordonnées polaires et modifier la classe PolarConverter en conséquence.

Modifier la classe Point pour qu'elle permette d'obtenir les coordonnées polaires en un seul appel de méthode et modifier la classe PolarConverter en conséquence.

Reconnaissance de motifs

Le but de cet exercice est la manipulation d'expressions régulières en Java. Nous utiliserons pour cela les classes du paquetage java.util.regex.

A quoi servent la classe java.util.regex.Pattern et sa méthode compile ?
A quoi sert la classe java.util.regex.Matcher ?

Écrire un programme qui lit des chaînes de caractères sur la ligne de commande et affiche les chaînes qui correspondent à des nombres, c'est-à-dire les chaînes dont tous les caractères sont compris entre '0' et '9'.

Modifier le programme pour que l'on reconnaisse (et extrait) un nombre même dans le cas où le nombre est précédé par des caractères qui ne sont pas des chiffres.
Par exemple, les chaînes "abc", "4de" et "f6h" ne sont pas valide car, soit il n'y a pas de nombre, soit elles ne finissent pas par un nombre. La chaîne "789" correspond au nombre 789 et la chaîne "ab3" correspond au nombre 3 (les caractères avant le nombre ne sont pas pris en compte).
Note: il y a plusieurs façons de faire, penser que matches() n'est pas la seule méthode qui existe sur un Matcher. Attention: on ne veut pas créer plusieurs Pattern différents, ou plusieurs Matcher différents (il y a plus simple).

Écrire une méthode qui prend en paramètre une chaîne de caractères contenant une adresse IPv4 et renvoie un tableau de 4 bytes. Il faut tester qu'il s'agit bien d'une adresse valide.
Vous utiliserez pour cela la notion de groupe.
Rappel: une adresse IPv4 est représentée sous la forme de quatre nombres entiers séparés par des points comme 193.43.55.67. Chacun des nombres représente un octet. La plage d'attribution s'étend de 0.0.0.0 à 255.255.255.255.
Note: essayer de déclarer le Pattern sous forme de constante !