I'm trying to detect if a List<Int?> contains any null elements. This is what I came up with:
var noNullElements: List<Int> = emptyList()
if (listWithPossibleNullElements.all { it != null }) {
noNullElements = listWithPossibleNullElements as List<Int>
}
//do something with noNullElements list
I'd be shocked if this was the best, most idiomatic way.
I don't want to filter out the null values, I want to use the list "as is" if it contains no null values; and convert it to a non-null type. Notice the change in type from List<Int?> to List< Int >
CodePudding user response:
Check with contains for the existence of at least one null value:
val noNullElements = if (list.contains(null)) emptyList() else list.map { it!! }
Edit: Added this extension function:
fun List<Int?>.castToIntOrEmpty() =
if (this.contains(null)) emptyList() else this.map { it as Int }
val noNullElements = list.castToIntOrEmpty()
Example with no null elements:
val list: List<Int?> = List(10) { it }
// list is: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
val noNullElements = if (list.contains(null)) emptyList() else list.map { it!! }
println(noNullElements) // Output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Example with some (or all) null elements:
val list: List<Int?> = List(10) { if (it % 2 == 0) it else null }
// list is: [0, null, 2, null, 4, null, 6, null, 8, null]
val nullElements = if (list.contains(null)) emptyList() else list.map { it!! }
println(nullElements) // Output: []
CodePudding user response:
I think your problem is you want to take a generic List<Int?>
type and treat it as a List<Int>
, which means casting it. Because those two types are unrelated (List<Int>
is not a subtype of List<Int?>
) you basically have to perform an unchecked cast in this situation:
if (list.contains(null)) emptyList else list as List<Int>
The compiler isn't smart enough to determine that this is a safe cast at runtime (and you have to be able to make that guarantee yourself, e.g. ensuring the list isn't being modified elsewhere to include a null after the check passes) so you'll get an unchecked cast warning. The documentation on generics goes into your options, but if you really want to pass in a List<Int?>
and treat it as a List<Int>
, you'll have to suppress that warning:
An unchecked cast warning can be suppressed by annotating the statement or the declaration where it occurs with
@Suppress("UNCHECKED_CAST")
I'd argue the more idiomatic approach is to just create a new List<Int>
instead of trying to cast the original one to a different generic type, or to make the original list provider pass a List<Int>?
instead (i.e. null if there were any non-null elements) which is maybe just moving the problem - but it really depends on what you're doing.
You can easily create filtered copies and compare it to the original to decide whether to use it, or write a for
loop building a new list that short-circuits if you hit a null, all those kinds of things. If you want to check first and only copy if you need it, lukas.j's answer has more examples (including a couple that throw exceptions, like requireNoNulls()
) so you have a lot of options, and there are different tradeoffs - it really depends on what you're doing and where the bottlenecks are (if any). Things can get a bit awkward when you work with generics, so some situations don't end up looking as neat as you'd like!
CodePudding user response:
Here's a one liner extension function:
fun <T : Any> Iterable<T?>.filterNoneNull(): List<T>? =
takeIf { null !in it }?.filterNotNull()
takeIf {}
- a scope function that will return null if the lambda evaluates to falsenull !in it
- use thein
operator function to determine if the list containsnull
- finally, if
takeIf {}
returns a non-null value,filterNotNull()
will convert the element type from the nullableT?
to non-nullT
Note that it will return null
if the provided Iterable<T?>
contains null
, else List<T>
, in case it's important to differentiate between the provided list being empty, or containing null
. An alternative can be provided using the elvis operator.
Example usage - run in Kotlin Playground:
fun main() {
val listA: List<Int?> = listOf(1, 2, 3, null)
// since listA contains 'null', the result is 'null'
println(listA.filterNoneNull())
// prints: null
// provide an alternative, using the elvis operator
println(listA.filterNoneNull() ?: emptyList<Int>())
// prints: []
val listB: List<Int> = listOf(1, 2, 3)
// since listB does not contain null, the same list is returned
println(listB.filterNoneNull())
// prints: [1, 2, 3]
}
inline fun <reified T : Any> Iterable<T?>.filterNoneNull(): List<T>? =
takeIf { null !in it }?.filterNotNull()
CodePudding user response:
Try using filterNotNull
:
val noNullElements: List<Int> = listWithPossibleNullElements.filterNotNull()