Les classes et les objets

Chapitres traités   


Les méthodes

Comme vous l'avez appris précédemment, une méthode définit le comportement d'un objet ce qui doit se passer lorsque cet objet est créé, et les différentes tâches que l'objet peut effectuer pendant sa durée de vie.

Définir une méthode

Les définitions de méthodes sont constituées de quatre éléments de base

  1. le nom de la méthode ;
  2. le type d'objet ou le type de données primaire que la méthode retourne ;
  3. une liste de paramètres ;
  4. le corps de la méthode.

Les trois premiers éléments d'une définition de méthode forment ce que l'on appelle la signature de la méthode. Voici un exemple type de définition de méthode :

typeRetour désigne le type de données primaire ou la classe de la valeur retournée par la méthode. Il peut s'agir de l'un des différents types de données primaires, d'un nom de classe, ou du mot clé void si la méthode ne retourne aucune valeur.

La liste de paramètres de la méthode est un ensemble de déclarations de variable, séparées par une virgule et placées entre parenthèses. Ces paramètres deviennent des variables locales dans le corps de la méthode ; ils reçoivent leur valeur quand la méthode est appelée. Notez qu'en Java les méthodes n'ont pas nécessairement des paramètres. Néanmoins, si une méthode n'a pas de paramètre, vous devez quand même utiliser les parenthèses lorsque vous la définissez et lorsque vous l'appelez.

Le corps de la méthode peut comprendre des instructions, des expressions, des appels de méthode à destination d'autres objets, des instructions conditionnelles, des boucles, etc. en substance, tout ce que vous avez découvert dans les chapitres précédents.

Les variables déclarées à l'intérieur d'une méthode sont locales à cette méthode. Elles ne sont pas accessibles par les autres méthodes de la classe et ne peuvent pas altérer des variables homonymes dans d'autres méthodes. Lorsque l'exécution du programme sort de la méthode, la mémoire allouée aux variables locales est automatiquement restituée.

Quand un programme fait référence à une variable au sein d'une définition de méthode, Java commence par chercher une définition de cette variable dans la portée en cours (qui peut être un bloc), puis dans chaque portée immédiatement extérieure, jusqu'à la définition de la méthode en cours. Si la variable n'est pas locale, Java recherche une définition de cette variable sous forme de variable d'instance ou de variable de classe dans la classe en cours. S'il ne trouve toujours pas la définition de la variable, il poursuit la recherche dans chacune des super-classes suivantes (voir la leçon sur l'héritage qui sera traitée ultérieurement).

La classe Test contient une variable d'instance test qui est initialisée à 10, ainsi que deux méthodes :

  1. la première s'appelle printTest() , elle ne contient pas de paramètre, et ne retourne aucune valeur. Elle comporte une variable entière locale initialisée à 20 et qui s'appelle test.
  2. la deuxième est la fonction principale(), elle ne contient pas de paramètre, et ne retourne aucune valeur.

Lorsque nous lançons le programme, la méthode main est appelée. Nous créons un objet test de type (de la classe Test). Une fois que cet objet est créé, nous faisons appel à la méthode principale de test. Celle-ci fait appel à la méthode printTest qui affiche le contenu de la variable locale test . Ensuite, la méthode principale affiche le contenu de la variable d'instance test.

La variable locale test située au sein de la méthode printTest() masque la variable d'instance test.
.

Remarquez la manière dont nous appelons les méthodes. Lorsque nous appelons une méthode qui ne fait pas partie de la classe, nous devons spécifier l'objet à qui appartient cette méthode. Par contre, lorsque nous appelons la méthode printTest() à partir de la méthode principale(), nous n'avons pas besoin de spécifier un nom de classe, car les méthodes principale() et printTest() appartiennent toutes les deux à la classe Test. Il est toujours possible d'appeler une méthode de la même classe en donnant simplement son nom et les paramètres requis.

Quand Java rencontre une instruction return, il quitte aussitôt la méthode. L'expression qui suit le mot clé return est la valeur retournée par cette méthode (autrement dit, le résultat de la méthode). Si une méthode n'a pas besoin de renvoyer une valeur, précisez-le dans l'en-tête en spécifiant void comme type de résultat.

A titre d'exemple, voici la définition de la méthode minimum qui renvoie la valeur minimale de deux entiers :

Et avec une écriture plus consise :

Cette même méthode définie et utilisée au sein de la classe Mathématique :

En cliquant sur le code ci-dessous vous lancerez la simulation qui vous montrera comment se passe l'appel d'une méthode avec le passage des paramètres

Lancement de la simulationIl est important de garder à l'esprit qu'en Java tous les arguments des méthodes sont passés par valeur et non par référence. Par conséquent, il est impossible de modifier des variables (tel que a et b par exemple) par des appels de méthodes (voir les chapitres suivants pour davantage d'informations sur ce point important). Les arguments (tels que x et y dans la méthode minimum) ne sont que des variables locales qui sont initialisées avec les valeurs passées lors d'un appel. Et ces variables locales sont supprimées lorsque le programme achève l'exécution de la méthode.

Choix du chapitre Utiliser la surcharge

Une classe peut posséder plusieurs méthodes avec le même nom mais différent par l'ensemble ou par l'ordre des types de leurs arguments. Au moment d'être invoquée, la bonne méthode est choisie pour qu'il y ait correspondance sur les paramètres de l'appel et les arguments de la méthode. Une différence qui porterait uniquement sur le type de la valeur de retour est interdite. Cette possibilité du langage s'appelle la "surcharge" ou la "surdéfinition".

Dans l'exemple ci-contre, nous possèdons deux méthodes minimum ; une pour les entiers, et la suivante pour les réels. Vous remarquez que nous faisons appel à la méthode minimum entre un réel et un entier. Il se trouve que cette méthode n'a pas été écrite. Le compilateur recherche alors la méthode qui se rapproche le plus et utilise le transtypage pour coller parfaitement à la signature de la méthode.

Exercices

Construire une classe Mathématique qui comporte plusieurs méthodes, savoir :

  • une fonction impaire
  • une fonction paire ( relative à la fonction impaire )
  • une fonction valeur absolue

Pour chacune de ces méthodes, vous déterminerez les types à utiliser. Vous testerez ce programme, par le rajout d'une classe principale avec sa méthode principale.

Récupération des sources des corrections


Choix des chapitres Références à des objets

Comme on peut s'y attendre, la classe Point va permettre d'instancier des objets de type Point et de leur appliquer à volonté les méthodes publiques initialise, déplace et affiche.

A l'intérieur d'une méthode quelconque, une déclaration telle que :

Point a ;

est tout à fait correcte. Cependant, contrairement à la déclaration d'une variable d'un type primitif (comme int n ; ), elle ne réserve pas d'emplacement pour un objet de type Point, mais seulement un emplacement pour une référence à un objet de type Point. L'emplacement pour l'objet proprement dit sera alloué sur une demande explicite du programme, en faisant appel à un opérateur unaire nommé new. Ainsi, l'expression :

new Point(); Attention à la présence des parenthèses ()

crée un emplacement pour un objet de type Point et fournit sa référence en résultat. Par exemple, on pourra procéder à cette affectation :

a = new Point() ; Crée d'un objet de type Point et place sa référence dans a

La situation peut être schématisée ainsi :

Pour l'instant, les champs x et y n'ont apparemment pas encore reçu de valeur (on verra plus tard qu'en réalité, ils ont été initialisés par défaut à 0).

Une fois qu'une référence à un objet a été convenablement initialisée (c'est obligatoire, sinon vous avez une erreur de compilation), on peut appliquer n'importe quelle méthode à l'objet correspondant. Par exemple, on pourra appliquer la méthode initialise à l'objet référencé par a, en procédant ainsi :

a.initialise (3, 5) ; Appelle la méthode initialise du type Point
en l'appliquant à l'objet de référence a, et
en lui transmettant les arguments 3 et 5

Si on fait abstraction du préfixe a, cet appel est analogue à un appel classique de fonction tel qu'on le rencontre dans la plupart des langages. Bien entendu, c'est ce préfixe qui va préciser à la méthode sur quel objet elle doit effectivement opérer. Ainsi, l'instruction x = abs de la méthode initialise placera la valeur reçue pour abs (ici 3) dans le champ x de l'objet a.

Exemple

Résultat :

Je suis un point de coordonnees 3 5
Je suis un point de coordonnees 5 5
Je suis un point de coordonnees 6 8




Choix des chapitres Initialisation correcte des objets

Généralités

Dans l'exemple de classe Point du paragraphe précédent, il est nécessaire de recourir à la méthode initialise pour attribuer des valeurs aux champs d'un objet de type Point. Une telle démarche suppose que l'utilisateur de l'objet fera effectivement l'appel voulu au moment opportun. En fait, la notion de constructeur vous permet d'automatiser le mécanisme d'initialisation d'un objet. En outre, cette initialisation ne sera pas limitée à la mise en place de valeurs initiales ; il pourra s'agir de n'importe quelles actions utiles au bon fonctionnement de l'objet.

Un constructeur n'est rien d'autre qu'une méthode, sans valeur de retour, portant le même nom que la classe. Il peut disposer d'un nombre quelconque d'arguments (éventuellement aucun).

Exemple de classe comportant un constructeur

Considérons la classe Point présentée au paragraphe précédent et transformons simplement la méthode initialise en un constructeur en la nommant Point. La définition de notre nouvelle classe se présente alors ainsi :

Comment utiliser cette classe ? Cette fois, une instruction telle que :

Point a = new Point();

ne convient plus : elle serait refusée par le compilateur (soucis de protection, il faut être sûr de bien utiliser l'objet). En effet, à partir du moment où une classe dispose d'un constructeur, il n'est plus possible de créer un objet sans l'appeler. Ici, notre constructeur a besoin de deux arguments. Ceux-ci doivent obligatoirement être fournis lors de la création, par exemple :

Point a = new Point(1, 3);

A titre d'exemple, voici comment on pourrait adapter le programme du paragraphe précédent en utilisant cette nouvelle classe Point :

Choix du chapitre Initialisation par défaut des champs d'un objet

Dans nos précédents exemples, le constructeur initialisait les différents champs privés de l'objet. En fait, contrairement à ce qui se passe pour les variables locales, les champs d'un objet (attributs) sont toujours initialisés par défaut. En outre, il est possible de leur attribuer explicitement une valeur au moment de leur déclaration.

Dès qu'un objet est créé, et avant l'appel du constructeur, ses champs sont initialisés à une valeur par défaut "nulle" ainsi définie :

Type du champ Valeur par défaut

boolean
char
entier (byte, short, int, long)
flottant (float, double)
objet

false
caractère de code nul
0
O.f ou 0.
null

Comme on peut s'y attendre, un champ d'une classe peut très bien être la référence à un objet. Dans ce cas, cette référence est initialisée à une valeur conventionnelle notée null.

Choix du chapitre Initialisation explicite des champs d'un objet

Une variable locale peut être initialisée lors de sa déclaration. Il en va de même pour un champ comme dans l'exemple ci-dessous.

Considérons l'instruction suivante :

A a = new A (...) ;

entraîne successivement :

  1. l'initialisation (explicite) du champ n à la valeur figurant dans sa déclaration, soit 10,
  2. l'initialisation (implicite) des champs non initialisés explicitement soit p de a à 0,
  3. l'exécution des instructions du constructeur.

Comme celle d'une variable locale, une initialisation de champ peut théoriquement comporter non seulement une constante ou une expression constante, mais également n'importe quelle expression (pour peu qu'elle soit calculable au moment voulu).

Notez que si l'on inverse l'ordre des déclarations de n et de p, on obtient une erreur de compilation car n n'est pas encore connu lorsqu'on rencontre l'expression d'initialisation n+2 :

Voici un dernier exemple avec une classe Init qui comporte uniquement des champs avec une initialisation explicite :

Choix du chapitre Les blocs d'initialisation

Un bloc d'initialisation est tout simplement un bloc placé, comme les attributs, à l'extérieur de toute méthode ou constructeur. Par exemple, nous pouvons initialiser la variable e de l'exemple précédent de la façon suivante :

Ici, la classe Init ne fait rien d'autre qu'initialiser ses attributs. La variable e est initialisée à la même valeur que dans l'exemple précédent. En fait, le cas où b vaut 0 n'est pas traité car, s'agissant d'un attribut, e est d'abord initialisée à 0. Si b est égal à 0, aucune autre initialisation de e n'est alors nécessaire.

Les blocs d'initialisation permettent d'effectuer des initialisations plus complexes que les initialiseurs de champs explicite.

Un autre avantage des initialiseurs est qu'ils permettent d'effectuer un traitement quelle que soit la signature du constructeur utilisé. Il est ainsi possible de placer le code d'initialisation commun à tous les constructeurs dans un initialiseur et de ne traiter dans les différents constructeurs que les opérations spécifiques. Si à la création d'un objet, vous n'avez pas besoin de paramètre, vous pouvez éventuellement vous passer de constructeur.

Choix du chapitre Ordre d'initialisation

En définitive, la création d'un objet entraîne toujours, par ordre chronologique, les opérations suivantes :

  1. Tous les champs sont initialisés dans l'ordre de leur déclaration. Ils reçoivent soit des valeurs par défaut, soit une valeur initiale spécifiée.
  2. Tous les blocs d'initialisation sont exécutés, dans l'ordre de leur position, à l'intérieur de la déclaration de classe.
  3. Si la première ligne du constructeur appelle un autre constructeur, ce dernier est exécuté.
  4. Le corps du constructeur est exécuté.

Choix du chapitre Cas des champs déclarés avec l'attribut final

Nous avons déjà vu qu'on pouvait déclarer une variable locale avec l'attribut final. Dans ce cas, sa valeur ne devait être définie qu'une seule fois. Cette possibilité se transpose aux champs d'un objet. Examinons quelques exemples, avant de dégager les règles générales.

Ici, la valeur de n est une constante fixée dans sa déclaration. Notez que tous les objets de type A posséderont un champ n contenant la valeur 10.

Ici, la valeur de n est définie par le constructeur de A. On a affaire à une initialisation tardive, comme pour une variable locale. Ici encore, telle que la classe A a été définie, tous les objets de type A auront un champ n comportant la même valeur.

Considérez maintenant :

Cette fois, les différents objets de type A pourront posséder des valeurs de n différentes.

Comme une variable locale, un champ peut donc être déclaré avec l'attribut final, afin d'imposer qu'il ne soit initialisé qu'une seule fois. Toute tentative de modification ultérieure conduira à une erreur de compilation. Mais, alors qu'une variable locale pouvait être initialisée tardivement n'importe où dans une méthode, un champ déclaré final doit être initialisé au plus tard par le constructeur (ce qui est une bonne précaution).

D'autre part, il n'est pas permis de compter sur l'initialisation par défaut d'un tel champ. Le schéma suivant conduira à une erreur de compilation


Choix des chapitres Retour aux références - Affectation et comparaison d'objets

Nous avons étudié le rôle de l'opérateur d'affectation sur des variables d'un type primitif. Par ailleurs, nous venons de voir qu'il existe des variables de type classe, destinées à contenir des références sur des objets. Comme on peut s'y attendre, ces variables pourront être soumises à des affectations. Mais celles-ci portent sur les références et non sur les objets eux-mêmes, ce qui modife quelque peu la sémantique de l'affectation.

Premier exemple

Supposons que nous disposions d'une classe Point possédant un constructeur à deux arguments entiers et considérons ces instructions :

Point a, b ;
...
a = new Point (3, 5) ;
b = new Point (2, 0) ;

Après leur exécution, on aboutit à la situation ci-contre

Exécutons maintenant l'affectation

a = b ;

 

 

Celle-ci recopie simplement dans a la référence contenue dans b, ce qui nous conduit à la situation ci-contre

 

 

Dorénavant, a et b désignent le même objet, et non pas deux objets de même valeur.

Deuxième exemple

Considérons les instructions suivantes :

Point a, b, c ;
a = new Point (1, 10) ;
b = new Point (2, 20) ;
c = a ; a = b ; b = c ;

Après leur exécution, on aboutit à la situation ci-contre :

Notez bien qu'il n'existe ici que deux objets de type Point et trois variables de type Point (trois références, dont deux de même valeur).

Initialisation de référence et référence nulle

Nous avons déjà vu qu'il n'est pas possible de définir une variable locale d'un type primitif sans l'initialiser. La règle se généralise aux variables locales de type classe. Considérez cet exemple utilisant une classe Point disposant d'une méthode affiche :

Comparaison d'objets

Les opérateurs == et != s'appliquent théoriquement à des objets. Mais comme ils portent sur les références elles-mêmes, leur intérêt est très limité. Ainsi, avec :

Point a, b ;

L'expression a == b est vraie uniquement si a et b font référence à un seul et même objet, et non pas seulement si les valeurs des champs de a et b sont les mêmes.


Choix des chapitres Le ramasse-miettes

Nous avons vu comment un programme peut donner naissance à un objet en recourant à l'opérateur new. A sa rencontre, Java alloue un emplacement mémoire pour l'objet et l'initialise (implicitement, explicitement, par le constructeur).

En revanche, il n'existe aucun opérateur permettant de détruire un objet dont on n'aurait plus besoin. En fait, la démarche employée par Java est un mécanisme de gestion automatique de la mémoire connu sous le nom de ramasse-miettes (en anglais Garbage Collector). Son principe est le suivant :

  1. A tout instant, on connaît le nombre de références à un objet donné. On notera que cela n'est possible que parce que Java gère toujours un objet par référence.
  2. Lorsqu'il n'existe plus aucune référence sur un objet, on est certain que le programme ne pourra plus y accéder. Il est donc possible de libérer l'emplacement correspondant, qui pourra être utilisé pour autre chose. Cependant, pour des questions d'efficacité, Java n'impose pas que ce travail de récupération se fasse immédiatement. En fait, on dit que l'objet est devenu candidat au ramasse-miettes.

Remarque On peut créer un objet sans en conserver la référence, comme dans cet exemple artificiel :

new Point(3,5).affiche() ;

Ici, on crée un objet dont on affiche les coordonnées. Dès la fin de l'instruction, l'objet (qui n'est pas référencé - objet anonyme) devient candidat au ramasse-miettes.

Pour en savoir plus sur les attributs et les méthodes statiques


Choix des chapitres Retour sur les constructeurs

Surdéfinition de constructeurs

Les constructeurs peuvent être surdéfinis comme n'importe quelle autre méthode. Voici un exemple dans lequel nous dotons une classe Point de constructeurs à 0, 1 ou 2 arguments et d'un constructeur de copie :

Choix du chapitre Autoréférence : le mot clé this

Tout attribut ou toute méthode est toujours relative à l'objet courant ; il convient donc de toujours nommer ces attributs ou ces méthodes en les faisant précéder de la référence de l'objet concerné (suivie d'un point). Lorsqu'on invoque, dans une méthode, un champ (attribut ou méthode) de la classe où est définie cette méthode, la référence de l'objet concerné porte le nom de this. La référence this est la référence, au moment de l'exécution, de l'objet sur lequel les instructions s'effectuent. Il n'est pas utile de préciser cette référence mais tout se passe comme si elle était systématiquement indiquée. On dit que la référence this est "implicite". Si la référence this n'était pas implicite, on aurait dû écrire la classe Simple définie à gauche ci-dessous par la façon équivalente qui se trouve à droite.

II peut être indispensable d'utiliser la référence this lorsqu'il y a ambiguïté comme dans l'exemple ci-dessous :

  1. this.x : désigne l'attribut x de la classe et pas la variable locale x de la méthode (qui est ici un constructeur).
  2. x : la variable locale x cache l'attribut x ; en l'absence de la précision donnée par la référence this, l'identificateur x représente la variable locale à la méthode.

Choix du chapitre Appel d'un constructeur au sein d'un autre constructeur

Au sein d'un constructeur, il est possible d'en appeler un autre de la même classe (et portant alors sur l'objet courant). Pour cela, on fait appel au mot clé this qu'on utilise cette fois comme un nom de méthode. Voici un exemple simple d'une classe Point dotée d'un constructeur sans argument qui se contente d'appeler un constructeur à deux arguments avec des coordonnées nulles :

L'appel this(...) doit obligatoirement être la première instruction du constructeur.


Pour en savoir plus sur les transmissions d'arguments dans les méthodes

Exercices

Vous allez construire une classe qui représente un point. Chaque instance (objet) de cette classe appelée Point possède 2 attributs privés x et y qui représente la coordonnée du point. A la création du point il est possible de fixer les coordonnées ou pas. Si on fixe les coordonnées, il faut que se soit à l'aide d'un point déjà existant ou alors en donnant les valeurs séparées de x et de y. Dans le cas où on ne fixe pas de coordonnées, cela correspond au point se situant à l'origine des axes. Prévoyez deux méthodes(surdéfinies) change qui permettent de modifier les coordonnées du point, soit en donnant les nouvelles valeurs de x et de y, soit en donnant un nouveau point. De plus, prévoyer deux méthodes qui permettent de récupérer la valeur d'un attribut, avec respectivement : getX qui renvoie la valeur de x et getY qui renvoie la valeur de y. Enfin, prévoyez une méthode equals qui détermine si les coordonnées du point passé en argument est égal aux coordonnées du point qui exécute la méthode. Le résultat du test est renvoyé par la méthode. Attention les coordonnées devront toujours être testées. Les valeurs possibles pour x et pour y doivent être comprises entre 0 et 1000. Si on dépasse ces valeurs, le point résultant deviendra le point origine. Ce principe sera vrai pour les classes suivantes Cercle et Carré.

Pour chacune de ces méthodes, vous déterminerez les types à utiliser. Vous testerez ce programme, par le rajout d'une classe principale avec sa méthode principale.

Construire une classe Cercle qui dispose de 2 attributs : d'une part, l'attribut centre (de type Point) qui fixe les coordonnées du centre du cercle, d'autre part, l'attribut rayon qui aura toujours une valeur fixe de 50. Plusieurs types de création seront possible, soit par rapport à un point, soit par rapport aux coordonnées séparées, soit par rapport à un autre cercle ou soit sans aucune précision en sachant que le cercle se trouvera alors à l'origine. A l'exemple de la classe Point, prévoyez 2 méthodes surdéfinies change qui permettent de modifier les coordonnées du centre du cercle. De même prévoyez une méthode equals qui permet de contrôler si les deux cercles possèdent les mêmes attributs. Enfin, prévoyez une méthode getCentre qui renvoie un point représentant les coordonnées du centre du cercle. Attention, il ne faut pas que la modification éventuelle des coordonnées de ce point à l'extérieur de la classe Cercle ait une répercussion sur l'attribut centre de cette même classe. Vous rajouterez cette classe sur le programme précédent.
Pour terminer, contruisez une classe Carré qui possède également deux attributs : d'une part l'attribut centre qui est identique à Cercle, d'autre part, l'attribut côté qui a une valeur par défaut de 50, mais il est possible de fixer une autre valeur à la construction, par contre une fois que la valeur est donnée, il n'est plus possible de la modifier. Prévoyez tous les types de constructeurs. Les constructeurs seront les seules méthodes de votre classe.

Récupération des sources des corrections


Choix des chapitres Chaînes de caractères

Les chaînes sont des suites de caractères (par exemple: "hello"). Java ne contient pas de type chaîne prédéfini. En revanche, la bibliothèque Java standard contient une classe prédéfinie appelée String. Chaque chaîne est un objet (une référence) de la classe String. Comme toute déclaration d'une variable objet, l'instruction :

String ch;

déclare que ch est destinée à contenir une référence à un objet de type String. Par ailleur, la notation "bonjour" désigne en fait un objet de type String (ou, pour être plus précis, sa référence), créé automatiquement par le compilateur. Ainsi, avec :

ch = "bonjour";

on abouti à une situation qu'on peut schématiser ainsi :

 

La classe String dispose de deux constructeurs, l'un sans argument créant une chaîne vide, l'autre avec un argument de type String qui crée une copie :

Il ne faut pas perdre de vue qu'on manipule constamment des références à des objets et que celles-ci peuvent voir leur valeur évoluer au fil du programme. Considérons l'instruction suivante :

Après l'exécution, nous obtenons la situation de droite

 

Exécutons maintenant ces instructions :

ch = ch1;
ch1 = ch2;
ch2 = ch;

Nous obtenons une permutation des références ch1 et ch2, par contre les deux objets de type chaîne n'ont pas été modifiés.

Choix du chapitre Concaténation

Java autorise l'emploi du signe + et += pour joindre (concaténer) deux chaînes.

Lorsque vous concaténez une chaîne et une valeur qui n'est pas une chaîne, cette valeur est convertie en chaîne.

Choix du chapitre Manipulation des chaînes

Il existe un certain nombre de méthodes intégrées à la classe String pour permettre différentes manipulations sur les chaînes :

  1. length : permet de connaître la longueur d'une chaîne
  2. charAt : renvoie le caractère (Unicode) situé à la position désirée (Attention, la première position est 0).
  3. indexOf : renvoie la position à partir du début de la chaîne la première occurence d'un caractère donné. (Attention, là aussi la première position est 0. Si le caractère n'existe pas dans la chaîne, la valeur retournée est -1).
  4. substring : permet de créer une nouvelle chaîne un extrayant de la chaîne courante tous les caractères compris entre deux positions données (la première incluse, la second exclue).

Attention : Les objets de la classe String sont inaltérables. Il n'existe donc pas de méthodes permettent de modifier un caractère dans une chaîne existante. Il faut alors utiliser une sous-chaîne.

Le fait que le compilateur construise un objet de type String lorsqu'il rencontre une constante chaîne (entre guillemets) signifie que vous pouvez parfaitement utiliser les méthodes de String avec cette chaîne. Vous pouvez écrire :

n = "Monsieur".length(); n est initialisé avec la taille de la chaîne (ici 8)

Choix du chapitre Test d'égalité des chaînes

Attention : N'employez pas l'opérateur == pour tester l'égalité de deux chaînes! Cet opérateur détermine seulement si les chaînes sont stockées au même emplacement (même référence - adresse de la variable). Il est évident que si deux chaînes se trouvent à la même adresse, elles doivent être égales. Mais des copies de chaînes identiques peuvent être stockées à des emplacements différents dans la mémoire.

La méthode equals

Fort heureusement, la classe String dispose d'une méthode equals qui compare le contenu de deux chaînes et renvoie true si elles sont égales, et false dans le cas contaire.

La méthode equalsIgnoreCase

La méthode equalsIgnoreCase effectue la même comparaison, mais sans distinguer les majuscules et les minuscules.

Choix du chapitre Passage en majuscules ou en minuscules

La méthode toLowerCase crée une nouvelle chaîne en remplaçant toutes les majuscules par leur équivalent en minuscules (lorsque celui-ci existe). La méthode toUpperCase crée une nouvelle chaîne en remplaçant toutes les minuscules par leur équivalent en majuscules.

Conversions entre chaînes et types primitifs

Conversion d'un type primitif en une chaîne

Nous avons déjà vu comment l'opérateur "+" effectuait un "formatage" en convertissant n'importe quel type primitif en une chaîne. Vous pouvez effectuer directement de telles conversions, en utilisant la méthode valueOf de la classe String.

La méthode valueOf peut recevoir un argument de type quelconque et pas seulement de type entier.

Conversion d'une chaîne en un type primitif

On peut réaliser les conversions inverses des précédentes. Il faut alors recourir à une méthode de la classe enveloppe associée au type primitif : Integer pour int, Double pour double, etc...

Par exemple, pour convertir une chaîne en un entier de type int, on utilisera la méthode statique parseInt de la classe enveloppe Integer, comme ceci :

D'une manière générale, nous disposons des méthodes pour chaque type primitif comme cela est précisé ci-dessus.


Choix des chapitres Tableaux

Introduction

Les tableaux permettent de regrouper une suite de variables de même type. Chaque élément du tableau est une variable que vous pouvez utiliser comme n'importe quelle variable de ce type. Il est possible de définir des tableaux pour les types primaires ou les classes. Cependant, tous les éléments doivent être du même type. En Java, les tableaux sont des objets. Pour pouvoir utiliser un tableau, vous devez respecter les trois étapes suivantes :

  1. Déclarer une variable pour référencer le tableau.
  2. Créer un objet de type tableau en initialisant la référence à ce tableau.
  3. Utiliser et manipuler les éléments de ce tableau.

Considérons cette déclaration :

int t[];

Elle précise que t est destiné à contenir la référence à un tableau d'entiers. Vous constatez qu'aucune dimension ne figure dans cette déclaration et, pour l'instant, aucune valeur n'a été attribuée à t.

On crée un tableau comme on crée un objet, c'est à dire en utilisant l'opérateur new. On précise à la fois le type des éléments, ainsi que leur nombre (dimension du tableau).

t = new int[5]; // t fait référence à un tableau de 5 entiers

Cette instruction alloue l'emplacement nécessaire à un tableau de 5 éléments de type int et place la référence dans t. Les 5 éléments sont initialisés par défaut (comme tous les champs d'un objet) à une valeur nulle (0 pour int).

Choix du chapitre Déclaration de tableaux

La déclaration d'une référence à un tableau précise donc simplement le type des éléments du tableau. Elle peut prendre deux formes différentes.

Création d'un tableau

Création par l'opérateur new

Après avoir déclaré la variable tableau, il faut allouer les éléments de ce tableau. On utilise pour ce faire l'opérateur new. Vous avez déjà mis en oeuvre cet opérateur pour créer des objets. A ce niveau, il est obligatoire de préciser le nombre d'éléments du tableau. Ces derniers sont initialisés automatiquement en fonction du type de tableau (0 pour les numériques, '\0' pour les caractères, false pour les boolean et null pour tout le reste).

Utilisation d'un initialiseur

Lors de la déclaration d'une référence, il est possible de fournir la valeur à chacune des cases du tableau. Il suffit de donner la liste entre accolades sans utiliser l'opérateur new. Java se sert du nombre de valeurs figurant dans l'initialiseur pour en déduire la taille du tableau à créer.

Choix du chapitre Utilisation d'un tableau

Accès individuel aux éléments d'un tableau

On peut manipuler un élément de tableau comme on le ferait avec n'importe quelle variable ou n'importe quel objet de ses éléments. On désigne un élément particulier en plaçant entre crochets, à la suite du nom du tableau, une expression entière nommée indice indiquant sa position. Le premier élément correspond à l'indice 0 (et non 1).

Affectation de tableaux

Java permet aussi de manipuler globalement des tableaux, par le biais d'affectations de leurs références.

Exécutons maintenant l'affectation :

t1 = t2; // la référence contenue dans t2 est recopiée dans t1


Nous aboutissons à la situation présentée ci-contre. Dorénavant, t1 et t2 désigne le même tableau. Ainsi, avec :

t1[1] = 5;
System.out.println (t2[1]);

on obtiendra l'affichage de la valeur 5, et non 11.

Si l'objet que constitue le tableau de trois entiers anciennement désigné par t1 n'est plus référencé par ailleurs, il deviendra candidat au ramasse-miettes.

Il est très important de noter que l'affectation de références de tableaux n'entraine aucune recopie des valeurs des éléments du tableau. On retrouve exactement le même phénomène que pour l'affectation d'objets.

Choix du chapitre Nombre d'éléments d'un tableau

Pour connaître le nombre d'éléments d'un tableau, vous pouvez utiliser l'attribut public length. Cet attribut peut être utilisé pour tous les types de tableaux. Considérez le code suivant :

int tab[] = new int [10];
int taille = tab.length; // taille est égal à 10

Après avoir déclaré un tableau, utilisez length pour récupérer le nombre d'éléments du tableau (ici 10).

Le mot length désigne en quelque sorte un attribut du tableau, attribut que l'on peut d'ailleurs uniquement lire ; ce n'est pas un attribut au même titre que les attributs d'une classe. Remarquons qu'il n'y a pas d'autre attribut pour une variable de type tableau.

Manipuler un tableau d'objets

Attention : Pour définir un tableau d'objets, il faut commencer par définir un tableau de références, puis, pour chaque référence, créer l'objet associé. Un tableau d'objets est en fait un tableau de références, et chaque référence pointe vers l'objet associé.

Le résultat est le suivant :

Point : 1, 2
Point : 4, 5
Point : 8, 9


Il est possible d'avoir une écriture plus concise de la classe TabPoint en utilisant l'initialiseur de tableau.

Choix du chapitre Tableau en argument

Lorsqu'on transmet un nom de tableau en argument d'une méthode, on transmet en fait (une copie) la référence au tableau. La méthode agit directement sur le tableau concerné et non sur une copie. Ce principe est d'ailleurs utilisé pour tous les types d'objets. D'une façon générale, lorsqu'on transmet un objet en argument d'une méthode, on transmet toujours sa référence.

Le résultat est :

Salut
bonjour
Hello
*
*
*


Tableau anonyme

Il est même possible d'initialiser un tableau anonyme :

new int[] { 2, 3, 5, 7, 11, 13 }

Cette expression alloue un nouveau tableau et le remplit avec les valeurs spécifiées entre parenthèses. Elle détermine le nombre de valeurs fournies et donne au tableau le même nombre d'éléments. Cette syntaxe est employée lorsqu'on désire passer un tableau à une méthode sans créer pour cela une variable locale. L'exemple :

printLabels(new String[] { "Région", "Ventes" });

est un raccourci pour :

String[] titres = { "Région", "Ventes"};
printLabels(titres);

Exercices

Soit la déclaration suivante dans la méthode main :
String bonjour = "salut la compagnie";

  1. Continuer le programme pour que le message bonjour s'affiche en majuscule.
  2. Compléter le programme en ajoutant une méthode qui permet de mettre uniquement la première lettre de chaque mot d'une chaîne de caractères en majuscule. Vous testerez cette méthode avec la chaîne bonjour.
  1. faire un programme qui utilise un tableau ordonné des 26 lettres de l'alphabet en minuscule. L'initialisation du tableau doit se faire en dehors de la déclaration (par une itérative). Le programme devra ensuite afficher le contenu du tableau dans l'ordre inverse.
  2. Faire un programme convivial (à l'aide d'un menu sommaire) qui permet de compléter un tableau d'entier de 10 cases, d'afficher son contenu, d'enlever des valeurs (le menu comporte 4 options :
    1. ajouter une valeur,
    2. enlever le dernière valeur,
    3. afficher le contenu du tableau
    4. et quitter le programme.

    Au départ le tableau doit être vide (valeur 0 dans toutes les cases). L'affichage lorsqu'il est demandé doit être sous la forme de, par exemple <5, 3> (on remarque dans cet affichage que le tableau dispose de deux valeurs entières respectivement 5 et 3). Il faut protéger votre programme par les malversation de l'utilisateur, en effet il ne doit pas y avoir plus de dix valeurs dans le tableau même si l'opérateur, sans le faire exprès, veut en rajouter d'autre.

  3. Définir une méthode appelée makeRange() qui admet deux entiers, une limite inférieure et une limite supérieure, et crée un tableau qui contient tous les entiers compris entre ces deux entiers (à l'inclusion des deux limites). Par exemple, lorsque j'écris ceci :
    makeRange(3, 7); le résultat doit-être : [ 3 4 5 6 7 ]
  1. Fabriquez une méthode qui permet d'afficher un tableau de chaînes de caractères, l'affichage de la chaîne sera systématiquement exprimé en lettres minuscules. A titre d'exemple, vous passerez à la méthode le tableau semaine ci-dessous :
    String semaine[] = {"Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"};
  2. Fabriquer une méthode qui admet comme argument un tableau de chaînes de caractères et qui retourne également un tableau de chaînes de caractères. Toutefois, toutes les chaînes du nouveau tableau seront systématiquement écrites en majuscules. Vous profiterez de cette méthode pour tester le passage de tableaux anonymes. Pour tester convenablement, vous afficherez le tableau original et le tableau final (sauf pour le tableau anonyme).
A l'aide de la classe System, réalisez un programme qui affiche le nom de l'utilisateur et le nom du système d'exploitation utilisés sur l'ordinateur où se trouve le programme.

Récupération des sources des corrections