Helpful NullPointerExceptions
Jednym z usprawnień, które zostały wprowadzone w Javie 14 jest JEP 358, czyli Helpful NullPointerExceptions.
Wprowadzenie
Do tej pory NullPointerException mówił nam o tym, że w kodzie wystąpił wyjątek, polegający na tym, że próbujemy się odwołać do jakiegoś pola lub metody poprzez pustą referencję (null). Niestety w przypadku, gdy w kodzie wywołujemy ciąg kilku metod, albo odwołujemy się do obiektów, które są zagregowane w innych obiektach, to wyjątek NPE nie był zbyt czytelny. Usprawnienie Helpful NullPointerExceptions rozwiązuje ten problem. Poniżej znajdziesz przykład, w którym pokażę Ci jak włączyć wyświetlanie dodatkowych informacji o wyjątku.
NullPointerException
Spójrzmy na przykład, w którym klasa Group reprezentuje grupę w pewnej szkole (np. językowej). Grupa składa się z wielu uczniów, których możemy dodawać do listy, dodatkowo grupy są tematyczne i opisane jest to przy pomocy dodatkowego pola. Każdy uczeń ma pola takie jak imię i nazwisko oraz ma przypisany adres.
Group.java
import java.util.ArrayList;
import java.util.List;
class Group {
private List<Student> students = new ArrayList<>();
void add(Student student) {
students.add(student);
}
public List<Student> getStudents() {
return students;
}
}
Student.java
class Student {
private String firstName;
private String lastName;
private Address address;
public Student(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
Address.java
class Address {
private String city;
private String street;
private String details; //home no / flat no
public Address(String city, String street, String details) {
this.city = city;
this.street = street;
this.details = details;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getDetails() {
return details;
}
public void setDetails(String details) {
this.details = details;
}
}
W klasie testowej tworzę teraz grupę i dodaję do niej kilku studentów. Dalej będę chciał odfiltrować studentów, którzy mieszkają we Wrocławiu i wyświetlić ich imię oraz nazwisko.
TestNpe.java
import java.util.List;
import java.util.stream.Collectors;
class TestNpe {
public static void main(String[] args) {
Group group = new Group();
group.add(new Student("Jan", "Kowalski"));
group.add(new Student("Anna", "Zawadzka"));
group.add(new Student("Piotr", "Adamczyk"));
List<Student> wroclawStudents = group.getStudents()
.stream().filter(s -> s.getAddress().getCity().equals("Wrocław"))
.collect(Collectors.toList());
wroclawStudents.forEach(s -> System.out.println(s.getFirstName() + " " + s.getLastName()));
}
}
Po uruchomieniu programu otrzymamy wyjątek NullPointerException o treści:
Na tej podstawie możemy wywnioskować, że wyjątek został rzucony gdzieś w wierszu 12, ale nie wiemy czy nullem jest referencja s, s.getAddress, czy też może miasto w adresie, czyli s.getAddress().getCity().
Helpful NullPointerExceptions
Od Javy 14 wprowadzono usprawnienie o nazwie Helpful NullPointerExceptions. Domyślnie jest ono wyłączone, aby je włączyć, podczas uruchamiania programu należy dodać flagę -XX:+ShowCodeDetailsInExceptionMessages. Jeżeli korzystasz z IntelliJ IDEA, to możesz to zrobić w konfiguracji uruchomieniowej aplikacji:
Po uruchomieniu programu z tą dodatkową flagą zobaczymy taki komunikat błędu:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Address.getCity()" because the return value of "Student.getAddress()" is null
at TestNpe.lambda$main$0(TestNpe.java:12)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:176)
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1624)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
at TestNpe.main(TestNpe.java:13)
W pierwszym wierszu otrzymujemy dokładną informację o tym co poszło nie tak. Nie musimy się już domyślać, która referencja jest pusta, ponieważ otrzymujemy jasny komunikat, że któryś student nie ma ustawionego adresu (u nas żaden student go nie ma).
Wyjątek NullPointerException jest często zmorą początkujących programistów. Dzięki temu usprawnieniu jego wykrycie i wyeliminowanie może być dużo prostsze.
Dyskusja i komentarze
Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.