Aller au contenu principal

Classes internes et lambdas

Notions théoriques​

Classes internes (inner classes)​

Une classe interne est une classe déclarée à l'intérieur d'une autre classe. Elle a accès à tous les membres (y compris private) de la classe externe.

public class Personnage {
private String nom;
private int pointsDeVie;

// Classe interne : accès à nom et pointsDeVie
class Journal {
void enregistrer(String action) {
System.out.println("[" + nom + "] " + action + " (PV: " + pointsDeVie + ")");
}
}
}
info

Une instance de classe interne est toujours liée à une instance de la classe externe. On ne peut pas créer new Journal() sans une instance de Personnage.

Classes statiques imbriquées​

Une classe statique imbriquée (static class) n'a pas besoin d'une instance de la classe externe. Elle n'accède pas aux membres non-statiques.

public class Combat {
private List<Personnage> participants;

// Classe statique : peut être utilisée sans instance de Combat
public static class Configuration {
int nbrTours = 10;
boolean autoSoin = false;
}
}

// Utilisation :
Combat.Configuration config = new Combat.Configuration();
config.nbrTours = 5;

Classes anonymes (avant Java 8)​

Une classe anonyme est une classe sans nom, créée et instanciée en une seule expression. Courante avant Java 8 pour implémenter des interfaces à la volée.

import java.util.Comparator;

// Classe anonyme implémentant Comparator<Personnage>
Comparator<Personnage> parForce = new Comparator<Personnage>() {
@Override
public int compare(Personnage a, Personnage b) {
return b.getForce() - a.getForce(); // ordre décroissant
}
};

guerriers.sort(parForce);

Lambdas (Java 8+) : la syntaxe concise​

Une lambda est une façon concise d'écrire une classe anonyme qui implémente une interface fonctionnelle (une interface avec une seule méthode abstraite).

// Comparateur avec classe anonyme (verbeux)
Comparator<Personnage> parForceVerbeux = new Comparator<Personnage>() {
@Override
public int compare(Personnage a, Personnage b) {
return b.getForce() - a.getForce();
}
};

// Même chose avec une lambda (concis)
Comparator<Personnage> parForce = (a, b) -> b.getForce() - a.getForce();

La syntaxe lambda : (paramètres) -> expression ou (paramètres) -> { instructions; }.

@FunctionalInterface​

Une interface fonctionnelle ne contient qu'une seule méthode abstraite. L'annotation @FunctionalInterface demande au compilateur de vérifier cette contrainte.

@FunctionalInterface
public interface CalculDegats {
int calculer(int force, int coefficient);
}

// Utilisation avec une lambda :
CalculDegats attaquePhysique = (force, coeff) -> force * coeff;
CalculDegats attaqueMagique = (force, coeff) -> force * coeff + 10;

System.out.println(attaquePhysique.calculer(18, 2)); // 36
System.out.println(attaqueMagique.calculer(12, 3)); // 46

Trier une liste avec une lambda​

import java.util.Collections;
import java.util.List;

List<Personnage> equipe = new ArrayList<>();
equipe.add(new Guerrier("Thor", 120, 18, "épée"));
equipe.add(new Mage("Elara", 80, 12, 60));
equipe.add(new Guerrier("Brunhild", 130, 22, "hache"));

// Trier par force décroissante avec une lambda
equipe.sort((a, b) -> b.getForce() - a.getForce());

// Avec une méthode encore plus lisible :
equipe.sort(Comparator.comparingInt(Personnage::getForce).reversed());

Références de méthodes​

Une référence de méthode (::) est une lambda encore plus courte quand on appelle simplement une méthode existante :

equipe.forEach(System.out::println); // équivalent à : p -> System.out.println(p)
equipe.forEach(Personnage::attaquer); // référence de méthode d'instance

Transition vers Java Expert​

Les lambdas ouvrent la porte à la programmation fonctionnelle en Java : streams, map(), filter(), reduce()... Ces sujets seront approfondis dans le module Java_Expert.

Exemple pratique​

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class Main {
// Interface fonctionnelle pour calculer des dégâts
@FunctionalInterface
interface CalculDegats {
int calculer(int force);
}

public static void main(String[] args) {
List<Personnage> equipe = new ArrayList<>();
equipe.add(new Guerrier("Thor", 120, 18, "épée runique"));
equipe.add(new Mage("Elara", 80, 12, 60));
equipe.add(new Guerrier("Brunhild", 130, 22, "hache de guerre"));
equipe.add(new Archer("Lyra", 90, 14, 20));

// 1. Afficher avec forEach + lambda
System.out.println("=== Équipe (ordre original) ===");
equipe.forEach(p -> System.out.println(p));

// 2. Trier par force décroissante avec lambda
equipe.sort((a, b) -> b.getForce() - a.getForce());
System.out.println("\n=== Équipe (triée par force décroissante) ===");
equipe.forEach(System.out::println); // référence de méthode

// 3. Trouver le plus fort avec stream + lambda
equipe.stream()
.max(Comparator.comparingInt(Personnage::getForce))
.ifPresent(p -> System.out.println("\nLe plus fort : " + p.getNom() + " (force: " + p.getForce() + ")"));

// 4. Compter les personnages vivants
long nbVivants = equipe.stream()
.filter(Personnage::estVivant)
.count();
System.out.println("Personnages vivants : " + nbVivants);

// 5. Interfaces fonctionnelles personnalisées
CalculDegats attaquePhysique = force -> force * 2;
CalculDegats attaqueMagique = force -> force * 3 + 10;

Personnage guerrier = equipe.get(0);
System.out.println("\nDégâts physiques de " + guerrier.getNom() + " : "
+ attaquePhysique.calculer(guerrier.getForce()));
System.out.println("Dégâts magiques simulés : "
+ attaqueMagique.calculer(guerrier.getForce()));

// 6. Classe anonyme (style pré-Java 8, pour comparaison)
Comparator<Personnage> parNom = new Comparator<Personnage>() {
@Override
public int compare(Personnage a, Personnage b) {
return a.getNom().compareTo(b.getNom());
}
};

equipe.sort(parNom);
System.out.println("\n=== Équipe (triée par nom) ===");
equipe.forEach(p -> System.out.println(p.getNom()));
}
}
info

equipe.stream().max(...).ifPresent(...) est un avant-goût des streams Java. Cette syntaxe fonctionnelle sera détaillée dans le module Java_Expert.

Test de mémorisation/compréhension​


Qu'est-ce qu'une interface fonctionnelle en Java ?


Quelle annotation vérifie qu'une interface est bien fonctionnelle ?


Quelle est la syntaxe d'une lambda qui prend deux Personnage et retourne la différence de leurs forces ?


Que fait System.out::println dans 'equipe.forEach(System.out::println)' ?


Quelle est la différence entre une classe interne et une classe statique imbriquée ?


Comment trier une liste 'equipe' de Personnage par force croissante avec une lambda ?


TP pour réfléchir et résoudre des problèmes​

Vous allez utiliser les lambdas pour trier, filtrer et traiter votre équipe de personnages.

Étape 1 — Trier l'équipe par force décroissante​

Utilisez sort() avec une lambda pour trier la liste equipe par force décroissante.


Bonne pratique - Lambdas courtes et lisibles

Une bonne lambda tient en une ligne. Si votre lambda dépasse 3-4 lignes, c'est le signe qu'elle devrait être une méthode nommée ou une classe à part entière. Les lambdas servent à exprimer une transformation simple, pas une logique complexe.

Étape 2 — Créer une interface fonctionnelle CalculDegats​

Créez l'interface CalculDegats avec @FunctionalInterface et instanciez-la avec deux lambdas différentes.


Bonne pratique - Nommer les lambdas pour réutilisation

Assigner une lambda à une variable nommée (attaquePhysique, attaqueMagique) permet de la réutiliser et de lui donner un sens. Une lambda anonyme passée directement dans une méthode est plus difficile à lire et à tester.

Étape 3 — Afficher l'équipe avec forEach et référence de méthode​

Utilisez equipe.forEach(System.out::println) pour afficher tous les personnages en profitant du toString() redéfini.


Bonne pratique - Préférer les références de méthodes aux lambdas triviales

System.out::println est plus lisible que p -> System.out.println(p) car il exprime directement l'intention : "afficher chaque élément". Les références de méthodes rendent le code plus proche d'un texte naturel.

📌 Une solution