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