14. Méthodes, Attributs et Variables
Table des matières
- 14.1 Méthodes
- 14.2 Java est un langage Pass-by-Value
- 14.3 Surcharge des méthodes
- 14.3.1 Appeler des méthodes surchargées
- 14.3.1.1 La correspondance exacte l’emporte
- 14.3.1.2 S’il n’existe pas de correspondance exacte Java choisit le type compatible le plus spécifique
- 14.3.1.3 L’élargissement des primitifs bat le boxing
- 14.3.1.4 Le boxing bat les varargs
- 14.3.1.5 Pour les références Java choisit le type de référence le plus spécifique
- 14.3.1.6 Lorsqu’il n’y a pas de plus spécifique non ambigu l’appel est une erreur de compilation
- 14.3.1.7 Surcharges mixtes primitifs + wrappers
- 14.3.1.8 Quand les primitifs se mélangent avec les types de référence
- 14.3.1.9 Quand Object gagne
- 14.3.1.10 Tableau récapitulatif de la résolution de surcharge
- 14.3.1 Appeler des méthodes surchargées
- 14.4 Variables locales et d’instance
- 14.5 Varargs listes d’arguments à longueur variable
- 14.6 Méthodes statiques, variables statiques et initialiseurs statiques
Ce chapitre introduit des concepts fondamentaux de la Programmation Orientée Objet (OOP) en Java, en commençant par les méthodes, le passage des paramètres, la surcharge, les variables locales vs. d’instance et les varargs.
14.1 Méthodes
Les méthodes représentent les opérations/comportements qui peuvent être effectués par un type de données particulier (une classe).
Elles décrivent ce que l’objet peut faire et comment il interagit avec son état interne et le monde extérieur.
Une déclaration de méthode est composée de composants obligatoires et optionnels.
14.1.1 Composants obligatoires d’une méthode
14.1.1.1 Modificateurs d’accès
Les modificateurs d’accès contrôlent la visibilité, pas le comportement.
( Se référer au paragraphe "Access Modifiers" dans le chapitre : 2. Basic Language Java Building Blocks )
14.1.1.2 Type de retour
Apparaît immédiatement avant le nom de la méthode.
- Si la méthode retourne une valeur → le type de retour spécifie le type de la valeur.
- Si la méthode ne retourne pas de valeur → le mot-clé
voiddoit être utilisé. - Une méthode avec un type de retour non void doit contenir au moins une instruction
return valeur;. - Une méthode
voidpeut : -
- omettre une instruction return
-
- inclure
return;(sans aucune valeur)
- inclure
14.1.1.3 Nom de la méthode
Suit les mêmes règles que les identificateurs ( Se référer au chapitre : 3. Java Naming Rules ).
14.1.1.4 Signature de la méthode
La signature de la méthode en Java inclut :
- le nom de la méthode
- la liste des types de paramètres (types + ordre)
Note
Les noms des paramètres NE font PAS partie de la signature, seuls les types et l’ordre comptent.
- Exemple de signatures distinctes :
void process(int x)
void process(int x, int y)
void process(int x, String y)
- Exemple de même signature (surcharge illégale) :
// ❌ même signature : seuls les noms des paramètres diffèrent
void m(int a)
void m(int b)
14.1.1.5 Corps de la méthode
Un bloc { } contenant zéro ou plusieurs instructions.
Si la méthode est abstract, le corps doit être omis.
14.1.2 Modificateurs optionnels
Les modificateurs optionnels de méthode incluent :
staticabstractfinaldefault(méthodes d’interface)synchronizednativestrictfp
Règles :
- Les modificateurs optionnels peuvent apparaître dans n’importe quel ordre.
-
Tous les modificateurs doivent apparaître avant le type de retour.
-
Exemple :
public static final int compute() {
return 10;
}
14.1.3 Déclarer des méthodes
public final synchronized String formatValue(int x, double y) throws IOException {
return "Result: " + x + ", " + y;
}
Décomposition :
| Partie | Signification |
|---|---|
| public | modificateur d’accès |
| final | ne peut pas être redéfinie |
| synchronized | modificateur de contrôle des threads |
| String | type de retour |
| formatValue | nom de la méthode |
| (int x, double y) | liste des paramètres |
| throws IOException | liste des exceptions |
| method body | implémentation |
14.2 Java est un langage « Pass-by-Value »
Java utilise uniquement le pass-by-value, sans exception.
Cela signifie :
- Pour les types primitifs → la méthode reçoit une copie de la valeur.
- Pour les types référence → la méthode reçoit une copie de la référence, ce qui signifie :
-
- la référence elle-même ne peut pas être modifiée par la méthode
-
- l’objet peut être modifié via cette référence
-
Exemple :
void modify(int a, StringBuilder b) {
a = 50; // modification de la *copie* → aucun effet à l’extérieur
b.append("!"); // modification de l’*objet* → visible à l’extérieur
}
public static void main(String[] args) {
int num1 = 11;
methodTryModif(num1);
System.out.println(num1);
}
public static void methodTryModif(int num1){
num1 = 10; // cette nouvelle affectation concerne uniquement le paramètre du méthode qui, accidentellement, porte le même nom que la variable externe.
}
14.3 Surcharge des méthodes
La surcharge des méthodes signifie même nom de méthode, signature différente.
Deux méthodes sont considérées comme surchargées si elles diffèrent par :
- le nombre de paramètres
- les types des paramètres
- l’ordre des paramètres
La surcharge NE dépend PAS de :
- le type de retour
- le modificateur d’accès
-
les exceptions
-
Exemple :
void print(int x)
void print(double x)
void print(int x, int y)
Méthode surchargée illégale :
// ❌ Le type de retour ne compte pas dans la surcharge
int compute(int x)
double compute(int x)
14.3.1 Appeler des méthodes surchargées
Lorsque plusieurs méthodes surchargées sont disponibles, Java applique la résolution de surcharge pour décider quelle méthode appeler.
Le compilateur sélectionne la méthode dont les types de paramètres sont les plus spécifiques et compatibles avec les arguments fournis.
La résolution de surcharge a lieu au moment de la compilation (contrairement à l’overriding, qui est basé sur l’exécution).
Java suit ces règles :
14.3.1.1 La correspondance exacte l’emporte
Si un argument correspond exactement à un paramètre de méthode, cette méthode est choisie.
void call(int x) { System.out.println("int"); }
void call(long x) { System.out.println("long"); }
call(5); // affiche : int (correspondance exacte pour int)
14.3.1.2 — S’il n’existe pas de correspondance exacte, Java choisit le type compatible le plus spécifique
Java préfère :
- l’élargissement plutôt que l’autoboxing
- l’autoboxing plutôt que les varargs
-
le type de référence plus large uniquement si un type plus spécifique n’est pas disponible
-
Exemple avec des primitifs numériques :
void test(long x) { System.out.println("long"); }
void test(float x) { System.out.println("float"); }
test(5); // littéral int : peut être élargi en long ou float
// mais long est plus spécifique que float pour les types entiers
// Output : long
14.3.1.3 — L’élargissement des primitifs bat le boxing
Si un argument primitif peut être soit élargi soit autoboxé, Java choisit l’élargissement.
void m(int x) { System.out.println("int"); }
void m(Integer x) { System.out.println("Integer"); }
byte b = 10;
m(b); // byte → int (élargissement) l’emporte
// Output : int
14.3.1.4 — Le boxing bat les varargs
void show(Integer x) { System.out.println("Integer"); }
void show(int... x) { System.out.println("varargs"); }
show(5); // int → Integer (boxing) préféré
// Output : Integer
14.3.1.5 — Pour les références, Java choisit le type de référence le plus spécifique
void ref(Object o) { System.out.println("Object"); }
void ref(String s) { System.out.println("String"); }
ref("abc"); // "abc" est une String → plus spécifique que Object
// Output : String
Plus spécifique signifie plus bas dans la hiérarchie d’héritage.
14.3.1.6 — Lorsqu’il n’y a pas de « plus spécifique » non ambigu, l’appel est une erreur de compilation
Exemple avec des classes sœurs :
void check(Number n) { System.out.println("Number"); }
void check(String s) { System.out.println("String"); }
check(null); // String et Number acceptent null
// String est plus spécifique car c’est une classe concrète
// Output : String
Mais si deux classes non liées entrent en concurrence :
void run(String s) { }
void run(Integer i) { }
run(null); // ❌ Erreur à la compilation : appel de méthode ambigu
14.3.1.7 — Surcharges mixtes primitifs + wrappers
Java évalue l’élargissement, le boxing et les varargs dans cet ordre :
élargissement → boxing → varargs
- Exemple :
void mix(long x) { System.out.println("long"); }
void mix(Integer x) { System.out.println("Integer"); }
void mix(int... x) { System.out.println("varargs"); }
short s = 5;
mix(s); // short → int → long (élargissement)
// boxing et varargs ignorés
// Output : long
14.3.1.8 — Quand les primitifs se mélangent avec les types de référence
void fun(Object o) { System.out.println("Object"); }
void fun(int x) { System.out.println("int"); }
fun(10); // la correspondance primitive exacte l’emporte
// Output : int
Integer i = 10;
fun(i); // référence acceptée → Object
// Output : Object
14.3.1.9 — Quand Object gagne
void fun(List<String> o) { System.out.println("O"); }
void fun(CharSequence x) { System.out.println("X"); }
void fun(Object y) { System.out.println("Y"); }
fun(LocalDate.now()); // Output : Y
14.3.1.10 Tableau récapitulatif (Résolution de la surcharge)
| Situation | Règle |
|---|---|
| Correspondance exacte | Toujours choisie |
| Élargissement primitif vs boxing | L’élargissement l’emporte |
| Boxing vs varargs | Le boxing l’emporte |
| Type de référence le plus spécifique | L’emporte |
| Types de référence non liés | Ambigu → erreur de compilation |
| Primitif + wrapper mélangés | Élargissement → boxing → varargs |
14.4 Variables locales et d’instance
14.4.1 Variables d’instance
Les variables d’instance sont :
- déclarées comme membres d’une classe
- créées lorsqu’un objet est instancié
- accessibles par toutes les méthodes de l’instance
Modificateurs possibles pour les variables d’instance :
- modificateurs d’accès (
public,protected,private) finalvolatile-
transient -
Exemple :
public class Person {
private String name; // variable d’instance
protected final int age = 0; // final signifie ne peut pas être réaffectée
}
14.4.2 Variables locales
Les variables locales :
- sont déclarées à l’intérieur d’une méthode, d’un constructeur ou d’un bloc
- n’ont aucune valeur par défaut → doivent être explicitement initialisées avant utilisation
-
seul modificateur autorisé : final
-
Exemple :
void calculate() {
int x; // déclarée
x = 10; // doit être initialisée avant utilisation
final int y = 5; // légal
}
Deux cas particuliers :
14.4.2.1 Variables locales effectivement final
Une variable locale est effectivement final si elle est assignée une seule fois, même sans final.
Les variables effectivement final peuvent être utilisées dans :
- expressions lambda
- classes locales/anonimes
14.4.2.2 Paramètres comme effectivement final
Les paramètres de méthode se comportent comme des variables locales et suivent les mêmes règles.
14.5 Varargs (Listes d’arguments à longueur variable)
Les varargs permettent à une méthode d’accepter zéro ou plusieurs paramètres du même type.
Syntaxe :
void printNames(String... names)
Règles :
- Une méthode peut avoir un seul paramètre varargs.
- Il doit être le dernier paramètre dans la liste.
-
Les varargs sont traités comme un tableau à l’intérieur de la méthode.
-
Exemple :
void show(int x, String... values) {
System.out.println(values.length);
}
show(10); // length = 0
show(10, "A"); // length = 1
show(10, "A", "B", "C"); // length = 3
Important
Les varargs et les tableaux participent à la surcharge des méthodes. La résolution de la surcharge peut devenir ambiguë.
14.6 Méthodes statiques, variables statiques et initialiseurs statiques
En Java, le mot-clé static marque des éléments qui appartiennent à la classe elle-même, et non aux instances individuelles.
Cela signifie :
- Ils sont chargés une seule fois en mémoire lorsque la classe est chargée pour la première fois par la JVM.
- Ils sont partagés entre toutes les instances.
- Ils peuvent être accessibles sans créer d’objet de la classe.
Les membres statiques sont stockés dans la method area de la JVM (mémoire au niveau de la classe), tandis que les membres d’instance vivent dans le heap.
14.6.1 Variables statiques (Variables de classe)
Une variable statique est une variable définie au niveau de la classe et partagée par toutes les instances.
Caractéristiques :
- Créées lorsque la classe est chargée.
- Existent même si aucune instance de la classe n’est créée.
- Tous les objets voient la même valeur.
-
Peuvent être marquées
final,volatileoutransient. -
Exemple :
public class Counter {
static int count = 0; // partagée par toutes les instances
int id; // variable d’instance
public Counter() {
count++;
id = count; // chaque instance obtient un id unique
}
}
14.6.2 Méthodes statiques
Une méthode statique appartient à la classe, et non à une instance d’objet.
Règles :
- Elles peuvent être appelées en utilisant le nom de la classe :
ClassName.method(). - Elles ne peuvent pas accéder directement aux variables ou méthodes d’instance, mais uniquement via une instance de la classe.
- Elles ne peuvent pas utiliser
thisousuper. - Elles sont couramment utilisées pour :
-
- des méthodes utilitaires (ex.
Math.sqrt())
- des méthodes utilitaires (ex.
-
- des méthodes de fabrique
-
- des comportements globaux qui ne dépendent pas de l’état d’instance
-
Exemple :
public class MathUtil {
static int square(int x) { // méthode statique
return x * x;
}
void instanceMethod() {
// System.out.println(count); // OK : accès à une variable statique
// square(5); // OK : méthode statique accessible
}
}
Erreurs courantes :
// ❌ Erreur de compilation : une méthode d’instance ne peut pas être appelée directement dans un contexte statique
static void go() {
run(); // run() est une méthode d’instance !
}
void run() { }
14.6.3 Blocs d’initialisation statique
Les blocs d’initialisation statique permettent d’exécuter du code une seule fois, lorsque la classe est chargée.
Syntaxe :
static {
// logique d’initialisation
}
Utilisation :
- initialisation de variables statiques complexes
- exécution d’un setup au niveau de la classe
-
exécution de code qui doit être exécuté exactement une fois
-
Exemple :
public class Config {
static final Map<String, String> settings = new HashMap<>();
static {
settings.put("mode", "production");
settings.put("version", "1.0");
System.out.println("Static initializer executed");
}
}
Important
Les blocs d’initialisation statique s’exécutent une seule fois, dans l’ordre où ils apparaissent, avant main() et avant qu’une méthode statique ne soit appelée.
14.6.4 Ordre d’initialisation (Statique vs. Instance)
( Se référer au chapitre : 15. Class Loading, Initialization, and Object Construction )
14.6.5 Accès aux membres statiques
14.6.5.1 Utiliser le nom de la classe
Math.sqrt(16);
MyClass.staticMethod();
14.6.5.2 Via une référence d’instance
MyClass obj = new MyClass();
obj.staticMethod();
14.6.6 Statique et héritage
Les méthodes statiques :
- peuvent être masquées, pas redéfinies
- le binding est à la compilation, pas à l’exécution
-
sont accessibles selon le type de la référence, et non le type de l’objet
-
Exemple :
class A {
static void test() { System.out.println("A"); }
}
class B extends A {
static void test() { System.out.println("B"); }
}
A ref = new B();
ref.test(); // affiche "A" — binding statique !
Note
Règle clé : les méthodes statiques utilisent le type de la référence, et non le type de l’objet.
14.6.7 Pièges courants
- Tenter de référencer une variable ou une méthode d’instance depuis un contexte statique.
- Supposer que les méthodes statiques sont redéfinies → elles sont masquées.
- Appeler une méthode statique via une référence d’instance (légal mais déroutant).
- Confondre l’ordre d’initialisation des éléments statiques et des éléments d’instance.
- Oublier que les variables statiques sont partagées entre tous les objets.
- Ignorer que les initialiseurs statiques s’exécutent une seule fois, dans l’ordre de déclaration.