3. Éléments statiques

On appelle élément statique d'une classe tout élément attaché à cette classe plutôt qu'à l'une de ses instances. Un élément statique peut exister, être référencé, ou s'exécuter même si aucune instance de cette classe n'existe. Un élément statique ne peut référencer this. Il est possible de définir quatre types d'éléments statiques :
  • des champs statiques : la valeur de ce champ est la même pour tous les objets instance de la classe ;
  • des méthodes statiques ;
  • des blocs statiques ;
  • des classes membre statiques.

3.1. Champ statique

Par défaut, chaque instance de chaque classe occupe son propre espace mémoire. Deux objets instances d’une même classe occupent donc deux espaces mémoire qui ne se recouvrent pas. Lorsque que l’on déclare un membre statique ( static), il est au contraire placé dans un espace mémoire commun à tous les objets de la classe. Si un des objets modifie la valeur d’un champ statique (par exemple), tous les objets verront la valeur de ce champ modifiée. Nous avons déjà rencontré une méthode statique : la méthode main, appelée lors du lancement d'une application Java. Dans la mesure où un élément statique ne dépend d’aucune instance de la classe à laquelle il appartient, il est possible de l’appeler avec une syntaxe particulière, sans passer par une instance particulière de la classe. Il est même possible d’invoquer un élément statique d’une classe sans que celle-ci n’ait jamais été instanciée. Il est une bonne pratique de n'appeler les membres statiques d'une classe que de façon "statique", c'est-à-dire en utilisant le nom de la classe plutôt qu'une de ses instances. Voyons ceci sur un exemple.

Exemple 25. Utilisation d'un champ statique

public  class Marin {  // dans le fichier Marin.java  
     public  static  int nombreMarins =  0 ;  
      
     public Marin() {  
        nombreMarins ++ ;  
    }  
}  
  
 public  class Application {  // dans le fichier Application.java  
      
     public  static  void main(String [] args) {  
        Marin marin1 =  new Marin() ;  
        Marin marin2 = null ;  
        
         // affiche 1  
        System.out.println("Nombre de marins = " + Marin.nombreMarins) ;
        
         // affiche 1, méthode non recommandée  
        System.out.println("Nombre de marins = " + marin1.nombreMarins) ; 
        
         // affiche 1, bien que marin2 soit null
        System.out.println("Nombre de marins = " + marin2.nombreMarins) ;   
    }  
}

Sur cette exemple, nous voyons trois façon d'accéder au champ statique nombreMarins. Notons qu'y accéder via une instance nulle de la classe Marin est valide, ce qui peut paraître bizarre au premier coup d'œil, et qu'il l'est sans aucun doute ! La méthode préférée pour accéder à un champ statique, est d'y accéder via le nom de la classe qui contient ce champ : Marin.nombreMarins.

3.2. Cas des constantes

L'utilisation de constantes en algorithmique est très fréquente et même indispensable. La bonne façon de définir une constante en Java est de la définir dans un champ public static final. Le fait qu'il soit publique et statique permet d'y accéder de n'importe où. Le déclarer final interdit sa modification, ce qui est en général recherché pour une constante ! Le premier exemple qui vient à l'esprit est la définition de PI dans la classe Math.

Exemple 26. Déclaration d'une constante

public  class Math {   
  
     public  static  final  double PI =  3.14159265358979323846 ;  
  
}

Il est donc possible d'utiliser la constante Math.PI dans n'importe quel programme Java. La gestion des constantes a évolué entre Java 1.4 et Java 5. Jusqu'en Java 1.4, la façon préférée pour déclarer une constante est celle que nous venons de décrire. En toute rigueur il en existe une autre, qui consiste à déclarer ce champ public final dans une interface plutôt qu'une classe. Ce pattern de Java 1.1 a été jugé hérétique il y a quelques temps déjà, et on ne doit donc plus l'utiliser sous peine de s'attirer les foudres de l'inquisition espagnole. La gestion des constantes a légèrement évolué entre Java 4 et Java 5. Lors de la compilation, un code qui référence une constante est remplacé par la valeur de la constante. Mais ce code, qui vit dans une classe possède un import vers la classe qui porte cette constante. En Java 5 ce point a été revu, avec l'introduction d'une nouvelle notion : l'import statique. Le code qui utilise une constante peut continuer à l'utiliser comme en 1.4 (dans le cas de Math.PI, c'est assez justifié...), mais il peut aussi choisir d'importer cette valeur de constante statiquement. Dans ce cas, la classe qui utilise cette constante ne dépend plus de celle qui déclare cette constante, et le Jar de cette classe n'a plus besoin d'ête en dépendance du premier.

3.3. Bloc statique

Un bloc statique est un ensemble d'instructions déclaré suivant une syntaxe particulière. Nous verrons dans la suite comment ce bloc est pris en compte lors de l'exécution d'un programme. Voyons un exemple de bloc statique.

Exemple 27. Déclaration d'un bloc statique

public  class Marin {  
      
     static {  
         // ceci est un bloc statique  
    }  
}

Ces blocs sont à utiliser avec précautions, notamment lorsque l'on se préoccupe de la testabilité du code que l'on écrit. Nous reverrons la testabilité d'un code en détails, et expliquerons comment et en quoi ce genre de choses peut nuire à la testabilité d'une classe.

3.4. Classe membre statique

Nous avons déjà vu les classes membre. Une classe membre peut être déclarée statique. Dans ce cas, on notera qu'il est possible à la machine Java de la charger, sans charger la classe dans laquelle cette classe est déclarée.
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