🚀 Java Avancé : Méta-programmation, Fonctionnel & Secrets du Langage
Ce module est destiné à aller sous le capot de la JVM. Nous allons explorer comment manipuler le langage lui-même, écrire du code plus concis (Fonctionnel) et comprendre les mécanismes d’initialisation complexes.
6.1. Programmation Fonctionnelle (Lambdas & Streams)
Depuis Java 8, Java n’est plus seulement Orienté Objet, il est hybride fonctionnel.
6.1.1. Les Lambdas et @FunctionalInterface
Une lambda est une fonction anonyme. Elle ne peut être utilisée que si l’interface cible n’a qu’une seule méthode abstraite.
// Interface Fonctionnelle (SAM - Single Abstract Method)
@FunctionalInterface
interface Calculateur {
int operer(int a, int b);
}
// Utilisation classique (Classe Anonyme - Verbose)
Calculateur addition = new Calculateur() {
@Override
public int operer(int a, int b) { return a + b; }
};
// Utilisation Lambda (Syntaxe fléchée)
Calculateur multiplication = (a, b) -> a * b;
Les 4 Interfaces Fonctionnelles natives (Package java.util.function) :
À connaître par cœur pour ne pas réinventer la roue.
| Interface | Signature | Rôle | Exemple |
|---|---|---|---|
| `Predicate | |||
|T -> boolean|Tester une condition|x -> x > 10` |
|||
| `Consumer | |||
|T -> void|Effectuer une action (effet de bord)|System.out::println` |
|||
| `Supplier | |||
|() -> T|Fournir une valeur|() -> new Player()` |
|||
Function<T,R> |
T -> R |
Transformer une donnée | user -> user.getName() |
6.1.2. L’API Stream
Les Streams permettent de traiter des collections de manière déclarative (SQL-like).
List
<String> noms = List.of("Alice", "Bob", "Charlie", "David");
List
<String> resultat = noms.stream()
.filter(nom -> nom.length() > 4) // Garde Alice, Charlie, David
.map(String::toUpperCase) // Transforme en Majuscules
.sorted() // Trie
.toList(); // Collecte (Java 16+)
// Résultat : [ALICE, CHARLIE, DAVID]
Attention : Un Stream est à usage unique. Une fois “consommé” (par
.toList(),.forEach(), etc.), il est fermé.
6.2. La Réflexivité (Reflection API)
La Réflexivité permet à un programme d’examiner et de modifier son propre comportement à l’exécution. C’est la base de tous les frameworks modernes (Spring, Hibernate, Jackson).
6.2.1. L’objet Class<?>
Tout est objet, même les classes. L’objet Class contient les métadonnées.
Class<?> clazz = unObjet.getClass();
// ou
Class<?> clazz = MaClasse.class;
System.out.println("Nom : " + clazz.getSimpleName());
System.out.println("Méthodes : " + Arrays.toString(clazz.getDeclaredMethods()));
6.2.2. Violer l’Encapsulation (setAccessible)
Avec la réflexion, private n’existe plus vraiment (sauf restriction module Java 9+).
public class Secret {
private String motDePasse = "1234";
}
// --- Hack via Reflection ---
Secret s = new Secret();
Field champ = s.getClass().getDeclaredField("motDePasse");
champ.setAccessible(true); // 🔓 On force l'accès !
String valeur = (String) champ.get(s);
System.out.println("Hacké : " + valeur); // Affiche 1234
Usage : Utile pour écrire des sérialiseurs JSON génériques ou des frameworks de Test, mais à éviter dans le code métier (lent et dangereux).
6.3. Les “Secrets” et Pièges du Langage
6.3.1. Héritage d’Enum et Méthodes Abstraites
On ne peut pas faire extends sur un Enum, mais un Enum peut avoir des comportements polymorphiques internes.
public enum Operation {
PLUS {
@Override double apply(double x, double y) { return x + y; }
},
MOINS {
@Override double apply(double x, double y) { return x - y; }
};
// Méthode abstraite forcée pour chaque constante
abstract double apply(double x, double y);
}
// Usage
double res = Operation.PLUS.apply(10, 5);
6.3.2. Blocs d’Initialisation (Static vs Instance)
L’ordre d’exécution est crucial et souvent source de questions d’entretien.
public class Demo {
// 1. Bloc Statique : Exécuté UNE SEULE FOIS au chargement de la classe
static {
System.out.println("A. Bloc Static (Chargement Classe)");
// Utile pour initialiser des Map statiques complexes
}
// 2. Bloc d'Instance : Exécuté AVANT le constructeur à CHAQUE new()
{
System.out.println("B. Bloc Instance");
}
public Demo() {
System.out.println("C. Constructeur");
}
}
// new Demo(); new Demo();
// Affiche : A, B, C, B, C
6.3.3. Litéraux, Unicode et Encodage
Java gère nativement l’Unicode. Vous pouvez coder avec des caractères échappés.
// Identifiants en unicode (Valide mais déconseillé !)
String \u006E\u006F\u006D = "Jean"; // équivalent à String nom = "Jean";
// Le caractère NULL
char c = '\u0000';
// Les Text Blocks (Java 15+) : Utile pour SQL ou JSON
String json = """
{
"nom": "Jean",
"age": 30
}
""";
6.4. Généricité Avancée (Wildcards & Erasure)
6.4.1. Type Erasure (Effacement)
À la compilation, `List
` devient `List