Algorithmique et programmation pour l'ingénieur¶
De la Procédure à l'Objet : Restitution du TD machine 1
Contexte et Systèmes Embarqués¶
Origines du langage¶
- 1991 (Projet "Oak") : Initialement conçu par James Gosling (Sun Microsystems) pour la télévision interactive et les décodeurs. Les contraintes étaient la fiabilité et la portabilité matérielle.
- 1995 (Lancement) : Le langage est réorienté vers le développement applicatif et web, porté par la promesse d'une exécution agnostique au système d'exploitation.
- 2010 : Rachat de Sun Microsystems par Oracle, qui assure depuis le développement et la maintenance de la plateforme.
Déploiement dans l'industrie¶
Java constitue aujourd'hui l'un des standards majeurs de l'industrie logicielle.
- Plus de 45 milliards de Machines Virtuelles Java (JVM) actives recensées.1
- Largement majoritaire dans les systèmes d'information critiques (secteur bancaire, assurances, aérospatial).
- Présence transversale : du back-end distribué (Hadoop, Kafka) jusqu'aux systèmes embarqués à fortes contraintes.
Pertinence pour l'informatique embarquée¶
Malgré sa réputation de langage de haut niveau, Java conserve des caractéristiques adaptées au matériel :
Avantages architecturaux
- Abstraction matérielle : Le code compilé s'exécute de manière identique sur différentes architectures de processeurs.
- Sécurité d'exécution : L'absence de manipulation directe des adresses mémoire prévient les dépassements de tampon (buffer overflows) fréquents en C.
Le profil Java Card¶
L'une des implémentations les plus répandues au monde concerne les environnements ultra-contraints.
- Principe : Un sous-ensemble de Java optimisé pour les microcontrôleurs disposant de quelques kilo-octets de mémoire (RAM et ROM).
- Applications : Cartes bancaires (standard EMV), cartes SIM, passeports biométriques.
- Volume : Environ 6 milliards de composants Java Card sont produits chaque année.2
Accélération matérielle¶
L'interprétation logicielle du code Java consomme des ressources CPU. L'industrie matérielle a développé des solutions spécifiques :
- Processeurs natifs (picoJava) : Architectures matérielles où le jeu d'instructions du processeur correspond directement au code binaire Java.
- Coprocesseurs (ARM Jazelle) : Intégration matérielle permettant d'exécuter une majorité d'instructions Java directement sur le processeur, réduisant l'empreinte mémoire et la consommation énergétique.
Modèle de Compilation¶
Rappel : La compilation native (C/C++)¶
En C, la chaîne de compilation est liée au processeur cible.
- Le code source (
main.c) est traité par le compilateur. - Le résultat est un fichier contenant du code machine natif (instructions binaires spécifiques à l'architecture, par exemple x86 ou ARM).
Contrainte technique
Un binaire compilé pour une architecture ARM sous Linux ne peut pas être exécuté sur un processeur x86 sous Windows. Une recompilation des sources est systématiquement requise.
Le modèle d'exécution Java (WORA)¶
Le paradigme "Write Once, Run Anywhere" s'appuie sur une architecture en deux phases :
- Compilation statique (
javac) : Le fichier.javaest traduit en un format intermédiaire indépendant : le Bytecode (.class). - Interprétation dynamique (JVM) : La Machine Virtuelle Java lit ce Bytecode lors de l'exécution et le traduit en instructions natives adaptées à l'hôte.
Bénéfice
Le fichier .class est portable. La gestion des spécificités matérielles est déléguée à la JVM locale.
Analyse Syntaxique Comparée¶
Point de départ : Le programme minimal¶
Différence 1 : Encapsulation obligatoire¶
Les fonctions et les variables globales peuvent être définies de manière autonome dans n'importe quel fichier source.
Tout élément doit structurellement appartenir à une Classe. L'existence de méthodes isolées est proscrite par le compilateur. C'est pourquoi la méthode main est incluse dans le bloc class.
Différence 2 : Signature du point d'entrée¶
public: Visibilité nécessaire pour que la JVM extérieure puisse l'invoquer.static: Indique que la méthode dépend de la classe entière, et ne nécessite pas l'instanciation préalable d'un objet.void: La remontée de code d'erreur vers l'OS est gérée différemment (viaSystem.exit()).String[] args: Le langage propose un véritable objet tableau contenant des objets chaînes de caractères.
Différence 3 : Gestion des types de base¶
Tableaux
En C, un tableau passé en argument se dégrade en simple pointeur. Sa dimension est perdue. En Java, le tableau est une entité complexe qui maintient sa propre taille de manière sécurisée (tableau.length).
Chaînes de caractères
La gestion manuelle de la mémoire et du caractère de fin de chaîne ('\0') disparaît au profit du type objet String, immuable et nativement outillé pour la concaténation (opérateur +).
Concepts Fondamentaux de la POO¶
Transition conceptuelle : Struct vs Classe¶
Les structures de données sont indépendantes des fonctions de traitement :
Instanciation : new remplace l'allocation¶
La création d'une occurrence physique de la classe en mémoire est l'instanciation.
Manipulation par Références¶
La notion de pointeur brut est absente en Java afin de prévenir les erreurs de segmentation et les accès illicites.
- Les objets sont manipulés via des Références.
- La référence est un identifiant sécurisé géré par la machine virtuelle.
- L'accès aux membres de l'objet se fait uniformément via l'opérateur de résolution (le point
.). L'opérateur flèche (->) n'existe pas. - Une référence non initialisée a pour valeur
null.
Principe d'Encapsulation¶
L'accès direct aux attributs pose un problème de robustesse (données altérables sans contrôle).
L'encapsulation consiste à restreindre la visibilité des données internes et à imposer l'utilisation d'interfaces programmatiques (méthodes) pour toute modification.
Mise en œuvre de l'Encapsulation¶
- Verrouillage de l'attribut avec
private. - Implémentation d'accesseurs (Getters) et de mutateurs (Setters) intégrant une logique de validation.
public class Capteur {
private double valeur;
public double getValeur() { return valeur; }
public void setValeur(double v) {
if(v >= 0) {
valeur = v; // La modification est filtrée
}
}
}
Portée des méthodes : Instance et Classe¶
- Méthode d'instance : Nécessite la création préalable d'un objet. Elle manipule l'état interne spécifique de cet objet.
Exemple :
capteur1.setValeur(10); - Méthode de classe (
static) : Rattachée à la définition de la classe, indépendante de toute instance. Utilisée pour des routines utilitaires ou globales. Exemple :Math.abs(-5);
Constructeurs et Initialisation¶
Le besoin d'initialisation¶
Dans les exemples précédents, l'objet est créé vide, puis configuré via des appels successifs.
Pour forcer un objet à posséder un état cohérent dès le moment de son allocation en mémoire, Java introduit la notion de Constructeur.
Définition du Constructeur¶
Le constructeur est un sous-programme spécifique appelé automatiquement lors de l'instruction new.
- Il porte exactement le nom de la classe.
- Il ne possède aucun type de retour (pas même
void).
public class Capteur {
private String reference;
// Le Constructeur
public Capteur(String refInitiale) {
reference = refInitiale;
}
}
// Instanciation directe : Capteur c = new Capteur("TEMP_01");
Résolution de portée : Le mot-clé this¶
Il est courant et recommandé de nommer les paramètres d'un constructeur de la même manière que les attributs visés. Pour lever l'ambiguïté lexicale (le paramètre "masquant" l'attribut), on utilise le mot-clé this, qui désigne la référence de l'objet en cours de manipulation.
public class Capteur {
private String reference;
public Capteur(String reference) {
// "this.reference" cible l'attribut de l'objet
// "reference" cible l'argument reçu
this.reference = reference;
}
}
Modèle Mémoire et Cycle de Vie¶
Espace d'exécution : La Pile (Stack)¶
L'organisation de la mémoire repose sur une dichotomie stricte.
Fonctionnement de la Pile
- Architecture LIFO gérant les contextes d'appel de méthodes.
- Stocke les variables locales de type primitif (
int,double). - Stocke les références contenant les adresses des objets.
- Libération immédiate à la sortie du bloc englobant.
Espace d'exécution : Le Tas (Heap)¶
Fonctionnement du Tas
- Zone de mémoire allouée dynamiquement pour les structures complexes.
- Espace exclusif de résidence pour tous les objets créés via l'opérateur
new. - Persistance décorrélée de la portée d'exécution des méthodes d'origine.
Comportement lors de la copie¶
La distinction entre référence et objet implique une vigilance particulière lors de l'affectation.
Capteur a = new Capteur("A");
Capteur b = a; // Affectation de la référence
b.setReference("B");
System.out.println(a.getReference()); // Affiche "B"
Les variables a et b situées dans la pile pointent vers la même adresse mémoire dans le tas. L'objet physique n'est pas dupliqué.
Gestion mémoire : C vs Java¶
La gestion manuelle de la mémoire en C/C++ expose à plusieurs risques architecturaux :
- Fuite mémoire : Omission de l'appel à
free()entraînant un épuisement des ressources allouables. - Pointeurs fantômes (Dangling Pointers) : Utilisation d'une adresse mémoire pointant vers une zone préalablement libérée.
- Double libération : Entraînant une corruption de l'allocateur système.
Automatisation : Le Garbage Collector¶
L'environnement Java supprime la libération explicite de mémoire (free et delete n'existent pas).
- Un processus système, le Garbage Collector (ramasse-miettes), analyse périodiquement le graphe des références.
- Tout objet résidant dans le tas n'étant plus pointé par aucune référence active est identifié comme obsolète.
- La zone mémoire correspondante est automatiquement récupérée et compactée sans intervention du développeur.
L'Objet String et l'Immuabilité¶
La particularité de l'Objet String¶
Contrairement au C (char*), Java propose la classe String. Sa caractéristique majeure est son immuabilité.
Qu'est-ce que l'immuabilité ?
Une fois qu'un objet String est créé dans le Tas (Heap), son contenu ne peut plus jamais être modifié.
- Sécurité : Une chaîne passée à une méthode critique (ex: ouverture de fichier) ne peut pas être altérée à votre insu.
- Optimisation : Java utilise un "String Pool" pour réutiliser les chaînes identiques en mémoire.
Le piège de la concaténation (l'opérateur +)¶
Puisqu'une String est immuable, que se passe-t-il lorsque l'on utilise l'opérateur + pour la modifier ?
Impact mémoire
Java ne modifie pas l'objet "Bonjour". Il crée un nouvel objet contenant "Bonjour le monde", puis redirige la référence texte vers lui. L'ancien objet devient obsolète et sera détruit par le Garbage Collector.
Dans une boucle (ex: 1000 itérations), cela crée 1000 objets inutiles, inondant le Tas et forçant le Garbage Collector à travailler intensément !
La solution de performance : StringBuilder¶
Pour manipuler intensivement du texte (boucles, assemblages complexes), Java fournit une classe dédiée et muable (modifiable) : StringBuilder.
// 1. Création d'un constructeur de chaîne (1 seul objet)
StringBuilder constructeur = new StringBuilder("Bonjour");
// 2. Modification de l'objet existant (Pas de nouvel objet !)
for(int i = 0; i < 3; i++) {
constructeur.append(" !");
}
// 3. Conversion finale en String immuable
String resultat = constructeur.toString();
Règle d'or
Utilisez String et + pour des assemblages simples (1 ou 2 variables). Utilisez StringBuilder dès qu'il y a une boucle.
L'optimisation silencieuse du compilateur¶
Les compilateurs Java modernes (javac) sont intelligents. Ils optimisent automatiquement les concaténations simples en utilisant StringBuilder (ou StringConcatFactory depuis Java 9).
Concaténation sur une ligne (Optimisée)
Le cas critique (Corner case) : La boucle
Ici, le compilateur crée un nouveauStringBuilder à chaque itération de la boucle. L'optimisation globale est inopérante, et le Tas (Heap) est tout de même inondé d'objets temporaires.
Conclusion et Perspectives¶
Bilan de la transition¶
- Architecture : Découplage de la compilation matérielle via le Bytecode et la JVM, assurant portabilité et sécurité.
- Syntaxe consolidée : Disparition de l'arithmétique de pointeurs au profit d'un système de références strict.
- Encapsulation : Architecture de défense des données internes.
- Gestion du cycle de vie : Création explicite par
newet constructeurs, libération implicite par le Garbage Collector.
Programme de la prochaine séance¶
La programmation Objet offre des mécanismes avancés pour factoriser le code et modéliser des systèmes complexes. Concepts au programme :
- L'Héritage : Créer de nouvelles classes basées sur des modèles existants (Ex: Dériver un
CapteurThermiqued'unCapteur). - Le Polymorphisme : Traiter des objets de natures différentes au travers d'un prisme commun.
- Classes Abstraites et Interfaces : Définir des contrats logiciels stricts entre les différentes briques de votre application.
Page rédigée à l'aide de Gemini (pas par Gemini)