Toutes les données manipulées dans un programme Java sont
typées statiquement. Il en est ainsi des constantes,
des variables et des expressions.
En particulier, la déclaration de toute variable suppose la
spécification de son type que le compilateur utilisera pour
vérifier la cohérence des différentes opérations la concernant
et déterminer le type des expressions limpliquant.
Toutes les vérifications et le typage des expressions sont réalisées
par le compilateur et donc avant toute exécution : on parle de
typage statique.
La notion de type présente en Java différents aspects ;
T est un type, alors
T [ ] est un type
(tableau sur le type T) ;Les types références sont utilisés pour typer des expressions, et en particulier des variables de type référence) et permettent de désigner des objets (c'est-à-dire des instances de classes ou de tableaux). Ces types jouent un rôle analogue à celui des pointeurs, du langage C par exemple, en ce sens que les grandeurs de ces types sont des adresses en mémoire permettant d'accéder à ces grandeurs.
Les méthodes (c'est-à-dire les fonctions) sont par ailleurs elles-aussi typées et leur type prend en compte d'une part le type du résultat et le type des différents paramètres. Il peut s'avérer commode de considérer le type d'une méthode comme un type fonctionnel (au sens de langages comme CAML).
Ainsi, une fonction telles que
T0 fonc(T1 v1, T2 v2, ..., Tn vn0 { ... }
a comme type
T1 x T2 x ... x Tn → T0
Comme c'est le cas pour les variables, le typage des fonctions est statique. Comme nous le verrons, il s'agit là d'un point sensible en raison de la possibilité donnée par le langage
Le typage statique des fonctions signifie que le type de la méthode qui sera
invoquée lors de l'exécution est déterminé dès la compilation.
Cependant, le choix de la méthode qui sera effectivement invoquée
à l'exécution n'est réalisé qu'à l'exécution.
Nous aurons bien évidemment l'occasion d'y revenir.
On retrouve tout d'abord, comme dans tout langage de programmation, la notion d'identificateur qui est une suite de longueur quelconque de lettres et chiffres Unicode, le premier caractère étant une lettre.
On peut distinguer différentes catégories de variables selon le type qui leur est associé .
Ce qui caractérise une telle variable, c'est que sa déclaration
entraîne implicitement l'allocation de l'espace mémoire
nécessaire à mémoriser une grandeur du type associé.
Ainsi, la déclaration d'une variable entière
int n; alloue les 32 bits
utilisés pour coder les entiers suceptibles d'être affectés à la variable
n.
Dans certaines circonstances (variable d'instance), une valeur par défaut
sera affectée à une telle variable lors de sa définition.
Toute variable dont le type associé n'est pas un type primitif est qualifiée de type référence. Le rôle d'une telle variable sera de référencer/repérer/adresser une zone mémoire contenant un objet du type correspondant.
Concernant ces variables de référence, il ne faut pas perdre de vue queleur déclaration ne donne lieu à aucune allocation en mémoire susceptible de contenir une grandeur du type référencé. Seul est alloué l'espace nécessaire à la mémorisation d'une adresse.
L'espace correspondant, fonction de la taille du type référencé, devra être alloué explicitement au travers de
l'opérateur spécifique new
qui réalise de fait la construction d'un objet du type donné (instanciation) et
renvoie une adresse permettant de localiser et don accéder à l'objet créé.
Dans le cas de variables d'instance, les variables de type référence
sont initialisées à la valeur
null :
toute tentative d'accès au travers d'une variable ayant cette «valeur»
(accès à une variable d'instance ou invocation de méthode) provoque la levée de
l'exception
NullPointerException
(un peu comme la tentative d'accès à
la mémoire via un pointeur mal initialisé provoque l'envoi du signal
SIGSEGV à un processus Unix et entraîne son interruption)
--> cat Null.java
class Null1 {
int a;
int getValeur(){return a;}
}
class Null2{
Null1 nl;
}
public class Null{
public static void main(String arg[]){
Null2 obj = new Null2();
System.out.println(obj.nl);
System.out.println(obj.nl.getValeur());
}
}
--> java Null
null
Exception in thread "main" java.lang.NullPointerException
at Null.main(null.java:12)
-->
|
null) comme ci-après :
--> cat NonDefini.java
class NonDefini1{
int a;
int getValeur(){return a;}
}
public class NonDefini{
public static void main(String arg[]){
NonDefini1 obj;
System.out.println(obj.getValeur());
}
}
--> javac NonDefini.java
NonDefini.java:8: variable obj might not have been initialized
System.out.println(obj.getValeur());
1 error
-->
|
Parmi les variables de réference, il est courant de distinguer celles correspondant à des tableaux, du fait de la spécificité de leur déclaration.
Null1[ ] tabNull1; tabNull1 = new Null1 [10] ; |
tabNull1 est déclarée comme
une variable référençant un tableau d'objets de type
Null1. La variable est ,
lors de sa définition, initialisée à
null :
aucune allocation n'est réalisée lors de la déclaration.
new :
chacun des 10 éléments du tableau est lui-même initialisé à la valeur
null, car le type
Null1 est un type
référence (s'il s'agissait d'un tableau d'objets de type primitif
la valeur serait 0).
Il est possible d'accéder à la taille d'un tableau au moyen de
la variable length que
tout tableau encapsule :
--> cat Tableau.java
class Tableau {
static int[][] t = new int [3][4];
public static void main(String [] arg){
System.out.println(t.length);
System.out.println(t[0].length);
}
}
--> java Tableau
3 // un tableau [3][4] est un tableau de 3 tableaux de 4 éléments chacun
4
-->
|
Il ressort de ce qui précède que les variables de types primitifs et les références ne sont pas traités de manière uniforme. Une variable de type primitif (et donc une valeur de ce type) est incompatible avec un type référence, ce qui en certaines circonstances constitue un handicap. Des passerelles sont fournies dans le package de base du langage sous la forme des classes constituant des enveloppes («wrappers») :
IntegerLongFloatDouble
Une expression de la forme Integer(1000)
renvoie une référence sur un objet encapsulant un entier de valeur 1000
dont la valeur peut être obtenue par invocation de la méthode
int intValue()
sur la référence de l'objet.