Règles de codage

Introduction

Dans cette partie, après avoir fait quelques programmes, nous allons voir ou revoir les règles essentielles du développement Java.

Paquetages (package)

Un package permet de regrouper des classes qui partagent un même domaine dans une application ou une librairie.

Par exemple, les classes du package java.net regroupent des outils d’accès au réseau. Les classes du package java.io regroupent les classes de gestion de flux d’entrées/sorties.

Les packages:

Nommage des packages

Les noms des paquetages sont TOUJOURS en minuscules (aucune majuscule).

Exercice

Vérifiez à quelles packages appartiennent les classes String, Object et Date.

Concernant la classe Date, déterminez si elle est unique (dans l’ensemble des classes de l’API Java SE).

Classes

Une classe est un modèle. Elle permet de déterminer le comportement d’un ensemble d’objets similaires et leurs caractéristiques et comportements.

La classe va servir de prototype pour créer une instance. L’instance (ou exemplaire) d’une classe se comportera comme définit dans ce modèle.

Définition d’une classe

class RendezVous {
    String nom; //attribut nom
}

Instantiation

L’instantiation se fait avec le mot clef new

RendezVous rendezVous = new RendezVous();

Identification d’une classe

L’identification d’une classe se fait à partir de son nom et de son paquetage.

Ainsi, le nom complet de la classe String est java.lang.String.

Le nom complet de la classe Date est java.util.Date ou java.sql.Date. En effet il y a plusieurs classes Date.

Import

Si on veut les différencier il faut utiliser le nom complet. Ce qui serait peu lisible en pratique. Comme dans une classe on n’utilise en général qu’une de ces deux classes, on va utiliser un import.

Ainsi, au lieu de devoir écrire:

class RendezVous {
  java.util.Date dateRDV;
}

On va écrire:

import java.util.Date;
class RendezVous {
  Date dateRDV;
}

Import de toutes les classes d’un paquet

Il est possible aussi d’importer toutes les classes d’un même paquet en une seule fois avec le joker *.

import java.util.*;
class RendezVous {
  Date dateRDV;
}

Attributs

Les attributs d’une classe sont les éléments contenus par une classe.

Dans l’exemple précédent, l’attribut nom permet de stocker le nom du RendezVous sous forme d’une chaîne de caractère.

Attributs d’instance

La valeur d’un attribut est propre à une instance de classe. Chaque exemplaire d’une classe conservera une valeur spécifique pour cet attribut.

Testez le code suivant (à mettre dans une méthode main):

RendezVous rendezVousA = new RendezVous();
rendezVousA.nom = "Premier rendez-vous";
RendezVous rendezVousB = new RendezVous();
rendezVousB.nom = "Second rendez-vous";
System.out.println(rendezVousA); //RendezVous A
System.out.println(rendezVousB); //RendezVous B

Attributs de classe

Il est possible de définir des attributs communs à toutes les instances d’une classe. Ce sont les attributs de classe.

Ils sont définis en utilisant le mot clé static.

class RendezVous{
  String nom;
  static int compteur;
}

Utilisation:


RendezVous rendezVousA = new RendezVous();
rendezVousA.compteur = 1;
RendezVous rendezVousB = new RendezVous();
rendezVousB.compteur += 1;
System.out.println(RendezVous.compteur); //2
System.out.println(RendezVous.compteur); //2

Remarques importantes

Au lieu d’écrire:

rendezVousA.compteur = 1;

Ecrire:

RendezVous.compteur = 1;

Méthodes

Une méthode permet de définir un comportement, d’implémenter un algorithme.

Elles sont de deux types:

Déclaration

class RendezVous {
  int dureeEnHeure = 0;
  void incrementerDuree(){
  dureeEnHeure ++ ;
  }
}

Utilisation

RendezVous rdv = new RendezVous();
rdv.incrementerDuree();

Déclaration

class RendezVous{
  static int compteur = 0;

  RendezVous(){
    compteur++;
  }

  static int getNombreDInstances(){
    return compteur;
  }
}

Utilisation

int valeur = RendezVous.getNombreDInstances();

Exercice

Nous allons tester les attributs de classe.

package com.example.java.exercices;

public class Forme {
    private String type ;
    private String nom ;

    public Forme(String type, String nom){
      this.nom = nom;
      this.type = type;
    }
    public String getType(){
      return this.type;
    }

    public String getNom(){
      return this.nom;
    }

    /**
     * Renvoie la concaténation du type et du nom.
     * Ex: triangle/bermudes
     */
    public String getRepresentation(){
      return this.type + "/" + this.nom ;
    }
}

Pour cela, implémentez la classe Forme ci-dessus.

Ajoutez une méthode dont la signature est la suivante:

Cette méthode renvoie une chaîne de caractères de la forme suivante:

"Le nombre de formes instanciées est 3"

Où 3 est à remplacer par le compteur de formes.

Ensuite, créer une classe principale GenereForme qui contiendra le code suivant:

Forme f ;

f = new Forme("Triangle", "Bermudes");
f = new Forme("Carré", "Magique");
System.out.println(f.getInformationNombreFormes());
f = new Forme("Rectangle", "Vert");
f = new Forme("Cercle", "Bleu");
System.out.println(f.getInformationNombreFormes());

Que dire de la variable f qui est ici utilisée ?

En lançant ce programme, que constatez-vous ?

Représentation UML des membres

Diagramme de classe

Diagramme de classe: RendezVous.png
Diagramme de classe: RendezVous.png

Import statique

Normalement pour utiliser un membre d’une classe, il faut l’écrire explicitement:

double rayon = Math.cos(beta * Math.PI);

Les static import permettent d’utiliser directement depuis une autre classe des membres statiques d’une classe particulière.

import static java.lang.Math.PI;
import static java.lang.Math.cos;

...

double rayon = cos(beta * PI);

ATTENTION

Ceci est à utiliser avec précaution et parcimonie !

Il est facile de se retrouver avec des membres avec le même nom que ceux importés d’une autre classe.

Constantes et le mot clé ‘final’

Les constantes sont des attributs qui ne peuvent plus changer de valeur.

Pour les définir on utilise le mot clé final

En fait, tout membre d’une classe ayant un attribut final ne pourra plus être modifié ou surchargé.

Ainsi une méthode déclarée final ne pourra pas être surchargée par une classe qui hériterait de la classe courante.

class Polygone{
  String nom ;
  final int JE_NE_PEUX_PAS_CHANGER = 0;

  final String getNom(){
    return nom;
  }

  void changer(){
    JE_NE_PEUX_PAS_CHANGER = 1 ; //ERREUR DE COMPILATION
  }
}
class Triangle extends Polygone{
  String getNom(){ // ERREUR de COMPILATION
      return "Triangle."+nom;
  }
}

Représentation UML

Diagramme de classe: Constantes.png
Diagramme de classe: Constantes.png

Conventions de codage

Définition

C’est un ensemble de règles qui permettent d’écrire un code dans une manière lisible et compréhensible par un maximum de développeurs.

Ces règles évoluent d’un langage à l’autre. En Java, elles sont essentielles car elles permettent, d’un seul coup d’oeil, de déterminer si on a affaire à une classe/un type, une variable/attribut/paramètre ou une constante.

Elles sont là pour améliorer la compréhension des éléments du code et faire en sorte qu’il soit “auto-commenté” le plus possible. C’est à dire que l’on va utiliser des noms qui soient parlant, que ce soit pour les méthodes, attributs, variables ou classes.

Référence (1)

Un lien vers les conventions de codage telles que définies aux origines du langage Java.

http://www.oracle.com/technetwork/java/codeconventions-150003.pdf

Référence (2)

Autre lien avec quelques exemples:

https://en.wikibooks.org/wiki/Java_Programming/Coding_conventions pour compléter vos connaissances.

Règles de nommage

Les mots réservés

Les mots qu’il est interdit d’utiliser pour nommer une attribut, une méthode, une variable, un paramètre, une classe, etc, sont donnés sur la page suivante:

Java Keywords

Nommer les éléments du code

Désigner pour utiliser

Tous les composants d’un programme Java doivent être nommés.

Les éléments

Ces noms sont donnés:

Ces noms sont appelés des identifiants.

Sensibilité à la casse

Leur nom est sensible à la casse (différence majuscule/minuscule).

Définir un identifiant

Les règles pour définir un identifiant sont les suivantes:

Identifiants autorisés

Exemples d’identifiants autorisés:

Exemples d’identifiants incorrects:

Notation CamelCase

En Java on utilise la notation CamelCase (casse en dos de chameau).

notationEnDosDeChameau

Pour nommer un attribut, une méthode ou une classe, on l’écrit sous la forme de mots accolés, avec la première lettre du mot qui commence par une majuscule.

La première lettre du premier mot est:

Exemple:

class FormeSpeciale {
  String leNomDeCetteForme ;

  static final int CODE_FORME = 3;

  String getLeNomDeCetteForme(){
    return leNomDeCetteForme;
  }

  void setLeNomDeCetteForme(String nouveauNom){
    leNomDeCetteForme = nouveauNom;
  }
}

Pour les constantes

Pour les constantes on utilise une règle différente: majuscules avec des mots séparés par des tirets-bas (underscore).

final String MA_CONSTANTE = "Je ne changerai jamais !" ;

Contrôle des accès

Les classes, attributs ou méthodes peuvent être accessibles (on dit aussi visibles) ou non à l’intérieur d’autres classes, qu’elles soient du même package ou pas.

Pour indiquer les degrés d’accessibilités on utilise des modificateurs d’accessibilité (access modifiers).

Les access modifiers

On utilise pour cela ces trois mots clés:

Modificateur Applicable à Signification
public classe est visible partout
public membre (méthode ou attribut) visible partout où sa classe est visible
par défaut (sans modificateur) classe ou membre seulement à l’intérieur de son package
protected méthode ou attribut à l’intérieur du package ou par les classes dérivées
private classe uniquement à l’intérieur de la classe où elle est déclarée
private méthode ou attribut uniquement à l’intérieur de la classe

Représentation UML

Diagramme de classe: Modificateurs d’accès
Diagramme de classe: Modificateurs d’accès

Application 1

Nous allons créer les classes suivantes dans le paquet com.example.java.voyage.

Des erreurs vont apparaître dans le code.

Commentez en indiquant pour chaque ligne:

Corrigez l’erreur en :

Note:

Un accesseur est une méthode permettant d’accéder ou de modifier la valeur d’un attribut

Vous pouvez expérimenter dans votre IDE.

Le code source des fichiers ci-dessous est téléchargeable ici.

Dans le fichier Trousse.java:

package com.example.java.voyage;

public class Trousse {   /* La classe est visible partout */

    public String publique ; /* Est visible partout où sa classe est visible*/
    protected int protege ;

    int defaut;
    private int prive;

    Trousse(String valPub, int valProt, int valDef, int valPriv){
        publique = valPub;
        protege = valProt;
        defaut = valDef;
        prive = valPriv;
    }
    Trousse(){
        /* ... */
    }

    /** Accesseur public */
    public int getProtege(){
        return protege;
    }
    /** Accesseur public */
    public int getPrive(){
        return prive;
    }
}

Dans le fichier SacDeVoyage.java :

package com.example.java.voyage; //Meme paquet que Trousse

class SacDeVoyage {
    Trousse trousse = new Trousse();
    int entier;
    String chaine;

    SacDeVoyage(){
        chaine = trousse.publique;
        entier = trousse.protege;
        entier = trousse.getProtege();
        entier = trousse.defaut;
        entier = trousse.prive ;
        entier = trousse.getPrive();
    }
}

Dans le fichier TrousseDeToilette.java :

package com.example.java.voyage;

public class TrousseDeToilette extends Trousse {
    int autreEntier ;
    String autreChaine ;
    public TrousseDeToilette() {
        autreChaine = publique;
        autreEntier = protege ;
        autreEntier = defaut;
        autreEntier = prive ;
        autreEntier = getPrive();
    }
}

Nous allons créer la classe suivante dans le paquet com.example.java.transport.

package com.example.java.transport; //Un autre paquet

import com.example.java.voyage.Trousse; //import de Trousse
class Voiture {
    Trousse trousse = new Trousse();
    int entier;
    String chaine;

    Voiture(){
        chaine = trousse.publique;
        entier = trousse.protege;
        entier = trousse.getProtege();
        entier = trousse.defaut;
        entier = trousse.prive ;
        entier = trousse.getPrive();
    }
}

Puis la classe TroussePremierSecours :

package com.example.java.transport;

import com.example.java.voyage.Trousse;

public class TroussePremierSecours extends Trousse {
    TroussePremierSecours(){
        String chaine = publique;
        int entier = protege;
        entier = getProtege();
        entier = defaut;
        entier = prive ;
        entier = getPrive();
    }
}

Corrigez toutes les erreurs (et expliquez en commentaire ce que vous avez fait et pourquoi) :

Dessinez la représentation UML des classes ainsi corrigées

Le mot clé this

Ce mot clé est très utile dans le cadre des setters.

En effet, dans la méthode setData(String data) suivante, comment accéder à l’attribut de l’objet data sachant que celui-ci est masqué par l’identifiant du paramètre data ?

class Information{
  private String data ; //attribut
  public void setData(String data){ //data est un paramètre
    data = data ; // !!!
  }
  public void getData(){
    return data ; // on renvoie la valeur de l attribut data
  }
}

L’appel à data correspond ici uniquement au paramètre nommé data.

Pour accéder à l’attribut, on le fait précéder du mot clé this.

public void setData(String data){ //data est un paramètre
  this.data = data ; // !!!
}

this pointe vers l’instance de l’objet sur lequel s’applique la méthode.

Même si data est privé, on a le droit d’y accéder ici, puisqu’on le fait depuis l’intérieur de la classe.

Application 2

Reprendre la classe Personne et rendre tous ses attributs inaccessibles depuis l’extérieur.

Créer une classe principale qui instancie une Personne

Mettre des getters pour tous les attributs de Personne.

Mettre des setters pour tous les attributs sauf nom, prenom et dateNaissance.

Ces trois attributs devront être positionnés/modifiés via un autre moyen que les accesseurs. Lequel ?

Est-ce logique de faire ainsi ?

Transtypage

Un entier n’est pas un double

Il est parfois nécessaire de convertir un type en un autre. Par exemple, on peut souhaiter transformer un entier en double.

int entier = 4 ;
double doublePrecision = 0.0;

doublePrecision = entier ; //Erreur de compilation

Un entier peut devenir un double

Le compilateur ne laisse pas passer ce genre d’erreur. Pour remédier à cela, on utilise le transtypage (on utilise aussi le mot cast):

int entier = 4 ;
double doublePrecision = 0.0;

doublePrecision = (double) entier ;

D’une classe à une autre

Il est possible de faire cela avec des objets:

Calcul calcul = new Addition();

Addition addition = (Addition) calcul;

addition.calculer(3, 4);

Code source pour les calculs

Pour vérifier, vous pouvez utiliser ce code source (une solution possible du chapitre 2).

Encapsulation

L’intérêt des modificateurs d’accès est de permettre de contrôler l’accès aux membres de la classe depuis d’autres classes (ou leurs instances, les objets).

En effet, lorsqu’on exécute un programme, il n’est pas souhaitable que des objets viennent modifier le comportement interne d’un autre objet. Cela pourrait engendrer des comportements indésirables.

On souhaite donc isoler certains membres d’un objet de l’extérieur afin qu’il soit inaccessibles.

On appelle ceci l’encapsulation. C’est un des concepts fondamentaux de la POO.

Encapsulation des calculs

Reprenons les classes développées pour les calculs arithmétiques.

La classe Calcul contient un résultat. Comment isoler ce résultat de l’extérieur pour qu’il ne soit pas modifiable ?

Comment le rendre accessible pour que l’on puisse en connaître la valeur ?

Une solution : on encapsule l’attribut resultat et on lui ajoute des accesseurs.

Les accesseurs sont aussi appelés:

Utilisez le code suivant et essayez de compiler votre programme:

public abstract class Calcul {
    private int resultat ;
    public abstract void calculer(int a, int b);

    public int getResultat() {
        return resultat;
    }
    public void setResultat(int r) {
        resultat = r;
    }

    public String toString() {
        return "Le résultat de l'opération "+getClass().getSimpleName()+"";
    }
}

On constate que certaines classes contiennent des erreurs de compilation.

Setter

Ainsi, la ligne suivante pose problème :

resultat = a * b;

Utilisons l’accesseur commençant par set (on l’appelle aussi setter) :

setResultat(a * b);

Getter

Idem pour la ligne suivante:

System.out.println(premierCalcul.resultat);

Utilisons l’accesseur commençant par get (on l’appelle aussi getter) :

System.out.println(premierCalcul.getResultat());

Attribut privé, attribut protégé ?

L’attribut resultat est désormais inaccessible depuis toute classe qui n’est pas Calcul.

Il est donc protégé de toute modification extérieure. La classe contrôle ce qui peut être définit sur cet attribut.

Pourtant, si on observe attentivement, on constate que n’importe quelle classe peut appeler la méthode setResultat().

Est-ce souhaitable ?

Quel modificateur d’accès utiliser pour remédier à cela parmi ceux vus précédemments ?

Sur quel membre le placer ?

Suppression du setter

On souhaite se débarrasser du setter : en effet, il n’est pas souhaitable que le résultat soit modifiable de l’extérieur.

Ainsi dans la classe Addition (par exemple), on veut que le code suivant fonctionne:

resultat = a + b;

Pour cela, on va modifier la classe Calcul comme suit:

public void calculer(int a, int b) {
  valeurA = a;
  valeurB = b;
  resultat = effectuerCalcul();
}

Une nouvelle méthode effectuerCalcul() est ici utilisée.

C’est elle qui va concrètement effectuer le calcul.

  1. Comment déclarer cette méthode dans la classe Calcul ?
  2. Comment la déclarer dans ses sous-classes ?

Code source (solution possible)

Code source

UML

Dessinez la représentation UML de Calcul et de ses sous-classes.

Exercices

Point

Voici un diagramme de classe :

Diagramme de classe: Point.png
Diagramme de classe: Point.png

Exercice 1: définition de la classe Point

Dans ce qui suit, pour réaliser des calculs de distance, vous pouvez utiliser la classe Math (en utilisant éventuellement les static import).

  1. Ecrire la classe Point qui décrit un point dans un repère en 2 dimensions avec des coordonnées entières (abscisse et ordonnée) et un nom (non modifiable).

Les informations du point doivent être encapsulées et en lecture seule (getter uniquement). On autorise ainsi la modification des coordonnées du point uniquement par le biais de la méthode translater.

  1. Ajouter un constructeur complet qui prend en paramètres le nom et les coordonnées du point.
  2. Vérifier que vous ne pouvez pas modifier l’attribut nom en dehors du constructeur. Pour pouvoir le modifier, rajouter un setter sur cet attribut et compiler la classe Point.
  3. Implémenter le code des deux méthodes de la classe Point:
  1. Ajouter une méthode String toString() permettant de représenter l’instance de Point par une chaîne de caractères de la forme nom(abscisse, ordonnee).

Exercice 2: utilisation de la classe Point

  1. Créer une classe ApplicationPoint pour tester votre classe Point : le programme crée un point A de coordonnées (3, 5) et l’affiche sur la console.
  2. Translater le point A d’un vecteur (4, -3) et l’afficher à nouveau.
  3. Créer un deuxième point B de coordonnées (3, 5), calculer sa distance avec le point A et l’afficher.

Prenez le soin de vérifier les valeurs données par le programme.