Question : Qu'est-ce qui cause un NullPointerException
(NPE) ?
Comme vous devez le savoir, les types Java sont divisés en types primitifs ( boolean
, int
etc.) et types de référence . Les types de référence en Java vous permettent d'utiliser la valeur spéciale null
ce qui est la façon Java de dire "sans objet".
A NullPointerException
est lancée au moment de l'exécution chaque fois que votre programme tente d'utiliser un fichier de type null
comme si c'était une vraie référence. Par exemple, si vous écrivez ceci :
public class Test {
public static void main(String[] args) {
String foo = null;
int length = foo.length(); // HERE
}
}
la déclaration étiquetée "ICI" va tenter d'exécuter les length()
sur un null
et cela déclenchera un NullPointerException
.
Il y a de nombreuses façons d'utiliser un null
qui donnera lieu à une NullPointerException
. En fait, les seules choses que vous peut faire avec un null
sans provoquer un NPE sont :
- l'affecter à une variable de référence ou la lire à partir d'une variable de référence,
- l'assigner à un élément du tableau ou le lire à partir d'un élément du tableau (à condition que la référence au tableau elle-même ne soit pas nulle !)
- le passer en tant que paramètre ou le retourner en tant que résultat, ou encore
- le tester en utilisant le
==
o !=
opérateurs, ou instanceof
.
Question : Comment lire le suivi de pile NPE ?
Supposons que je compile et exécute le programme ci-dessus :
$ javac Test.java
$ java Test
Exception in thread "main" java.lang.NullPointerException
at Test.main(Test.java:4)
$
Première constatation : la compilation réussit ! Le problème dans le programme n'est PAS une erreur de compilation. Il s'agit d'une temps de fonctionnement erreur. (Certains IDEs peuvent avertir que votre programme lèvera toujours une exception ... mais la norme javac
compilateur ne le fait pas).
Deuxième observation : lorsque je lance le programme, il produit deux lignes de "charabia". FAUX ! Ce n'est pas du charabia. C'est un suivi de pile ... et cela fournit information vitale qui vous aidera à retrouver l'erreur dans votre code si vous prenez le temps de le lire attentivement.
Regardons donc ce qu'il dit :
Exception in thread "main" java.lang.NullPointerException
La première ligne de la trace de la pile vous indique un certain nombre de choses :
- Il vous indique le nom du thread Java dans lequel l'exception a été déclenchée. Pour un programme simple avec un seul thread (comme celui-ci), ce sera "main". Continuons...
- Il vous indique le nom complet de l'exception qui a été levée, à savoir
java.lang.NullPointerException
.
- Si l'exception est associée à un message d'erreur, celui-ci sera affiché après le nom de l'exception.
NullPointerException
est inhabituel à cet égard, car il affiche rarement un message d'erreur.
La deuxième ligne est la plus importante pour diagnostiquer un NPE.
at Test.main(Test.java:4)
Cela nous dit un certain nombre de choses :
- "at Test.main" dit qu'on était dans les
main
de la méthode Test
classe.
- "Test.java:4" donne le nom du fichier source de la classe, ET il nous indique que l'instruction où cela s'est produit se trouve à la ligne 4 du fichier.
Si vous comptez les lignes dans le fichier ci-dessus, la ligne 4 est celle que j'ai étiquetée avec le commentaire "HERE".
Notez que dans un exemple plus compliqué, il y aura beaucoup de lignes dans la trace de la pile NPE. Mais vous pouvez être sûr que la deuxième ligne (la première ligne "at") vous indiquera où le NPE a été lancé. 1 .
En bref, la trace de la pile nous indique sans ambiguïté quelle instruction du programme a déclenché le NPE.
Voir aussi : Qu'est-ce qu'une trace de pile, et comment puis-je l'utiliser pour déboguer les erreurs de mon application ?
1 - Pas tout à fait vrai. Il existe des choses appelées exceptions imbriquées...
Question : Comment puis-je trouver la cause de l'exception NPE dans mon code ?
C'est la partie la plus difficile. La réponse courte consiste à appliquer une inférence logique aux preuves fournies par la trace de la pile, le code source et la documentation API pertinente.
Illustrons d'abord avec l'exemple simple (ci-dessus). Nous commençons par examiner la ligne qui, d'après le suivi de la pile, est à l'origine du NPE :
int length = foo.length(); // HERE
Comment cela peut-il déclencher un NPE ?
En fait, il n'y a qu'un seul moyen : cela ne peut se produire que si foo
a la valeur null
. Nous essayons ensuite d'exécuter le length()
méthode sur null
et... BANG !
Mais (je vous entends dire) que se passerait-il si le NPE était lancé à l'intérieur de la fonction length()
l'appel de la méthode ?
Si c'était le cas, la trace de la pile serait différente. La première ligne "at" indiquerait que l'exception a été levée à une certaine ligne de l'application java.lang.String
et la ligne 4 de Test.java
serait la deuxième ligne "at".
Alors où est-ce que null
venir ? Dans ce cas, c'est évident, et ce que nous devons faire pour y remédier l'est tout autant. (Attribuer une valeur non nulle à foo
.)
OK, essayons un exemple un peu plus délicat. Il faudra pour cela déduction logique .
public class Test {
private static String[] foo = new String[2];
private static int test(String[] bar, int pos) {
return bar[pos].length();
}
public static void main(String[] args) {
int length = test(foo, 1);
}
}
$ javac Test.java
$ java Test
Exception in thread "main" java.lang.NullPointerException
at Test.test(Test.java:6)
at Test.main(Test.java:10)
$
Nous avons donc maintenant deux lignes "at". La première est pour cette ligne :
return args[pos].length();
et la deuxième est pour cette ligne :
int length = test(foo, 1);
En regardant la première ligne, comment cela peut-il générer un NPE ? Il y a deux façons :
- Si la valeur de
bar
est null
puis bar[pos]
entraînera un NPE.
- Si la valeur de
bar[pos]
est null
puis en appelant length()
sur elle lancera un NPE.
Ensuite, nous devons déterminer lequel de ces scénarios explique ce qui se passe réellement. Nous allons commencer par explorer le premier :
Où se trouve bar
proviennent-ils ? C'est un paramètre de la test
et si nous regardons comment test
a été appelé, nous pouvons voir qu'il provient de l' foo
variable statique. En outre, nous pouvons voir clairement que nous avons initialisé foo
à une valeur non nulle. C'est suffisant pour écarter provisoirement cette explication. (En théorie, quelque chose d'autre pourrait changement foo
a null
... mais ce n'est pas le cas ici).
Alors qu'en est-il de notre deuxième scénario ? Eh bien, nous pouvons voir que pos
est 1
donc cela signifie que foo[1]
doit être null
. Est-ce possible ?
En effet, c'est le cas ! Et c'est là que réside le problème. Quand on initialise comme ceci :
private static String[] foo = new String[2];
nous attribuons un String[]
avec deux éléments qui sont initialisés à null
. Après cela, nous n'avons pas modifié le contenu du fichier foo
... donc foo[1]
sera toujours null
.
Et sur Android ?
Sur Android, la recherche de la cause immédiate d'un NPE est un peu plus simple. Le message d'exception vous indique généralement le type (au moment de la compilation) de la référence nulle que vous utilisez. y la méthode que vous avez essayé d'appeler lorsque le NPE a été lancé. Cela simplifie le processus d'identification de la cause immédiate.
Mais d'un autre côté, Android a des causes communes spécifiques à la plate-forme pour les NPE. Une cause très courante est lorsque getViewById
renvoie inopinément un null
. Je vous conseille de rechercher des questions-réponses sur la cause de l'imprévu. null
valeur de retour.