Up Next

B.1  Un langage plutôt classe

Java est un langage objet avec des classes. Les classes ont une double fonction : structurer les programmes et dire comment on construit les objets. Pour ce qui est de la seconde fonction, il n’est pas si facile de définir ce qu’est exactement un objet dans le cas général. Disons qu’un objet possède un état et des méthodes qui sont des espèces de fonctions propres à chaque objet. Par exemple tous les objets possèdent une méthode toString sans argument et qui renvoie une chaîne représentant l’objet et normalement utilisable pour l’affichage. La section B.2 décrit la construction des objets à partir des classes.

Mais décrivons d’abord la structuration des programmes réalisée par les classes. Les classes regroupent des membres qui sont le plus souvent des variables (plus précisément des champs) et des méthodes, mais peuvent aussi être d’autres classes.

B.1.1  Programme de classe

Un programme se construit à partir de une ou plusieurs classes, dont une au moins contient une méthode main qui est le point d’entrée du programme. Les variables des classes sont les variables globales du programme et leurs méthodes sont les fonctions du programme. Une variable ou une méthode qui existent dès que la classe existe sont dites statiques.

Commençons par un programme en une seule classe. Par exemple, la classe simple suivante est un programme qui affiche Coucou ! sur la console :

class Simple {
  static String msg = "Coucou !" ;    // déclaration de variable

  public static void main (String [] arg) // déclaration de méthode
  {
    System.out.println(msg) ;
  }
}

Cette classe ne sert pas à fabriquer des objets. Elle se suffit à elle même. Par conséquent tout ce qu’elle définit (variable msg et méthode main) est statique. Par re-conséquent, toutes les déclarations sont précédées du mot-clé static, car si on ne met rien les membres ne sont pas statiques. Si le source est contenu dans un fichier Simple.java, il se compile par javac Simple.java et se lance par java Simple. C’est une bonne idée de mettre les classes dans des fichiers homonymes, ça permet de s’y retrouver.

En termes de programmation objet, la méthode main invoque la méthode println de l’objet System.out, avec l’argument msg. System.out désigne la variable out de la classe System, qui fait partie de la bibliothèque standard de Java. Notons que msg est en fait Simple.msg, mais dans la classe Simple, on peut se passer de rappeler que msg est une variable de la classe Simple, alors autant en profiter.

Reste à se demander quel est l’objet rangé dans System.out. Et bien, disons que c’est un objet d’une autre classe (la classe PrintStream) qui a été mis là par le système Java et ne nous en préoccupons plus pour le moment.

B.1.2  Complément : la méthode main

La déclaration de cette méthode doit obligatoirement être de la forme :

  public static void main (String [] arg)

En plus d’être statique, la méthode main doit impérativement être publique (mot-clé public) et prendre un tableau de chaîne en argument. Le sens du mot-clé public est expliqué plus loin.

Le reste des obligations porte sur le type de l’argument de main (son nom est libre). Le tableau de chaîne est initialisé par le système pour contenir les arguments de la ligne de commande. De sorte que l’on peut facilement écrire une commande echo en Java.1

class Echo {
  public static void main (String [] arg) {
    for (int i = 0 ; i < arg.length ; i++) {
      System.out.println(arg[i]);
    }
  }
}

Ce qui nous donne après compilation :

% java Echo coucou foo bar
coucou
foo
bar

B.1.3  Collaboration de classe

La classe-programme Simple utilise déjà une autre classe, la classe System écrite par les auteurs du système Java. Pour structurer vos programmes, vous pouvez (devez) vous aussi écrire plusieurs classes. Par exemple, récrivons le programme simple à l’aide de deux classes. Le message est fourni par une classe Message

class Message {
  static String msg = "Coucou !" ;
}

Tandis que le programme est modifié ainsi :

class Simple {
  public static void main (String [] arg) { // déclaration de méthode
    System.out.println(Message.msg) ;
  }
}

Si on met la classe Message dans un fichier Message.java, elle sera compilée automatiquement lorsque l’on compile le fichier Simple.java (par javac Simple.java). Encore une bonne raison pour mettre les classes dans des fichiers homonymes.

B.1.4  Méfiance de classe

Lorsque l’on fabrique un programme avec plusieurs classes, l’une d’entre elles contient la méthode main. Les autres fournissent des services, en général sous forme de méthodes accessibles à partir des autres classes.

Supposons que la classe Hello doit fournir un message de bienvenue, en anglais ou en français. On pourra écrire.

class Hello {
  private static String hello ;

  static void setEnglish() { hello = "Hello!" ; }
  static void setFrench()  { hello = "Coucou !" ; }
  static String getHello() { return hello ; }

  static { setEnglish() ; }
}

Classe utilisée par une nouvelle classe Simple.

class Simple {
  public static void main (String [] arg) {
    System.out.println(Hello.getHello()) ;
  }
}

La variable hello est privée (mot-clé private) ce qui interdit son accès à partir de code qui n’est pas dans la classe Hello. Une méthode getHello est donc fournie, pour pouvoir lire le message. Deux autres méthodes laissent la possibilité aux utilisateurs de la classe de sélectionner le message anglais ou le message français. Enfin, le bout de code static { setEnglish() ; } est exécuté lors de la création de la classe en machine, ce qui assure le choix initial de la langue du message. Finalement, la conception de la classe Hello garantit une propriété : à tout instant, Hello.hello contient nécessairement un message de bienvenue en français ou en anglais.

La pratique de restreindre autant que possible la visibilité des variables et méthodes améliore à la fois la sûreté et la structuration des programmes.

On parle d’abstraction, la séparation en classe segmente le programme en unités plus petites, dont on n’a pas besoin de tout savoir.

À l’intérieur de la classe elle-même, la démarche d’abstraction revient à écrire plusieurs méthodes, chaque méthode réalisant une tâche spécifique. Les classes elle-mêmes peuvent être regroupées en packages, qui constituent une nouvelle barrière d’abstraction. La bibliothèque de Java, qui est énorme, est structurée en packages.

Le découpage en packages, puis en classes, puis en méthodes, qui interagissent selon des conventions claires qui disent le quoi et cachent les détails du comment, est un fondement de la bonne programmation, c’est-à-dire de la production de programmes compréhensibles et donc de programmes qui sont (plus) facilement mis au point, puis modifiés.

Il y a en Java quatre niveaux de visibilité, du plus restrictif au plus permissif.

Nous connaissons déjà quelques emplois de public.

B.1.5  Reproduction de classe par héritage

La plus grande part de la puissance de la programmation objet réside dans le mécanisme de l’héritage. Comme première approche, nous examinons rapidement l’héritage et seulement du point de vue de la classe. Soit une classe Foo qui hérite d’une classe Bar.

class Foo extends Bar {
  …
}

La classe Foo démarre dans la vie avec toutes les variables et toutes les méthode de la classe Bar. Mais la classe Foo ne va pas se contenter de démarrer dans la vie, elle peut effectivement étendre la classe dont elle hérite en se donnant de nouvelles méthodes et de nouvelles variables. Elle peut aussi redéfinir les méthodes et variables héritées. Par exemple, on peut construire une classe HelloGoodBye qui offre un message d’adieu en plus du message de bienvenue de Hello, et garantit que les deux messages sont dans la même langue.

class HelloGoodBye extends Hello {
  private static String goodbye ;

  static void setEnglish() { Hello.setEnglish() ; goodbye = "Goodbye!" ; }
  static void setFrench() { Hello.setFrench() ; goodbye = "Adieu !" ; }
  static String getGoodBye() { return goodbye ; }

  static { setEnglish() ; }
}

On note que deux méthodes sont redéfinies (setEnglish et setFrench) et qu’une méthode est ajoutée (getGoodBye). La méthode setEnglish ci-dessus doit, pour assurer la cohérence des deux messages, appeler la méthode setEnglish de la classe Hello, d’où l’emploi d’une notation complète Hello.setEnglish.

Comme démontré par la nouvelle et dernière classe Simple, la classe HelloGoodBye a bien reçu la méthode getHello en héritage.

class Simple {
  public static void main (String [] arg) {
    System.out.println(HelloGoodBye.getHello()) ;
    System.out.println(HelloGoodBye.getGoodBye()) ;
  }
}

En fait, l’héritage des classes n’a que peu d’intérêt en pratique, l’héritage des objets est bien plus utile.


1
echo est une commande Unix qui affiche ses arguments

Up Next