Приведение или преобразование списка в список <T>? - PullRequest
1 голос
/ 20 февраля 2020

Я ищу удобный способ безопасно привести List<T?> к List<T>? (или, в более общем смысле, Iterable<T?> к Iterable<T>?) без фильтрации каких-либо элементов.

Другими словами Возьмите список, если все элементы не равны NULL.

В Swift я могу сделать это:

let a: [Int?] = [1, nil, 3]
let b: [Int?] = [1, 2, 3]

let aa = a as? [Int] // nil
let bb = b as? [Int] // [1, 2, 3]

В Kotlin аналогично выглядящий код просто выполняет непроверенное приведение и не выполняет в результате aa будет null:

val a = listOf<Int?>(1, null, 3)
val b = listOf<Int?>(1, 2, 3)

val aa = a as? List<Int> // [1, null, 3]
val bb = b as? List<Int> // [1, 2, 3]

Таким образом, я ищу такой метод:

@Suppress("UNCHECKED_CAST")
fun <T : Any> Iterable<T?>.takeIfAllNotNull(): Iterable<T>? =
    takeIf { all { el -> el != null } } as? Iterable<T>

С этим я могу написать свой код следующим образом:

val a = listOf<Int?>(1, null, 3)
val b = listOf<Int?>(1, 2, 3)

val aa = a.takeIfAllNotNull() // null
val bb = b.takeIfAllNotNull() // [1, 2, 3]

Я должен что-то здесь упустить. Разве в стандартной библиотеке нет такого метода?

1 Ответ

7 голосов
/ 20 февраля 2020

Встроенной функции нет, но вы можете комбинировать filterNotNull и takeIf, чтобы получить желаемое поведение:

val a: List<Int?> = listOf(1, null, 3)
val aa: List<Int>? = a.filterNotNull().takeIf { it.size == a.size }

-

Редактировать: согласно @ DrawnRacoon's Предполагается, что было бы быстрее использовать просто takeIf:

val aa: List<Int>? = a.takeIf { null !in a } as List<Int>

Это не создает промежуточный список и короткое замыкание, когда он находит ноль. Требуется небезопасное приведение, чтобы заставить компилятор изменить тип.

Извлеченный из функции расширения, он будет выглядеть так:

@Suppress("UNCHECKED_CAST")
fun <T> Iterable<T?>.takeIfAllNotNull(): Iterable<T>? {
    return takeIf { null !in this } as? Iterable<T>
}

Кажется, мы вернулись к вашему первоначальному предложению. Поэтому, возможно, ответ должен был быть следующим:

Нет , вы ничего не упускаете.
В стандартной библиотеке нет метода для такого точного поведения.


Чтобы коснуться того, что происходит в вопросе:
Это связано с эрозией дженериков. Во время выполнения нет универсальных шаблонов, поэтому приведение выполняется с List до List, что всегда успешно, и возвращает исходный объект.
Компилятор правильно предупредит вас о небезопасном приведении. Игнорирование этого предупреждения может привести к неправильным типам во время выполнения:

val a = listOf(1, null, 3)     
val aa = a as? List<Int> // unsafe cast List<Int?> to List<Int> 
// aa is now List<Int> but contains a null

Вы приводите List<Int?> к List<Int>, но среда выполнения не может дифференцировать эти типы.
Чтобы привести это безопасно, где-то у нас есть проверить содержимое списка. Лучше сделать это явно, чтобы было ясно, что происходит, поэтому в наших примерах дополнительные вызовы функций.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...