Premiers pas en Java, premiers objets

Hello World

Dans un premier temps nous allons écrire des petits programmes permettant de se familiariser avec le compilateur, la machine virtuelle et la syntaxe du langage.
En Java, les classes publiques sont typiquement définie dans un fichier qui lui est propre. Le nom du fichier doit être le nom de la classe qu'il contient, auquel on ajoute le suffixe .java. Les noms des classes doivent être constitués de mots accolés dont la première lettre est une majuscule (c'est ce qu'on appelle le CamelCase.

Écrire le programme suivant :

      public class HelloWorld {
        public static void main(String[] args) {
          System.out.println("Hello World");
        }
      }
     
dans votre éditeur de texte préféré et sauvegarder celui-ci sous le nom HelloWorld.java

Compiler le programme en utilisant le commande javac puis vérifier que le fichier .class correspondant existe bien.

      javac HelloWorld.java
    

Exécuter le programme avec la commande java

      java HelloWorld
     
On ne met pas .class parce que la machine virtuelle le rajoute toute seule.

Note: la commande java est aussi capable de compiler en mémoire si on lui indique un seul fichier simple, comme par exemple java HelloWorld.java. Dans ce cas, le fichier .class ne sera pas visible sur le disque car la compilation à lieu uniquement en mémoire.

Arguments de la ligne de commande, boucles

Le but de cet exercice est d'écrire le main d'une classe PrintArgs qui affiche les arguments de la ligne de commande.

La classe PrintArgs s'utilisera comme suit:

      $ java PrintArgs Voici des arguments
      Voici
      des
      arguments
    

Les arguments de la ligne de commande sont stockés dans le tableau de chaînes de caractères passé en argument à la méthode public static void main(String[] args).

Dans un premier temps, afficher le premier argument de la ligne de commande (dans notre exemple Voici).

Que se passe-t-il si l'on ne passe pas d'argument lors de l'exécution du programme ?

Écrire une boucle affichant le contenu du tableau, sachant qu'en Java, les tableaux possèdent un champ (un attribut) length qui correspond à la taille du tableau.

Changer votre programme pour utiliser la syntaxe dite du 'for deux points', c'est à dire for(var value: array).

Calculette simple

Le but de cet exercie est d'écrire une petit calculatrice.

Écrire un programme prenant un nombre sur l'entrée standard et affichant celui-ci.
Pour cela, on utilisera un objet Scanner et particulièrement sa méthode nextInt().

    import java.util.Scanner; 

    public class Calc { 
    	public static void main(String[] args) {
    		Scanner scanner;
    		scanner = new Scanner(System.in);
    		int value;
    		value = scanner.nextInt();
    		// compléter ici
    	}
    }
    
Pour comprendre le programme, il est utile de regarder la documentation disponible ici. Il est fortement conseillé de mettre ce lien en bookmark.

Indiquer dans le programme où sont les variables et quel est leur type.
Modifier le programme pour déclarer et initialiser les variables en une seule ligne.

Dogme:À partir de maintenant, on déclarera les variables en les initialisant et en utilisant le mot-clé var quand c'est possible.

Expliquer la ligne suivante :

    import java.util.Scanner;
    

Modifier le programme pour qu'il demande de saisir deux entiers au clavier et qu'il affiche leur somme.

Afficher en plus de la somme, la différence, le produit, le quotient et le reste.

Record et conversion de String en entier

On souhaite écrire un programme calculant la distance entre deux points, ou plus exactement la distance entre un point et l'origine, i.e. le point de coordonnées (0,0).
Pour cela, on va introduire un record Point qui va nous servir à représenter un point avec ses deux coordonnées.

Voici un exemple d'exécution que l'on souhaite obtenir :

      $ java Point 3 4
      x=3, y=4
      Point[x=3, y=4]
      dist = 5.0
    

Dans un premier temps, créer un record Point (dans un fichier Point.java) avec deux composants x et y, tous les deux de type int.
Quelle doit être la ligne de commande pour compiler le fichier Point.java ?

Écrire une méthode main qui déclare deux variables entières a et b, qui leur assigne les valeurs 0, 5, puis qui crée un point p avec ces deux coordonnées. Enfin, faire afficher le point p.

Rajouter dans la méthode main de quoi prendre les coordonnées x et y sur la ligne de commande (3 et 4 dans l'exemple ci-dessus), les convertire en valeurs entières et afficher celles-ci (la ligne x=3, y=4 dans l'exemple).

Note : La méthode static parseInt(String s) de la classe java.lang.Integer permet de récupérer la valeur d'un entier stockée dans une chaîne de caractères.

Que veut dire static pour une méthode ?

Que se passe-t-il lorsque l'un des arguments n'est pas un nombre ?

Dans le main, ajouter des instructions pour créer un instance du record Point, avec le deux entiers x et y et afficher celui-ci.

On souhaite ajouter au record Point une méthode d'instance (une méthode non statique) nommée distance qui va prendre un autre point en paramètre et qui va calculer la distance entre ces deux points sous forme d'un nombre à virgule flottante.
Quels sont les paramètres et le type de retour de la méthode distance ?
Ecrire la méthode distance et afficher dans le main la distance entre le point saisi par l'utilisateur et l'origine (0, 0).

Calculer la distance entre deux points est équivalent à utiliser Pythagore sur la distance en ordonnée et la distance en absice entre les deux points et qu'il existe une méthode statique Math.sqrt qui renvoie la racine carrée d'un nombre.

Record Circle (A faire à la maison)

Dans cet exercice, on va créer un nouveau record un peu plus évolué, écrire quelques méthodes d'instances et tester tout cela.
Nous avons créé des Point dans l'exercice précédent. Maintenant, nous allons définir des Cercle. Un cercle peut être caractérisé par son centre, et par son rayon.

Créer un nouveau record Circle avec un champ center de type Point et un champ radius de type double.

Ajouter une méthode main dans laquelle vous allez créer un nouveau cercle de centre (0,0) et de rayon 1, puis afficher l'objet.

  public record Circle(Point center, double radius) {
    public static void main(String[] args) {
      var circle = new Circle(new Point(0, 0), 1.0);
      System.out.println(circle);
    }
  }
      

Ecrire une méthode d'instance perimeter qui calcule le périmètre du cercle.
Tester votre méthode dans le main.

Il existe une constante Math.PI de type double qui représente π.

Ecrire une méthode d'instance area qui calcule l'aire du cercle.
Tester votre méthode dans le main.

Ecrire une méthode qui calcule la distance de deux cercles centre à centre. Vous utiliserez pour cela la méthode distance du record Point (cf. exercice précédent).
Tester la méthode dans le main.

Ecrire une méthode d'instance intersect qui renvoie true si le cercle instancié a une intersection non nulle avec le cercle passé en paramètre.
Dans le main, testez avec l'intersection de notre premier cercle avec un cercle de rayon 1 et de centre (5,5), puis avec un cercle de rayon 7 et de centre (3,2).

Lorsque l'on affiche un objet comme ci-dessous:

  var circle = new Circle(new Point(1,1),1.0);
  System.out.println(circle);

l'objet circle est transformé en chaîne de caractères en appelant sa méthode d'instance String toString().
Pour les records, la méthode String toString() est écrite automatiquement. Elle resemble à peu prêt à cela:

 public record Circle(Point center, double radius) {

    public String toString() {
      return "Circle[center=" + center + ", radius=" + radius + "]";
    }
  }

On peut redéfinir la méthode toString pour un record. Par exemple, on peut vouloir afficher le cercle avec le format from (1,1) with raduis 1.0.

Redénissez la méthode toString pour le record Circle afin d'afficher le cercle avec le format voulu.

La bonne approche est de redéfinir la méthode toString pour le record Point pour avoir un affichage au format (x,y) et de redéfinir la méthode toString pour le record Circle pour avoir un affichage au format from (x,y) with radius r.

Le code obtenu jusqu'ici permet de créer des cercles avec un rayon négatif ou un centre qui vaut null.

var point = new Point(0, 0);
var wrongCircle = new Circle(point, -1.0);
var wrongCircle2 = new Circle(null, 1.0);

Le problème de laisser construire de tel cercle est que leur utilisation va induire des erreurs plus tard dans le code.
On lever une erreur à la construction du cercle pour permettre de détecter les erreurs au plus tôt. En Java, on dit blow early, blow often.

Par défaut, le constructeur (qu'on appelle avec new dans le main) d'un record ne fait pas de vérifications. Modifier le constructeur pour qu'il vérifie que le rayon est positif et que le centre n'est pas null. Si le rayon est négatif, le constructeur doit lancer une IllegalArgumentException. Si le centre est null, le constructeur doit lancer une NullPointerException.

Pour aller plus loin : C vs Java

Le but de cet exercice est de montrer que Java n'est pas forcément plus lent que C. Cela peut paraître contre-intuitif. En effet, l'exécution d'un programme Java passse par lancement d'une machine virtuelle qui va exécuter le bytecode produit par le compilateur Java. De l'autre côté, le code C est transformé directement en code exécutable sur votre machine (pas besoin de démarrer une machine virtuelle). Le mystère c'est donc de savoir comment le code Java s'exécute aussi vite.

Considérons le programme suivant en C :

#include <stdio.h> 
#include <stdlib.h>

	int pascal (int nBut, int pBut){
       int * tab;
       unsigned int n, i;

       tab = (int *)malloc ((nBut+1)*sizeof(int));
       if(tab==NULL){
         fprintf(stderr,"Pas assez de place\n");
         exit(0);
       }

       tab[0] = 1;

       for(n=1; n<=nBut; n++){
         tab[n] = 1;

         for(i=n-1; i>0; i--)
           tab[i] = tab[i-1] + tab[i];
       }

       int result=tab[pBut];
       free(tab);
       return result;
    }

    int main(int argc, char * argv[]) {
       printf(" Cn, p = %d\n", pascal (30000, 250));
       return 0;
    }

Compiler (gcc pascal.c) et exécuter le programme a.out en demandant au système le temps d'exécution du programme avec la commande time a.out.

Écrire le programme (Pascal.java) équivalent en Java. Pour une fois, servez-vous du copier/coller. Compiler le programme puis l'exécuter en mesurant le temps (toujours avec time).

Normalement, vous devriez obtenir des temps d'exécution comparables.
Comment peut-on expliquer que le code Java soit ausssi performant que le code C ?

Hint 1: Rappelon que votre programme Java a été transformé (sans aucune optimisation) en bytecode qui est exécuté par un machine virtuelle (cf. cours). Le code C est transformé directement en code exécutable sur votre machine (pas besoin de démarrer une machine virtuelle). Le mystère c'est donc de savoir comment le code Java s'exécute aussi vite.

Hint 2: Si vous voulez percer le mystère, regardez ce qu'est le JIT.

Hint 3: Si vous voulez voir qu'on vous ment un peu, essayez de compiler le code C avec l'option -O3 qui produit un code plus optimisé.