Kotlin: В чем разница между Apply and Also

В чем разница между применением, а также. Из того, что я знаю, следующий код делает то же самое:

Применить

val person = Person().apply {
    name = "Tony Stark"
    age = 52
    // More such stuff
}

также

val person = Person().also {
  it.name = "Tony Stark"
  it.age = 52
  // More such stuff
}

Есть ли разница, и я должен использовать один над другим? Кроме того, есть ли случаи, когда можно было бы работать, а другой - нет?

Ответ 1

TL; DR разница

also функция получает лямбда, к которому T передается в реализации, таким образом, внутри лямбда вы обратитесь к нему с именем (it по умолчанию, можно переименовать { otherName ->...}).

val person = Person().also {
    it.name = "Tony Stark"
}

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

val person = Person().apply {
    name = "Tony Stark"
}

также

Декларация:

inline fun <T> T.also(block: (T) -> Unit): T (source)

Вызывает указанный функциональный блок с this (получателя) в качестве аргумента и возвращает this (получателя).

применять

Декларация:

inline fun <T> T.apply(block: T.() -> Unit): T (source)

Вызывает указанный функциональный блок с this значением в качестве получателя и возвращает this (получателя).

когда использовать что

Примеры использования объясняются в этой теме.

Ответ 2

Короткий ответ: also был введен по смысловым причинам.

Длинный ответ:

Если вы используете apply, вы всегда ссылаетесь на ресивер с помощью this.

val person = Person().apply {
    name = "Tony Stark" // this. can be omitted
    age = 52 // this. can be omitted
    // ...
}

Таким образом, вам не нужно повторять несколько раз, как показано здесь:

person.name = "Tony Stark"
person.age = 52

Если блок станет длиннее, вы можете указать this имя. Вот почему also был введен. Теперь вы можете обратиться к получателю либо с помощью it, либо с явным именем. Это особенно полезно, если вы хотите использовать другое имя, чем (в данном случае person) до:

val person = Person().also { newPerson ->
  newPerson.name = "Tony Stark"
  newPerson.age = 52
  // ...
}

Итак, в зависимости от того, насколько хорошо вы должны читать код, вы всегда можете использовать тот или иной.

Ответ 3

Ответы, приведенные выше, имеют мало смысла, но не много. Я не понимаю это правильно, но я хотел бы добавить к вопросу здесь.

В Standard.kt это фактическая реализация для двух методов.

Для применения

/**
 * Calls the specified function [block] with `this` value as its receiver and returns `this` value.
 */
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

Кроме того,

/**
 * Calls the specified function [block] with `this` value as its argument and returns `this` value.
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

Два метода почти одинаковы, кроме одной строки. Только после объяснений я увидел разницу. Функциональный язык, такой как Kotlin, действительно сложный для Java-единомышленника, такого как я.