Qu'est-ce qu'une NullPointerException et comment y remédier?
Que sont les exceptions de pointeur nul ( java.lang.NullPointerException
) et quelles en sont les causes?
Quelles méthodes / outils peuvent être utilisés pour déterminer la cause afin que vous empêchiez l'exception de provoquer l'arrêt prématuré du programme?
Réponses
Lorsque vous déclarez une variable de référence (c'est-à-dire un objet), vous créez réellement un pointeur vers un objet. Considérez le code suivant où vous déclarez une variable de type primitif int
:
int x;
x = 10;
Dans cet exemple, la variable x
est un int
et Java l'initialisera 0
pour vous. Lorsque vous lui attribuez la valeur de 10
sur la deuxième ligne, votre valeur de 10
est écrite dans l'emplacement mémoire référencé par x
.
Mais, lorsque vous essayez de déclarer un type de référence , quelque chose de différent se produit. Prenez le code suivant:
Integer num;
num = new Integer(10);
La première ligne déclare une variable nommée num
, mais elle ne contient pas encore de valeur primitive. Au lieu de cela, il contient un pointeur (car le type est Integer
qui est un type de référence). Puisque vous n'avez pas encore dit sur quoi pointer, Java le définit null
, ce qui signifie " Je ne pointe vers rien ".
Dans la deuxième ligne, le new
mot-clé est utilisé pour instancier (ou créer) un objet de type Integer
, et la variable pointeur num
est affectée à cet Integer
objet.
Le NullPointerException
produit lorsque vous déclarez une variable mais n'avez pas créé un objet et l' affecter à la variable avant d'utiliser le contenu de la variable (appelée déréférencement ). Vous indiquez donc quelque chose qui n'existe pas réellement.
Le déréférencement se produit généralement lors de l'utilisation .
pour accéder à une méthode ou d'un champ, ou lors de l'utilisation [
pour indexer un tableau.
Si vous essayez de déréférencer num
AVANT de créer l'objet, vous obtenez un fichier NullPointerException
. Dans les cas les plus triviaux, le compilateur détecte le problème et vous informe que " num may not have been initialized
," mais parfois vous pouvez écrire du code qui ne crée pas directement l'objet.
Par exemple, vous pouvez avoir une méthode comme suit:
public void doSomething(SomeObject obj) {
//do something to obj, assumes obj is not null
obj.myMethod();
}
Dans ce cas, vous ne créez pas l'objet obj
, mais supposez plutôt qu'il a été créé avant l' doSomething()
appel de la méthode. Remarque, il est possible d'appeler la méthode comme ceci:
doSomething(null);
Dans ce cas, obj
is null
, et l'instruction obj.myMethod()
lèvera un NullPointerException
.
Si la méthode est destinée à faire quelque chose à l'objet passé comme le fait la méthode ci-dessus, il est approprié de lancer le NullPointerException
car il s'agit d'une erreur de programmeur et le programmeur aura besoin de ces informations à des fins de débogage.
En plus de NullPointerException
s lancés en raison de la logique de la méthode, vous pouvez également vérifier les arguments de méthode pour les null
valeurs et lancer explicitement les NPE en ajoutant quelque chose comme ce qui suit au début d'une méthode:
//Throws an NPE with a custom error message if obj is null
Objects.requireNonNull(obj, "obj must not be null");
Notez qu'il est utile d'indiquer clairement dans votre message d'erreur quel objet ne peut pas être null
. L'avantage de faire une validation comme celle-ci est que 1) vous pouvez renvoyer vos propres messages d'erreur plus clairs et 2) pour le reste de la méthode, vous savez qu'à moins d' obj
être réaffecté, il n'est pas nul et peut être déréférencé en toute sécurité.
Alternativement, il peut y avoir des cas où le but de la méthode n'est pas uniquement d'opérer sur l'objet passé, et donc un paramètre nul peut être acceptable. Dans ce cas, vous devez rechercher un paramètre nul et vous comporter différemment. Vous devez également l'expliquer dans la documentation. Par exemple, doSomething()
pourrait être écrit comme suit:
/**
* @param obj An optional foo for ____. May be null, in which case
* the result will be ____.
*/
public void doSomething(SomeObject obj) {
if(obj == null) {
//do something
} else {
//do something else
}
}
Enfin, comment identifier l'exception et la cause à l'aide de Stack Trace
Quelles méthodes / outils peuvent être utilisés pour déterminer la cause afin que vous empêchiez l'exception de provoquer l'arrêt prématuré du programme?
Le sondeur avec des bogues de recherche peut détecter NPE. Le sondeur peut-il intercepter dynamiquement les exceptions de pointeur nul causées par JVM
Maintenant, Java 14 a ajouté une nouvelle fonctionnalité de langage pour montrer la cause première de NullPointerException. Cette fonctionnalité linguistique fait partie de la JVM commerciale SAP depuis 2006. Ce qui suit est de 2 minutes pour comprendre cette fonctionnalité linguistique incroyable.
https://jfeatures.com/blog/NullPointerException
Dans java 14, voici un exemple de message d'exception NullPointerException:
dans le thread "main" java.lang.NullPointerException: Impossible d'appeler "java.util.List.size ()" car "list" est nul
NullPointerException
s sont des exceptions qui se produisent lorsque vous essayez d'utiliser une référence qui ne pointe vers aucun emplacement en mémoire (null) comme si elle faisait référence à un objet. Appeler une méthode sur une référence nulle ou essayer d'accéder à un champ d'une référence nulle déclenchera un NullPointerException
. Ce sont les plus courantes, mais d'autres méthodes sont répertoriées sur la NullPointerExceptionpage javadoc.
L'exemple de code le plus rapide que je pourrais trouver pour illustrer un NullPointerException
serait probablement:
public class Example {
public static void main(String[] args) {
Object obj = null;
obj.hashCode();
}
}
Sur la première ligne à l'intérieur main
, je mets explicitement la Object
référence obj
égale à null
. Cela signifie que j'ai une référence, mais elle ne pointe vers aucun objet. Après cela, j'essaye de traiter la référence comme si elle pointe vers un objet en appelant une méthode dessus. Cela entraîne un NullPointerException
car il n'y a aucun code à exécuter à l'emplacement vers lequel pointe la référence.
(C'est un détail technique, mais je pense qu'il convient de le mentionner: une référence qui pointe vers null n'est pas la même chose qu'un pointeur C qui pointe vers un emplacement mémoire non valide. Un pointeur nul ne pointe littéralement nulle part , ce qui est subtilement différent de pointant vers un emplacement qui n'est pas valide.)
Qu'est-ce qu'une NullPointerException?
Les JavaDocs constituent un bon point de départ . Ils ont ce couvert:
Lancé lorsqu'une application tente d'utiliser null dans un cas où un objet est requis. Ceux-ci inclus:
- Appel de la méthode d'instance d'un objet nul.
- Accéder ou modifier le champ d'un objet nul.
- Prenant la longueur de null comme s'il s'agissait d'un tableau.
- Accéder ou modifier les emplacements de null comme s'il s'agissait d'un tableau.
- Lancer null comme s'il s'agissait d'une valeur Throwable.
Les applications doivent lancer des instances de cette classe pour indiquer d'autres utilisations illégales de l'objet nul.
Il est également le cas que si vous essayez d'utiliser une référence nulle avec synchronized
, cela lèvera également cette exception, selon le JLS :
SynchronizedStatement: synchronized ( Expression ) Block
- Sinon, si la valeur de l'expression est nulle, a
NullPointerException
est levé.
Comment je le répare?
Donc, vous avez un NullPointerException
. Comment y remédier? Prenons un exemple simple qui lance un NullPointerException
:
public class Printer {
private String name;
public void setName(String name) {
this.name = name;
}
public void print() {
printString(name);
}
private void printString(String s) {
System.out.println(s + " (" + s.length() + ")");
}
public static void main(String[] args) {
Printer printer = new Printer();
printer.print();
}
}
Identifiez les valeurs nulles
La première étape consiste à identifier exactement les valeurs à l'origine de l'exception . Pour cela, nous devons effectuer un débogage. Il est important d'apprendre à lire un stacktrace . Cela vous montrera où l'exception a été levée:
Exception in thread "main" java.lang.NullPointerException
at Printer.printString(Printer.java:13)
at Printer.print(Printer.java:9)
at Printer.main(Printer.java:19)
Ici, nous voyons que l'exception est lancée à la ligne 13 (dans la printString
méthode). Regardez la ligne et vérifiez quelles valeurs sont nulles en ajoutant des instructions de journalisation ou en utilisant un débogueur . Nous découvrons que la valeur s
est nulle et l'appel de la length
méthode lève l'exception. Nous pouvons voir que le programme arrête de lancer l'exception lorsqu'il s.length()
est supprimé de la méthode.
Tracez d'où viennent ces valeurs
Vérifiez ensuite d'où vient cette valeur. En suivant les appelants de la méthode, nous voyons qu'elle s
est passée avec printString(name)
dans la print()
méthode et qu'elle this.name
est nulle.
Tracez où ces valeurs doivent être définies
Où est this.name
défini? Dans la setName(String)
méthode. Avec un peu plus de débogage, nous pouvons voir que cette méthode n'est pas du tout appelée. Si la méthode a été appelée, assurez-vous de vérifier l' ordre dans lequel ces méthodes sont appelées et la méthode set n'est pas appelée après la méthode print.
Cela suffit à nous donner une solution: ajoutez un appel à printer.setName()
avant d'appeler printer.print()
.
Autres correctifs
La variable peut avoir une valeur par défaut (et setName
peut empêcher qu'elle soit définie sur null):
private String name = "";
La méthode print
ou printString
peut vérifier la valeur null , par exemple:
printString((name == null) ? "" : name);
Ou vous pouvez concevoir la classe de sorte qu'elle name
ait toujours une valeur non nulle :
public class Printer {
private final String name;
public Printer(String name) {
this.name = Objects.requireNonNull(name);
}
public void print() {
printString(name);
}
private void printString(String s) {
System.out.println(s + " (" + s.length() + ")");
}
public static void main(String[] args) {
Printer printer = new Printer("123");
printer.print();
}
}
Voir également:
- Vous évitez les instructions «! = Null» en Java?
Je ne trouve toujours pas le problème
Si vous avez essayé de déboguer le problème et que vous n'avez toujours pas de solution, vous pouvez publier une question pour obtenir de l'aide supplémentaire, mais assurez-vous d'inclure ce que vous avez essayé jusqu'à présent. Au minimum, incluez le stacktrace dans la question et marquez les numéros de ligne importants dans le code. Essayez également de simplifier d'abord le code (voir SSCCE ).
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 les types de référence . Les types de référence en Java vous permettent d'utiliser la valeur spéciale null
qui est la manière Java de dire "pas d'objet".
A NullPointerException
est lancé à l'exécution chaque fois que votre programme tente d'utiliser a null
comme s'il s'agissait d'une référence réelle. Par exemple, si vous écrivez ceci:
public class Test {
public static void main(String[] args) {
String foo = null;
int length = foo.length(); // HERE
}
}
l'instruction étiquetée "ICI" va tenter d'exécuter la length()
méthode sur une null
référence, et cela lancera un NullPointerException
.
Il existe de nombreuses façons d'utiliser une null
valeur qui entraînera un fichier NullPointerException
. En fait, les seules choses que vous pouvez faire avec un null
sans provoquer de 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 du tableau elle-même soit non nulle!),
- passez-le en paramètre ou renvoyez-le comme résultat, ou
- tester à l'aide des
==
ou!=
opérateurs, ouinstanceof
.
Question: Comment lire le stacktrace 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)
$
Premier constat: la compilation réussit! Le problème dans le programme n'est PAS une erreur de compilation. C'est une erreur d' exécution . (Certains IDE peuvent avertir que votre programme lèvera toujours une exception ... mais le javac
compilateur standard ne le fait pas.)
Deuxième constat: lorsque je lance le programme, il sort deux lignes de "gobbledy-gook". MAL!! Ce n'est pas gobbledy-gook. C'est un stacktrace ... et il fournit des informations vitales qui vous aideront à localiser l'erreur dans votre code si vous prenez le temps de le lire attentivement.
Alors regardons ce qu'il dit:
Exception in thread "main" java.lang.NullPointerException
La première ligne de la trace de pile vous indique un certain nombre de choses:
- Il vous indique le nom du thread Java dans lequel l'exception a été lancée. Pour un programme simple avec un thread (comme celui-ci), ce sera "principal". Allons-nous en ...
- Il vous indique le nom complet de l'exception qui a été levée; ie
java.lang.NullPointerException
. - Si l'exception a un message d'erreur associé, celui-ci sera affiché après le nom de l'exception.
NullPointerException
est inhabituel à cet égard, car il contient rarement un message d'erreur.
La deuxième ligne est la plus importante dans le diagnostic d'un NPE.
at Test.main(Test.java:4)
Cela nous dit un certain nombre de choses:
- "at Test.main" dit que nous étions dans la
main
méthode de laTest
classe. - "Test.java:4" donne le nom de fichier source de la classe, ET il nous dit que l'instruction où cela s'est produit est à 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 "ICI".
Notez que dans un exemple plus compliqué, il y aura beaucoup de lignes dans la trace de 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 pile nous dira sans ambiguïté quelle instruction du programme a jeté le NPE.
1 - Pas tout à fait vrai. Il y a des choses appelées exceptions imbriquées ...
Question: Comment rechercher la cause de l'exception NPE dans mon code?
C'est la partie difficile. La réponse courte consiste à appliquer l'inférence logique aux preuves fournies par la trace de pile, le code source et la documentation API pertinente.
Illustrons d'abord avec l'exemple simple (ci-dessus). Nous commençons par regarder la ligne que la trace de pile nous a indiquée où le NPE s'est produit:
int length = foo.length(); // HERE
Comment cela peut-il jeter un NPE?
En fait, il n'y a qu'une seule façon: cela ne peut arriver que si foo
a la valeur null
. Nous essayons ensuite d'exécuter la length()
méthode null
et ... BANG!
Mais (je vous entends dire) et si le NPE était jeté à l'intérieur de l' length()
appel de méthode?
Eh bien, si cela se produisait, la trace de la pile serait différente. La première ligne «at» dirait que l'exception a été lancée dans une ligne de la java.lang.String
classe et la ligne 4 de Test.java
serait la deuxième ligne «at».
Alors d'où cela null
vient-il? Dans ce cas, c'est évident et ce que nous devons faire pour y remédier est évident. (Attribuez une valeur non nulle à foo
.)
OK, essayons donc un exemple un peu plus délicat. Cela nécessitera une 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) $
Alors maintenant, nous avons deux lignes «at». Le premier concerne cette ligne:
return args[pos].length();
et le second est pour cette ligne:
int length = test(foo, 1);
En regardant la première ligne, comment cela pourrait-il lancer un NPE? Il y a deux manières:
- Si la valeur de
bar
estnull
alorsbar[pos]
lancera un NPE. - Si la valeur de
bar[pos]
estnull
alors appelerlength()
, il lancera un NPE.
Ensuite, nous devons déterminer lequel de ces scénarios explique ce qui se passe réellement. Nous commencerons par explorer le premier:
D'où bar
vient-il? C'est un paramètre de l' test
appel de méthode, et si nous regardons comment a test
été appelé, nous pouvons voir qu'il provient de la foo
variable statique. De plus, nous pouvons voir clairement que nous avons initialisé foo
à une valeur non nulle. Cela suffit pour écarter provisoirement cette explication. (En théorie, quelque chose d'autre pourrait changer foo
en null
... mais cela ne se produit pas ici.)
Alors qu'en est-il de notre deuxième scénario? Eh bien, nous pouvons voir que pos
c'est 1
, donc cela signifie que cela foo[1]
doit être null
. Est-ce possible?
En effet, ça l'est! Et voilà le problème. Lorsque nous initialisons comme ceci:
private static String[] foo = new String[2];
nous allouons a String[]
avec deux éléments qui sont initialisésnull
. Après cela, nous n'avons pas changé le contenu de foo
... il le foo[1]
sera toujours null
.
C'est comme si vous tentiez d'accéder à un objet qui l'est null
. Considérez l'exemple ci-dessous:
TypeA objA;
À ce stade, vous venez de déclarer cet objet mais pas initialisé ni instancié . Et chaque fois que vous essayez d'accéder à une propriété ou à une méthode, cela lancera NullPointerException
ce qui a du sens.
Voir également cet exemple ci-dessous:
String a = null;
System.out.println(a.toString()); // NullPointerException will be thrown
Une exception de pointeur null est levée lorsqu'une application tente d'utiliser null dans un cas où un objet est requis. Ceux-ci inclus:
- Appel de la méthode d'instance d'un
null
objet. - Accéder ou modifier le champ d'un
null
objet. - Prenant la longueur de
null
comme s'il s'agissait d'un tableau. - Accéder ou modifier les emplacements de
null
comme s'il s'agissait d'un tableau. - Lancer
null
comme s'il s'agissait d'une valeur Throwable.
Les applications doivent lancer des instances de cette classe pour indiquer d'autres utilisations illégales de l' null
objet.
Référence: http://docs.oracle.com/javase/8/docs/api/java/lang/NullPointerException.html
Un null
pointeur est celui qui ne pointe nulle part. Lorsque vous déréférencer un pointeur p
, vous dites "donnez-moi les données à l'emplacement stocké dans" p ". Quand p
est un null
pointeur, l'emplacement stocké p
est nowhere
, vous dites" donnez-moi les données à l'emplacement "nulle part" ". De toute évidence, il ne peut pas faire cela, donc il lance un null pointer exception
.
En général, c'est parce que quelque chose n'a pas été initialisé correctement.
De nombreuses explications sont déjà présentes pour expliquer comment cela se produit et comment y remédier, mais vous devez également suivre les meilleures pratiques pour éviter les NullPointerExceptions.
Voir aussi: Une bonne liste de bonnes pratiques
J'ajouterais, très important, faire un bon usage du final
modificateur. Utilisation du modificateur "final" le cas échéant en Java
Résumé:
- Utilisez le
final
modificateur pour appliquer une bonne initialisation. - Évitez de renvoyer null dans les méthodes, par exemple renvoyer des collections vides le cas échéant.
- Utilisez des annotations @NotNullet@Nullable
- Échouez rapidement et utilisez des assertions pour éviter la propagation d'objets nuls dans toute l'application alors qu'ils ne devraient pas être nuls.
- Utilisez d'abord égal à un objet connu:
if("knownObject".equals(unknownObject)
- Préfère
valueOf()
plustoString()
. - Utilisez des StringUtilsméthodes sûres nulles
StringUtils.isEmpty(null)
. - Utilisez Java 8 Facultatif comme valeur de retour dans les méthodes, la classe Optional fournit une solution pour représenter des valeurs facultatives au lieu de références nulles.
En Java, tout (à l'exception des types primitifs) est sous la forme d'une classe.
Si vous souhaitez utiliser un objet, vous avez deux phases:
- Déclarer
- Initialisation
Exemple:
- Déclaration:
Object object;
- Initialisation:
object = new Object();
Idem pour le concept de tableau:
- Déclaration:
Item item[] = new Item[5];
- Initialisation:
item[0] = new Item();
Si vous ne donnez pas la section d'initialisation, le NullPointerException
problème survient.
Une exception de pointeur null est un indicateur que vous utilisez un objet sans l'initialiser.
Par exemple, ci-dessous est une classe d'étudiants qui l'utilisera dans notre code.
public class Student {
private int id;
public int getId() {
return this.id;
}
public setId(int newId) {
this.id = newId;
}
}
Le code ci-dessous vous donne une exception de pointeur nul.
public class School {
Student student;
public School() {
try {
student.getId();
}
catch(Exception e) {
System.out.println("Null pointer exception");
}
}
}
Parce que vous utilisez student
, mais que vous avez oublié de l'initialiser comme dans le code correct ci-dessous:
public class School {
Student student;
public School() {
try {
student = new Student();
student.setId(12);
student.getId();
}
catch(Exception e) {
System.out.println("Null pointer exception");
}
}
}
En Java, toutes les variables que vous déclarez sont en fait des "références" aux objets (ou primitives) et non aux objets eux-mêmes.
Lorsque vous essayez d'exécuter une méthode d'objet, la référence demande à l'objet vivant d'exécuter cette méthode. Mais si la référence fait référence à NULL (rien, zéro, void, nada), il n'y a aucun moyen pour la méthode d'être exécutée. Ensuite, le runtime vous l'a fait savoir en lançant une NullPointerException.
Votre référence "pointe" vers null, donc "Null -> Pointer".
L'objet vit dans l'espace mémoire de la VM et le seul moyen d'y accéder consiste à utiliser des this
références. Prenons cet exemple:
public class Some {
private int id;
public int getId(){
return this.id;
}
public setId( int newId ) {
this.id = newId;
}
}
Et à un autre endroit de votre code:
Some reference = new Some(); // Point to a new object of type Some()
Some otherReference = null; // Initiallly this points to NULL
reference.setId( 1 ); // Execute setId method, now private var id is 1
System.out.println( reference.getId() ); // Prints 1 to the console
otherReference = reference // Now they both point to the only object.
reference = null; // "reference" now point to null.
// But "otherReference" still point to the "real" object so this print 1 too...
System.out.println( otherReference.getId() );
// Guess what will happen
System.out.println( reference.getId() ); // :S Throws NullPointerException because "reference" is pointing to NULL remember...
C'est une chose importante à savoir - quand il n'y a plus de références à un objet (dans l'exemple ci-dessus, quand reference
et les otherReference
deux pointent vers null) alors l'objet est "inaccessible". Il n'y a aucun moyen de travailler avec lui, donc cet objet est prêt à être récupéré et à un moment donné, la VM libèrera la mémoire utilisée par cet objet et en allouera une autre.
Une autre occurrence de a NullPointerException
se produit lorsque l'on déclare un tableau d'objets, puis essaie immédiatement de déréférencer les éléments à l'intérieur de celui-ci.
String[] phrases = new String[10];
String keyPhrase = "Bird";
for(String phrase : phrases) {
System.out.println(phrase.equals(keyPhrase));
}
Ce NPE particulier peut être évité si l'ordre de comparaison est inversé; à savoir, utilisation .equals
sur un objet non nul garanti.
Tous les éléments à l'intérieur d'un tableau sont initialisés à leur valeur initiale commune ; pour tout type de tableau d'objets, cela signifie que tous les éléments sont null
.
Vous devez initialiser les éléments du tableau avant d'y accéder ou de les déréférencer.
String[] phrases = new String[] {"The bird", "A bird", "My bird", "Bird"};
String keyPhrase = "Bird";
for(String phrase : phrases) {
System.out.println(phrase.equals(keyPhrase));
}