Функции расширения Kotlin - Разница между Any? и универсальный т? - PullRequest
0 голосов
/ 09 января 2019

Я занимался программированием для развлечения, и мне было интересно, какой из них использовать. Пробовал оба, и они дали мне тот же результат. Итак, в чем разница между этими двумя?

Примеры:

fun Any?.foo() = this != null

fun <T> T?.foo() = this != null

Реальная функция немного сложнее, и она фактически что-то делает на основе реального типа объекта (например, when с некоторыми опциями)

Ответы [ 2 ]

0 голосов
/ 10 января 2019

Если вы запустите это на JVM, вы получите следующее

java.lang.ClassFormatError: дублирование имени метода и подписи в классе файл ...

Это интересно, поэтому с точки зрения подписи они идентичны.

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

fun Any?.foo() = this
fun <T> T?.bar() = this

fun main(args: Array<String>) {
    val x = 5.foo() // Any?
    val y = 5.bar() // Int?
}

Все свойства и функции, доступные в Int?, будут недоступны для x, пока я не приведу его явным образом (к и Int?). y с другой стороны "знает", что он вернул Int?.


В вашем примере это не будет иметь значения, поскольку вы всегда будете возвращать Boolean, и, как уже показано, подпись одинакова.

0 голосов
/ 09 января 2019

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

В качестве довольно синтетического примера listOf(this, this) внутри второй функции будет напечатано как List<T?>, сохраняя при этом знание, что тип элементов совпадает с типом получателя, тогда как то же выражение в первой функции будет List<Any?>.

Первая функция не позволяет использовать тип получателя в общем случае для хранения элементов этого типа, принятия дополнительных элементов того же типа в качестве параметров или использования типа получателя в типе возвращаемого значения функции, в то время как вторая функция позволяет все это.

Эти функции эквивалентны с точки зрения времени выполнения, так как обобщения стираются из байт-кода JVM при компиляции кода, поэтому вы не сможете определить тип T во время выполнения и действовать в зависимости от этого, если вы не конвертируете функцию в функцию inline с параметром типа reified .


В качестве очень важного особого случая захват типа с сайта вызова в параметр типа позволяет функции более высокого порядка принимать другую функцию, используя T в своей подписи. Стандартная библиотека имеет набор функций определения объема (run, apply, let, also), которые показывают разницу.

Предположим, что подпись also не использовала дженерики и выглядела так:

fun Any?.also(block: (Any?) -> Unit): Any? { ... }

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

val s: String = "abc"

// won't compile: `it` is typed as `Any?`, the returned value is `Any?`, too
val ss1: String = (s + s).also { println(it.length) } 

// this will work, but it's too noisy
val ss2: String = (s + s).also { println((it as String).length) } as String 

Теперь, захват параметра type - это точно способ показать, что это один и тот же тип во всех трех местах. Мы изменяем подпись следующим образом:

fun <T : Any?> T.also(block: (T) -> Unit): T { ... }

И теперь компилятор может выводить типы, зная, что это тот же тип T везде, где он появляется:

val s: String = "abc"

// OK!
val ss: String = (s + s).also { println(it.length) } 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...