Les bases de Java (Packages, Interfaces, Layouts, Exceptions, AWT, Swing, listeners, JavaBeans, factory, Applet, ...)

posté par Anthony Heukmes le 11-05-2007

Vous trouverez sur cette page mes réponses aux questions préparatoires à un examen sur les bases de Java (2ème BAC, Inpres en 2005).

  1. Expliquer pourquoi les deux grandes qualités attribuées au langage Java sont la portabilité et la sécurité. En particulier, en quoi des classes comme System ou Layout, ou encore des interfaces comme Cloneable ou Serializable, contribuent-ils à ces aspects ?
  2. Décrire les principaux programmes exécutables que l'on utilise couramment pour développer, déployer et utiliser des applications Java selon J2SE. En particulier, qu'est-ce que le JRE (synonyme de JDK ?) et tous ces programmes exécutables en font-ils partie ?
  3. Expliquer la notion d'interface en Java. Quelles sont les analogies et les différences avec la notion de classe abstraite ? Illustrer en quoi l'utilisation des interfaces permet une programmation générique. Combien de méthodes trouve-t-on au minium dans un interface ?
  4. Expliquer le concept de package en Java et comment le compilateur et l'interpréteur utilisent une classe d'un package donné. En particulier, l'accès package est-il suffisant pour pouvoir utiliser une classe définie dans une librairie Java définie dans un package ? A quoi correspondent les packages javax.* ?
  5. Comment les exceptions peuvent-elles traitées en Java ? Sont-elles toutes obligatoirement traitées ? Qu'en est-il des exceptions définies par le développeur ?
  6. Expliquer en quoi la construction des GUIs selon AWT en Java reflète effectivement des préoccupations de portabilité. En particulier, que sont les classes "peers" ? Swing procède-t-il de la même manière ?
  7. Comment les événements graphiques sont-ils gérés en Java ? En particulier, pourquoi utilise-t-on pour cela des interfaces ? Expliquer comment l'on peut séparer, dans la conception d'une application Java, la logique de traitement, l'interface graphique et la gestion des événements.
  8. Expliquer brièvement en quoi consiste X-Window. Existe-t-il un rapport direct avec la programmation Java ?
  9. Décrire les containers de type associatif utilisables en Java. En particulier, expliquer comment certains peuvent être persistants.
  10. Comment les dates sont-elles manipulées en Java ?
  11. Expliquer ce qu'est l'utilitaire jar. A quoi sert le fichier manifeste ? Comment l'utiliser pour rendre les applications Java facilement portables ? Les fichiers de données sot-ils également placés dans les jars ou vaut-il mieux les traiter différemment pour assurer une portabilité maximale ?
  12. Expliquer schématiquement les divers types de flux orientés bytes rencontrés en Java : flux de base, flux bas niveau, flux d'interprétation, flux de traitement complémentaires, etc.
  13. Expliquer le mécanisme de sérialisation en Java. Expliquer en quoi un fichier de sérialisation est ou n'est pas un "fichier d'objets".
  14. Décrire les flux orientés caractères couramment utilisés. En quoi diffèrent-ils des flux orientés bytes ? Est-il possible de passer d'un type de flux à l'autre ?
  15. Expliquer globalement comment l'on peut, en cas de nécessité, créer et gérer un fichier classique à enregistrements, similaires à ceux utilisés en C. Quelle est en fait la principale difficulté ?
  16. Expliquer les principales caractéristiques d'un Java Bean. Quel rapport avec la gestion des événements de l'AWT ? Pourquoi les Java Beans sont-ils instanciables d'une manière particulière ?
  17. Qu'est-ce qu'un container de Java Beans ? Expliquer ce que l'on entend par "connecter deux beans". A quoi ressemble une application Java basée sur ces notions ?
  18. Dans le contexte des Java Beans, expliquer ce que l'on entend par "propriété liée" et "propriété contrainte".
  19. Expliquer ce que l'on entend par "méthode factory". Donner quelques exemples rencontrés couramment. Quel rapport avec certains packages de type javax.* ou de type "providers" ?
  20. Expliquer comment on utilise une applet Java. Quelles en sont les caractéristiques du point de vue programmation ? Expliquer comment les jars peuvent être utiles pour déployer ces applets.

1) Expliquer pourquoi les deux grandes qualités attribuées au langage Java sont la portabilité et la sécurité. En particulier, en quoi des classes comme System ou Layout, ou encore des interfaces comme Cloneable ou Serializable, contribuent-ils à ces aspects ?

Un des objectifs des fondateurs de Java était d’en faire un langage portable. Pour ce faire, ils ont donc voulu rendre l’architecture logicielle, la moins dépendante possible de l’architecture matérielle. Ainsi, les instructions du code généré par un compilateur Java (bytecode) sont exprimées au niveau le plus bas possible sans devenir dépendant de la machine d’exécution. Ce code intermédiaire est destiné à une machine virtuelle Java (interpréteur), qui va le transformer en du code compréhensible par la machine hôte. Un gros intérêt de Java est donc de permettre de développer des applications indépendamment de la plate-forme sur laquelle elles fonctionneront effectivement. Au niveau programmation, c’est la classe system qui rend cette indépendance possible : elle fournit les E/S standards, les caractéristiques du système et diverses autres informations. Cette classe possède notamment une méthode très intéressante : getProperty. Elle permet de récupérer de nombreuses propriétés du système (path, separator...) avec un accès très simple de type clé/valeur.
Au niveau des interfaces graphiques, il va de soi, que les divers composants doivent être disposés à l’écran d’une manière indépendante de l’environnement hôte. On entend par là qu’il faut éviter de fixer les différents éléments au moyen de coordonnées graphiques exprimées par exemple, en pixels. Pour cette raison, Java possède des gestionnaires de mise en page (layout), dont le rôle est de déterminer comment l’écran va être découpé et comment les composants y seront disposés. L’autre objectif principal de Java est la sécurité. Cela est du au fait que, à la base, Java devait surtout être utilisé pour le web. Le code devait donc pouvoir traverser différents réseaux sans qu’il ne puisse être « trafiqué » par un éventuel esprit mal-veillant. Tout est donc prévu pour que le transfert d’un applet ou l’exécution d’une application ne puisse endommager le site d’exécution.
Pour garantir cela, il existe un mécanisme appelé « sandbox » qui distingue le code fiable de celui qui ne l’est pas forcément. Le code local (compilé sur la machine elle-même) est bien sûr fiable : il a donc accès à toutes les ressources. Par contre, du code téléchargé (applet) est à priori suspect : elle ne pourra être exécutée que dans un espace aux possibilités très restreintes par rapport aux ressources. C’est cet espace qui est appelé « sandbox ». Avec l’évolution de Java, des possibilités telles que signer un applet pour lui offrir les mêmes permissions que du code local sont apparues. Mais également la possibilité de définir des permissions pour un code quelconque. Un autre élément de la sécurité est l’interdiction pour le programmeur d’utiliser des pointeurs. Il ne peut ainsi pas effectuer des accès mémoire qui pourraient mettre en péril l’intégrité du système hôte.
Il existe également le mot clé final qui s'applique aux variables de classe, aux méthodes et aux classes. Il permet de rendre l'entité sur laquelle il s'applique non modifiable une fois qu'elle est déclarée pour une méthode ou une classe et initialisée pour une variable. Des outils tels que le chargeur de classe et le vérificateur sont également utilisés pour assurer la meilleure sécurité possible. Le premier permet d’éviter qu’une classe locale interagisse avec une classe venue du réseau. Le vérificateur quant à lui traite le bytecode en y recherchant tout élément susceptible d’être dangereux (accès interdits à des ressources, redéfinitions...).
Au niveau de la programmation, des interfaces comme cloneable et serializable sont de simples flags permettant par exemple dans ces cas aux classes qui les implémentent d’être dupliquées ou sérialisées. Une fois de plus ces interfaces qui pourraient paraître inutiles permettent en fait d’ajouter un peu plus de contrôle aux application et donc de la sécurité.

2) Décrire les principaux programmes exécutables que l'on utilise couramment pour développer, déployer et utiliser des applications Java selon J2SE. En particulier, qu'est-ce que le JRE (synonyme de JDK ?) et tous ces programmes exécutables en font-ils partie ?

La plate-forme J2SE (Java 2 Standard Edition) est destinée au développement d’applications classiques et d’applications web.
Cette plate-forme définit en fait des architectures de programmation (orientées web et objets distribués), des APIs fournissant divers services au moyen de techniques qui constituent des extensions par rapport au Java de base (JDBC, servlets...) et un environnement d’exécution (runtime) permettant aux applications d’accéder aux implémentations de ces APIs.
Le JDK (ou SDK) est en fait un environnement de développement qui comporte donc : des outils de développement comme le compilateur (javac), l’interpréteur (java), le générateur de documentation (javadoc)... Mais aussi l’environnement d’exécution, le JRE (Java Runtime Environment). Celui-ci permet l’exécution du bytecode Java, mais pas son développement : on y retrouve donc tout ce dont a besoin une application Java pour s’exécuter (machine virtuelle, vérificateur, interpréteur, librairies...).
Le JDK comporte également les librairies nécessaires aux outils de développement ainsi que éventuellement, les fichiers sources contenant toutes les classes faisant partie de l’API de base.

3) Expliquer la notion d'interface en Java. Quelles sont les analogies et les différences avec la notion de classe abstraite ? Illustrer en quoi l'utilisation des interfaces permet une programmation générique. Combien de méthodes trouve-t-on au minium dans un interface ?

Une interface est une collection de déclarations de méthodes et constantes. Cela signifie qu’elle contient les prototypes (0 au minimum) mais non l’implémentation de différentes méthodes.
Par la suite, toute classe qui implémente cette interface s’engage à fournir une implémentation des méthodes déclarées dans celle-ci.
Les interfaces permettent donc de définir un protocole de comportement pour des classes quelconques, c’est à dire non reliées par des relations d’héritage. Ce qui est une des différences fondamentales avec les classes abstraites étant donné que celles-ci ne permettent de définir une « interface » commune qu’aux classes d’une même hiérarchie. L’autre différence étant bien sur le fait qu’une classe abstraite peut contenir différentes variables et peut apporter certaines définitions à ces méthodes (une méthode sans définition étant nécessaire pour considérer cette classe comme abstraite).
L’utilisation des interfaces permet d’apporter une « généricité » aux programmes.
En effet, imaginons par exemple (à juste titre) une classe opération financière encapsulant deux acteurs financiers (un créditeur et un débiteur). Ces deux acteurs pourraient bien sur être d’origines complètement différentes : médecin, patient, fournisseur, institution... Ces acteurs n’ayant pas spécialement une hiérarchie commune (classe abstraite), il va de soi qu’il est assez difficile de trouver un type commun pour les encapsuler dans notre classe opération financière. Les interfaces viennent heureusement à notre secours, il suffit par exemple de faire implémenter à ces différents acteurs une interface très finement nommée ActeurFinancier (apportant par exemple une méthode de débit et de crédit). Cela aura pour effet de considérer tous ces acteurs aussi éloigné soient-ils comme des ActeurFinancier. Il nous suffira alors dans notre classe d’implémenter un débiteur et un créditeur de type ActeurFinancier.
Les interfaces connaissent l’héritage simple et l’héritage multiple. La notion d’interface permet donc également de contourner le problème de l’absence d’héritage multiple en Java. Mais il ne remplace pas cette notion car on n’hérite pas de variables ou d’implémentations de méthodes à partir de cette interface.
Des interfaces comme cloneable ou serializable sont également utilisées dans un but de sécurité. En effet, de telles interfaces n’apportent aucune méthode supplémentaire aux classes qui les implémentent mais bien la possibilité dans ce cas d’être dupliquée ou sérialisée.

4) Expliquer le concept de package en Java et comment le compilateur et l'interpréteur utilisent une classe d'un package donné. En particulier, l'accès package est-il suffisant pour pouvoir utiliser une classe définie dans une librairie Java définie dans un package ? A quoi correspondent les packages javax.* ?

Il existe un moyen de regrouper les classes voisines ou qui couvrent un même domaine : ce sont les packages. Ils permettent de donner une certaine structure à l’application.
Partant de cette idée les concepteurs du langage ont développé une série de packages permettant d'utiliser l'ensemble des ressources de la machine (c'est l'API de Java). Chacun de ces packages ayant un domaine d'action bien déterminé, comme par exemple, les connexions réseaux, les composants graphiques, ... Il est donc possible d’utiliser ces différentes fonctionnalités en spécifiant le package que l’on souhaite utiliser.
Ce sont des packages tels que javax.mail, javax.xml. Ils contiennent des classes abstraites et des interfaces. Différentes sociétés (providers) peuvent ensuite fournir des implémentations pour ces méthodes mais ils doivent bien sur implémenter les interfaces du package, permettant ainsi au programmeur de coder de la même manière sans devoir changer ses appels de fonctions suivant qu’il utilise les classes d’un ou l’autre provider. Ceci permet de mettre une fois de plus en avant la portabilité qui caractérise pleinement Java.
Il est bien sur possible de créer ses propres packages. Pour cela il faut préciser en première ligne de chaque fichier source à quel package il appartient (package nomPackage ;).
Les packages Java doivent être organisés comme des répertoires. En particulier, un package doit se trouver dans un répertoire portant le même nom que lui. La structure des packages reflète donc celle des répertoires correspondants et vice-versa.
Les fichiers sources peuvent être enregistrés dans un répertoire quelconque mais il est plus ordonné de les placer dans le répertoire du package (idem pour tous les autres fichiers du projet). Lors de la compilation, tous les fichiers .class générés se trouveront automatiquement dans le dossier du package auquel ils appartiennent.
Pour accéder aux différentes classes il faut donc utiliser un chemin du type LePackage.LaClasse. Le compilateur et l’interpréteur Java utilisent ces chemins pour retrouver les classes du package. Ils ont pour cela besoin d’une autre information : le nom des répertoires à partir desquels ils doivent rechercher ces classes. C’est le rôle de la variable d’environnement CLASSPATH.
Les packages permettent également d’éviter certains conflits. En effet, lorsqu’une application atteint une certaine ampleur, il est fréquent que le même identificateurs soit utilisé avec des significations différentes en différents points de l’application. La solution consiste à placer les définitions dans des packages. De cette manière, un nom sera connu à l’intérieur de son package et sera utilisé à l’extérieur préfixé du nom de son package. Les conflits sont ainsi évités...
Remarque importante : Java ajoute l’accès package qu’il est possible de spécifier pour les différentes variables et méthodes des classes. Ainsi, si une méthode est précédée du mot clé package, cela signifie qu’elle sera accessible pour une classe dérivée et pour une autre classe du même package mais pas pour une classe d’un autre package.
Les classes d’un package peuvent être utilisées soit en préfixant leur nom du nom du package, soit en incluant l’instruction import nomPackage (qui permet d’importer toutes .* ou seulement certaines classes .nomClasse) en début de fichier.
A noter qu’il est également possible de créer des sous-packages et donc une hiérarchie de packages.
L’utilisation des packages Java ne peut pas être comparée aux includes bien connus. L’utilisation d’une librairie quelconque en Java est dynamique : lors de la création, il n’y a pas de création de liens comme dans le processus de création d’exécutable classique. En fait, les objets auxquels il fait référence seront retrouvés et chargés dynamiquement, c’est-à-dire au moment de l’exécution du bytecode par l’interpréteur.

5) Comment les exceptions peuvent-elles traitées en Java ? Sont-elles toutes obligatoirement traitées ? Qu'en est-il des exceptions définies par le développeur ?

Pour une exception donnée, une méthode a le choix entre 3 stratégies :

  • elle la traite en fournissant un gestionnaire d’erreurs (catch)
  • elle la capte (catch) mais pour relancer une de ses propres exceptions (throw)
  • elle ne la traite pas mais la relance implicitement (throws)
Si l'on décide de traiter une exception, il faut alors définir un certain nombre de blocs d'instructions. Pour ce faire, trois mots clés sont à disposition : try, catch et finally.
Le mot clé try permet de spécifier une section de code susceptible de générer une exception.
Ce bloc doit ensuite être suivi d’un ou plusieurs catch permettant chacun de spécifier le code à exécuter pour une exception donnée.
La clause finally (rarement utilisée) permet quant à elle de spécifier du code à exécuter après une instruction try, qu’une exception se soit produite ou non.
Une méthode Java peut déclarer les exceptions qu’elle est susceptible de lancer dans une clause throws suivant son en-tête. Une application instanciant un objet de cette classe n’a que deux possibilités : ou elle traite l’exception qui pourrait être lancée par un catch, ou elle relance cette exception et, pour ce faire, doit annoncer dans son en-tête qu’elle peut relancer cette exception.
Cette exception doit donc obligatoirement être prise en charge par le programme appelant sinon le compilateur signalera une erreur.
Si le programmeur veut déclarer sa propre classe d’exception, il doit obligatoirement la faire dériver de la classe Throwable (ou une de ses classes dérivées).
Cette classe comportement comme le constructeur, fournir le message associé à l’exception...
Throwable fournit également le mécanisme de base à deux filles :
  • Error : cette classe correspond à des erreurs sérieuses, qu’il n’est pas intéressant de traiter pour une application Java (problèmes hardware...).
  • Exception : elle correspond à tous ce qui serait intéressant de traiter pour une application Java. Une exception créée par l’utilisateur doit toujours dériver de cette classe.
Seules les classes dérivées d’exception font l’objet d’un contrôle de la part du compilateur. Cela signifie que le compilateur vérifiera que les méthodes de nos classes traitent ou relancent les exceptions possibles.

6) Expliquer en quoi la construction des GUIs selon AWT en Java reflète effectivement des préoccupations de portabilité. En particulier, que sont les classes "peers" ? Swing procède-t-il de la même manière ?

Les composants AWT sont conçus pour être indépendants des plates-formes. Ainsi, le même code tournant par exemple sous Windows et sur Macintosh donnera respectivement un bouton au style Windows et un bouton au style Macintosh.
Autrement dit, si le code du programmeur reste le même quelque soit la plate-forme, l’implémentation fournie pour AWT ne l’est pas. Pour chaque composant AWT correspond un objet spécifique à chaque plate-forme. Cet objet est désigné par le mot Peers et correspond à une classe Java.
Donc avec AWT, Java fait appel au système d'exploitation utilisé pour afficher les composants graphiques. Pour cette raison, l'affichage de l'interface utilisateur d'un programme peut diverger sensiblement : chaque système d'exploitation dessine à sa manière un bouton.
AWT présente un avantage indéniable : en utilisant les objets graphiques de l'OS, l’affichage se fait beaucoup plus rapidement.
Swing a quant à lui été mis en place pour pouvoir garder un affichage tel quel, quelle que soit la plate-forme utilisée (même un pixel doit avoir la même couleur).
Pour assurer cela, un bouton (au tout autre composant graphique) est dessiné non plus par l'OS, mais par Java (ce qui demande un temps d’exécution plus élevé).
Mais il faut bien entendu que Java puisse interagir avec l'OS pour pouvoir tracer un point. Or c'est ce que font les classes de base de AWT. En conséquence, tous les composants de SWING dérive d'une classe d’AWT.
Autre point important pour la mise en page avec AWT : il va de soi, que les divers composants doivent être disposés à l’écran d’une manière indépendante de l’environnement hôte. On entend par là qu’il faut éviter de fixer les différents éléments au moyen de coordonnées graphiques exprimées par exemple, en pixels.
Pour cette raison, Java possède des gestionnaires de mise en page (layout), dont le rôle est de déterminer comment l’écran va être découpé et comment les composants y seront disposés.

7) Comment les événements graphiques sont-ils gérés en Java ? En particulier, pourquoi utilise-t-on pour cela des interfaces ? Expliquer comment l'on peut séparer, dans la conception d'une application Java, la logique de traitement, l'interface graphique et la gestion des événements.

Lorsqu’un évènement se produit, tout objet de l’application peut en recevoir une notification. Il doit pour cela :

  • Implémenter les méthodes de traitement de l’évènement en question. En pratique, il implémentera une interface appropriée appelée « listener ».
  • Se faire enregistrer comme listener potentiel de l’évènement.
>Un évènement est donc généré par une source et transmis à un (ou plusieurs) listener : cette transmission s’effectue par invocation d’une méthode du listener à qui on passe comme paramètre l’objet évènement généré.
Ce mécanisme de gestion d'évènements n'est, en fait, pas directement rattaché à une quelconque API d'interface graphique. Au contraire, il se veut indépendant de tout contexte. On peut donc traités n’importe quel type d’évènement , il suffit pour cela d’implémenter le listener adéquat.
En fait, ce ne sont que des interfaces comportant une méthode par type d’évènement. A charge donc pour l’application qui les utilisent d’implémenter la ou les méthodes qui correspond(ent) aux évènements qu’elle souhaite traiter. Le programmeur peut donc décider très précisément quels évènements il souhaite faire traiter par tel composant logiciel.
Les évènements sont représentés comme des instances d’une classe appartenant à une hiérarchie de classe d’évènements dont EventObject est la classe de base. L’objet instanciant l’une de ces classes contiendra évidemment toutes les informations concernant l’évènement détecté ainsi que l’identification de sa source.
Il existe deux types d’évènements :
  • bas niveau : il s’agit d’entrée clavier ou d’action sur les composants visuels de l’interface
  • sémantiques : ils correspondent à des actions qui dépassent le stade de la simple modification d’interface qui provoquent au contraire des actions plus profondes sur les données mêmes de l’application (exemple : modification dans une base de données).
Il existe donc également les listeners correspondants (bas niveau et sémantiques) permettant de traiter ces évènements.
L'utilisation d'interfaces comme WindowListener impose à l'utilisateur l'implémentation de toutes les méthodes de l'interface. Pour pallier à ce problème, on utilise des "adapters" comme WindowAdapter qui permettent de n’implémenter que les méthodes qui nous intéressent.
Pour répondre aux exigences de l’analyse orientée objet, il peut être intéressant de séparer les différentes parties du programme (interface, traitement, gestion des évènements) pour notamment, lui apporter une certaine modularité.
Pour ce faire il faut bien entendu créer différentes classes : une contenant l’interface graphique, l’autre contenant la gestion des évènements et une dernière pour les traitements.
La classe s’occupant de l’interface graphique implémente bien sûr ActionListener et instancie la classe de gestion des évènements qui elle implémente WindowListener et possède une variable membre référençant la fenêtre qu’elle doit gérer (cette variable sera initialisée par le constructeur lors de l’instanciation). La fenêtre n’a ensuite plus qu’à s’inscrire au gestionnaire et implémenter son actionPerformed.

8) Expliquer brièvement en quoi consiste X-Window. Existe-t-il un rapport direct avec la programmation Java ?

Le but de X-Window est d’exécuter une application sur une machine et de réaliser les affichages sur une autre. L’objectif était donc de pouvoir afficher sur un même écran les résultats de plusieurs programmes différents tournant sur des machines différentes.
L’architecture de ce système est une architecture client-serveur.

  • Le client est l’application dont on veut voir apparaître l’affichage
  • Le serveur est un programme qui est exécuté sur une machine capable de réaliser des affichages graphiques fenêtrés.
Le serveur et le client communiquent par réseau selon un protocole X.
Il faut spécifier du côté client où se trouve le serveur qui réalisera les affichages graphiques. On utilise pour celà la variable d'environnement DISPLAY.
L'affichage est alors réalisé sur le serveur avec l'apparence du client. On aura donc une apparence UNIX sur un serveur windows si on lance l'application sur un client UNIX.
Il n’existe pas de rapport direct entre ce système et la programmation Java. Le programmeur ne doit rien changer à son code pour qu’il puisse fonctionner avec X-Window. Seule la personne désireuse d’afficher l’interface graphique sur une autre machine devra configurer son poste de travail avec un client X-Window.
De plus, L'affichage de l'application sur le serveur peut se faire indépendamment du langage de programmation utilisé.

9) Décrire les containers de type associatif utilisables en Java. En particulier, expliquer comment certains peuvent être persistants.

Le principe est de retrouver une information, classiquement un enregistrement, au sein d'une structure de données à partir de la valeur d'un de ses champs (la clé).
L’interface Map propose les différentes méthodes à implémenter pour réaliser un tel système de recherche par clé. Les méthodes à définir sont essentiellement put(object key, object value), get(object key) et remove(object key).
Il est donc possible d’implémenter son propre dictionnaire mais c’est plus simple d’utiliser la classe Hashtable proposée par les librairies Java. Cette classe propose une table appliquant les principes du hash coding et implémentant l’interface Map (recherche par clé/valeur). Cet outil très intéressant permet donc de récupérer directement un élément via une clé.
Ces différents containers peuvent être sérialisés sur fichiers grâce aux flux ObjectInput/Output et à l’interface serializable notamment. Ils peuvent être directement écrit dans leur entierté ou bien il est possible de boucler sur chaque élément qu’ils contiennent. Le fichier contiendra alors les définitions des classes sérialisées et si le container a été directement écrit, le fichier contiendra alors également sa définition.
Le dernier exemple de container associatif est les Properties. Il s’agit également d’un ensemble de couples clé/valeur dans lequel on retrouve une information selon le principe de la hashtable. Une utilisation classique est via la classe System qui permet de récupérer de nombreuses informations sur le système où tourne l’application.
Mais il également possible de créer un objet Properties personnalisé qui sera initialisé à partir d’un fichier .properties. Il s’agit d’un simple fichier texte structuré en lignes formées selon la syntaxe :
< : ou =>.
Ces fichiers sont courants dans les applications Java : leur rôle est bien clairement de servir de fichiers de configuration simples.

10) Comment les dates sont-elles manipulées en Java ?

La classe Date permet de manipuler les dates. Le constructeur d'un objet Date l'initialise avec la date et l'heure courante du système. De nombreuses méthodes et constructeurs de la classe Date sont deprecated, notamment celles qui permettent de manipuler les éléments qui composent la date et leur formatage : il faut utiliser la classe Calendar.
Le rôle de cette classe est de déterminer la manière d’enregistrer et de manipuler les données temporelles. Elle permet de se déplacer dans le temps par rapport à une date en respectant le système de calendrier utilisé.
Dans notre cas, on peut se contenter de la classe GregorianCalendar (calendrien gégorien occidental courant) et de la méthode de la classe calendar : getInstance().
Cette méthode est factory (fabrique l’objet le mieux adapté en fonction du contexte d’exécution), ce qui signifie qu’elle fournit l’objet calendrier le mieux adapté à la plate-forme support en termes de conventions locales d’affichage des fuseaux horaire.
Il est ensuite très simple de récupérer la date courante grâce à la fonction getTime().
Il est également possible d’extraire de la date les composantes qui nous intéressent en utilisant la méthode get() et les constantes adéquates (WEEK, YEAR...).
La classe calendar possède d’autre nombreuses méthodes permettant par exemple de vérifier qu’une date entrée est strictement correct, de créer une date fixée, etc...
Une astuce permet également de traiter un problème pratique : afficher une date au format habituel pour l’utilisateur.
Cela nécessite la combinaison de différentes classes :

  • Locale dont le rôle est d’encapsuler tout ce qui caractérise une région géographique en termes de pays, de langue, d’indication d’heure... Cette classe définit des constantes de classes qui vont nous permettre de spécifier le contexte régional dans lequel nous travaillons (FRANCE, US...).
  • DateFormat qui permet de gérer le format d’affichage d’une date en tenant compte du contexte régional. Et notamment une méthode particulièrement intéressante : getDateTimeInstance qui fournit un objet en tenant compte de la région et du format souhaité pour la date et l’heure.
La méthode format de DateFormat permet quant à elle de retirer une chaîne de caractère des précédentes étapes.
Date maintenant=new Date() ;
String maDate=DateFormat.getDateTimeInstance(DateFormat.FULL,DateFormat.FULL, Locale.FRANCE).format(maintenant) ;

11) Expliquer ce qu'est l'utilitaire jar. A quoi sert le fichier manifeste ? Comment l'utiliser pour rendre les applications Java facilement portables ? Les fichiers de données sot-ils également placés dans les jars ou vaut-il mieux les traiter différemment pour assurer une portabilité maximale ?

L’utilitaire Jar permet de regrouper en un seul fichier un certain nombre d’autres fichiers qui peuvent être de n’importe quel type. L’intérêt est de produire un fichier à la fois comprimé (il prend donc moins de place sur le disque ou sur le réseau) et sécurisé (il est possible d’y intégrer une signature digitale).
Il est ainsi très simple de faire passer une application d’une machine à l’autre. Il suffit pour cela de regrouper tous les fichiers la composant en un seul Jar (en respectant bien sur l’architecture des répertoires). Il suffit en fait d’ajouter au Jar le répertoire principal de l’application.
Une fois le Jar créé, il ne reste plus qu’à l’envoyer sur la machine et soit exécuter directement le . jar ou bien le décompresser et lancer le programme de manière habituelle.
Lors de la création du Jar, un fichier manifeste est automatiquement ajouté à celui-ci. Son rôle est de contenir des informations sur les fichiers encapsulés, notamment au niveau de la sécurité : signature électronique, authentification, contrôle de version etc. Mais surtout, elle spécifie où trouver le main pour pouvoir lancer l’application.
Pour assurer une portabilité maximale au niveau des fichiers de données, il est préférable de ne pas les placer dans le Jar mais dans le répertoire de l’utilisateur. De cette façon, il est très simple pour le programmeur de spécifier les accès aux fichiers dans son application grâce à la classe System et la méthode getProperty qui permet de récupérer de nombreuses informations sur le système dont notamment le répertoire de l’utilisateur, le file separator etc. Il suffit ensuite d’envoyer le Jar et les fichiers de données dans le répertoire de l’utilisateur ce qui permet d’accéder aux fichiers de données sans devoir modifier le chemin d’accès à chaque fois.

12) Expliquer schématiquement les divers types de flux orientés bytes rencontrés en Java : flux de base, flux bas niveau, flux d'interprétation, flux de traitement complémentaires, etc.

Les classes de base sont notamment, deux classes abstraites : InputStream et OutputStream.
Ces deux classes considèrent les entrées et les sorties comme des suites de bytes.
Les classes dérivées implémentent les caractéristiques générales de leur classe mère en les adaptant à une entrée ou sortie particulière.
La classe InputStream comporte une interface minimale et une implémentation minimale des flux d’entrée. Les traitements sont de bas niveaux, c’est à dire qu’il travaille en termes de bytes : lecture de bytes, adapter la position sur le flux etc.
De manière similaire, OutputStream s’occupe des flux de sortie. Elles comportent des fonctions élémentaires de bas niveau : écriture de bytes, vidange du buffer etc.
Pour une utilisation classique des fichiers, l’utilisation des classes FileInputStream et FileOutputStream est aisée. Ces deux classes réclament un objet de type File qui permet le traitement classique de fichiers.
Ces deux classes de lecture et d’écriture sur fichier possèdent évidemment différentes méthodes de lecture et d’écriture mais qui se font à un niveau brut : lecture et écriture de bytes.
Comment faire pour traiter ces bytes et par exemple les afficher de manière compréhensible ?
Il faut utiliser une classe FilterInputStream qui met en place des mécanismes additionnels qui permettent à ses sous-classes de réaliser l’interprétation, c’est-à-dire le filtrage des données.
C’est ce que font les classes dérivées DataInputStream et DataOutputStream.
Ces classes se construisent logiquement sur le flux bas niveu qu’elles seront chargées de traiter et elles fournissent bien sur les différentes méthodes de lecture et d’écriture.
Ces deux classes sont parmi les plus importantes de tout l’ensemble des flux Java. Ces flux manipulent les types primitifs de Java, ainsi que les Strings d’une manière indépendante de la machine.
Parmi les classes dérivées de FilterInputStream, on trouve encore, notamment, deux classes correspondant aux flux bufférisés au niveau du run-time lui-même. Les données ne sont pas lues ou écrites byte par byte mais le sont par blocs au sein d’un tableau interne de bytes, les accès suivant se faisant alors dans le buffer lui-même. Ces classes sont BufferedInputStream et BufferedOutputStream réclamant à la construction un InputStream ou un OutputStream.
L’intérêt est que les accès se font directement sur le buffer interne ce qui permet donc d’améliorer les performances. Ces flux bufférisés présentent notamment de l’intérêt pour les flux alimenté par une connexion réseau.

13) Expliquer le mécanisme de sérialisation en Java. Expliquer en quoi un fichier de sérialisation est ou n'est pas un "fichier d'objets".

La sérialisation est le fait de sauver des objets sur un support permanent.
On parvient à un tel objectif en utilisant les classes ObjectOutputStream et ObjectInputStream dérivées respectivement d’OutputStream et d’InputStream.
L’écritures d’objets nécessite l’utilisation de ObjectOutputStram qui implémente l’interface ObjectOutput qui étend la zone d’action de DataOutput aux objets en tous genres et aux tableaux.
Il s’agit d’une classe de haut niveau qui doit s’appuyer sur une classe implémentant la version native du fichier (FileOutputStream). Elle possède donc différentes méthodes telles que WriteInt, WriteFloat, et surtout, WriteObject. Cette dernière écrit dans le fichier la classe de l’objet ainsi que sa signature et les valeurs des différentes variables membres d’instance.
L’écriture peut ensuite se faire objet par objet mais il est à noter que si l’on possède un tableau d’objet il est possible d’écrire directement l’entierté de ce tableau.
La lecture se fait tout aussi simplement en utilisant la classe ObjectInputStream qui implémente l’interface ObjectInput et qui dispose bien sûr de méthodes du style : ReadInt, ReadFloat, ReadObject... Il est bien entendu vital que les classes des objets utilisés pour l’écriture et la lecture soient rigoureusement les mêmes.
Tout ceci ne fonctionnera cependant pas si la classe des objets à sauver n’implémente pas l’interface serializable. Cette interface ne contient aucune méthode à implémenter, il s’agit juste de flag autorisant la classe à être sérialisée (sécurité !)
Un ficher sérialisé est bien un fichier d’objets mais uniquement dans le but d’une récupération ultérieure. Sérialisation est indissociable de séquentialité, il n’y aura jamais d’accès direct dans ce genre de fichier. Etant qu’il comporte notamment la description des classes, il n’est pas question de gérer un « fichier d’objets ».
Il est intéressant de remarquer qu’on peut sérialiser des objets instanciant des classes différentes dans le même fichier. Celui n’enregistrera qu’une seule fois la définition de chaque classe. A la lecture, on retrouvera bien les types si l’ont respecte un ordre de lecture qui est le même que celui de l’écriture.

14) Décrire les flux orientés caractères couramment utilisés. En quoi diffèrent-ils des flux orientés bytes ? Est-il possible de passer d'un type de flux à l'autre ?

Certaines applications orientées « texte » lisent ou écrivent sur des flux de caractères.
La différence fondamentale avec les flux orientés bytes est la représentation en mémoire.
En effet, un byte occupe 8 bits, quelle que soit la machine ou le système d’exploitation tandis qu’un caractère peut être codé sur un nombre variant de 1 à 3 bytes.
De plus, un byte n’est pas interprété alors qu’un caractère l’est immédiatement. Or, un caractère donné peut être codé de manière différente selon le jeu de caractères utilisé sur la machine.
Ces différences qui sont basées sur la différence même entre les données « binaires » et « textes », justifie l’existence en Java d’une seconde hiérarchie orientée caractères.
Ces flux proposent des méthodes qui sont capables de lire ou d’écrire non plus des simples bytes, mais des caractères avec le travail de décodage que cela implique. Ce sont donc des flux plus évolués puisqu’ils interprètent selon le contexte machin dans lequel ils travaillent.
Les méthodes sont très similaires à celles des flux orientées bytes, mais elles permettent de tenir compte des manières d’encodage. On trouve cependant en supplément des méthodes utilisant des objets String ce qui est normal étant donné que les classes de type OutputStream ne connaissent que les bytes.
Les classes InputStreamReader et OutputStreamWriter dérivées respectivement de Reader e Writer réalisent le pont entre les flux orientés bytes et ceux orientés caractères puisqu’elles transforment les bytes lus en caractères selon la manière d’encodage active ou précisée.
En pratique, on utilise des flux de niveau supérieur pour lire ou écrire des lignes de caractères : BufferedReader et BufferedWriter prenant respectivement à la construction un objet Reader et Writer.
Il n’est pour moi pas vraiment question d’un passage d’un flux à un autre. Les flux orientés caractères peuvent être considérés comme étant une couche supplémentaire située au dessus de la couche byte. Cette couche effectuant en quelque sorte un certains filtrage en transformant les bytes en caractères. Pour utiliser un flux de type bytes il suffit donc de ne pas utiliser cette couche de conversion et vice-versa.

15) Expliquer globalement comment l'on peut, en cas de nécessité, créer et gérer un fichier classique à enregistrements, similaires à ceux utilisés en C. Quelle est en fait la principale difficulté ?

Il est possible de manipuler en Java des fichiers à accès direct, qui permettent à la fois la lecture et l’écriture ainsi que le positionnement. On utilise pour cela la classe RandomAccessFile.
Celle-ci est directement dérivée d’object, elle implémente à la fois les interfaces DataInput et DataOutput et manipule les données sous forme de bytes. Son constructeur demande soit un nom de fichier ou objet de type file et un mode d’ouverture. Elle possède différentes méthodes permettant notamment de se positionner dans le fichier, etc.
Le problème principal pour créer de tels fichiers vient des chaînes de caractères et aussi par conséquent des dates. En effet, les objets String ont une longueur dynamique : le nombre de caractères qu’ils comportent varient selon l’information qu’ils contiennent effectivement.
Lors de la lecture il est donc difficile de savoir combien de caractères lire pour reconstituer un champs. Le seul moyen est de forcer la longueur de chaque variable (grâce à la méthode setLength de la classe StringBuffer) membre à une quantité fixe, de façon à reconstruire la structure classique du C. On détruit ainsi l’aspect dynamique encapsulé dans la classe String mais c’est le prix à payer.
Reste le problème de la date. Rien n’est prévu pour écrire une date dans un fichier. Ils nous faut donc la convertir en chaîne de caractères mais sa longueur varie selon la représentation locale utilisée. Il nous faudra donc une variable membre statique pour déterminer la longueur de la chaîne correspondant à la date traitée.

16) Expliquer les principales caractéristiques d'un Java Bean. Quel rapport avec la gestion des événements de l'AWT ? Pourquoi les Java Beans sont-ils instanciables d'une manière particulière ?

Les JavaBeans sont des composants logiciels réutilisables, en fait des classes Java qui possèdent des propriétés (en rapport avec des variables membres) et des fonctionnalités (matérialisées par des méthodes). En assemblant de tels composants, il est possible de construire une application Java.
Les beans peuvent communiquer et se connecter entre eux pour former des applications complètes.
Un composant est un objet qui obéit à certaines conventions de nommage quant à ses méthodes d'accès aux attributs et à ses événements. Dès lors, il est possible de le manipuler via un outil de manipulation de composants. L'intérêt est le suivant : un outil de manipulation va permettre de créer plus rapidement une application que le programmeur.
Un bean s’exécute dans un environnement plus vaste appelé container, c’est-à-dire une autre classe qui définit une structure capable d’instancier et d’assembler des composants comme les JavaBeans.
Il leur fournit un contexte d’utilisation et permet leur interaction ainsi que l’exécution de leurs méthodes.
Un composant est considéré comme un JavaBean si il est conforme à un certain nombre de règles de conception. Il doit donc proposer un certain nombre de services de base :

  • Indication de son état
  • Possibilité pour le développeur de prendre connaissance des propriétés et méthodes du composant
  • Gestion des évènements qu’il est capable de gérer et de répercuter au niveau des autres composants
  • Persistance (sérialisation)
  • Mécanisme d’appel dynamique et de réception des notifications d’évènements par d’autres composants
  • Existence d’une entité physique contenant les divers fichiers nécessaires
On peut dire qu’un Bean est un morceau de code compilé :
  • dont le source a été écrit exclusivement en Java et est donc une classe
  • qui peut être configuré via ses propriétés
  • qui sera utilisé et manipulé au moyen de méthodes
  • qui possède un constructeur par défaut
  • qui est susceptible de générer des évènements qui permettront de faire savoir aux autres acteurs de l’environnement que « quelque chose » s’est passé.
Une propriété correspond le plus souvent à une variable membre. C’est la propriété qui dicte le nom des méthodes getXXX et setXXX associées, et non pas la variable membre associée. Cette convention de nommage va permettre à un outil de manipulation de composant de « s’y retrouver » dans le bean.
La gestion des évènements est exactement la même que pour les évènements d’AWT. Un JavaBean doit tout comme une classe qui prétend gérer les évènements d’AWT :
  • Implémenter une interface bien précise, à savoir l’ActionListener
  • Se faire enregistrer comme listener sur le composant visé
De même, toute classe qui souhaitera être avisée d’une génération d’alerte par le bean devra :
  • Implémenter une interface que le programmeur aura créée
  • Se faire enregistrer comme souhaitant être avisé d’un déclenchement. Le bean devra donc disposer d’une méthode d’enregistrement et de suppression.
Il reste ensuite au développeur à créer l’évènement qui est sensé contenir toutes les informations nécessaires à son traitement et qui dérive de la classe EventObject.
L’instanciation d’un bean ne se fait jamais par appel du constructeur. Il existe une méthode instanciate de type factory qui permet d’instancier (via le constructeur par défaut) un bean :
  • au moyen du chargeur de classe spécifié par le premier paramètre
  • en recherchant une classe ayant comme nom celui qui est spécifié dans le second paramètre
Grâce à cela on peut choisir au moment de l’exécution, donc de manière dynamique, la classe à instancier selon des critères qui ne peuvent être évalués qu’à ce moment là.

17) Qu'est-ce qu'un container de Java Beans ? Expliquer ce que l'on entend par "connecter deux beans". A quoi ressemble une application Java basée sur ces notions ?

Un bean étant un composant destiné à être utilisé par d’autres classes, il ne possède pas de main.
Il sera instancié dans une autre classe qui lui fournira son contexte d’exécution : cette autre classe est appelée le container.
Connecter deux beans signifie qu’ils vont s’enregistrer l’un chez l’autre et vont ainsi pouvoir communiquer aux moyens d’évènements qu’ils sont capables de générer et de détecter.
Une application Java basée sur les JavaBeans est donc une association de différents composants.
Ces beans sont réutilisables et permettent donc de créer des applications très rapidement.
Cette rapidité est surtout due aux différentes conventions de nommage pour les beans grâce auxquelles il est possible de les manipuler via un outil de manipulation de composants.

18) Dans le contexte des Java Beans, expliquer ce que l'on entend par "propriété liée" et "propriété contrainte".

Une propriété liée est une propriété qui donne lieu à l’émission d’un évènement à chaque fois que sa valeur change.
Il existe une classe PropertyChangeSupport qui a pour rôle de gérer les évènements associés aux changements de propriétés d’un bean. Typiquement, le bean considéré possède un tel objet comme variable membre et lui délègue cette partie du travail.
Le constructeur de cette classe demande le bean source comme paramètre, elle doit en effet seulement connaître le bean dont elle est chargée de gérer les changements de propriétés.
A chaque changement d’une propriété du bean, la notification est effectuée en appelant la méthode firePropertyChange qui prend comme paramètre le nom de la propriété, son ancienne et sa nouvelle valeur. La notification ne sera donc réalisée que si la nouvelle et l’ancienne valeur sont différentes.
Les objets intéressés par le changement de propriété doivent bien sur se faire enregistrer auprès de l’objet visé. Ils doivent également implémenter l’interface PropertyChangeListener qui comporte la méthode PropertyChange.
Une propriété contrainte est quant à elle, une propriété qui ne peut être modifiée que si la modification envisagée est validée par tous les beans cibles.
Une modification de cette propriété donne donc toujours lieu à une notification de ce changement à un ou plusieurs autres beans mais chacun d’eux peut refuser cette modification parce qu’il l’estime inappropriée.
Il existe donc une classe VetoableChangeSupport qui a pour rôle de gérer les évènements associés aux changements validés de propriétés d’un bean. Elle est analogue à la classe PropertyChangeSupport. Le fonctionnement est le même si ce n’est que la méthode fireVetoableChange est susceptible de lancer une exception qui signifie que l’un des beans cibles a refusé la modification.
Le bean cible doit toujours implémenter une interface VetoableChangeListener qui comporte une méthode vetoableChange mais la différence est que celle-ci est susceptible de lancer une exception.

19) Expliquer ce que l'on entend par "méthode factory". Donner quelques exemples rencontrés couramment. Quel rapport avec certains packages de type javax.* ou de type "providers" ?

Une méthode est factory lorsqu’elle fabrique l’objet le mieux adapté au contexte d’exécution.
Elles sont par exemple utilisées pour les gestions dates et notamment pour les calendriers.
En effet, la classe Calendar possède une fonction getInstance qui fournit l’objet calendrier le mieux adapté à la plate-forme support en termes de conventions locales d’affichage des fuseaux horaire.
Egalement, l’instanciation d’un bean ne se fait jamais par appel du constructeur. Il existe une méthode instanciate de type factory qui permet d’instancier (via le constructeur par défaut) un bean :

  • au moyen du chargeur de classe spécifié par le premier paramètre
  • en recherchant une classe ayant comme nom celui qui est spécifié dans le second paramètre
Grâce à cela on peut choisir au moment de l’exécution, donc de manière dynamique, la classe à instancier selon des critères qui ne peuvent être évalués qu’à ce moment là.
Les méthodes factory sont aussi extrêmement utilisées dans la cryptographie.

20) Expliquer comment on utilise une applet Java. Quelles en sont les caractéristiques du point de vue programmation ? Expliquer comment les jars peuvent être utiles pour déployer ces applets.

Une applet est un module Java intégré dans une page HTML téléchargée par le client à partir du serveur. Cette applet sera ensuite exécutée par le browser du client (si il possède le plugin adéquat qui lui permet d’utiliser une machine virtuelle).
Une applet Java n’a donc pas pour but de fonctionner seule : elle doit toujours être appelée depuis une page HTML qui lui fournit son contexte d’exécution.
La structure d’une applet diffère donc de celle d’une application. Une applet est toujours dérivée de la classe Applet.
En plus d’apporter différentes méthodes (gestion des évènements, gestion des composants etc.), cette classe apporte le mécanisme de chargement et déchargement de l’applet.
Il faut également importer la classe Graphics qui représente le contexte écran et permet donc d’afficher différents éléments (via la méthode paint).
Le fait de charger une applet, donc un élément actif, sur une machine pourrait être dangereux si des restrictions de sécurité n’avaient pas été mises en place par le langage lui même.
Ainsi, une applet ne peut pas :

  • lire ou écrire un fichier sur la machine qui l’accueille
  • réaliser de connexions réseaux, sauf vers la machine dont elle provient
  • exécuter un programme sur son hôte
  • charger des librairies
  • lire certaines propriétés du système
Elle peut par contre :
  • appeler les méthodes publiques des applets de la même page
  • provoquer l’apparition de pages HTML
Enfin, une applet chargée en local n’est soumise à aucune des restrictions auxquelles sont soumises les applets chargées par le réseau.
Une fois l’applet compilée, elle doit être appelée au sein d’une page html grâce aux tags < APPLET > et < / APPLET > .
Il est bon de noter que l’applet sera exécutée sur la machine virtuelle du navigateur du client. Il est possible que celle-ci varie d’une machine à l’autre. C’est pourquoi il est possible d’imposer l’utilisation de la machine virtuelle de Sun en utilisant un plugin. Il faut alors utiliser le tag < OBJECT >. Les méthodes classiques d’une applet sont :
  • init : appelée chaque fois que l’applet est chargée ou rechargée
  • start : démarre l’applet à chaque fois que la page est visitée ou revisitée
  • stop : arrête la page, par exemple quand l’utilisateur quitte la page ou le browser
  • destroy : nettoyage final avant le déchargement de l’applet
Jar est un utilitaire très intéressant pour les applets. Celle-ci étant comprimée, elle est donc téléchargée plus rapidement sur le réseau.
De plus, il permet de regrouper tous les fichiers (bytecode, images etc.) dans un même fichier.
Il est ensuite possible de demander à la page d’exécuter directement le . jar grâce à :
< APPLET archive="nom du .jar" >< / APPLET >




  Version imprimable


Commentaires

Pseudo :
Code anti-spam : (recopier : )



Je cherchais justement d'autres réponses pour confirmer et completer ma préparation des questions d'examen.
Merci ;)
Posté par ZaT le 17-05-2007



© 2dconcept Tous droits réservés 2007 | Plan du site | Hébergement par Netux