Odpowiedzi:
Kiedy deklarujesz zmienną odniesienia (tj. Obiekt), tak naprawdę tworzysz wskaźnik do obiektu. Rozważ następujący kod, w którym deklarujesz zmienną typu pierwotnego int
:
int x;
x = 10;
W tym przykładzie zmienną x
jest an, int
a Java zainicjuje ją 0
dla Ciebie. Kiedy przypisujesz mu wartość 10
w drugim wierszu, twoja wartość 10
jest zapisywana w lokalizacji pamięci, o której mowa x
.
Ale kiedy próbujesz zadeklarować typ odwołania , dzieje się coś innego. Weź następujący kod:
Integer num;
num = new Integer(10);
Pierwszy wiersz deklaruje zmienną o nazwie num
, ale tak naprawdę nie zawiera pierwotnej wartości. Zamiast tego zawiera wskaźnik (ponieważ typ jest Integer
typem odniesienia). Ponieważ nie powiedziałeś jeszcze, na co ma wskazywać, Java ustawia to null
, co oznacza „ Nie wskazuję na nic ”.
W drugim wierszu new
słowo kluczowe służy do tworzenia (lub tworzenia) obiektu typu, Integer
a zmienna wskaźnikowa num
jest przypisana do tego Integer
obiektu.
NullPointerException
Występuje podczas zadeklarować zmienną, ale nie utworzyć obiekt i przypisać do zmiennej przed próbują wykorzystać zawartość zmiennej (tzw wyłuskania ). Wskazujesz więc na coś, co tak naprawdę nie istnieje.
Dereferencje zwykle występują podczas .
uzyskiwania dostępu do metody lub pola lub [
indeksowania tablicy.
Jeśli spróbujesz wyrejestrować num
PRZED utworzeniem obiektu, otrzymasz NullPointerException
. W najbardziej trywialnych przypadkach kompilator złapie problem i poinformuje, że „ num may not have been initialized
,”, ale czasami możesz napisać kod, który nie tworzy bezpośrednio obiektu.
Na przykład możesz mieć następującą metodę:
public void doSomething(SomeObject obj) {
//do something to obj
}
W takim przypadku obiekt nie jest tworzony obj
, a raczej zakładany, że został utworzony przed doSomething()
wywołaniem metody. Uwaga: możliwe jest wywołanie metody w ten sposób:
doSomething(null);
W takim przypadku obj
jest null
. Jeśli metoda ma na celu zrobienie czegoś z przekazanym obiektem, należy zgłosić błąd, NullPointerException
ponieważ jest to błąd programisty, a programista będzie potrzebował tych informacji do celów debugowania. Podaj nazwę zmiennej obiektowej w komunikacie wyjątku, np
Objects.requireNonNull(a, "a");
Alternatywnie mogą wystąpić przypadki, w których celem metody nie jest wyłącznie działanie na przekazanym obiekcie, a zatem parametr zerowy może być akceptowalny. W takim przypadku należy sprawdzić parametr zerowy i zachowywać się inaczej. Powinieneś także wyjaśnić to w dokumentacji. Na przykład doSomething()
można zapisać jako:
/**
* @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
}
}
Wreszcie, jak wskazać wyjątek i przyczynę za pomocą śledzenia stosu
Jakich metod / narzędzi można użyć do ustalenia przyczyny, aby zatrzymać wyjątek powodujący przedwczesne zakończenie działania programu?
Sonar z findbugs może wykryć NPE. Czy sonar może wychwytywać wyjątki wskaźnika zerowego spowodowane przez JVM Dynamicznie
int a=b
mogą rzucać NPE, jeśli b jest an Integer
. Są przypadki, w których debugowanie jest mylące.
NullPointerException
problemów w kodzie jest użycie @Nullable
i @NotNull
adnotacje. Poniższa odpowiedź zawiera więcej informacji na ten temat. Chociaż ta odpowiedź dotyczy w szczególności IntelliJ IDE, ma ona również zastosowanie do innych narzędzi, podobnie jak przyrząd z komentarzy. (BTW Nie mogę bezpośrednio edytować tej odpowiedzi, być może autor może ją dodać?)
NullPointerException
s są wyjątkami występującymi, gdy próbujesz użyć odwołania, które wskazuje na brak położenia w pamięci (null), tak jakby odnosiło się do obiektu. Wywołanie metody w odwołaniu zerowym lub próba dostępu do pola odwołania zerowego spowoduje wywołanie metody NullPointerException
. Są to najczęściej, ale inne sposoby są wymienione na stronie NullPointerException
javadoc.
Prawdopodobnie najszybszym przykładowym kodem, jaki mogłem wymyślić w celu zilustrowania, NullPointerException
byłoby:
public class Example {
public static void main(String[] args) {
Object obj = null;
obj.hashCode();
}
}
W pierwszym wierszu w środku main
wyraźnie ustawiam wartość Object
odniesienia obj
równą null
. Oznacza to, że mam odniesienie, ale nie wskazuje ono żadnego obiektu. Następnie staram się traktować referencję tak, jakby wskazywała na obiekt, wywołując na nim metodę. Powoduje to, NullPointerException
ponieważ w miejscu wskazywanym przez odwołanie nie ma kodu do wykonania.
(Jest to technika, ale myślę, że należy wspomnieć: Odwołanie wskazujące na null nie jest tym samym co wskaźnik C wskazujący na nieprawidłowe położenie pamięci. Wskaźnik null dosłownie nie wskazuje nigdzie , co jest subtelnie różne niż wskazując lokalizację, która jest nieprawidłowa).
null
przed użyciem jej, jak to . W przypadku zmiennych lokalnych kompilator wychwytuje ten błąd, ale w tym przypadku nie. Może to byłby użyteczny dodatek do twojej odpowiedzi?
Dobrym miejscem do rozpoczęcia jest JavaDocs . Obejmują to:
Zgłaszane, gdy aplikacja próbuje użyć wartości NULL w przypadku, gdy wymagany jest obiekt. Obejmują one:
- Wywołanie metody wystąpienia obiektu zerowego.
- Dostęp do lub modyfikowanie pola obiektu zerowego.
- Przyjmuje długość null, jakby to była tablica.
- Uzyskiwanie dostępu do szczelin zerowania lub modyfikowanie ich tak, jakby to była tablica.
- Rzucanie zerowe, jakby to była wartość Throwable.
Aplikacje powinny zgłaszać instancje tej klasy, aby wskazać inne nielegalne użycie obiektu zerowego.
Jest tak również w przypadku, gdy próba użycia odwołania zerowego z synchronized
, spowoduje również zgłoszenie tego wyjątku dla JLS :
SynchronizedStatement: synchronized ( Expression ) Block
- W przeciwnym razie, jeśli wartość wyrażenia jest równa null, generowane
NullPointerException
jest a .
Więc masz NullPointerException
. Jak to naprawić? Weźmy prosty przykład, który rzuca 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();
}
}
Zidentyfikuj wartości zerowe
Pierwszym krokiem jest dokładne określenie, które wartości powodują wyjątek . W tym celu musimy przeprowadzić debugowanie. Ważne jest, aby nauczyć się czytać ślad stosu . To pokaże, gdzie został zgłoszony wyjątek:
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)
Tutaj widzimy, że wyjątek jest zgłaszany w wierszu 13 (w printString
metodzie). Spójrz na linię i sprawdź, które wartości są puste, dodając instrukcje rejestrowania lub używając debugera . Dowiadujemy się, że s
jest to null, a wywołanie na nim length
metody zgłasza wyjątek. Widzimy, że program przestaje zgłaszać wyjątek, gdy s.length()
zostanie usunięty z metody.
Śledź, skąd pochodzą te wartości
Następnie sprawdź, skąd pochodzi ta wartość. Postępując zgodnie z rozmówców sposobu, widzimy, że s
jest przekazywana ze printString(name)
w print()
metodzie, a this.name
jest null.
Śledź, gdzie należy ustawić te wartości
Gdzie jest this.name
ustawiony? W setName(String)
metodzie Po kolejnych debugowaniach możemy zobaczyć, że ta metoda w ogóle nie jest wywoływana. Jeśli metoda została wywołana, sprawdź kolejność wywoływania tych metod, a ustawiona metoda nie jest wywoływana po metodzie drukowania.
To wystarczy, aby dać nam rozwiązanie: dodaj połączenie printer.setName()
przed, zanim zadzwonisz printer.print()
.
Zmienna może mieć wartość domyślną (i setName
może uniemożliwić jej ustawienie na null):
private String name = "";
Albo print
albo printString
metoda może sprawdzić zerowy , na przykład:
printString((name == null) ? "" : name);
Lub możesz zaprojektować klasę, aby name
zawsze miała wartość inną niż null :
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();
}
}
Zobacz też:
Jeśli próbujesz debugować problem i nadal nie masz rozwiązania, możesz wysłać pytanie, aby uzyskać dodatkową pomoc, ale pamiętaj, aby dołączyć to, co próbujesz do tej pory. Uwzględnij co najmniej stacktrace w pytaniu i zaznacz ważne numery wierszy w kodzie. Spróbuj także najpierw uprościć kod (patrz SSCCE ).
NullPointerException
(NPE)?Jak powinieneś wiedzieć, typy Java są podzielone na prymitywnych typów ( boolean
, int
etc.) i typów referencyjnych . Typy referencyjne w Javie pozwalają na użycie specjalnej wartości, null
która jest w Javie sposobem mówienia „brak obiektu”.
A NullPointerException
jest generowane w czasie wykonywania, ilekroć twój program próbuje użyć null
tak, jakby to było prawdziwe odniesienie. Na przykład, jeśli napiszesz to:
public class Test {
public static void main(String[] args) {
String foo = null;
int length = foo.length(); // HERE
}
}
instrukcja oznaczona „TUTAJ” będzie próbowała uruchomić length()
metodę na null
referencji, a to rzuci NullPointerException
.
Istnieje wiele sposobów wykorzystania null
wartości, która spowoduje, że wartość NullPointerException
. W rzeczywistości jedyne rzeczy, które możesz zrobić null
bez powodowania NPE to:
==
lub !=
operatorów, lub instanceof
.Załóżmy, że skompilowałem i uruchomiłem powyższy program:
$ javac Test.java
$ java Test
Exception in thread "main" java.lang.NullPointerException
at Test.main(Test.java:4)
$
Pierwsza obserwacja: kompilacja się udała! Problemem w programie NIE jest błąd kompilacji. Jest to błąd czasu wykonywania . (Niektóre IDE mogą ostrzegać, że Twój program zawsze zgłasza wyjątek ... ale standardowy javac
kompilator nie.)
Druga obserwacja: kiedy uruchamiam program, wypisuje on dwa wiersze „gobbledy-gook”. ŹLE!! To nie jest pożeracz. Jest to stacktrace ... i dostarcza istotnych informacji , które pomogą ci wyśledzić błąd w kodzie, jeśli poświęcisz trochę czasu na jego dokładne przeczytanie.
Spójrzmy więc na to, co mówi:
Exception in thread "main" java.lang.NullPointerException
Pierwszy wiersz śladu stosu mówi ci kilka rzeczy:
java.lang.NullPointerException
.NullPointerException
jest pod tym względem niezwykły, ponieważ rzadko zawiera komunikat o błędzie.Druga linia jest najważniejsza w diagnozowaniu NPE.
at Test.main(Test.java:4)
To mówi nam wiele rzeczy:
main
metodzie Test
klasy.Jeśli policzysz linie w powyższym pliku, linia 4 to ta, którą oznaczyłem komentarzem „TUTAJ”.
Zauważ, że w bardziej skomplikowanym przykładzie w śladzie stosu NPE będzie wiele linii. Ale możesz być pewien, że druga linia (pierwsza linia „w”) powie ci, gdzie został rzucony NPE 1 .
W skrócie, ślad stosu jednoznacznie powie nam, która instrukcja programu wyrzuciła NPE.
1 - Niezupełnie prawda. Są rzeczy zwane wyjątkami zagnieżdżonymi ...
To jest najtrudniejsza część. Krótka odpowiedź polega na zastosowaniu logicznego wnioskowania do dowodów dostarczonych przez ślad stosu, kod źródłowy i odpowiednią dokumentację API.
Zilustrujmy najpierw prostym przykładem (powyżej). Zaczynamy od spojrzenia na wiersz, który powiedział nam ślad stosu, gdzie jest miejsce NPE:
int length = foo.length(); // HERE
Jak może to rzucić NPE?
W rzeczywistości istnieje tylko jeden sposób: może się to zdarzyć tylko wtedy, gdy foo
ma wartość null
. Następnie próbujemy uruchomić length()
metodę null
i ... BANG!
Ale (słyszę, jak mówisz) co się stanie, jeśli NPE zostanie wrzucony do length()
wywołania metody?
Gdyby tak się stało, ślad stosu wyglądałby inaczej. Pierwsza linia „at” oznaczałaby, że wyjątek został zgłoszony w pewnej linii w java.lang.String
klasie, a linia 4 Test.java
byłaby drugą linią „at”.
Więc skąd to się null
wzięło? W tym przypadku jest to oczywiste i oczywiste jest, co musimy zrobić, aby to naprawić. (Przypisz wartość inną niż null do foo
.)
OK, spróbujmy więc nieco trudniejszego przykładu. Będzie to wymagało logicznej dedukcji .
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)
$
Więc teraz mamy dwie linie „at”. Pierwszy dotyczy tej linii:
return args[pos].length();
a drugi dotyczy tej linii:
int length = test(foo, 1);
Patrząc na pierwszą linię, w jaki sposób może to rzucić NPE? Istnieją dwa sposoby:
bar
jest, null
to bar[pos]
wyrzuci NPE.bar[pos]
to null
wywołanie length()
spowoduje wygenerowanie NPE.Następnie musimy dowiedzieć się, który z tych scenariuszy wyjaśnia, co się faktycznie dzieje. Zaczniemy od zbadania pierwszego:
Skąd bar
pochodzi? Jest to parametr test
wywołania metody, a jeśli spojrzymy na to, jak test
zostało wywołane, możemy zobaczyć, że pochodzi on od foo
zmiennej statycznej. Ponadto wyraźnie widać, że zainicjowaliśmy wartość foo
inną niż zero. To wystarczy, aby wstępnie odrzucić to wyjaśnienie. (Teoretycznie coś innego może zmienić się foo
na null
... ale tak się nie dzieje.)
A co z naszym drugim scenariuszem? Cóż, możemy zobaczyć, że pos
jest 1
, to znaczy, że tak foo[1]
musi być null
. czy to możliwe?
Rzeczywiście jest! I to jest problem. Kiedy inicjalizujemy w ten sposób:
private static String[] foo = new String[2];
alokujemy String[]
z dwoma elementami, które są inicjowanenull
. Po tym nie zmieniliśmy zawartości foo
... i tak foo[1]
pozostanie null
.
To tak, jakbyś próbował uzyskać dostęp do obiektu, który jest null
. Rozważ poniższy przykład:
TypeA objA;
W tej chwili właśnie zadeklarowałeś ten obiekt, ale nie został on zainicjowany ani utworzony . I za każdym razem, gdy spróbujesz uzyskać dostęp do dowolnej właściwości lub metody, rzut będzie miał NullPointerException
sens.
Zobacz także poniższy przykład:
String a = null;
System.out.println(a.toString()); // NullPointerException will be thrown
Wyjątek wskaźnika zerowego jest zgłaszany, gdy aplikacja próbuje użyć wartości null w przypadku, gdy wymagany jest obiekt. Obejmują one:
null
obiektu.null
obiektu.null
tak, jakby to była tablica.null
tak, jakby to była tablica.null
jakby to była wartość Throwable.Aplikacje powinny zgłaszać instancje tej klasy w celu wskazania innych nielegalnych zastosowań null
obiektu.
Odniesienie: http://docs.oracle.com/javase/8/docs/api/java/lang/NullPointerException.html
null
jako celu synchronized
bloku, 2) użycie a null
jako celu a switch
i rozpakowanie null
.
null
Wskaźnik jest, że punkty do nikąd. Kiedy wyrenderujesz wskaźnik p
, mówisz „daj mi dane w lokalizacji zapisanej w„ p ”. Kiedy p
jest null
wskaźnik, lokalizacją w p
jest nowhere
, mówisz„ daj mi dane w lokalizacji „nigdzie” ”. Oczywiście nie może tego zrobić, więc rzuca null pointer exception
.
Zasadniczo dzieje się tak, ponieważ coś nie zostało poprawnie zainicjowane.
NULL
jest napisane jak null
w Javie. I jest to sprawa uwzględniająca wielkość liter.
Istnieje wiele wyjaśnień, aby wyjaśnić, jak to się dzieje i jak to naprawić, ale należy również postępować zgodnie z najlepszymi praktykami, aby NullPointerException
w ogóle uniknąć s.
Zobacz także: Dobra lista najlepszych praktyk
Chciałbym dodać, bardzo ważne, aby dobrze wykorzystać final
modyfikator.
Używanie modyfikatora „końcowego”, jeśli dotyczy Java
Podsumowanie:
final
modyfikatora, aby wymusić dobrą inicjalizację.@NotNull
i@Nullable
if("knownObject".equals(unknownObject)
valueOf()
ponad toString()
.StringUtils
metod zerowania StringUtils.isEmpty(null)
.@Nullable
. Wymienionych powyżej) i ostrzegają przed potencjalnymi błędami. Możliwe jest również wnioskowanie i generowanie takich adnotacji (np. IntelliJ może to zrobić) w oparciu o istniejącą strukturę kodu.
if (obj==null)
.Jeśli jest to null, powinieneś napisać kod, aby to również obsłużyć.
W Javie wszystko (z wyjątkiem typów pierwotnych) ma postać klasy.
Jeśli chcesz użyć dowolnego obiektu, masz dwie fazy:
Przykład:
Object object;
object = new Object();
To samo dotyczy koncepcji tablicy:
Item item[] = new Item[5];
item[0] = new Item();
Jeśli nie podajesz sekcji inicjalizacyjnej, NullPointerException
powstają.
Wyjątkiem wskaźnika zerowego jest wskaźnik używania obiektu bez inicjowania go.
Na przykład poniżej znajduje się klasa ucznia, która wykorzysta ją w naszym kodzie.
public class Student {
private int id;
public int getId() {
return this.id;
}
public setId(int newId) {
this.id = newId;
}
}
Poniższy kod zawiera wyjątek wskaźnika zerowego.
public class School {
Student student;
public School() {
try {
student.getId();
}
catch(Exception e) {
System.out.println("Null pointer exception");
}
}
}
Ponieważ używasz student
, ale zapomniałeś zainicjować go tak jak w poprawnym kodzie pokazanym poniżej:
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");
}
}
}
W Javie wszystkie deklarowane zmienne są w rzeczywistości „odwołaniami” do obiektów (lub prymitywów), a nie do samych obiektów.
Podczas próby wykonania jednej metody obiektowej odwołanie prosi żywy obiekt o wykonanie tej metody. Ale jeśli odwołanie odwołuje się do NULL (nic, zero, void, nada), wówczas nie ma możliwości wykonania metody. Następnie środowisko wykonawcze informuje o tym, zgłaszając wyjątek NullPointerException.
Twoje odwołanie wskazuje „null”, a zatem „Null -> Pointer”.
Obiekt żyje w przestrzeni pamięci maszyny wirtualnej, a jedynym sposobem na dostęp do niego jest użycie this
referencji. Weź ten przykład:
public class Some {
private int id;
public int getId(){
return this.id;
}
public setId( int newId ) {
this.id = newId;
}
}
I w innym miejscu w kodzie:
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...
To ważne, aby wiedzieć - kiedy nie ma już odniesienia do obiektu (w powyższym przykładzie, kiedy reference
i otherReference
jak punkt null) następnie przedmiotem jest „nieosiągalny”. Nie ma sposobu, abyśmy mogli z nim pracować, więc ten obiekt jest gotowy do wyrzucania elementów bezużytecznych, aw pewnym momencie maszyna wirtualna zwolni pamięć używaną przez ten obiekt i przydzieli inny.
Kolejne zjawisko NullPointerException
występuje, gdy deklaruje się tablicę obiektów, a następnie natychmiast próbuje się od niej oddzielić elementy.
String[] phrases = new String[10];
String keyPhrase = "Bird";
for(String phrase : phrases) {
System.out.println(phrase.equals(keyPhrase));
}
Tego konkretnego NPE można uniknąć, jeśli kolejność porównania zostanie odwrócona; mianowicie użycie .equals
na gwarantowanym obiekcie innym niż null.
Wszystkie elementy wewnątrz tablicy są inicjowane do wspólnej wartości początkowej ; dla dowolnego typu tablicy obiektowej oznacza to, że wszystkie elementy sąnull
.
Państwo musi zainicjować elementów tablicy przed dostępem lub ich dereferencji.
String[] phrases = new String[] {"The bird", "A bird", "My bird", "Bird"};
String keyPhrase = "Bird";
for(String phrase : phrases) {
System.out.println(phrase.equals(keyPhrase));
}
Optional
było zwrócenie wartości zerowej. Słowo kluczowe jest w porządku. Umiejętność obrony przed tym jest niezwykle ważna. Zapewnia to jedno powszechne jego wystąpienie i sposoby jego złagodzenia.