3

Here's my Foo data class definition

data class Foo(
    var fooArg: Array<Int?>? = null, 
)

And here's the call to it:

val bar: Array<Int> = arrayOf(1,2,3)
val foo = Foo(fooArg = bar)

But this gives an error type mismatch: required Array<Int?>? but found Array<Int>

I am confused, it is expecting a nullable type, and I provide it with a non-null value, how is that type mismatch?

2 Answers 2

6

You declared bar as Array<Int>. Non-null types are not compatible with nullable types*. Change it to Array<Int?> and it will work:

val bar: Array<Int?> = arrayOf(1,2,3)
val foo = Foo(fooArg = bar)

Or alternatively:

val bar = arrayOf<Int?>(1, 2, 3)

*I think the correct thing to say is that arrays are invariant on the type parameter. But I get lost every time I try to understand it properly. 🤯

Sign up to request clarification or add additional context in comments.

4 Comments

Got it. My bar is coming from another method which returns Array<Int> so I have to cast it, which is ugly but works. Thanks!
The real question is why do you want to allow null items in your array? 🧐
It's generated code from Jooq...
“so I have to cast it” — Can you clarify that please? (I suspect you don't mean ‘cast’.)
3

Adam's answer covers the solution, but it's also worth mentioning why what you're doing doesn't work. Java uses call-site variance annotations, unlike most other languages in existence. Kotlin takes the more traditional approach of using declaration-site variance annotations. That means that, when declaring a class which takes generic arguments, the writer of the class decides how it behaves with respect to subtypes.

Now, Int is a subtype of Int?. Namely, every Int is an Int?, but the reverse is not true. The question is: is Array<Int> a subtype of Array<Int?>? Well, covariant types such as List<T> preserve subtyping, so List<Int> is actually a subtype of List<Int?>. If you replace your example in the question with lists rather than arrays, everything works.

On the other hand, Array<T> is an invariant type. We can both read and write to an array, so it's never safe to upcast or downcast the type parameter. If we could, then the following would typecheck.

// Doesn't compile, for good reason
val myHealthyArray: Array<Int> = arrayOf(1);
val myScaryNullableArray: Array<Int?> = myHealthyArray;
myScaryNullableArray[0] = null;

Now my perfectly innocent myHealthyArray variable has a null in it that the type Array<Int> can't account for. That's contrary to everything Kotlin stands for, so it's disallowed on principle.

If you're only ever going to be using this data structure for reading, not writing, consider using List<T> or something covariant, which better describes the type of your function and also allows the subtyping relationships to work more fully.

2 Comments

Good effort typing into the void 🙉 ;) Marcin Moskala has a similar example and explanation: Kotlin generic variance modifiers
Another reference point is MutableList, which is invariant like Array (and unlike List).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.