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.

Kurs Java

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:

NullPointerException console

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:

intellij_flag_helpful_nullpointerexceptionsPo 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.