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 + ")");
}
}
}
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()));
}
}
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
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.
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.
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.
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.