2. Classes

En Java, les classes sont écrites dans des fichiers texte. Bien qu'il soit possible d'écrire plusieurs classes dans un même fichier, la règle communément utilisée est de n'en écrire qu'une seule. Il existe quatre types de classe en Java : les classes publiques, les classes locales, les classes membres et les classes anonymes. Les classes publiques sont de loin les plus utilisées, on peut même concevoir des applications entières en n'utilisant que celles-là. Il existe deux types de règles à suivre lorsque l'on écrit du code Java. Les premières sont des obligations, le plus souvent imposées par la JLS ( Java Language Specification), et les autres sont des bonnes habitudes. En fait, on ne peut pas, ou difficilement, voire dangereusement, s'écarter de ces bonnes habitudes, car de nombreuses API, standard ou non, s'appuient sur le fait que le code écrit les respectent. Dans la suite du texte, on signalera ce qui relève de la JLS ou des bonnes habitudes, mais ce point est en fait anecdotique : il est essentiel de suivre les bonnes habitudes. La première bonne habitude à prendre, consiste à écrire correctement ses identificateurs : noms de classes, de méthodes ou de champs. En Java, un identificateur de champ ou de méthode commence systématiquement par une minuscule, et ne comporte pas de caractère "_", bien qu'il soit possible d'utiliser ce caractère. Lorsqu'un identificateur est composé de plusieurs mots, alors on met en majuscule la première lettre des mots qui le composent. Par exemple, age_marin n'est pas un identificateur conventionnel en Java, on écrira ageMarin. On appelle cette façon d'écrire les variables le camel case , du fait de l'aspect bosselé qu'elles prennent.

2.1. Classes publiques

Une classe publique est déclarée par les mots-clés public class. Elle doit être enregistrée dans un fichier qui porte le même nom qu'elle, et son nom commence en général par une majuscule, se différenciant en cela des identificateurs internes à une classe (champs et méthodes). Voici un exemple de déclaration de classe.

Exemple 19. Exemple d'une classe publique

public  class Marin {  // doit être écrite dans le fichier Marin.java !  
    ...  // code de la classe  
}

2.2. Classes internes

On peut pas avoir plus d'une classe publique dans un fichier donné. En revanche, on peut ajouter d'autres classes, non publiques. La déclaration qui suit est légale, même si elle n'est pas conseillée.

Exemple 20. Exemple d'une classe interne

public  class Marin {   // doit être écrite dans le fichier Marin.java
    ...  // code de la classe  
}  
  
 class Capitaine {   // écrite dans le même fichier
    ...  // code de la classe Capitaine  
}

La classe Capitaine est une classe interne. On peut avoir autant de classes internes qu'on le souhaite dans un fichier. Il est possible de ne pas avoir de classe publique dans un fichier, et uniquement des classes internes. Ce genre de bizarreries est évidemment à proscrire.

2.3. Classe membre

Une classe membre est une classe déclarée à l'intérieur d'une autre classe. Elle peut être static ou non, final ou non, public, private ou protected. Voici un exemple de déclaration d'une telle classe, tiré de la classe Character de l'API standard (version 5).

Exemple 21. Classes membre de la classe Character

public  final  class Character {   // déclarée dans le fichier java/lang/Character
  
    ...  // code de la classe Character  
      
     public  static  class Subset {  
        ...  // code de la classe Subset  
    }  
      
     public  static  final  class UnicodeBlock  extends Subset {  
        ...  // code de la classe UnicodeBlock  
    }   
}

La classe Character est déclarée comme étant public final, ce qui signifie qu'il n'est pas possible de l'étendre, tout comme la classe String. Elle contient une première classe membre : Subset, publique et statique, que l'on peut donc appeler Character.SubSet. Comme on le voit sur la classe Character.UnicodeBlock, une classe membre peut étendre une autre classe, ici une autre classe membre.

2.4. Classes locales

Une classe locale est déclarée dans une méthode et donc ne fait pas partie des membres de la classe qui contient cette méthode. Voici un exemple de déclaration d'une classe locale.

Exemple 22. Exemple d'une classe locale

public  class Marin  implements Comparable<Marin> {  
  
     private String nom, prenom ;  
  
     public  int compareTo(Marin marin) {  
          
         class MarinComparator  implements Comparator<Marin> {  
  
             public  int compare(Marin m1, Marin m2) {  
                 int order = m1.nom.compareTo(m2.nom) ;  
                 return order !=  0 ? order : m1.prenom.compareTo(m2.prenom) ;  
            }                 
        }  
          
         return (new MarinComparator()).compare(this, marin) ;  
    }  
}

Notons que l'accès aux éléments de la méthode qui contient cette classe obéit à des règles particulières, que nous verrons en fin de cette partie.

2.5. Classes anonymes

Une classe anonyme peut être déclarée dans une méthode, comme une classe locale, ou comme classe membre. La différence est qu'elle ne porte pas de nom, qu'elle utilise un mécanisme de déclaration qui se fusionne avec son instanciation. Nous pouvons reprendre le même exemple que celui vu au paragraphe précédent, mais au lieu de déclarer une classe MarinComparator, nous pouvons déclarer une classe anonyme. Voyons ici un exemple de classe anonyme locale.

Exemple 23. Exemple d'une classe anonyme locale

public  class Marin  implements Comparable<Marin> {  
  
     private String nom, prenom ;  
  
     public  int compareTo(Marin marin) {  
          
        Comparator<Marin> comparator =    
             new Comparator<Marin>() {  
      
                 public  int compare(Marin m1, Marin m2) {  
                     int order = m1.nom.compareTo(m2.nom) ;  
                     return order !=  0 ? order : m1.prenom.compareTo(m2.prenom) ;  
                }                 
            } ;  
              
         return comparator.compare(this, marin) ;  
    }  
}

Comme on le voit, le code de la classe est écrit entre accolades, directement après l'appel au constructeur. Cette technique est très utilisée dans la programmation de callbacks, dans les interfaces graphiques et les appels asynchrones que l'on rencontre en programmation Web. Elle est à utiliser avec précautions toutefois : il est très facile de rendre un code complètement illisible par une utilisation déraisonnée des classes anonymes.

2.6. Le mot-clé this

Le mot-clé this désigne en permanence l'objet dans lequel on se trouve. Il existe dès l'instant que l'on se trouve dans l'instance de quelque chose. En particulier, il n'est pas défini dans un élément statique. En général, ce mot-clé est utilisé pour lever l'ambigüité qui peut exister entre le champ d'une classe et un paramètre d'une méthode dans laquelle on se trouve, comme dans l'exemple qui suit.

Exemple 24. Utilisation de this pour lever l'ambiguïté entre un champ et un paramètre

public  class Marin {   // écrit dans le fichier Marin.java
  
     private String nom ;  
      
     public Marin(String nom) {  
         this.nom = nom ;   // utilisation de this pour lever l'ambigüité 
                           // entre le paramètre et le champ
    }  
  
     public  void setNom(String nom) {  
         this.nom = nom ;  // utilisation de this pour lever l'ambigüité 
                          // entre le paramètre et le champ
    }  
      
     public String getNom() {  
         return  this.nom ;  // ici l'utilisation de this est facultative  
    }  
}

Notons que dans ce cas, l'utilisation de this est indispensable dans le constructeur et dans la méthode setNom(), et facultative dans la méthode getNom().
Java langage & API
Retour au blog Java le soir
Cours & Tutoriaux
Table des matières
Introduction : un peu d'histoire
1. Java : genèse d'un nouveau langage
Programmer en Java
1. Un premier exemple
1.1. Aperçu général, cycle de vie
1.2. Un premier programme
1.3. Programmes, applets, servlets, etc...
2. Une première classe
2.1. Écriture d'une classe
2.2. Instanciation de cette classe
3. Types de base, classes et objets
3.1. Types de base
3.2. Classes et objets
Classes Object et String
1. Introduction
2. La classe Object
2.1. La méthode toString()
2.2. La méthode clone()
2.3. La méthode equals()
2.4. La méthode hashCode()
2.5. La méthode finalize()
2.6. La méthode getClass()
3. La classe String
3.1. Introduction
3.2. Construction d'un objet de type String
3.3. Concaténation, StringBuffer et StringBuilder
3.4. Concaténations de chaînes de caractères depuis Java 8
3.5. Concaténations de chaînes de caractères depuis Java 11
3.6. Extraction d'une sous-chaîne de caractères
3.7. Comparaison de deux chaînes de caractères
3.8. Méthodes de comparaisons lexicographiques
3.9. Méthode de recherche de caractères
3.10. Méthode de modification de chaîne
3.11. Méthode de duplication
3.12. Support de l'unicode, internationalisation
Structure d'une classe
1. Introduction
2. Classes
2.1. Classes publiques
2.2. Classes internes
2.3. Classe membre
2.4. Classes locales
2.5. Classes anonymes
2.6. Le mot-clé this
3. Éléments statiques
3.1. Champ statique
3.2. Cas des constantes
3.3. Bloc statique
3.4. Classe membre statique
4. Membres d'une classe, visibilité
4.1. Bloc non statique
4.2. Accès à un membre, visibilité
4.3. Les champs
4.4. Signature d'une méthode
4.5. Les méthodes
4.6. Getters et Setters
5. Constructeur, instanciation
5.1. Chargement d'une classe
5.2. Constructeurs d'une classe
5.3. Instanciation d'un objet
5.4. Destruction d'objets
5.5. Le mot-clé final
6. Énumérations
6.1. Déclaration d'une énumération
6.2. Classe énumération
6.3. Méthode toString()
6.4. Méthode valueOf()
6.5. Méthode values()
6.6. Méthode ordinal()
6.7. Méthode compareTo()
6.8. Constructeurs privés
6.9. Classe utilitaire : EnumSet
Noms, opérateurs, tableaux
1. Introduction
2. Identificateurs, noms et expressions
2.1. Identificateurs et noms
2.2. Expressions
3. Opérateurs, ordre d'exécution
3.1. Ordre d'exécution
3.2. Les opérateurs
3.3. Les opérateurs ++ et --
3.4. Les opérateurs % et /
3.5. Les opérateurs <<, >> et >>>
3.6. L'opérateur instanceof
3.7. Les opérateurs &, | et ^
3.8. Les opérateurs && et ||
3.9. L'opérateur ? ... :
3.10. Les opérateurs d'affectation
4. Tableaux
4.1. Création d'un tableau
4.2. Initialisation d'un tableau
4.3. Utilisation d'un tableau comme un Object
4.4. Tableaux de tableaux
4.5. Copie de tableaux
5. Blocs, boucles et contrôles
5.1. Blocs
5.2. Mots-clés réservés
5.3. Tests : if et switch
5.4. Boucles : for, while, do ... while
5.5. Commandes continue et break
5.6. Commandes return et goto
Nombres, précision, calculs
1. Introduction
2. Calculs
2.1. Précision
2.2. Codage des nombres flottants
2.3. Le mot-clé strictfp
2.4. Conversion de types
3. Dépassements de capacité
3.1. Cas des entiers
3.2. Cas des flottants
3.3. Bibliothèques BigInteger et BigDecimal
4. Fonctions mathématiques
4.1. Fonctions usuelles
4.2. Générateurs aléatoires
5. Classes enveloppe
5.1. Associer les types de base à des objets
5.2. Auto-boxing
Héritage, abstraction, interfaces
1. Introduction
2. Abstraction et encapsulation
2.1. Abstraction
2.2. Encapsulation
3. Héritage
3.1. Définition de l'héritage
3.2. Conséquences pour les membres
3.3. Polymorphisme
3.4. Empêcher l'héritage
4. Classes abstraites
5. Interfaces
5.1. Introduction
5.2. Définition
5.3. Java 8 et les interfaces
5.4. Utilisation des interfaces
5.5. Définition de constantes dans les interfaces
5.6. Utilité des interfaces
Packages
1. Introduction
2. Notion de paquet
2.1. Déclaration d’appartenance à un paquet
2.2. Chargement d’une classe
2.3. Choix de nom
3. Archives, chemin de recherche, classpath
3.1. Archives
3.2. Variable CLASSPATH
3.3. Notion de classloader
3.4. Bilan sur les classes chargées
3.5. Visibilité
3.6. Conseils d'écriture, bibliothèque standard
Exceptions
1. Introduction
2. Erreurs et Exceptions
2.1. Classe Throwable, notion de stack trace
2.2. Classe Error
2.3. Classe RuntimException
2.4. Classe Exception
3. Déclenchement d'une exception
3.1. Exceptions déclenchées par la JVM
3.2. Exceptions déclenchées par l'application
4. Capter une exception
4.1. Traiter une exception localement
4.2. Code de captage
5. Créer ses propres exceptions
Entrées / sorties
1. Introduction
2. Notion de fichier
2.1. Introduction
2.2. La classe File
2.3. Construction d'une instance de File
2.4. Méthodes exposées
3. Flux de sortie
3.1. Introduction, notion de flux
3.2. Écriture de caractères, classe Writer
3.3. Bufferisation, construction d'un flux sur un autre
3.4. Utilisation de PrintWriter
3.5. Écriture d'octets, OuputStream
3.6. Écriture de types primitifs : DataOutputStream
3.7. Écriture d'objets : ObjectOutputStream
4. Flux d'entrée
4.1. Introduction
4.2. Lecture de caractères, classe Reader
4.3. Bufferisation, lecture ligne par ligne
4.4. Lecture d'octets : InputStream
4.5. Lecture de types primitifs : DataInputStream
4.6. Lecture d'objets : ObjectInputStream
5. Lecture et écriture de flux croisés
5.1. Introduction
5.2. Lire des caractères sur un flux binaire : classe InputStreamReader
5.3. Écrire des caractères sur un flux binaire : classe OutputStreamWriter
5.4. Remarque sur les jeux de caractères
6. Serialization d'objets
6.1. Enjeu de la sérialization d'objets
6.2. Serialization d'un objet
6.3. Sérialization d'une grappe d'objets
6.4. Première surcharge : méthode writeObject() readObject()
6.5. Deuxième surcharge : utilisation d'un externalizer
6.6. Troisième surcharge : utilisation d'un objet proxy
7. Flux compressés
7.1. Introduction
7.2. Flux de type gzip
7.3. Flux de type zip