Как я могу сказать Kotlin, что массив или коллекция не могут содержать нули?

Если я создам массив, то заполните его, Kotlin считает, что в массиве могут быть нули, и заставляет меня учитывать это

val strings = arrayOfNulls<String>(10000) strings.fill("hello") val upper = strings.map { it!!.toUpperCase() } // requires it!! val lower = upper.map { it.toLowerCase() } // doesn't require !! 

Создание заполненного массива не имеет этой проблемы

 val strings = Array(10000, {"string"}) val upper = strings.map { it.toUpperCase() } // doesn't require !! 

Как я могу сказать компилятору, что результат strings.fill("hello") является массивом NonNull?

Эмпирическое правило: если вы сомневаетесь, укажите типы явно (для этого есть специальный рефакторинг):

 val strings1: Array<String?> = arrayOfNulls<String>(10000) val strings2: Array<String> = Array(10000, {"string"}) 

Итак, вы видите, что strings1 содержат элементы с strings2 значением, а strings2 – нет. Это и только то, что определяет, как работать с этими массивами:

 // You can simply use nullability in you code: strings2[0] = strings1[0]?.toUpperCase ?: "KOTLIN" //Or you can ALWAYS cast the type, if you are confident: val casted = strings1 as Array<String> //But to be sure I'd transform the items of the array: val asserted = strings1.map{it!!} val defaults = strings1.map{it ?: "DEFAULT"} 

Невозможно рассказать об этом компилятору. Тип переменной определяется при ее объявлении. В этом случае переменная объявляется как массив, который может содержать нули.

Метод fill () не объявляет новую переменную, он только модифицирует содержимое существующего, поэтому он не может вызвать изменение типа переменной.

Почему заполненный массив работает нормально

Заполненный массив определяет тип массива во время вызова из лямбда, используемого в качестве второго аргумента:

 val strings = Array(10000, {"string"}) 

создает Array<String>

 val strings = Array(10000, { it -> if (it % 2 == 0) "string" else null }) 

создает Array<String?>

Поэтому изменение объявления слева от = которое не соответствует лямбда, не помогает. Если есть конфликт, возникает ошибка.

Как заставить arrayOfNulls работать

Для проблемы arrayOfNulls они указывают, что вы указываете на вызов arrayOfNulls<String> используется в сигнатуре функции как общий тип T а функция arrayOfNulls возвращает Array<T?> arrayOfNulls означает значение NULL. Ничто в вашем коде не меняет этот тип. Метод fill только устанавливает значения в существующий массив.

Чтобы преобразовать этот массив с нулевым элементом в список не-nullable-element, используйте:

 val nullableStrings = arrayOfNulls<String>(10000).apply { fill("hello") } val strings = nullableStrings.filterNotNull() val upper = strings.map { it.toUpperCase() } // no !! needed 

Это нормально, потому что ваш map вызов все равно преобразуется в список, поэтому почему бы не конвертировать заранее. Теперь, в зависимости от размера массива, это может быть выполнено или нет, копия может быть быстрой, если в кэше процессора. Если он большой и не работает, вы можете сделать это ленивым:

 val nullableStrings = arrayOfNulls<String>(10000).apply { fill("hello") } val strings = nullableStrings.asSequence().filterNotNull() val upper = strings.map { it.toUpperCase() } // no !! needed 

Или вы можете остаться с массивами, сделав копию, но на самом деле это не имеет смысла, потому что вы отмените ее с помощью map :

 val nullableStrings = arrayOfNulls<String>(10000).apply { fill("hello") } val strings: Array<String> = Array(nullableStrings.size, { idx -> nullableStrings[idx]!! }) 

Массивы действительно не так распространены в Java или кодеке Kotlin (JetBrains изучал статистику), если только код не делает действительно низкую оптимизацию уровня. Лучше использовать списки.

Учитывая, что вы, возможно, закончите со списками, возможно, начните там тоже и откажитесь от массива.

 val nullableStrings = listOf("a","b",null,"c",null,"d") val strings = nullableStrings.filterNotNull() 

Но, если вы не можете остановить квест, чтобы использовать массивы, и на самом деле нужно бросить один без копии …

Вы всегда можете написать функцию, которая выполняет две функции: во-первых, убедитесь, что все значения не равны нулю, и если да, то возвращаем массив, который отличен как null. Это немного взломанно, но безопасно только потому, что разница недействительна.

Во-первых, создайте функцию расширения в Array<T?> :

  fun <T: Any> Array<T?>.asNotNull(): Array<T> { if (this.any { it == null }) { throw IllegalStateException("Cannot cast an array that contains null") } @Suppress("CAST_NEVER_SUCCEEDS") return this as Array<T> } 

Затем используйте эту функцию для выполнения функции new (элемент проверен как недействительный):

 val nullableStrings = arrayOfNulls<String>(10000).apply { fill("hello") } val strings = nullableStrings.asNotNull() // magic! val upperStrings = strings.map { it.toUpperCase() } // no error 

Но я чувствую себя грязным, даже разговаривая об этом последнем варианте.