NullPointerException – jak uniknąć najpopularniejszego wyjątku w języku Java?

sages.pl 3 lat temu
Jeśli programujesz w Javie, to na pewno kojarzysz wyjątek podany w tytule. Mimo iż jego naprawa zwykle jest prosta, to kluczem do sukcesu jest uniknięcie wyjątku, zanim on nastąpi.

### Z pozoru prosta implementacja

Załóżmy, iż mamy dane *id* użytkownika, następnie chcemy go pobrać (z jakiejś bazy / repozytorium użytkowników) i wyświetlić w konsoli jego nazwę. Brzmi banalnie? Pierwsza implementacja mogłaby wyglądać w ten sposób:

```
User user = findUser(userId);
System.out.println(user.getName());
```

Gdzie metoda **findUser** wyglądałaby tak:

```
public User findUser() {
return users.get(userId);
}
```

Na pierwszy rzut oka, wszystko wygląda w porządku. Jednak co w przypadku, gdy dostaniemy id, którego **nie ma** w bazie? Cóż, ponieważ metoda **findUser** musi zwrócić obiekt typu **User**, więc zwróci null. prawdopodobnie stwierdzicie, iż wystarczy dodać proste sprawdzenie:

```
User user = findUser(userId);
if (user != null) {
System.out.println(user.getName());
}
```

Faktycznie, załatwi ono sprawę. Jednak zastanówmy się... *dlaczego* dodaliśmy to sprawdzenie? Stało się tak, ponieważ otrzymaliśmy NullPointerException. Tylko iż wolelibyśmy w ogóle uniknąć jego wystąpienia. Możemy spróbować dodać sprawdzenie dla każdej możliwej zmiennej w projekcie, jednak wówczas takie sprawdzenia stanowiłyby połowę naszego kodu, a tego przecież nie chcemy.

### Optional

Z pomocą przychodzi **Optional**, klasa z nie tak nowej już zresztą Javy 8. Jest to swego rodzaju „opakowanie” dla zmiennej, która może (ale nie musi) być nullem. Zmieńmy zatem implementację metody **findUser**:

```
public Optional findUser(Long userId) {
return Optional.ofNullable(users.get(userId));
}
```

Jak widzimy, klasę **User** opakujemy w klasę **Optional**. najważniejszy tutaj jest fakt, iż zmienił się zwracany typ w metodzie - zmieniliśmy API. Użycie może wyglądać następująco:

```
findUser(userId)
.ifPresent(u -> System.out.println(u.getName()));
```

Dodaliśmy bardziej funkcyjny charakter do naszego kodu, natomiast to, co najważniejsze to fakt, iż teraz **fizycznie nie jesteśmy w stanie zapomnieć o obsłużeniu nulla** – po prostu kompilator nam na to nie pozwoli.

### Użyteczne metody Optionala

Oprócz samego sprawdzania istnienia danego obiektu, do dyspozycji mamy bogate API. Załóżmy, iż mamy klasę *User* z następującymi polami:

```
String name;
int age;
List roles
```

Następnie chcielibyśmy, na podstawie obiekty typu User, wyekstraktować jego nazwę, ale tylko pod warunkiem, iż dany użytkownik jest adminem i jest dorosły. Przykładowa metoda spełniająca te wymagania mogłaby wyglądać w ten sposób:

```
public String extractAdultAdminName(User user) {
return Optional.ofNullable(user)
.filter(u -> u.getAge() >= 18)
.filter(u -> u.getRoles().contains(“ADMIN”))
.map(u -> u.getName())
.orElse(“”);
}
```

Omówmy powyższy przykład po kolei: Najpierw chcemy opakować w Optionala argument metody, gdyż nie wiemy, czy ktoś nie poda nam nulla. Następnie, metoda **filter(..)** oraz **map(..)** wykonają się jedynie wtedy, kiedy Optional jest „pełny”. W przeciwnym wypadku zostaną pominięte. Metoda **filter(..)** , jak wynika z nazwy, filtruje wnętrze Optionala – przejdziemy dalej, o ile obiekt siedzący wewnątrz, spełni dany warunek. Metoda **map(..)** z kolei mapuje obiekt na cokolwiek innego. W powyższym przypadku obiekt typu *User* zamienimy w Stringa. Na końcu, metoda orElse(..) zwróci nam wewnętrzny obiekt (jeśli tam do tej pory był), lub w przeciwnym wypadku – to, co podamy.

Byłoby prawie dobrze, natomiast zwróćcie uwagę, iż przy powyższej implementacji, brak dorosłego admina poskutkuje zwróceniem pustego Stringa. Zatem w miejscu wywołania tej metody, musielibyśmy znowu sprawdzać, czy wynik nie jest pustym Stringiem. Łatwe do przeoczenia. Zatem poprawmy powyższą metodę na ostateczną implementację:

```
public Optional extractAdultAdminName(User user) {
return Optional.ofNullable(user)
.filter(u -> u.getAge() >= 18)
.filter(u -> u.getRoles().contains(“ADMIN”))
.map(u -> u.getName());
}
```

Tym sposobem, ktokolwiek wywoła naszą metodę, będzie wiedział, iż musi obsłużyć również sytuację, kiedy dorosłego admina brak.

### Podsumowanie
Jeżeli wiemy, iż nasza metoda może zwrócić nulla, to postarajmy się zamienić zwracany typ na Optionala. Dzięki temu potem, używając gdziekolwiek powyższej metody, możemy być pewni, iż nie zapomnimy o obsłużeniu nulla (sprawdzenie na etapie kompilacji, a nie w runtime), a NullPointerException się nie pojawi (a przynajmniej nie z tego powodu).
Idź do oryginalnego materiału