Zero safety in legacy Java libraries used in Kotlin projects

Let's say I have some specific code in an old / old 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 annotated correctly with @NotNull and @Nullable annotations (using jetbrains.nnotes). The third ( unnanotatedString ) is left without a corresponding annotation.

When I use this class in my Kotlin code and set all 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 not 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 compile time error how good is that. 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 need for a safe call, only a runtime NullPointerException:

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

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

      

Is this the expected behavior? Does the Kotlin compiler handle non-annotated Java methods the same as those annotated with @NotNull?

+3


source to share


2 answers


The type of this variable from the Kotlin view will be String!

, which is the platform type .

They originally made every variable coming from Java to be NULL, but later changed this decision during the development of the language because it required too much processing null

and too many safe calls that cluttered the code.

Instead, you should evaluate if the object coming from Java can null

and mark its type accordingly. The compiler does not provide null safety for these objects.




As an additional example, if you override a method from Java, the parameters will be platform types again, and it's up to you to decide if they are marked as nullable or not. If you have this Java interface:

interface Foo {
    void bar(Bar bar);
}

      

Then they are valid implementations in Kotlin:

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

class B : Foo {
    fun bar(bar: Bar) { ... }
}

      

+6


source


Whenever the Kotlin compiler doesn't know what type nullability is, the type becomes the platform type , denoted by one !

:

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 "I don't know what the type is, you might need to check it."



The Kotlin compiler does not enforce null checking of these types, because it might not be necessary:

Any reference in Java can be null, which makes Kotlin's strict null safety requirements impractical for objects coming from Java. (...) When we call methods on variable platform types, Kotlin will not throw compile-time errors, but the call may be due to a null pointer exception or assertion that Kotlin generates to prevent null propagation:

val item = list[0] // platform type inferred (ordinary Java object)
item.substring(1) // allowed, may throw an exception if item == null

      

+4


source







All Articles