🚀 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` (ou le type borné). C’est pourquoi on ne peut pas faire `new T()` ou `instanceof T`. Le type générique n’existe plus au Runtime. #### 6.4.2. PECS (Producer Extends, Consumer Super) Règle d’or pour les jokers (`?`). – `? extends T` (Producer) : Si vous voulez **lire** (get) des T d’une liste. – `? super T` (Consumer) : Si vous voulez **écrire** (add) des T dans une liste. “`java // Accepte une liste de Number ou d’enfants (Integer, Double) public void lireNombres(List extends Number> liste) { Number n = liste.get(0); // OK // liste.add(12); // 🛑 INTERDIT ! On ne sait pas si c’est une liste de Double } // Accepte une liste d’Integer ou de parents (Number, Object) public void ecrireEntiers(List super Integer> liste) { liste.add(42); // OK // Integer n = liste.get(0); // 🛑 INTERDIT ! Ça pourrait être un Object } “`