Vous êtes sur la page 1sur 6

Institut Universitaire De la Côte Programmation en java

Chapitre 3 : Techniques fournies par java

SPECIALITE : 3IL II A et B

MATIERE : Programmation en Java

Enseignant : FUSHE Serge Alain Année scolaire 2017/2018

I. Fonction récursive
Nous avons précédemment précisé qu’il était possible qu’une méthode appelle dans son bloc une
autre méthode. Il est également possible qu’elle s’appelle elle-même. A priori, l’intérêt peut
paraître limité, mais la récursivité permet d’effectuer certains calculs mathématiques, en
particulier avec les suites définies par récurrence. Par exemple, on peut calculer la factorielle d’un
nombre entier en utilisant la récursivité. Pour cela, on peut procéder de la manière suivante, par
exemple si l’on souhaite calculer la factorielle de 4 :
4! = 4 × 3!
3! = 3 × 2!
2! = 2 × 1!
1! = 0!
0! = 1
Dans ce calcul, on constate une récurrence : pour calculer 4!, il suffit de calculer 3!, puis 2!, et ainsi
de suite, jusqu’à un cas directement résolu (sans expression récursive, le cas de base). Nous
pouvons alors créer une méthode calculeFactorielle ayant comme paramètre un entier n, et
renvoyant l’entier n!
On voit qu’il suffit que la fonction s’appelle elle-même suivant ce même principe pour calculer
facilement la factorielle de n’importe quel nombre entier. Cependant, arrivé au calcul de 0!, il est
nécessaire que la fonction renvoie directement la valeur 1, sans faire d’appel récursif. En d’autres
mots, il ne faut pas que l’appel de calculeFactorielle(0) provoque l’appel de calculeFactorielle(-1).
Il renvoie directement 1, afin de ne pas provoquer une infinité d’appels :

[Date] 1
Institut Universitaire De la Côte Programmation en java

II. Exception
Lors de l’écriture d’un programme, la prise en compte d’erreurs prend une place très importante
si l’on souhaite écrire un programme robuste. Par exemple, la simple ouverture d’un fichier
peut provoquer beaucoup d’erreurs telles que l’inexistence du fichier, un mauvais format, une
interdiction d’accès, une erreur de connexion au périphérique, … Pour que notre programme soit
robuste, il faut que toutes les erreurs possibles soient détectées et traitées.
Certains langages de programmation, dont le langage Java, proposent un mécanisme de prise
en compte des erreurs, fondé sur la notion d’exception. Une exception est un objet qui peut être
émis par une méthode si un événement d’ordre “exceptionnel” (les erreurs rentrent dans cette
catégorie) se produit. La méthode en question ne renvoie alors pas de valeur de retour, mais émet
une exception expliquant la cause de cette émission. La propagation d’une émission se déroule
selon les étapes suivantes :
 Une exception est générée à l’intérieur d’une méthode ;
 Si la méthode prévoit un traitement de cette exception, on va au point , sinon au point
;
 L’exception est renvoyée à la méthode ayant appelé la méthode courante, on retourne au
point ;
 L’exception est traitée et le programme reprend son cours après le traitement de
l’exception.
1. Les Bloc try-catch-finally
i. Instructions try et catch
Un bloc try est destiné à être protégé, gardé contre toute exception susceptible de survenir. Juste
derrière un bloc try, il faut mettre un bloc catch qui sert de gestionnaire d’exception. Le paramètre
de l’instruction catch indique le type et le nom de l’instance de l’exception gérée.
class ExcepDiv0 {
public static void main(String args[]) {
try {
int d = 0;
int a = 42 / d;
} catch (ArithmeticException e) {
System.out.println("Div par zero");
}}}
La portée d’un bloc catch est restreinte aux instructions du bloc try immédiatement précédent.
ii. Instructions catch multiples
On peut gérer plusieurs exceptions à la suite l’une de l’autre. Lorsqu’une exception survient,
l’environnement d’exécution inspecte les instructions catch les unes après les autres, dans l’ordre
où elles ont été écrites. Il faut donc mettre les exceptions les plus spécifiques d’abord.
Instruction throw
Elle permet de générer une exception, via un appel de la forme throw ThrowableInstance ; Cette
instance peut être crée par un new ou être une instance d’une exception déjà existante. Le flux
d’exécution est alors stoppé et le bloc try immédiatement englobant est inspecté, afin de voir s’il
possède une instruction catch correspondante à l’instance générée. Si ce n’est pas le cas, le 2ième
bloc try englobant est inspecté ; et ainsi de suite.

[Date] 2
Institut Universitaire De la Côte Programmation en java

Exemple
class ThrowDemo {
static void demoproc() {
try {
throw new NullPointerException("demo");
} catch (NullPointerException e2) {
System.out.print("attrapee ds demoproc()");
throw e2;
}
public static void main(String args[]) {
try {
demoproc();
} catch(NullPointerException e1) {
System.out.print("attrapee ds main()");
}}}
Instruction throws
Si une méthode est susceptible de générer une exception qu’elle ne gère pas, elle doit le
spécifier, de façon que ceux qui l’appellent puissent se prémunir contre l’exception. L’instruction
throws est utilisée pour spécifier la liste des exceptions qu’une méthode est susceptible de
générer. Pour la plupart des sous-classes d’Exception, le compilateur forcera à déclarer quels
types d’exception peuvent être générées (sinon, le programme ne compile pas). Cette règle ne
s’applique pas à Error, RuntimeException ou à leurs sous-classes.
L’exemple suivant ne compilera pas :
class ThrowsDemo1 {
static void proc() {
System.out.println("dans proc()");
throw new IllegalAccessException("demo");
}
public static void main(String args[]) {
proc();
}}
ii. Instruction finally
Un bloc finally est toujours exécuté, qu’une exception ait été générée ou non. Il est exécuté avant
l’instruction suivant le bloc try précédent. Si le bloc try précédent contient un return, le bloc finally
est exécuté avant que la méthode ne retourne. Ceci peut être pratique pour fermer des fichiers
ouverts et pour libérer diverses ressources. Le bloc finally est optionnel.
III. Mise en œuvre de la généricité
1. Classes et méthodes génériques
Il est parfois utile de définir des classes paramétrées par un type de données (ou une classe).
Par exemple, dans le package java.util, de nombreuses classes sont génériques et notamment
les classes représentant des ensembles (Vector, ArrayList, etc.). Ces classes sont génériques dans
le sens où elles prennent en paramètre un type (classe ou interface) quelconque E. E est en
quelque sorte une variable qui peut prendre comme valeur un type de donné. Ceci se note comme
suit, en prenant l’exempe de java.util.ArrayList :
package java.util ;
public class ArrayList<E>
extends AbstractList<E>
implements List<E>, ...

[Date] 3
Institut Universitaire De la Côte Programmation en java

{
...
public E set(int index, E element) {
...
}
public boolean add(E e) {
...
}
...
}
Nous pouvons remarquer que le type passé en paramètre est noté entre chevrons (ex : <E>), et
qu’il peut ensuite être réutilisé dans le corps de la classe, par des méthodes (ex : la méthode set
renvoie un élément de classe E). Il est possible de définir des contraintes sur le type passé en
paramètre, comme par exemple une contrainte de type extends:
public class SortedList<T extends Comparable<T>> {
...
}
Ceci signifie que la classe SortedList (liste ordonnée que nous voulons définir) est paramé-
trée par le type T qui doit être un type dérivé (par héritage ou interfaçage) de Comparable<T>.
En bref, nous définissons une liste ordonnée d’éléments comparables entre eux (pour pouvoir
les trier), grâce à la méthode int compareTo(T o) de l’interface Comparable qui permet de
comparer un Comparable à un élément de type T.
IV. Classes enveloppes (wrappers) des types élémentaires
Comme on a pu le voir, les objets (instances d’une classe) et les variables (d’un type primitif)
ne se comportent pas exactement de la même manière. Par exemple :
 l’affectation porte sur l’adresse d’un objet, sur la valeur d’une variable,
 les règles de compatibilité de l’affectation se fondent sur une hiérarchie d’héritage pour
les objets, sur une hiérarchie de type pour les variables,
 le polymorphisme ne s’applique qu’aux objets,
 comme nous le verrons par la suite au semestre 2, les collections ne sont définies que pour
des éléments qui sont des objets.
Les classes enveloppes (wrappers en anglais) vont permettre de manipuler les types primitifs
comme des objets. Plus précisément, il existe des classes nommées Boolean, Character, Byte, Short,
Integer, Long, Float et Double qui encapsulent des valeurs du type primitif correspondant
(boolean, char, byte, short, int, long, float et double).
1. Construction et accès aux valeurs
Toutes les classes enveloppes disposent d’un constructeur recevant un argument d’un type
primitif :
Integer nObj = new Integer (12) ; // nObj contient la référence à un objet
// de type Integer encapsulant la valeur 12
Double xObj= new Double (5.25) ; // xObj contient la référence à un objet
// de type Double encapsulant la valeur 5.25
Elles disposent toutes d’une méthode de la forme xxxValue (xxx représentant le nom du type
primitif) qui permet de retrouver la valeur dans le type primitif correspondant:

[Date] 4
Institut Universitaire De la Côte Programmation en java

int n = nObj.intValue() ; // n contient 12


double x = xObj.doubleValue() ; // x contient 5.25
Nous verrons un peu plus loin que ces instructions peuvent être abrégées grâce aux facilités
dites de "boxing/unboxing" automatiques introduites par le JDK 5.0. Ces classes enveloppes sont
finales (on ne peut pas créer de classes dérivées) et inaltérables puisque les valeurs qu’elles
encapsulent ne sont pas modifiables. Les six classes à caractère numérique dérivent de la classe
Number.
2. Comparaisons avec la méthode equals
On n’oubliera pas que l’opérateur == appliqué à des objets se contente d’en comparer les
adresses. Ainsi, avec:
Integer nObj1 = new Integer (5);
Integer nObj2 = new Integer (5) ;
Il est probable que l’expression nObj1 == nObj2 aura la valeur false. Notez que cela n’est
toutefois pas certain car rien n’interdit au compilateur de n’implémenter qu’une seule fois
des valeurs identiques. Autrement dit, dans le cas présent, il peut très bien ne créer qu’un
seul objet de type Integer contenant la valeur 5.
En revanche, la méthode equals a bien été redéfinie dans les classes enveloppes, de manière à
comparer effectivement les valeurs correspondantes. Ici, l’expression nObj1.equals(nObj2)
aura toujours la valeur true.
3. Emballage et déballage automatique (JDK 5.0)
a. Présentation
Avant le JDK 5.0, la manipulation des classes enveloppes devait se faire comme on vient de voir, à
l’aide d’appels explicites à des méthodes :
 le constructeur, pour créer un objet enveloppe à partir d’un type primitif ; on parle parfois
d’emballage (boxing en anglais) ;
 une méthode de la forme xxxValue pour accéder à la valeur encapsulée dans l’objet ; on
parle alors de déballage (unboxing en anglais).
Le JDK 5.0 a introduit des possibilités de conversions mises en place automatiquement par le
compilateur ; on parle alors d’emballage ou de déballage automatique (autoboxing en
anglais). Ainsi, les instructions du paragraphe 1 pourront s’écrire:
Integer nObj = 12 // au lieu de : = new Integer (12) ;
Double xObj= 5.25 ; // au lieu de : = new Double (5.25) ;
.....
int n = nObj ; // au lieu de : = nObj.intValue() ;
double x = xObj // au lieu de = xObj.doubleValue() ;
Dans la première affectation, la valeur entière 12 est convertie en objet de type Integer et sa
référence est affectée à nObj. De même, dans la troisième affectation (n = nObj), la valeur
entière encapsulée dans nObj est affectée à n.
Dans des expressions arithmétiques, ces conversions s’ajoutent aux conversions implicites
usuelles comme dans ces exemples où l’on suppose que nObj1 et nObj2 sont de type Integer :
nObj1 = nObj2 + 2 ; // nObj2 est converti en int auquel on ajoute 2 ;
// le résultat est converti en Integer nObj1++ ; // nObj1 est converti en int, auquel on ajoute 1 ;
// le résultat est converti en Integer

[Date] 5
Institut Universitaire De la Côte Programmation en java

On notera toutefois que de telles opérations arithmétiques, effectuées apparemment directement


sur des types enveloppes, peuvent s’avérer relativement peu efficaces compte tenu des
conversions supplémentaires qu’elles entraînent lors de l’exécution du code.
4. Limitations
Ces conversions ne sont toutefois possibles qu’entre un type enveloppe et son type primitif
correspondant. Ainsi, cette instruction est incorrecte (comme le serait l’affectation double x = 5) :
Double xObj = 5 ; // 5, de type int, ne peut pas être converti en Double
De même, ceci est incorrect
Integer nObj ;
Double xObj = nObj ; // erreur de compilation
En effet, il n’existe pas de conversion implicite de Integer en Double car il n’existe aucune
relation d’héritage entre les deux classes (tout au plus, héritent-elles toutes les deux de
Integer).
5. Conséquences sur la surdéfinition des méthodes
Les règles de recherche d’une méthode surdéfinie ont dû être complétées par le JDK 5.0 pour
tenir compte des possibilités d’emballage/déballage automatique, comme elles l’ont également
été pour tenir compte de l’ellipse. Dans tous les cas, la compatibilité avec les versions
précédentes de Java est assurée, ainsi un ancien code continue d’avoir le même comportement
avec les nouvelles versions de Java.
Depuis le JDK 5.0, la recherche d’une méthode surdéfinie se fait donc tout d’abord sans tenir
compte, ni des possibilités d’emballage/déballage automatique, ni de l’ellispe. Si aucune
méthode ne convient (et uniquement dans ce cas), on poursuit la recherche en acceptant les
conversions d’emballage/déballage automatique. Si une seule méthode convient, elle est
choisie ; si plusieurs conviennent, il y a (comme d’habitude) erreur. Enfin, si aucune méthode
ne convient, on effectue une nouvelle recherche en tenant compte de l’ellipse.
Par exemple, en définissant simultanément :
void f (double x) { ..... }
void f (Double xObj) { ..... }
on pourra distinguer convenablement entre un argument de type double et un argument de
type Double :
double x1 = 8.5 ; Double xO1 = 5.25 ;
f (x1) ; // appel f (double)
f( xO1) ; // appel f (Double)
Néanmoins, la distinction entre int et Integer ne sera pas pour autant possible :
int n1 = 2 ; Integer n01 = 5 ;
f (n1) ; // appel de f(double) après conversion de int en double
f (nO1) ; // erreur : pas de conversion implicite de Integer en double ou en Double

[Date] 6

Vous aimerez peut-être aussi