Sięgam tam gdzie wzrok nie sięga…
… a tam NullPointerException. W pierwszej pracy jeden z kolegów walcząc dość długo z jakimś kawałkiem kodu, podsumował swoje zmagania stwierdzeniem:
Wszystko działa. Nawet nullpointerki lecą.
null to taki wredny typ, który rozwalił już nie jeden kod. Dziś przyjrzymy się jak Kotlin radzi sobie z tym problemem.
Nie możesz być null
Domyślnie, zmienna nie może być null. Po prostu takie jest założenie na poziomie gramatyki języka. Kod:
Listing 1. To się nie skompiluje
fun main(args: Array<String>) {
var a:String = "Donkey";
var b:String = null;
}
Nie skompiluje się, ponieważ b jest null. Ten sam mechanizm sprawdza też, czy do metod i funkcji nie przekazujemy null:
Listing 2. To też się nie skompiluje
fun main(args: Array<String>) {
print("Ania", null)
}
fun print(a: String, b: String): Unit {
println(a + "/" + b)
}
Operator ki diabeł ?
Jednak zło nulla istnieje i trzeba czasami przywołać tego demona. By oznaczyć zmienną, że może być null używamy operatora ?, który nazywam ki diabeł? – dobrze oddaje sens działania. Jeżeli napiszemy:
Listing 3. A to się skompiluje
fun main(args: Array<String>) {
var a:String = "Ania";
var b:String? = null;
print(a, b)
}
fun print(a: String, b: String?): Unit {
println(a + "/" + b)
}
to kompilator nie będzie gardłował. Możemy też użyć tego operatora dla typu zwracanego:
Listing 4. Uwaga! Kod zionie nullem
fun l(): Int? {
return null;
}
Przy czym typ Int? nie jest tym samym co Int:
Listing 5. Bezpieczeństwo typów przede wszystkim
fun main(args: Array<String>) {
val message = l()
println(message is Int) // false
}
fun l(): Int? {
return null;
}
Przy czym można dokonać bezpiecznego rzutowania:
Listing 6. Rzutowanie
fun main(args: Array<String>) {
val message = l()
println(message as? Int) // null
}
fun l(): Int? {
return null;
}
Bardzo ważne jest, by pamiętać, iż kompilator śledzi stan zmiennych oraz to, czy sprawdzamy ten stan samodzielnie. Zatem możemy napisać:
Listing 7. Śledzenie stanu zmiennej
fun main(args: Array<String>) {
print("Ania", "Ela")
print("Ania", null)
}
fun print(a: String, b: String?): Unit {
if(b != null)
println("${a.length} ~ ${a.length}" )
else
println(a + "/" + b)
}
i wszystko ładnie się skompiluje. Co, jeżeli nie wiemy, czy zmienna jest null, czy też nie, a pisanie konstrukcji IF null → null nas nie interesuje? Możemy wtedy dokonać za pomocą ki diabła? bezpiecznego wywołania (mentalnie – rysujemy w kodzie pentagram i w nim umieszczamy nasze wywołanie):
Listing 8. Bezpieczne wywołanie
data class Person(val name:String, val partner:Person?);
fun main(args: Array<String>) {
val ania = Person("Ania", null);
val ela = Person("Ela", ania);
println(ela.partner?.partner?.name)
}
W przypadku gdy zmienna będzie null to zapis zmienna? zwróci też null.
Padu-Padu
Po czym przez lata można było poznać użytkowników GG?? Po używaniu podwójnych znaków interpunkcyjnych!!
I mam wrażenie, że twórcy Kotlina też używali GG 🙂 Drugim operatorem przeznaczonym do pracy z nullem jest !!, zwany przeze mnie ot czort!!. Jego użycie jest takie samo jak w ki diabła?. Różnica leży w działaniu. Otóż jeżeli napiszemy:
Listing 9. NPE z zaskoczenia
fun main(args: Array<String>) {
var a:String = "Ania";
var b:String? = null;
print(a, b)
}
fun print(a: String, b: String?): Unit {
println(a + "/" + b!!)
}
To lico naszej konsoli przyozdobi KNPE. Ważne jest to, że kompilator nie śledzi tego typu wywołania zatem napisanie czegoś takiego może spowodować „ciekawe” błędy.
Wywołania z javy
Tu sprawa ma się trochę gorzej, ale nadal nic straconego:
Listing 10. Wywołanie kodu Kotlina z Javy
public class App {
public static void main(String[] args) {
NullsKt.print("", null);
}
}
Zwróci:
Exception in thread "main" java.lang.IllegalArgumentException: Parameter specified as non-null is null: method pl.koziolekweb.blog.nulls.NullsKt.print, parameter b
at pl.koziolekweb.blog.nulls.NullsKt.print(Nulls.kt)
at pl.koziolekweb.blog.nulls.App.main(App.java:10)
Zatem kod, choć się skompiluje – dlaczego by nie, w Javie nie ma ograniczeń – to się wywali.
Podsumowanie
Kotlin robi nam dobrze w sprawie NPE. Znacznie lepiej niż Java ze swoim JSR-305. Jest to kolejny argument, by przesiąść się na ten język.