Oracle JDK : Version commerciale avec support à long terme
OpenJDK : Version open source utilisée par de nombreuses distributions
🔤 Les Bases de Java
Types de Variables
Catégorie
Type
Taille
Description
Entiers
byte
1 octet
-128 à 127
short
2 octets
-32,768 à 32,767
int
4 octets
~-2 milliards à 2 milliards
long
8 octets
Très grands nombres
Booléen
boolean
1 octet
true ou false
Caractère
char
2 octets
Un caractère Unicode
Décimaux
float
4 octets
Précision simple
double
8 octets
Précision double
Structures de Contrôle
Conditions
// Si condition ET conditionif (true && true) {
// Code à exécuter
}
// Sinon si condition OU conditionelse if (true || false) {
// Code alternatif
}
// Sinonelse {
// Code par défaut
}
Boucles
// Boucle Tant quewhile (condition) {
// Code répété tant que condition est vraie
}
// Boucle Do-While (exécutée au moins une fois)do {
// Code exécuté
} while (condition);
// Boucle Pourfor (int i = 0; i < 10; i++) {
// Code répété 10 fois
}
Tableaux
// Déclaration et initialisationint[] monTableau = newint[5]; // Tableau de 5 entiers// Initialisation avec valeursint[] nombres = {1, 2, 3, 4, 5};
// Parcours d'un tableaufor (int i = 0; i < monTableau.length; i++) {
System.out.println(monTableau[i]);
}
⚙️ Programmation Procédurale
📖 Définition :
La programmation procédurale organise le code en séquences d'instructions et en fonctions réutilisables. C'est l'approche la plus directe et linéaire.
Fonctions
Une fonction est un bloc d'instructions réutilisable qu'on peut appeler plusieurs fois dans un programme. Elle peut :
Accepter des paramètres
Retourner une valeur d'un certain type
Ou ne rien retourner (void)
// Syntaxe générale d'une fonctionTypeDeRetour nomDeLaFonction(TypeParametre1 parametre1, TypeParametre2 parametre2) {
// Corps de la fonctionreturn valeurDeTypeTypeDeRetour;
}
// Fonction sans retourvoid fonctionSansRetour(TypeParametre parametre) {
// Corps de la fonction
}
Exemple : Rendu de Monnaie
public classrendumonnaie {
public static void main(String[] args) {
double montant = 0.97;
double[] pieces = {100, 50, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05, 0.02, 0.01};
int[] rendu = newint[pieces.length];
for (int i = 0; i < pieces.length; i++) {
while (montant >= pieces[i]) {
montant -= pieces[i];
rendu[i]++;
}
}
System.out.println("Rendu de monnaie :");
for (int i = 0; i < pieces.length; i++) {
if (rendu[i] > 0) {
System.out.println(rendu[i] + " x " + pieces[i] + "€");
}
}
}
}
⚠️ Limites de l'approche procédurale :
Difficile à maintenir pour de gros projets
Code moins réutilisable
Pas de notion d'encapsulation des données
🎭 Programmation Orientée Objet (POO)
📖 Définition :
La POO est un paradigme qui utilise des "objets" pour représenter des données et des comportements. Les objets sont des instances de classes qui définissent leurs caractéristiques.
Les Classes et Objets
// Définition d'une classeclassNomDeLaClasse {
// Attributs (propriétés)TypeAttribut1 attribut1;
TypeAttribut2 attribut2;
// Constructeur
NomDeLaClasse(TypeAttribut1 param1, TypeAttribut2 param2) {
this.attribut1 = param1;
this.attribut2 = param2;
}
// Méthodes (comportements)TypeRetour methode1(TypeParametre parametre) {
// Corps de la méthodereturn valeurDeTypeTypeRetour;
}
}
L'Héritage
💡 L'héritage permet à une classe de hériter des attributs et méthodes d'une autre classe. Utilisez le mot-clé extends.
public classEquipement {
int[] adresseIp = newint[4];
String localisation;
void demarrer() {
System.out.println("L'équipement démarre...");
}
}
public classRouteurextendsEquipement {
String bonjour;
// Hérite de tous les attributs et méthodes d'Equipement
}
Classes et Méthodes Abstraites
// Classe abstraite : ne peut pas être instanciée directementabstract classClasseAbstraite {
// Méthode abstraite : pas de corps, doit être implémentéeabstractvoid methodeAbstraite();
}
classClasseConcrèteextendsClasseAbstraite {
@Overridevoid methodeAbstraite() {
System.out.println("Implémentation de la méthode abstraite");
}
}
✅ Avantages de la POO :
Encapsulation : Données et méthodes regroupées
Réutilisabilité : Héritage et polymorphisme
Maintenabilité : Code plus organisé et modulaire
Abstraction : Masque la complexité
🔄 Programmation Fonctionnelle
📖 Définition :
La programmation fonctionnelle traite le calcul comme l'évaluation de fonctions mathématiques. Elle privilégie l'immutabilité et les fonctions pures.
Interfaces Fonctionnelles en Java
// Les principales interfaces fonctionnelles :// Supplier → Retourne un type, attend 0 paramètre
Supplier<String> supplier = () -> "Bonjour";
// Consumer → Retourne rien, attend 1 paramètre
Consumer<String> consumer = str -> System.out.println(str);
// Function → Retourne un type, attend 1 paramètre
Function<String, Integer> function = str -> str.length();
// Predicate → Retourne un boolean, attend 1 paramètre
Predicate<Integer> predicate = num -> num > 10;
// BiFunction → Retourne un type, attend 2 paramètres
BiFunction<Integer, Integer, Integer> biFunction = (a, b) -> a + b;
Streams et Opérations Fonctionnelles
List<Integer> listeNombres = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// map : Transforme chaque élément
listeNombres.stream()
.map(nombre -> nombre * nombre)
.forEach(System.out::println);
// filter : Garde les éléments qui respectent une condition
listeNombres.stream()
.filter(nombre -> nombre > 5)
.forEach(System.out::println);
// reduce : Agrège tous les élémentsint sum = listeNombres.stream()
.reduce(0, Integer::sum);
✅ Avantages de la programmation fonctionnelle :
Concision : Code plus court et expressif
Immutabilité : Pas d'effets de bord inattendus
Composabilité : Facilite la combinaison d'opérations
Parallélisation : Plus facile à paralléliser
⚡ Programmation Réactive
📖 Définition :
La programmation réactive est un paradigme de programmation orienté vers les flux de données asynchrones et la propagation automatique du changement. Elle se base sur le pattern Observable/Observer.
Le Manifeste Réactif
Les applications réactives doivent être :
🎯 Réactives
Répondent rapidement aux événements et requêtes avec une faible latence.
💪 Résilientes
Restent fonctionnelles face aux erreurs et pannes.
📈 Élastiques
S'adaptent dynamiquement à la demande en ajustant les ressources.
📨 Orientées Messages
Utilisent la communication asynchrone basée sur les messages.
Concepts Clés
Flux de Données Asynchrones
Les flux constituent l'épine dorsale de la programmation réactive. Ils permettent un flux continu de données entre composants, créant une expérience en temps réel.
Observable : Source qui émet des données
Observer : Consommateur qui s'abonne au flux
Opérateurs : Transforment et combinent les flux
Bibliothèques Réactives en Java
Bibliothèque
Description
Utilisation
RxJava
Extensions Réactives pour Java
Applications Android, microservices
Project Reactor
Framework réactif Spring
Spring WebFlux, applications web
Akka Streams
Traitement de flux avec Akka
Systèmes distribués, traitement de données
Reactive Streams
API standard Java 9+
Spécification commune, interopérabilité
Exemple avec RxJava
import io.reactivex.subjects.Subject;
import io.reactivex.subjects.ReplaySubject;
public classExempleReactif {
public static void main(String[] args) {
// Créer une source de données réactive
Subject<Integer> source = ReplaySubject.create();
// S'abonner aux événements de la source
source.subscribe(result ->
System.out.println("Valeur reçue : " + result)
);
// Émettre des données
source.onNext(1);
source.onNext(2);
System.out.println("Autre tâche exécutée");
// Continuer à émettre des données
source.onNext(3);
source.onNext(4);
}
}
// Sortie :
// Valeur reçue : 1
// Valeur reçue : 2
// Autre tâche exécutée
// Valeur reçue : 3
// Valeur reçue : 4
Opérateurs Réactifs Courants
// map : Transforme les éléments du flux
observable.map(x -> x * 2)
// filter : Filtre les éléments
observable.filter(x -> x > 10)
// flatMap : Transforme et aplatit les flux
observable.flatMap(x -> Observable.just(x, x * 2))
// merge : Combine plusieurs flux
Observable.merge(obs1, obs2)
// zip : Combine des flux en paires
Observable.zip(obs1, obs2, (a, b) -> a + b)
// debounce : Limite la fréquence d'émission
observable.debounce(300, TimeUnit.MILLISECONDS)
Comparaison Impératif vs Réactif
Approche Impérative
List<Integer> liste = Arrays.asList(1, 2, 3, 4, 5);
for (Integer num : liste) {
System.out.println("Nombre : " + num);
}
// Si on ajoute un élément après, il faut tout refaire
liste.add(6); // Ne fonctionne pas avec une liste immuable
Approche Réactive
Subject<Integer> flux = ReplaySubject.create();
// On s'abonne une fois au flux
flux.subscribe(num ->
System.out.println("Nombre : " + num)
);
// On peut continuer à émettre des données
flux.onNext(1);
flux.onNext(2);
flux.onNext(3);
// Et ajouter des données plus tard, l'observateur réagira automatiquement
flux.onNext(4);
flux.onNext(5);
✅ Avantages de la programmation réactive :
Asynchrone par nature : Gestion élégante des opérations non-bloquantes
Scalabilité : Utilisation optimale des ressources
Gestion d'erreurs : Propagation et traitement des erreurs intégrés
Composabilité : Facilite la création de flux complexes
Temps réel : Parfait pour les applications temps réel
⚠️ Points d'attention :
Courbe d'apprentissage plus élevée
Débogage plus complexe (flux asynchrones)
Consommation mémoire potentiellement plus élevée
Nécessite un changement de mentalité
Quand utiliser la programmation réactive ?
Applications avec beaucoup d'I/O (API, bases de données)
Interfaces utilisateur réactives (mises à jour en temps réel)
Systèmes nécessitant haute disponibilité et résilience
Traitement de flux de données en temps réel
Applications avec beaucoup d'événements asynchrones
🧩 Programmation Logique
📖 Définition :
La programmation logique est un paradigme déclaratif où le programme décrit des relations et des règles logiques plutôt que des séquences d'instructions. Le système déduit automatiquement les solutions.
Principe Fondamental
Programme = Théorie Logique
Exécution = Recherche de Preuve
On ne dit pas au programme "comment" résoudre le problème, mais "quoi" est le problème.
Prolog : Le Langage de Programmation Logique
Historique
1972 : Création de Prolog par Alain Colmerauer et Philippe Roussel à Marseille
1977 : Premier compilateur par D.H. Warren à Édimbourg
1980 : Reconnaissance comme langage IA de référence
Aujourd'hui : Standard ISO, utilisé en IA et traitement du langage naturel
Composants de Base
1. Les Faits
Déclarations de vérités simples sur le monde
% Déclarer que Fido est un chien
chien(fido).
% Déclarer le genre de personnes
masculin(gabriel).
masculin(raphael).
feminin(emma).
feminin(alice).
% Relations familiales
parent(emma, raphael).
parent(emma, alice).
parent(gabriel, raphael).
2. Les Règles
Définissent des relations conditionnelles
% Tout chien est un mammifère
mammifere(X) :- chien(X).
% X est le grand-parent de Z si X est parent de Y et Y est parent de Z
grand_parent(X, Z) :- parent(X, Y), parent(Y, Z).
% X est un ancêtre de Y si X est parent de Y
ancetre(X, Y) :- parent(X, Y).
% OU si X est parent de quelqu'un qui est ancêtre de Y (récursion)
ancetre(X, Y) :- parent(X, Z), ancetre(Z, Y).
3. Les Requêtes
Questions posées au système
% Est-ce que Fido est un chien ?
?- chien(fido).
true.
% Qui sont les personnes de sexe masculin ?
?- masculin(X).
X = gabriel ;
X = raphael.
% Qui est le parent de Raphael ?
?- parent(X, raphael).
X = emma ;
X = gabriel.
% Emma est-elle grand-parent de quelqu'un ?
?- grand_parent(emma, X).
Variables en Prolog
Majuscules : Variables (X, Y, Personne)
Minuscules : Atomes/constantes (gabriel, chien)
Underscore : Variable anonyme (_)
Mécanisme d'Unification et Backtracking
Unification
Processus de mise en correspondance de termes
% Prolog tente d'unifier les termes% X = 5 unifie X avec 5% parent(emma, X) cherche tous les X où emma est parent
Backtracking
Exploration systématique des solutions possibles
% Si une tentative échoue, Prolog revient en arrière% et essaie une autre possibilité
parent(jean, marie).
parent(jean, pierre).
parent(marie, sophie).
% Requête : ?- parent(jean, X), parent(X, Y).% 1. Essaie X = marie, trouve Y = sophie ✓% 2. Backtrack, essaie X = pierre, ne trouve pas de Y ✗
Exemple Complet : Arbre Généalogique
% Base de faits
homme(gabriel).
homme(raphael).
homme(leo).
femme(emma).
femme(alice).
femme(jade).
parent(gabriel, leo).
parent(emma, leo).
parent(gabriel, alice).
parent(emma, alice).
parent(leo, jade).
parent(alice, raphael).
% Règles dérivées
pere(X, Y) :- homme(X), parent(X, Y).
mere(X, Y) :- femme(X), parent(X, Y).
enfant(X, Y) :- parent(Y, X).
frere(X, Y) :-
homme(X),
parent(P, X),
parent(P, Y),
X \= Y.
soeur(X, Y) :-
femme(X),
parent(P, X),
parent(P, Y),
X \= Y.
grand_pere(X, Z) :- homme(X), parent(X, Y), parent(Y, Z).
grand_mere(X, Z) :- femme(X), parent(X, Y), parent(Y, Z).
% Requêtes possibles :% ?- pere(gabriel, X). % Qui sont les enfants de Gabriel ?% ?- grand_mere(emma, X). % Qui sont les petits-enfants d'Emma ?% ?- frere(X, alice). % Qui sont les frères d'Alice ?
Applications de la Programmation Logique
Domaine
Utilisation
Intelligence Artificielle
Systèmes experts, raisonnement automatique
Traitement du Langage
Analyse syntaxique, sémantique, grammaires
Bases de Données
Bases de données déductives, requêtes complexes
Planification
Ordonnancement, résolution de contraintes
CAO
Conception assistée, vérification de circuits
Programmation Logique en Java
Bien que Java ne soit pas un langage de programmation logique, il existe des bibliothèques :
tuProlog : Interpréteur Prolog en Java
JIProlog : Moteur Prolog pour applications Java
Frameworks de règles : Drools, Easy Rules
Exemple avec tuProlog en Java
import alice.tuprolog.*;
public classExempleProlog {
public static void main(String[] args) {
try {
// Créer un moteur Prolog
Prolog engine = new Prolog();
// Définir des faits et règles
engine.setTheory(new Theory(
"parent(jean, marie). " +
"parent(jean, pierre). " +
"parent(marie, sophie). " +
"grand_parent(X, Z) :- parent(X, Y), parent(Y, Z)."
));
// Poser une requête
SolveInfo info = engine.solve("grand_parent(jean, X).");
if (info.isSuccess()) {
System.out.println("Jean est grand-parent de : " +
info.getVarValue("X"));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
✅ Avantages de la programmation logique :
Déclarative : Focus sur "quoi" plutôt que "comment"
Concise : Règles simples pour problèmes complexes
Puissante : Déduction automatique de solutions
Maintenable : Ajout/suppression de règles facile
Vérifiable : Preuves mathématiques possibles
⚠️ Limites :
Performance parfois limitée sur gros volumes
Combinatoire peut exploser rapidement
Difficile de contrôler l'ordre d'exécution
Courbe d'apprentissage pour penser "déclaratif"
⚖️ Comparaison Complète des Paradigmes
Tableau Comparatif Global
Critère
Procédural
Orienté Objet
Fonctionnel
Réactif
Logique
Focus
Séquence d'instructions
Objets et interactions
Transformations
Flux de données
Relations logiques
Organisation
Fonctions
Classes et objets
Fonctions pures
Observables/Flux
Faits et règles
État
Variables mutables
Attributs d'objets
Immutable
Flux de changements
Base de faits
Exécution
Séquentielle
Appels de méthodes
Évaluation lazy
Asynchrone
Déduction
Complexité
Simple
Moyenne
Moyenne
Élevée
Élevée
Maintenance
Difficile (gros projets)
Bonne
Excellente
Bonne
Excellente
Performance
Très bonne
Bonne
Bonne
Excellente (I/O)
Variable
Quand Utiliser Chaque Paradigme ?
⚙️ Procédural
Utilisez quand :
Scripts simples et rapides
Prototypes
Algorithmes séquentiels
Petits projets
🎭 Orienté Objet
Utilisez quand :
Modélisation du monde réel
Gros projets
Code réutilisable
Maintenance long terme
🔄 Fonctionnel
Utilisez quand :
Traitement de collections
Transformations de données
Parallélisme
Code sans effets de bord
⚡ Réactif
Utilisez quand :
Applications temps réel
Beaucoup d'I/O asynchrones
UI réactives
Microservices
🧩 Logique
Utilisez quand :
Systèmes experts
Résolution de contraintes
Traitement du langage
Raisonnement automatique
Combinaison des Paradigmes
💡 En Pratique :
Les meilleurs programmes modernes combinent souvent plusieurs paradigmes !