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