ENPC - Programmation - Corrections 2

Utilisation d'un tableau

package seance2;

public class Exercice1 {
  public static void main(String[] args) {
    double r=0.0;
    for (int i=0; i<args.length; ++i)
      r += Integer.parseInt(args[i]);
    System.out.println("somme des valeurs: " + r);
    System.out.println("nombre de valeurs: " + args.length);
    System.out.println("moyenne: " + r/args.length);
  }
}

Déclaration et définition d'un tableau; méthodes d'initialisation et d'affichage

package seance2;

public class Exos {
  /*
    Cette méthode initialise les éléments du tableau tab
    au carré de leur indice.
  */
  public static void initCarreIndice(int[] tab) {
    for(int i=0; i<tab.length; i++) {
      tab[i] = i*i;
    }
  }

  /*
    Cette méthode affiche sur la sortie standard (System.out) les valeurs
    des éléments du tableau.
  */
  public static void affiche(int[] tab) {
    for(int i=0; i<tab.length; i++) {
      // Ici, on affiche les valeurs sur la même ligne
      // en les séparant par un blanc
      System.out.print(tab[i] + " ");
    }
  }

  public static void main(String[] args) {
    // Définition d'un tableau de 10 entiers
    int t[] = new int[10];

    // Affichage de ce tableau avant initialisation
    System.out.println("Avant initialisation:");
    affiche(t);

    // Initialisation des éléments du tableau au carré de leur indice
    initCarreIndice(t);
    
    // Affichage du tableau après cette initialisation
    // Remarque: le caractère '\n' est interprété comme un retour à la ligne
    System.out.println("\nAprès initialisation:");
    affiche(t);
  }
}

Méthode de tri d'un tableau d'entier

  public static void trier(int[] tab) {
    int aux;
    for(int i=tab.length-1; i>1; i--) {
      for(int j=0; j<i; j++) {
	if(tab[j] > tab[i]) {
	  aux = tab[j];
	  tab[j] = tab[i];
	  tab[i] = aux;
	}
      }
    }
  }

Pour les tableaux de double on écrira exactement la même chose, mais le prototype de la fonction sera public static void trier(double[] tab). Si les deux fonctions coexistent dans la même classe, alors on dit que trier est une fonction surchargée : il existe dans la même classe deux fonctions de même nom mais avec des types de paramètres différents. C'est le compilateur qui décide, en fonction de l'argument que l'on passe à la méthode lors de l'appel, de quelle méthode il s'agit.

Tri d'une liste de valeurs entières entrées sur la ligne de commande

  public static void main(String[] args) {
    int[] t = new int[args.length];
    for(int i=0; i<args.length; i++) {
      t[i] = Integer.parseInt(args[i]);
    }
    trier(t);
    affiche(t);   
  }

Méthode de tri d'un tableau de chaînes de caractères (String)

On surcharge encore la méthode trier:

  public static void trier(String[] tab) {
    String aux;
    for(int i=tab.length-1; i>1; i--) {
      for(int j=0; j<i; j++) {
	if(tab[j].compareTo(tab[i]) > 0) {
	  aux = tab[j];
	  tab[j] = tab[i];
	  tab[i] = aux;
	}
      }
    }
  }

La classe Point

Voici une définition (une classe) pour un type Point. Les objets qui auront ce type possèdent deux attributs de type double, les coordonnées du point dans un repère cartésien, et trois opérations : la première réalisant la translation d'un point, la seconde l'obtention d'une chaîne de caractères à partir d'un point, et la troisième, dont on remarque qu'elle est déclarée static, ne fait rien de particulier, sur le point : c'est la méthode main qui est exécutée au départ de la machine virtuelle.
public class Point {
  // Coordonnées du point
  private double x;
  private double y;

  /**
   * Constructeur d'un point acceptant ses deux coordonnées.
   */
  public Point(double leX, double leY) {
    x = leX;
    y = leY;
  }

  /**
   * Déplace un point de dx en abscisse et de dy en ordonnée.
   */
  public void translate(double dx, double dy) {
    x = x + dx;
    y = y + dy;
  }

  /**
   * Donne la représentation d'un point sous la forme (x,y).
   */
  public String toString() {
    return "(" + x + "," + y + ")";
  }
  
  public static void main(String[] args) {
    Point o = new Point(0,0);
    System.out.println(o.toString());
    Point p = new Point(1.5,3.4);
    System.out.println(p.toString());
    p.translate(0.5,-1.4);
    System.out.println("Après translation: " + p.toString());
  }
}

Le constructeur sert à construire une valeur à partir de la définition du type. Il sert essentiellement à donner des valeurs aux attributs de l'objet. On l'utilise de la manière suivante : new Point(1.0,2.0). Cette expression retourne une référence sur la valeur de type Point créée. On peut la récupérer, et la stocker dans une variable de type référence sur Point : Point p = new Point(1.0,2.0). On peut alors accèder aux attributs de l'objet Point désigné par p (à condition qu'ils aient les bonnes visibilités) ; par exemple p.translate(0.5,-1.4) applique une translation de vecteur (0.5,-1.4) sur le point désigné par p. L'objet désigné par p est appelé cible de l'appel de la méthode translate().

Chaque instance d'une classe possède ses propres membres, à condition qu'ils n'aient pas été déclarés static (par exemple, chaque instance de Point possède son propre attribut x, son propre attribut y, sa propre méthode toString() et sa propre méthode translate()). En revanche, les membres déclarés static ne sont pas propre à un objet particulier : ils sont propres à la classe, et en particulier partagés par toutes les instances de cette classe. Ces membres se trouvant dans la classe et non dans ses instances, on peut y accéder directement en utilisant le nom de la classe :

class A {
   int x;          // Propre à chaque instance de A
   static int n=0; // Propre à la classe A
   A(int x) { x=y; ++n; }
   public int getValue() { return x; }   // Propre à chaque instance de A
   public static int nb() { return nb; } // Propre à la classe A
}
class Essai {
  public static void main(String[] args) {
    System.out.println(A.nb()); // Correct. Affiche 0;
    System.out.println(A.getValue()); // Erreur de compilation car getValue()
                                      // n'existe que dans les objets
    A a = new A(10); // Correct. Nouvelle instance de A
    System.out.println(A.nb()); // Correct. Affiche 1
    System.out.println(a.nb()); // Correct. Affiche 1
    System.out.println(a.getValue()); // Correct. Affiche 10
    A b = new A(20); // Correct. Nouvelle instance de A
    System.out.println(A.nb()); // Correct. Affiche 2
    System.out.println(a.nb()); // Correct. Affiche 2
    System.out.println(b.nb()); // Correct. Affiche 2
    System.out.println(a.getValue()); // Correct. Affiche 10
    System.out.println(b.getValue()); // Correct. Affiche 20
  }
}
Autour des constructeurs

Quand on ne défini pas de constructeur dans une classe, le compilateur Java en met un tout seul, le constructeur par défaut, dont la définition exacte est: public NomDeLaClasse() { super(); }. On ne sait pas encore ce que ca veut dire exactement, il faut pour l'instant retenir que ca ne fait rien (on verra le moment venu que ca n'est pas tout à fait exact), en particulier, il n'initialise rien. Une bonne règle de programmation est de toujours mettre au moins un constructeur dans une classe, afin que le compilateur n'y ajoute pas ce constructeur par défaut.

Ceci explique que quand le programmeur ne défini pas explicitement de constructeur dans Point on peut quand même exécuter l'instruction Point p = new Point() : on obtient alors un objet de type Point aux coordonnées non initialisées.

Rien n'empêche le programmeur de mettre dans sa classe plusieurs constructeurs. Ils doivent cependant différer par les types de leurs paramètres. Ce mécansime, appelé surcharge, fonctionne aussi pour les méthodes. Le constructeur qui sera appelé sera choisit automatiquement en fonction des types des paramètres données lors de l'utilisation de l'opérateur new. Par exemple:

class A {
  A(int x) { System.out.println("Constructeur 1 appelé"); }
  A(String x) { system.out.println("Constructeur 2 appelé"); }
}

class Essai {
  public static void main(String[] args) {
    A a = new A("toto"); // Affiche Constructeur 2 appelé
    A b = new A(5);      // Affiche Constructeur 1 appelé
  }
}

Autour de toString()

Tout objet contient une méthode de profil public String toString() qui calcule une chaîne de caractères d'après un objet. Le mécanisme qui fait que tout objet contient cette méthode nous est encore inconnu : c'est l'héritage. Ca n'est pas le même mécanisme que pour les constructeurs par défaut (on aura l'occasion de le voir bientôt). Si on n'a pas écrit de méthode toString(), celle qui est dans l'objet par défaut se contente de retourner une chaîne de caractères de la forme MonType@321ba3214 composée du nom du type de l'objet et de son adresse écrite en base 16. On peut écrire sa propre méthode toString() dans ses classes, comme par exemple celle de la classe Point qui vous a été donnée.

Autour de l'égalité

Attention quand on compare des objets. il ne faut pas confondre égalité des objets avec égalité des références sur les objets. L'égalité des références est testée avec l'opérateur ==, comme l'égalité classique des valeurs en Java. Deux références sont égales ssi elles désignent le même objet (même emplacement mémoire). La machine virtuelle Java ne peut pas toute seule tester l'égalité des objets : en effet, cette dernière est associée au sens que donne le programmeur à ses objets, et elle ne peut pas devienner ce sens (par exemple, que signifie l'égalité de deux instances de Point ? Pour nous, êtres humains, deux points sont égaux s'ils sont aux même coordonnées, mais comment la machine pourrait -elle le deviner ?). Cette égalité doit être définie par une méthode que le programmeur ajoute dans la classe des objets qu'il veut comparer (les deux objets doivent être de même nature (même type)).

class Point {
  private int x;
  private int y;
  public Point(int x, int y) { this.x=x; this.y=y; }
  ...
  public boolean same(Point p) {
    return x==p.x && y==p.y; // Compare les coordonnées de ce point avec
  }                          // celles du point désigné par p
}
class Essai {
  public static void main(String[] args) {
    Point p1 = new Point(1,2);
    Point p2 = p1;
    System.out.println(p1==p2); // affiche true : les deux refs désignent
                                // le même objet
    System.out.println(p1.same(p2)); // affiche true : logique !
    p2 = new Point(1,2);
    System.out.println(p1==p2); // affiche false : les deux refs ne désignent
                                // pas le même objet
    System.out.println(p1.same(p2)); // Affiche true: en effet, au sens de
                                     // la méthode same() de la classe Point,
                                     // les points désignés par p1 et p2
                                     // sont identiques
  }
}

Etienne.Duris[at]univ-mlv.fr - © École Nationale des Ponts et Chaussées - - http://www-igm.univ-mlv.fr/~duris/ENPC