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 d'empêcher l'exception de provoquer l'arrêt prématuré du programme?
Lorsque vous déclarez une variable de référence (c'est-à-dire un objet), vous créez réellement un pointeur sur un objet. Considérez le code suivant dans lequel vous déclarez une variable de type primitif int
:
int x;
x = 10;
Dans cet exemple, la variable x
est une int
et Java l'initialisera à 0
pour vous. Lorsque vous lui affectez la valeur de 10
sur la deuxième ligne, votre valeur de 10
est écrite dans l'emplacement mémoire désigné par x
.
Mais lorsque vous essayez de déclarer une référence , tapez , 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 à quoi pointer, Java le définit sur null
, ce qui signifie " que je désigne rien ".
Dans la deuxième ligne, le mot clé new
est utilisé pour instancier (ou créer) un objet de type Integer
et la variable de pointeur num
est affectée à cet objet Integer
.
La NullPointerException
se produit lorsque vous déclarez une variable sans créer d'objet. Vous pointez donc vers quelque chose qui n'existe pas réellement.
Si vous essayez de déréférencer num
AVANT de créer l'objet, vous obtenez un NullPointerException
. Dans les cas les plus triviaux, le compilateur détecte le problème et vous indique que "num may not have been initialized
", mais vous pouvez parfois é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
}
Dans ce cas, vous ne créez pas l'objet obj
, mais supposez qu'il a été créé avant l'appel de la méthode doSomething()
. Notez qu'il est possible d'appeler la méthode comme ceci:
doSomething(null);
Dans ce cas, obj
est null
. Si la méthode est destinée à modifier l’objet transmis, il est approprié d’envoyer le NullPointerException
car c’est une erreur de programmation et le programmeur aura besoin de ces informations à des fins de débogage.
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 qu’un paramètre nul peut donc être acceptable. Dans ce cas, vous devez rechercher un paramètre null et vous comporter différemment. Vous devriez également expliquer cela 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 avec Stace Trace
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 s'il s'agissait d'un objet de référence. L'appel d'une méthode sur une référence null ou la tentative d'accès à un champ d'une référence null déclenchera un NullPointerException
. Celles-ci sont les plus courantes, mais d’autres méthodes sont répertoriées sur la page NullPointerException
javadoc.
Le code d'exemple le plus rapide que je pourrais trouver pour illustrer un NullPointerException
serait:
public class Example {
public static void main(String[] args) {
Object obj = null;
obj.hashCode();
}
}
Sur la première ligne de main
, je mets explicitement la référence Object
à obj
égale à null
. Cela signifie que j'ai une référence, mais elle ne pointe pas vers un objet. Après cela, j'essaie de traiter la référence comme si elle pointait sur un objet en appelant une méthode dessus. Cela se traduit par un NullPointerException
car il n'y a pas de code à exécuter à l'emplacement pointé par la référence.
(C’est un détail technique, mais je pense qu’il convient de mentionner: une référence pointant sur null n’est pas identique à un pointeur C pointant vers un emplacement mémoire invalide. Un pointeur nul ne pointe pas littéralement n'importe où , ce qui est légèrement différent de celui de pointer sur un emplacement invalide.)
Un bon point de départ est le JavaDocs . Ils ont ceci 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 null.
- Accéder ou modifier le champ d'un objet null.
- Prendre la longueur de null comme s'il s'agissait d'un tableau.
- Accéder ou modifier les slots de null comme s'il s'agissait d'un tableau.
- Lancer null comme s'il s'agissait d'une valeur Throwable.
Les applications doivent utiliser des instances de cette classe pour indiquer d'autres utilisations illégales de l'objet null.
Il est également vrai que si vous essayez d'utiliser une référence null avec synchronized
, cette exception lèvera également cette exception, pour le JLS :
SynchronizedStatement: synchronized ( Expression ) Block
- Sinon, si la valeur de l'expression est nulle, un
NullPointerException
est renvoyé.
Donc vous avez un NullPointerException
. Comment corrigez-vous cela? Prenons un exemple simple qui jette 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();
}
}
Identifie les valeurs nulles
La première étape consiste à identifier exactement quelles valeurs sont à l'origine de l'exception. Pour cela, nous devons faire du 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)
Nous voyons ici que l'exception est levée sur la ligne 13 (dans la méthode printString
). Regardez la ligne et vérifiez quelles valeurs sont nulles en ajoutant instructions de consignation ou en utilisant un débogueur. Nous découvrons que s
est nul et que l'appel de la méthode length
lève l'exception. Nous pouvons voir que le programme cesse de lever l'exception lorsque s.length()
est supprimé de la méthode.
Trace d'où proviennent ces valeurs
Vérifiez ensuite d'où provient cette valeur. En suivant les appelants de la méthode, nous voyons que s
est passé avec printString(name)
dans la méthode print()
et que this.name
est nul.
Trace où ces valeurs doivent être définies
Où est this.name
défini? Dans la méthode setName(String)
. Avec un peu plus de débogage, nous pouvons voir que cette méthode n’est pas appelée du tout. Si la méthode a été appelée, vérifiez que order indique que ces méthodes sont appelées et que la méthode set n'est pas appelée after la méthode print.
Cela suffit pour nous donner une solution: ajoutez un appel à printer.setName()
avant d'appeler printer.print()
.
La variable peut avoir un valeur par défaut (et setName
peut l'empêcher d'être 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 que 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 aussi:
Si vous avez essayé de résoudre le problème et que vous n'avez toujours pas de solution, vous pouvez poser une question pour obtenir de l'aide, 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 ).
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 dans Java vous permettent d'utiliser la valeur spéciale null
, qui est la manière Java de dire "pas d'objet".
Un NullPointerException
est lancé au moment de l'exécution chaque fois que votre programme tente d'utiliser un 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 intitulée "ICI" va tenter d'exécuter la méthode length()
sur une référence null
, ce qui jettera un NullPointerException
.
Vous pouvez utiliser une valeur null
de plusieurs façons pour obtenir un NullPointerException
. En fait, les seules choses que vous pouvez faire avec un null
sans provoquer de NPE sont les suivantes:
==
ou !=
ou instanceof
.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 vous avertir que votre programme lèvera toujours une exception ... mais pas le compilateur standard javac
.)
Deuxième observation: lorsque je lance le programme, il génère deux lignes de "gobbledy-gook". FAUX !! 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. soigneusement.
Alors regardons 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:
Java.lang.NullPointerException
.NullPointerException
est inhabituel à cet égard, car il comporte 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:
main
de la classe Test
.Si vous comptez les lignes du fichier ci-dessus, la ligne 4 est celle que j'ai étiquetée avec le commentaire "ICI".
Notez que dans un exemple plus complexe, la trace de la pile NPE comportera de nombreuses lignes. 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 déclaration du programme a lancé le NPE.
1 - Pas tout à fait vrai. Il y a des choses appelées exceptions imbriquées ...
C'est la partie difficile. La réponse courte consiste à appliquer une inférence logique aux preuves fournies par la trace de pile, le code source et la documentation de l'API pertinente.
Illustrons d'abord par l'exemple simple (ci-dessus). Nous commençons par examiner la ligne indiquée par la trace de la pile à l'endroit où le NPE s'est passé:
int length = foo.length(); // HERE
Comment cela peut-il lancer un NPE?
En fait, il n'y a qu'un moyen: cela ne peut se produire que si foo
a la valeur null
. Nous essayons ensuite d’exécuter la méthode length()
sur null
et .... BANG!
Mais (je vous entends dire), que se passe-t-il si le NPE a été jeté dans l'appel de méthode length()
?
Eh bien, si cela se produisait, la trace de la pile aurait un aspect différent. La première ligne "at" indiquerait que l'exception a été renvoyée dans une ligne de la classe Java.lang.String
, et la ligne 4 de Test.Java
serait la deuxième ligne "at".
Alors d'où vient ce null
? Dans ce cas, c'est évident, et ce que nous devons faire pour le résoudre est évident. (Attribuez une valeur non nulle à foo
.)
OK, essayons donc un exemple un peu plus compliqué. 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)
$
Nous avons donc maintenant deux lignes "at". Le premier est pour 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:
bar
est null
, alors bar[pos]
lancera un NPE.bar[pos]
est null
, l'appel de length()
dessus enverra 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:
D'où vient bar
? C'est un paramètre de l'appel de méthode test
, et si nous regardons comment test
a été appelé, nous pouvons voir qu'il provient de la variable foo
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
est 1
, ce qui signifie que foo[1]
doit être null
. Est-ce possible?
En effet, ça l'est! Et voilà le problème. Quand on initialise comme ça:
private static String[] foo = new String[2];
nous allouons un String[]
avec deux éléments initialisés à null
. Après cela, nous n'avons pas modifié le contenu de foo
... donc foo[1]
restera toujours null
.
C'est comme si vous tentiez d'accéder à un objet qui est null
. Considérons ci-dessous exemple:
TypeA objA;
A ce moment vous venez de déclarer cet objet mais pas initialisé ou instancié . Et chaque fois que vous essayez d’accéder à une propriété ou à une méthode qu’elle contient, elle jette NullPointerException
ce qui a un sens.
Voir aussi cet exemple ci-dessous:
String a = null;
System.out.println(a.toString()); // NullPointerException will be thrown
Une exception de pointeur null est générée lorsqu'une application tente d'utiliser null dans le cas où un objet est requis. Ceux-ci inclus:
null
.null
.null
comme s'il s'agissait d'un tableau.null
comme s'il s'agissait d'un tableau.null
comme s'il s'agissait d'une valeur Throwable.Les applications doivent utiliser des instances de cette classe pour indiquer d'autres utilisations illégales de l'objet null
.
Référence: http://docs.Oracle.com/javase/8/docs/api/Java/lang/NullPointerException.html
Un pointeur null
en est un qui ne pointe nulle part. Lorsque vous déréférencez un pointeur p
, vous dites "donnez-moi les données à l'emplacement stocké dans" p ". Lorsque p
est un pointeur null
, l'emplacement stocké à p
is nowhere
, vous dites "donnez-moi les données à l'emplacement" nulle part "". Évidemment, cela ne peut pas être fait, alors il jette 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 passe et comment y remédier, mais vous devez également suivre les meilleures pratiques pour éviter NullPointerException
s du tout.
Voir aussi: ne bonne liste de bonnes pratiques
J'ajouterais, très important, de bien utiliser le modificateur final
. tilisation du modificateur "final" le cas échéant en Java
Résumé:
final
pour appliquer une bonne initialisation.@NotNull
et @Nullable
if("knownObject".equals(unknownObject)
valueOf()
sur toString ().StringUtils
StringUtils.isEmpty(null)
.Une exception de pointeur nul indique que vous utilisez un objet sans l'initialiser.
Par exemple, ci-dessous est une classe d’élèves 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 null.
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 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, tout (à l'exception des types primitifs) se présente sous la forme d'une classe.
Si vous souhaitez utiliser un objet, vous avez deux phases:
Exemple:
Object object;
object = new Object();
Idem pour le concept de tableau:
Item item[] = new Item[5];
item[0] = new Item();
Si vous ne donnez pas la section d'initialisation, la NullPointerException
apparaît.
Dans Java , toutes les variables que vous déclarez sont en réalité 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), la méthode n'est en aucun cas exécutée. Ensuite, le moteur d'exécution vous a fait savoir cela en lançant une exception NullPointerException.
Votre référence est "pointant" sur null, donc "Null -> Pointer".
L'objet vit dans l'espace mémoire VM et le seul moyen d'y accéder consiste à utiliser les références this
. 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 dans 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 - lorsqu'il n'y a plus de références à un objet (dans l'exemple ci-dessus, lorsque reference
et otherReference
désignent tous deux la valeur null), l'objet est "inaccessible". Nous ne pouvons absolument pas travailler avec cet objet. Cet objet est prêt à être récupéré. À un moment donné, le VM libère la mémoire utilisée par cet objet et en alloue un autre.
Une autre occurrence d'un NullPointerException
se produit lorsque l'on déclare un tableau d'objets, puis tente immédiatement de déréférencer des é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, utilisez .equals
sur un objet garanti non nul.
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 de pouvoir y accéder ou 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));
}