Home > Back-end >  How to verify no null elements in list, Kotlin
How to verify no null elements in list, Kotlin

Time:12-28

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 false
  • null !in it - use the in operator function to determine if the list contains null
  • finally, if takeIf {} returns a non-null value, filterNotNull() will convert the element type from the nullable T? to non-null T

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()
  • Related