Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Null safety in legacy Java libraries used in Kotlin projects

Let's say I have particular code in old/legacy Java library:

public class JavaClass {
    private String notNullString;
    private String nullableString;
    private String unannotatedString;

    public JavaClass(@NotNull String notNullString,
                     @Nullable String nullableString,
                     String unannotatedString) {

        this.notNullString = notNullString;
        this.nullableString = nullableString;
        this.unannotatedString = unannotatedString;
    }

    @NotNull
    public String getNotNullString() {
        return notNullString;
    }

    @Nullable
    public String getNullableString() {
        return nullableString;
    }

    public String getUnannotatedString() {
        return unannotatedString;
    }
}

The first two parameters are properly annotated with @NotNull and @Nullable annotations (using jetbrains.annotations). The third one (unnanotatedString) is left without proper annotation.

When I use this class in my Kotlin code and set all the constructor arguments to non-null values, everything is fine:

val foo = JavaClass("first string", "second string", "third string")

println("Value1: ${foo.notNullString.length}")
println("Value2: ${foo.nullableString?.length}")
println("Value3: ${foo.unannotatedString.length}")

The first value is non-null so I can access it without a safe call. Second value and I need to use safe call (nullableString?.length), if not, I have a compile-time error, so far so good. On the third value (unannotatedString) I can use it without a safe call, it compiles fine.

But when I set the third parameter to "null" I don't get a compile-time error (no safe call required, only runtime NullPointerException:

val bar = JavaClass("first string", "second string", null)

println("Value4: ${bar.unannotatedString.length}") // throws NPE

Is that expected behaviour? Is Kotlin's compiler treating not annotated Java methods same as the ones annotated with @NotNull?

like image 456
Jan Slominski Avatar asked Jun 14 '17 17:06

Jan Slominski


People also ask

Does Kotlin have null safety?

Kotlin's type system is aimed at eliminating the danger of null references, also known as The Billion Dollar Mistake. One of the most common pitfalls in many programming languages, including Java, is that accessing a member of a null reference will result in a null reference exception.

What is null safety in Kotlin with example?

Kotlin null safety is a procedure to eliminate the risk of null reference from the code. Kotlin compiler throws NullPointerException immediately if it found any null argument is passed without executing any other statements. Kotlin's type system is aimed to eliminate NullPointerException form the code.

How do you handle null in Kotlin?

You can use the "?. let" operator in Kotlin to check if the value of a variable is NULL. It can only be used when we are sure that we are refereeing to a non-NULL able value. The following example demonstrates how this operator works.


2 Answers

The type of that variable from Kotlin's view will be String!, which is a platform type.

They initially made every variable coming from Java nullable, but they changed that decision later during the design of the language, because it required too much null handling and required too many safe calls that cluttered the code.

Instead, it's up to you to assess whether an object coming from Java might be null, and mark their type accordingly. The compiler doesn't enforce null safety for these objects.


As an additional example, if you're overriding a method from Java, the parameters will be platform types yet again, and it's up to you whether you mark them nullable or not. If you have this Java interface:

interface Foo {
    void bar(Bar bar);
}

Then these are both valid implementations of it in Kotlin:

class A : Foo {
    fun bar(bar: Bar?) { ... }
}

class B : Foo {
    fun bar(bar: Bar) { ... }
}
like image 74
zsmb13 Avatar answered Oct 23 '22 13:10

zsmb13


Whenever the Kotlin compiler does not know what the nullability of a type is, the type becomes a platform type, denoted with a single !:

public String foo1() { ... }
@NotNull public String foo2() { ... }
@Nullable public String foo3() { ... }

val a = foo1() // Type of a is "String!"
val b = foo2() // Type of b is "String"
val c = foo3() // Type of c is "String?"

This means a much as, "I don't know what the type is, you may need to check it".

The Kotlin compiler does not enforce null-checking on these types, because it may be unnecessary:

Any reference in Java may be null, which makes Kotlin's requirements of strict null-safety impractical for objects coming from Java. (...) When we call methods on variables of platform types, Kotlin does not issue nullability errors at compile time, but the call may fail at runtime, because of a null-pointer exception or an assertion that Kotlin generates to prevent nulls from propagating:

val item = list[0] // platform type inferred (ordinary Java object)
item.substring(1) // allowed, may throw an exception if item == null
like image 5
nhaarman Avatar answered Oct 23 '22 12:10

nhaarman